diff --git a/app/src/androidTest/java/com/todoroo/andlib/test/TranslationTests.kt b/app/src/androidTest/java/com/todoroo/andlib/test/TranslationTests.kt index d7eb6b8d2..0e6fcf201 100644 --- a/app/src/androidTest/java/com/todoroo/andlib/test/TranslationTests.kt +++ b/app/src/androidTest/java/com/todoroo/andlib/test/TranslationTests.kt @@ -12,7 +12,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.tasks.Callback import org.tasks.R.string import java.util.* @@ -25,10 +24,10 @@ import java.util.* @RunWith(AndroidJUnit4::class) class TranslationTests { /** Loop through each locale and call runnable */ - private fun forEachLocale(callback: Callback) { + private fun forEachLocale(callback: (Resources) -> Unit) { val locales = Locale.getAvailableLocales() for (locale in locales) { - callback.call(getResourcesForLocale(locale)) + callback.invoke(getResourcesForLocale(locale)) } } @@ -86,28 +85,27 @@ class TranslationTests { failures.append(String.format("error opening %s: %s\n", name, e.message)) } } - forEachLocale( - Callback { r: Resources -> - val locale = r.configuration.locale - for (i in strings.indices) { - try { - if (strings[i] == string.abc_shareactionprovider_share_with_application) { - continue - } - val string = r.getString(strings[i]) - val newFS = FormatStringData(string) - if (!newFS.matches(formatStrings[i])) { - val name = r.getResourceName(strings[i]) - failures.append(String.format( - "%s (%s): %s != %s\n", name, locale.toString(), newFS, formatStrings[i])) - } - } catch (e: Exception) { - val name = r.getResourceName(strings[i]) - failures.append(String.format( - "%s: error opening %s: %s\n", locale.toString(), name, e.message)) - } + forEachLocale { r: Resources -> + val locale = r.configuration.locale + for (i in strings.indices) { + try { + if (strings[i] == string.abc_shareactionprovider_share_with_application) { + continue } - }) + val string = r.getString(strings[i]) + val newFS = FormatStringData(string) + if (!newFS.matches(formatStrings[i])) { + val name = r.getResourceName(strings[i]) + failures.append(String.format( + "%s (%s): %s != %s\n", name, locale.toString(), newFS, formatStrings[i])) + } + } catch (e: Exception) { + val name = r.getResourceName(strings[i]) + failures.append(String.format( + "%s: error opening %s: %s\n", locale.toString(), name, e.message)) + } + } + } assertEquals(failures.toString(), 0, errorCount(failures)) } @@ -125,14 +123,13 @@ class TranslationTests { @Test fun testSpecialStringsMatch() { val failures = StringBuilder() - forEachLocale( - Callback { r: Resources -> - contains(r, string.CFC_tag_text, failures, "?") - contains(r, string.CFC_title_contains_text, failures, "?") - contains(r, string.CFC_dueBefore_text, failures, "?") - contains(r, string.CFC_tag_contains_text, failures, "?") - contains(r, string.CFC_gtasks_list_text, failures, "?") - }) + forEachLocale { r: Resources -> + contains(r, string.CFC_tag_text, failures, "?") + contains(r, string.CFC_title_contains_text, failures, "?") + contains(r, string.CFC_dueBefore_text, failures, "?") + contains(r, string.CFC_tag_contains_text, failures, "?") + contains(r, string.CFC_gtasks_list_text, failures, "?") + } assertEquals(failures.toString(), 0, failures.toString().replace("[^\n]".toRegex(), "").length) } diff --git a/app/src/debug/java/org/tasks/BuildSetup.kt b/app/src/debug/java/org/tasks/BuildSetup.kt index 25db2778e..1c23aba1c 100644 --- a/app/src/debug/java/org/tasks/BuildSetup.kt +++ b/app/src/debug/java/org/tasks/BuildSetup.kt @@ -18,7 +18,7 @@ import timber.log.Timber import timber.log.Timber.DebugTree import javax.inject.Inject -internal class BuildSetup @Inject constructor(@ForApplication private val context: Context, private val preferences: Preferences) { +class BuildSetup @Inject constructor(@param:ForApplication private val context: Context, private val preferences: Preferences) { fun setup() { Timber.plant(DebugTree()) SoLoader.init(context, false) diff --git a/app/src/debug/java/org/tasks/DebugNetworkInterceptor.kt b/app/src/debug/java/org/tasks/DebugNetworkInterceptor.kt index 0c17cb99a..73e926968 100644 --- a/app/src/debug/java/org/tasks/DebugNetworkInterceptor.kt +++ b/app/src/debug/java/org/tasks/DebugNetworkInterceptor.kt @@ -11,7 +11,7 @@ import org.tasks.injection.ForApplication import java.io.IOException import javax.inject.Inject -class DebugNetworkInterceptor @Inject constructor(@ForApplication private val context: Context) { +class DebugNetworkInterceptor @Inject constructor(@param:ForApplication private val context: Context) { fun add(builder: OkHttpClient.Builder) { builder.addNetworkInterceptor(FlipperOkhttpInterceptor(getNetworkPlugin(context))) } diff --git a/app/src/googleplay/java/org/tasks/analytics/Firebase.kt b/app/src/googleplay/java/org/tasks/analytics/Firebase.kt index 21dda29df..859915305 100644 --- a/app/src/googleplay/java/org/tasks/analytics/Firebase.kt +++ b/app/src/googleplay/java/org/tasks/analytics/Firebase.kt @@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject @ApplicationScope -class Firebase @Inject constructor(@ForApplication val context: Context, preferences: Preferences) { +class Firebase @Inject constructor(@param:ForApplication val context: Context, preferences: Preferences) { private var enabled: Boolean = preferences.isTrackingEnabled private var analytics: FirebaseAnalytics? = null diff --git a/app/src/main/java/com/todoroo/astrid/core/CriterionViewHolder.kt b/app/src/main/java/com/todoroo/astrid/core/CriterionViewHolder.kt index dca1a761a..27cd0b959 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CriterionViewHolder.kt +++ b/app/src/main/java/com/todoroo/astrid/core/CriterionViewHolder.kt @@ -8,7 +8,6 @@ import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import butterknife.ButterKnife import butterknife.OnClick -import org.tasks.Callback import org.tasks.R import org.tasks.locale.Locale import org.tasks.preferences.ResourceResolver @@ -17,7 +16,7 @@ class CriterionViewHolder( private val context: Context, itemView: View, private val locale: Locale, - private val onClick: Callback) + private val onClick: (String) -> Unit) : RecyclerView.ViewHolder(itemView) { @BindView(R.id.divider) @@ -73,7 +72,7 @@ class CriterionViewHolder( } @OnClick(R.id.row) - fun onClick() = this.onClick.call(criterion.id) + fun onClick() = this.onClick.invoke(criterion.id) fun setMoving(moving: Boolean) { if (moving) { diff --git a/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java b/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java index f1d5c88ff..1ba6a4ff7 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java +++ b/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java @@ -14,19 +14,20 @@ import androidx.recyclerview.widget.ListUpdateCallback; import androidx.recyclerview.widget.RecyclerView; import com.google.common.collect.ImmutableList; import java.util.List; -import org.tasks.Callback; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; import org.tasks.R; import org.tasks.locale.Locale; public class CustomFilterAdapter extends RecyclerView.Adapter implements ListUpdateCallback { - private final Callback onClick; + private final Function1 onClick; private final Locale locale; private final AsyncListDiffer differ; public CustomFilterAdapter( - List objects, Locale locale, Callback onClick) { + List objects, Locale locale, Function1 onClick) { this.locale = locale; this.onClick = onClick; differ = new AsyncListDiffer<>(this, new AsyncDifferConfig.Builder<>(new CriterionDiffCallback()).build()); diff --git a/app/src/main/java/com/todoroo/astrid/core/CustomFilterItemTouchHelper.kt b/app/src/main/java/com/todoroo/astrid/core/CustomFilterItemTouchHelper.kt index c24ba2ddc..6784affd9 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CustomFilterItemTouchHelper.kt +++ b/app/src/main/java/com/todoroo/astrid/core/CustomFilterItemTouchHelper.kt @@ -5,15 +5,13 @@ import android.graphics.Canvas import android.graphics.drawable.ColorDrawable import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import org.tasks.Callback -import org.tasks.Callback2 import org.tasks.R class CustomFilterItemTouchHelper( private val context: Context, - private val onMove: Callback2, - private val onDelete: Callback, - private val onClear: Runnable) : ItemTouchHelper.Callback() { + private val onMove: (Int, Int) -> Unit, + private val onDelete: (Int) -> Unit, + private val onClear: () -> Unit) : ItemTouchHelper.Callback() { override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { return if (viewHolder.adapterPosition > 0) makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) else 0 } @@ -32,7 +30,7 @@ class CustomFilterItemTouchHelper( if (toPosition == 0) { return false } - onMove.call(src.adapterPosition, toPosition) + onMove.invoke(src.adapterPosition, toPosition) return true } @@ -62,10 +60,10 @@ class CustomFilterItemTouchHelper( (viewHolder as CriterionViewHolder).setMoving(false) - onClear.run() + onClear.invoke() } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - onDelete.call(viewHolder.adapterPosition) + onDelete.invoke(viewHolder.adapterPosition) } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/Callback2.java b/app/src/main/java/org/tasks/Callback2.java deleted file mode 100644 index 5d131ce86..000000000 --- a/app/src/main/java/org/tasks/Callback2.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.tasks; - -public interface Callback2 { - void call(T1 t1, T2 t2); -} diff --git a/app/src/main/java/org/tasks/Function.java b/app/src/main/java/org/tasks/Function.java deleted file mode 100644 index 606a5fa02..000000000 --- a/app/src/main/java/org/tasks/Function.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.tasks; - -public interface Function { - T call(); -} diff --git a/app/src/main/java/org/tasks/Notifier.java b/app/src/main/java/org/tasks/Notifier.java deleted file mode 100644 index 70ffc2269..000000000 --- a/app/src/main/java/org/tasks/Notifier.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.tasks; - -import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static com.google.common.collect.Iterables.skip; -import static com.google.common.collect.Lists.newArrayList; -import static org.tasks.notifications.NotificationManager.MAX_NOTIFICATIONS; -import static org.tasks.time.DateTimeUtils.currentTimeMillis; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import androidx.core.app.NotificationCompat; -import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.astrid.activity.MainActivity; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.dao.TaskDao; -import com.todoroo.astrid.data.Task; -import com.todoroo.astrid.reminders.ReminderService; -import com.todoroo.astrid.voice.VoiceOutputAssistant; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; -import org.tasks.injection.ForApplication; -import org.tasks.notifications.AudioManager; -import org.tasks.notifications.Notification; -import org.tasks.notifications.NotificationManager; -import org.tasks.notifications.TelephonyManager; -import org.tasks.preferences.Preferences; -import org.tasks.themes.ColorProvider; -import timber.log.Timber; - -public class Notifier { - - private final Context context; - private final TaskDao taskDao; - private final NotificationManager notificationManager; - private final TelephonyManager telephonyManager; - private final AudioManager audioManager; - private final VoiceOutputAssistant voiceOutputAssistant; - private final Preferences preferences; - private final ColorProvider colorProvider; - - @Inject - public Notifier( - @ForApplication Context context, - TaskDao taskDao, - NotificationManager notificationManager, - TelephonyManager telephonyManager, - AudioManager audioManager, - VoiceOutputAssistant voiceOutputAssistant, - Preferences preferences) { - this.context = context; - this.taskDao = taskDao; - this.notificationManager = notificationManager; - this.telephonyManager = telephonyManager; - this.audioManager = audioManager; - this.voiceOutputAssistant = voiceOutputAssistant; - this.preferences = preferences; - this.colorProvider = new ColorProvider(context, preferences); - } - - public void triggerFilterNotification(final Filter filter) { - List tasks = taskDao.fetchFiltered(filter); - int count = tasks.size(); - if (count == 0) { - return; - } - - Intent intent = new Intent(context, MainActivity.class); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); - intent.putExtra(MainActivity.OPEN_FILTER, filter); - PendingIntent pendingIntent = - PendingIntent.getActivity( - context, filter.listingTitle.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT); - - String summaryTitle = - context.getResources().getQuantityString(R.plurals.task_count, count, count); - NotificationCompat.InboxStyle style = - new NotificationCompat.InboxStyle().setBigContentTitle(summaryTitle); - int maxPriority = 3; - for (Task task : tasks) { - style.addLine(task.getTitle()); - maxPriority = Math.min(maxPriority, task.getPriority()); - } - - NotificationCompat.Builder builder = - new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_TASKER) - .setSmallIcon(R.drawable.ic_done_all_white_24dp) - .setCategory(NotificationCompat.CATEGORY_REMINDER) - .setTicker(summaryTitle) - .setContentTitle(summaryTitle) - .setContentText(filter.listingTitle) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - .setWhen(currentTimeMillis()) - .setShowWhen(true) - .setColor(colorProvider.getPriorityColor(maxPriority, true)) - .setGroupSummary(true) - .setGroup(filter.listingTitle) - .setStyle(style); - - notificationManager.notify(filter.listingTitle.hashCode(), builder, true, false, false); - } - - public void triggerNotifications(List entries) { - List notifications = new ArrayList<>(); - boolean ringFiveTimes = false; - boolean ringNonstop = false; - for (int i = 0; i < entries.size(); i++) { - org.tasks.notifications.Notification entry = entries.get(i); - Task task = taskDao.fetch(entry.getTaskId()); - if (task == null) { - continue; - } - if (entry.getType() != ReminderService.TYPE_RANDOM) { - ringFiveTimes |= task.isNotifyModeFive(); - ringNonstop |= task.isNotifyModeNonstop(); - } - NotificationCompat.Builder notification = notificationManager.getTaskNotification(entry); - if (notification != null) { - notifications.add(entry); - } - } - - if (notifications.isEmpty()) { - return; - } else { - Timber.d("Triggering %s", notifications); - } - - if (notifications.size() > MAX_NOTIFICATIONS) { - notifications = newArrayList(skip(notifications, notifications.size() - MAX_NOTIFICATIONS)); - } - - notificationManager.notifyTasks(notifications, true, ringNonstop, ringFiveTimes); - - if (preferences.getBoolean(R.string.p_voiceRemindersEnabled, false) - && !ringNonstop - && !audioManager.notificationsMuted() - && telephonyManager.callStateIdle()) { - for (org.tasks.notifications.Notification notification : notifications) { - AndroidUtilities.sleepDeep(2000); - voiceOutputAssistant.speak( - notificationManager.getTaskNotification(notification).build().tickerText.toString()); - } - } - } -} diff --git a/app/src/main/java/org/tasks/Notifier.kt b/app/src/main/java/org/tasks/Notifier.kt new file mode 100644 index 000000000..1db58e97a --- /dev/null +++ b/app/src/main/java/org/tasks/Notifier.kt @@ -0,0 +1,106 @@ +package org.tasks + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.astrid.activity.MainActivity +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.dao.TaskDao +import com.todoroo.astrid.reminders.ReminderService +import com.todoroo.astrid.voice.VoiceOutputAssistant +import org.tasks.injection.ForApplication +import org.tasks.notifications.AudioManager +import org.tasks.notifications.Notification +import org.tasks.notifications.NotificationManager +import org.tasks.notifications.TelephonyManager +import org.tasks.preferences.Preferences +import org.tasks.themes.ColorProvider +import org.tasks.time.DateTimeUtils +import timber.log.Timber +import java.util.* +import javax.inject.Inject +import kotlin.math.min + +class Notifier @Inject constructor( + @param:ForApplication private val context: Context, + private val taskDao: TaskDao, + private val notificationManager: NotificationManager, + private val telephonyManager: TelephonyManager, + private val audioManager: AudioManager, + private val voiceOutputAssistant: VoiceOutputAssistant, + private val preferences: Preferences) { + + private val colorProvider: ColorProvider = ColorProvider(context, preferences) + + fun triggerFilterNotification(filter: Filter) { + val tasks = taskDao.fetchFiltered(filter) + val count = tasks.size + if (count == 0) { + return + } + val intent = Intent(context, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK + intent.putExtra(MainActivity.OPEN_FILTER, filter) + val pendingIntent = PendingIntent.getActivity( + context, filter.listingTitle.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT) + val summaryTitle = context.resources.getQuantityString(R.plurals.task_count, count, count) + val style = NotificationCompat.InboxStyle().setBigContentTitle(summaryTitle) + var maxPriority = 3 + for (task in tasks) { + style.addLine(task.title) + maxPriority = min(maxPriority, task.priority) + } + val builder = NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_TASKER) + .setSmallIcon(R.drawable.ic_done_all_white_24dp) + .setCategory(NotificationCompat.CATEGORY_REMINDER) + .setTicker(summaryTitle) + .setContentTitle(summaryTitle) + .setContentText(filter.listingTitle) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setWhen(DateTimeUtils.currentTimeMillis()) + .setShowWhen(true) + .setColor(colorProvider.getPriorityColor(maxPriority, true)) + .setGroupSummary(true) + .setGroup(filter.listingTitle) + .setStyle(style) + notificationManager.notify(filter.listingTitle.hashCode().toLong(), builder, true, false, false) + } + + fun triggerNotifications(entries: List) { + val notifications: MutableList = ArrayList() + var ringFiveTimes = false + var ringNonstop = false + for (entry in entries.takeLast(NotificationManager.MAX_NOTIFICATIONS)) { + val task = taskDao.fetch(entry.taskId) ?: continue + if (entry.type != ReminderService.TYPE_RANDOM) { + ringFiveTimes = ringFiveTimes or task.isNotifyModeFive + ringNonstop = ringNonstop or task.isNotifyModeNonstop + } + val notification = notificationManager.getTaskNotification(entry) + if (notification != null) { + notifications.add(entry) + } + } + if (notifications.isEmpty()) { + return + } + + Timber.d("Triggering %s", notifications) + + notificationManager.notifyTasks(notifications, true, ringNonstop, ringFiveTimes) + + if (preferences.getBoolean(R.string.p_voiceRemindersEnabled, false) + && !ringNonstop + && !audioManager.notificationsMuted() + && telephonyManager.callStateIdle()) { + for (notification in notifications) { + AndroidUtilities.sleepDeep(2000) + voiceOutputAssistant.speak( + notificationManager.getTaskNotification(notification).build().tickerText.toString()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/PermissionUtil.java b/app/src/main/java/org/tasks/PermissionUtil.java deleted file mode 100644 index 8452f96ef..000000000 --- a/app/src/main/java/org/tasks/PermissionUtil.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.tasks; - -import android.content.pm.PackageManager; - -public abstract class PermissionUtil { - - public static boolean verifyPermissions(int[] grantResults) { - if (grantResults.length == 0) { - // request canceled - return false; - } - - for (int result : grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - return true; - } -} diff --git a/app/src/main/java/org/tasks/PermissionUtil.kt b/app/src/main/java/org/tasks/PermissionUtil.kt new file mode 100644 index 000000000..9190d5777 --- /dev/null +++ b/app/src/main/java/org/tasks/PermissionUtil.kt @@ -0,0 +1,9 @@ +package org.tasks + +import android.content.pm.PackageManager.PERMISSION_GRANTED + +object PermissionUtil { + @JvmStatic + fun verifyPermissions(grantResults: IntArray) = + grantResults.isNotEmpty() && grantResults.all { it == PERMISSION_GRANTED } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/Strings.java b/app/src/main/java/org/tasks/Strings.java deleted file mode 100644 index 6bed94485..000000000 --- a/app/src/main/java/org/tasks/Strings.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.tasks; - -public class Strings { - public static boolean isNullOrEmpty(String string) { - return string == null || string.isEmpty(); - } -} diff --git a/app/src/main/java/org/tasks/Strings.kt b/app/src/main/java/org/tasks/Strings.kt new file mode 100644 index 000000000..c0cc115b0 --- /dev/null +++ b/app/src/main/java/org/tasks/Strings.kt @@ -0,0 +1,6 @@ +package org.tasks + +object Strings { + @JvmStatic + fun isNullOrEmpty(string: String?) = string?.isEmpty() ?: true +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/Tasks.java b/app/src/main/java/org/tasks/Tasks.java deleted file mode 100644 index d52e9e5e9..000000000 --- a/app/src/main/java/org/tasks/Tasks.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.tasks; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.core.app.JobIntentService; -import androidx.work.Configuration; -import com.jakewharton.processphoenix.ProcessPhoenix; -import com.jakewharton.threetenabp.AndroidThreeTen; -import com.todoroo.astrid.service.Upgrader; -import dagger.Lazy; -import io.reactivex.Completable; -import io.reactivex.schedulers.Schedulers; -import javax.inject.Inject; -import org.tasks.billing.BillingClient; -import org.tasks.billing.Inventory; -import org.tasks.files.FileHelper; -import org.tasks.injection.ApplicationComponent; -import org.tasks.injection.ForApplication; -import org.tasks.injection.InjectingApplication; -import org.tasks.injection.InjectingJobIntentService; -import org.tasks.jobs.WorkManager; -import org.tasks.location.GeofenceApi; -import org.tasks.preferences.Preferences; -import org.tasks.receivers.RefreshReceiver; -import org.tasks.scheduling.CalendarNotificationIntentService; -import org.tasks.scheduling.NotificationSchedulerIntentService; -import org.tasks.scheduling.RefreshScheduler; -import org.tasks.themes.ThemeBase; -import org.tasks.widget.AppWidgetManager; -import timber.log.Timber; - -public class Tasks extends InjectingApplication implements Configuration.Provider { - - @Inject @ForApplication Context context; - @Inject Preferences preferences; - @Inject BuildSetup buildSetup; - @Inject Inventory inventory; - @Inject LocalBroadcastManager localBroadcastManager; - @Inject Lazy upgrader; - @Inject Lazy workManager; - @Inject Lazy refreshScheduler; - @Inject Lazy geofenceApi; - @Inject Lazy billingClient; - @Inject Lazy appWidgetManager; - - @Override - public void onCreate() { - super.onCreate(); - - if (ProcessPhoenix.isPhoenixProcess(this)) { - return; - } - - buildSetup.setup(); - - upgrade(); - - AndroidThreeTen.init(this); - - preferences.setSyncOngoing(false); - - ThemeBase.getThemeBase(preferences, inventory, null).setDefaultNightMode(); - - localBroadcastManager.registerRefreshReceiver(new RefreshBroadcastReceiver()); - - Completable.fromAction(this::doInBackground).subscribeOn(Schedulers.io()).subscribe(); - } - - private void upgrade() { - final int lastVersion = preferences.getLastSetVersion(); - final int currentVersion = BuildConfig.VERSION_CODE; - - Timber.i("Astrid Startup. %s => %s", lastVersion, currentVersion); - - // invoke upgrade service - if (lastVersion != currentVersion) { - upgrader.get().upgrade(lastVersion, currentVersion); - preferences.setDefaults(); - } - } - - private void doInBackground() { - NotificationSchedulerIntentService.enqueueWork(context, false); - CalendarNotificationIntentService.enqueueWork(context); - refreshScheduler.get().scheduleAll(); - workManager.get().updateBackgroundSync(); - workManager.get().scheduleMidnightRefresh(); - workManager.get().scheduleBackup(); - workManager.get().scheduleConfigRefresh(); - geofenceApi.get().registerAll(); - FileHelper.delete(context, preferences.getCacheDirectory()); - billingClient.get().queryPurchases(); - appWidgetManager.get().reconfigureWidgets(); - } - - @Override - protected void inject(ApplicationComponent component) { - component.inject(this); - } - - @NonNull - @Override - public Configuration getWorkManagerConfiguration() { - return new Configuration.Builder() - .setMinimumLoggingLevel(BuildConfig.DEBUG ? Log.DEBUG : Log.INFO) - .build(); - } - - private static class RefreshBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - JobIntentService.enqueueWork( - context, - RefreshReceiver.class, - InjectingJobIntentService.JOB_ID_REFRESH_RECEIVER, - intent); - } - } -} diff --git a/app/src/main/java/org/tasks/Tasks.kt b/app/src/main/java/org/tasks/Tasks.kt new file mode 100644 index 000000000..cf11c6c13 --- /dev/null +++ b/app/src/main/java/org/tasks/Tasks.kt @@ -0,0 +1,104 @@ +package org.tasks + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.core.app.JobIntentService +import androidx.work.Configuration +import com.jakewharton.processphoenix.ProcessPhoenix +import com.jakewharton.threetenabp.AndroidThreeTen +import com.todoroo.astrid.service.Upgrader +import dagger.Lazy +import io.reactivex.Completable +import io.reactivex.schedulers.Schedulers +import org.tasks.billing.BillingClient +import org.tasks.billing.Inventory +import org.tasks.files.FileHelper +import org.tasks.injection.ApplicationComponent +import org.tasks.injection.ForApplication +import org.tasks.injection.InjectingApplication +import org.tasks.injection.InjectingJobIntentService +import org.tasks.jobs.WorkManager +import org.tasks.location.GeofenceApi +import org.tasks.preferences.Preferences +import org.tasks.receivers.RefreshReceiver +import org.tasks.scheduling.CalendarNotificationIntentService +import org.tasks.scheduling.NotificationSchedulerIntentService +import org.tasks.scheduling.RefreshScheduler +import org.tasks.themes.ThemeBase +import org.tasks.widget.AppWidgetManager +import timber.log.Timber +import javax.inject.Inject + +class Tasks : InjectingApplication(), Configuration.Provider { + @Inject @ForApplication lateinit var context: Context + @Inject lateinit var preferences: Preferences + @Inject lateinit var buildSetup: BuildSetup + @Inject lateinit var inventory: Inventory + @Inject lateinit var localBroadcastManager: LocalBroadcastManager + @Inject lateinit var upgrader: Lazy + @Inject lateinit var workManager: Lazy + @Inject lateinit var refreshScheduler: Lazy + @Inject lateinit var geofenceApi: Lazy + @Inject lateinit var billingClient: Lazy + @Inject lateinit var appWidgetManager: Lazy + + override fun onCreate() { + super.onCreate() + if (ProcessPhoenix.isPhoenixProcess(this)) { + return + } + buildSetup.setup() + upgrade() + AndroidThreeTen.init(this) + preferences.isSyncOngoing = false + ThemeBase.getThemeBase(preferences, inventory, null).setDefaultNightMode() + localBroadcastManager.registerRefreshReceiver(RefreshBroadcastReceiver()) + Completable.fromAction { doInBackground() }.subscribeOn(Schedulers.io()).subscribe() + } + + private fun upgrade() { + val lastVersion = preferences.lastSetVersion + val currentVersion = BuildConfig.VERSION_CODE + Timber.i("Astrid Startup. %s => %s", lastVersion, currentVersion) + + // invoke upgrade service + if (lastVersion != currentVersion) { + upgrader.get().upgrade(lastVersion, currentVersion) + preferences.setDefaults() + } + } + + private fun doInBackground() { + NotificationSchedulerIntentService.enqueueWork(context, false) + CalendarNotificationIntentService.enqueueWork(context) + refreshScheduler.get().scheduleAll() + workManager.get().updateBackgroundSync() + workManager.get().scheduleMidnightRefresh() + workManager.get().scheduleBackup() + workManager.get().scheduleConfigRefresh() + geofenceApi.get().registerAll() + FileHelper.delete(context, preferences.cacheDirectory) + billingClient.get().queryPurchases() + appWidgetManager.get().reconfigureWidgets() + } + + override fun inject(component: ApplicationComponent) = component.inject(this) + + override fun getWorkManagerConfiguration(): Configuration { + return Configuration.Builder() + .setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.INFO) + .build() + } + + private class RefreshBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + JobIntentService.enqueueWork( + context, + RefreshReceiver::class.java, + InjectingJobIntentService.JOB_ID_REFRESH_RECEIVER, + intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.java b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.java deleted file mode 100644 index f9a3b0ece..000000000 --- a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.java +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ - -package org.tasks.activities; - -import static com.google.common.collect.Iterables.find; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Lists.transform; -import static com.todoroo.andlib.utility.AndroidUtilities.mapToSerializedString; -import static org.tasks.Strings.isNullOrEmpty; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.FrameLayout; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import butterknife.BindView; -import butterknife.OnClick; -import butterknife.OnTextChanged; -import com.google.android.material.button.MaterialButtonToggleGroup; -import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; -import com.todoroo.andlib.data.Property.CountProperty; -import com.todoroo.andlib.sql.Query; -import com.todoroo.andlib.sql.UnaryCriterion; -import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.astrid.activity.MainActivity; -import com.todoroo.astrid.activity.TaskListFragment; -import com.todoroo.astrid.api.CustomFilter; -import com.todoroo.astrid.api.CustomFilterCriterion; -import com.todoroo.astrid.api.MultipleSelectCriterion; -import com.todoroo.astrid.api.PermaSql; -import com.todoroo.astrid.api.TextInputCriterion; -import com.todoroo.astrid.core.CriterionInstance; -import com.todoroo.astrid.core.CustomFilterAdapter; -import com.todoroo.astrid.core.CustomFilterItemTouchHelper; -import com.todoroo.astrid.dao.Database; -import com.todoroo.astrid.dao.TaskDao.TaskCriteria; -import com.todoroo.astrid.data.Task; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import javax.inject.Inject; -import org.tasks.R; -import org.tasks.data.FilterDao; -import org.tasks.dialogs.AlertDialogBuilder; -import org.tasks.filters.FilterCriteriaProvider; -import org.tasks.injection.ActivityComponent; -import org.tasks.locale.Locale; - -public class FilterSettingsActivity extends BaseListSettingsActivity { - - public static final String TOKEN_FILTER = "token_filter"; - public static final String EXTRA_TITLE = "extra_title"; - public static final String EXTRA_CRITERIA = "extra_criteria"; - @Inject FilterDao filterDao; - @Inject Locale locale; - @Inject Database database; - @Inject FilterCriteriaProvider filterCriteriaProvider; - private List criteria; - - @BindView(R.id.name) - TextInputEditText name; - - @BindView(R.id.name_layout) - TextInputLayout nameLayout; - - @BindView(R.id.recycler_view) - RecyclerView recyclerView; - - @BindView(R.id.fab) - ExtendedFloatingActionButton fab; - - private CustomFilter filter; - private CustomFilterAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - filter = getIntent().getParcelableExtra(TOKEN_FILTER); - - super.onCreate(savedInstanceState); - - if (savedInstanceState == null && filter != null) { - selectedColor = filter.tint; - selectedIcon = filter.icon; - name.setText(filter.listingTitle); - } - - if (savedInstanceState != null) { - criteria = - CriterionInstance.fromString( - filterCriteriaProvider, savedInstanceState.getString(EXTRA_CRITERIA)); - } else if (filter != null) { - criteria = CriterionInstance.fromString(filterCriteriaProvider, filter.getCriterion()); - } else if (getIntent().hasExtra(EXTRA_CRITERIA)) { - name.setText(getIntent().getStringExtra(EXTRA_TITLE)); - criteria = - CriterionInstance.fromString( - filterCriteriaProvider, getIntent().getStringExtra(EXTRA_CRITERIA)); - } else { - CriterionInstance instance = new CriterionInstance(); - instance.criterion = filterCriteriaProvider.getStartingUniverse(); - instance.type = CriterionInstance.TYPE_UNIVERSE; - criteria = newArrayList(instance); - } - - adapter = new CustomFilterAdapter(criteria, locale, this::onClick); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setAdapter(adapter); - new ItemTouchHelper( - new CustomFilterItemTouchHelper(this, this::onMove, this::onDelete, this::updateList)) - .attachToRecyclerView(recyclerView); - - fab.setExtended(isNew() || adapter.getItemCount() <= 1); - - if (isNew()) { - toolbar.inflateMenu(R.menu.menu_help); - } - - updateList(); - - updateTheme(); - } - - private void onDelete(int index) { - criteria.remove(index); - updateList(); - } - - private void onMove(int from, int to) { - CriterionInstance criterion = criteria.remove(from); - criteria.add(to, criterion); - adapter.notifyItemMoved(from, to); - } - - private void onClick(String replaceId) { - CriterionInstance criterionInstance = find(criteria, c -> c.getId().equals(replaceId)); - - View view = - getLayoutInflater().inflate(R.layout.dialog_custom_filter_row_edit, recyclerView, false); - MaterialButtonToggleGroup group = view.findViewById(R.id.button_toggle); - int selected = getSelected(criterionInstance); - group.check(selected); - dialogBuilder - .newDialog(criterionInstance.getTitleFromCriterion()) - .setView(view) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton( - android.R.string.ok, - (dialog, which) -> { - criterionInstance.type = getType(group.getCheckedButtonId()); - updateList(); - }) - .setNeutralButton(R.string.help,(v, which) -> help()) - .show(); - } - - private int getSelected(CriterionInstance instance) { - switch (instance.type) { - case CriterionInstance.TYPE_ADD: - return R.id.button_or; - case CriterionInstance.TYPE_SUBTRACT: - return R.id.button_not; - default: - return R.id.button_and; - } - } - - private int getType(int selected) { - switch (selected) { - case R.id.button_or: - return CriterionInstance.TYPE_ADD; - case R.id.button_not: - return CriterionInstance.TYPE_SUBTRACT; - default: - return CriterionInstance.TYPE_INTERSECT; - } - } - - @OnClick(R.id.fab) - void addCriteria() { - AndroidUtilities.hideKeyboard(this); - fab.shrink(); - - List all = filterCriteriaProvider.getAll(); - List names = transform(all, CustomFilterCriterion::getName); - dialogBuilder.newDialog() - .setItems(names, (dialog, which) -> { - CriterionInstance instance = new CriterionInstance(); - instance.criterion = all.get(which); - showOptionsFor(instance, () -> { - criteria.add(instance); - updateList(); - }); - dialog.dismiss(); - }) - .show(); - } - - /** Show options menu for the given criterioninstance */ - private void showOptionsFor(final CriterionInstance item, final Runnable onComplete) { - AlertDialogBuilder dialog = dialogBuilder.newDialog(item.criterion.name); - - if (item.criterion instanceof MultipleSelectCriterion) { - MultipleSelectCriterion multiSelectCriterion = (MultipleSelectCriterion) item.criterion; - final String[] titles = multiSelectCriterion.entryTitles; - DialogInterface.OnClickListener listener = - (click, which) -> { - item.selectedIndex = which; - if (onComplete != null) { - onComplete.run(); - } - }; - dialog.setItems(titles, listener); - } else if (item.criterion instanceof TextInputCriterion) { - TextInputCriterion textInCriterion = (TextInputCriterion) item.criterion; - FrameLayout frameLayout = new FrameLayout(this); - frameLayout.setPadding(10, 0, 10, 0); - final EditText editText = new EditText(this); - editText.setText(item.selectedText); - editText.setHint(textInCriterion.hint); - frameLayout.addView( - editText, - new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); - dialog - .setView(frameLayout) - .setPositiveButton( - android.R.string.ok, - (dialogInterface, which) -> { - item.selectedText = editText.getText().toString(); - if (onComplete != null) { - onComplete.run(); - } - }); - } - - dialog.show(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putString(EXTRA_CRITERIA, CriterionInstance.serialize(criteria)); - } - - @Override - protected boolean isNew() { - return filter == null; - } - - @Override - protected String getToolbarTitle() { - return isNew() ? getString(R.string.FLA_new_filter) : filter.listingTitle; - } - - @OnTextChanged(R.id.name) - void onTextChanged() { - nameLayout.setError(null); - } - - @Override - public void inject(ActivityComponent component) { - component.inject(this); - } - - @Override - protected void save() { - String newName = getNewName(); - - if (isNullOrEmpty(newName)) { - nameLayout.setError(getString(R.string.name_cannot_be_empty)); - return; - } - - if (hasChanges()) { - org.tasks.data.Filter f = new org.tasks.data.Filter(); - f.setTitle(newName); - f.setColor(selectedColor); - f.setIcon(selectedIcon); - f.setValues(mapToSerializedString(getValues())); - f.setCriterion(CriterionInstance.serialize(criteria)); - f.setSql(getSql()); - if (isNew()) { - f.setId(filterDao.insert(f)); - } else { - f.setId(filter.getId()); - filterDao.update(f); - } - setResult( - RESULT_OK, - new Intent(TaskListFragment.ACTION_RELOAD) - .putExtra(MainActivity.OPEN_FILTER, new CustomFilter(f))); - } - - finish(); - } - - private String getNewName() { - return name.getText().toString().trim(); - } - - @Override - protected boolean hasChanges() { - if (isNew()) { - return !isNullOrEmpty(getNewName()) - || selectedColor != 0 - || selectedIcon != -1 - || criteria.size() > 1; - } - return !getNewName().equals(filter.listingTitle) - || selectedColor != filter.tint - || selectedIcon != filter.icon - || !CriterionInstance.serialize(criteria).equals(filter.getCriterion()) - || !getValues().equals(filter.valuesForNewTasks) - || !getSql().equals(filter.getOriginalSqlQuery()); - } - - @Override - public void finish() { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(name.getWindowToken(), 0); - super.finish(); - } - - @Override - protected int getLayout() { - return R.layout.filter_settings_activity; - } - - @Override - protected void delete() { - filterDao.delete(filter.getId()); - setResult( - RESULT_OK, new Intent(TaskListFragment.ACTION_DELETED).putExtra(TOKEN_FILTER, filter)); - finish(); - } - - @Override - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.menu_help) { - help(); - return true; - } else { - return super.onMenuItemClick(item); - } - } - - private void help() { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://tasks.org/filters"))); - } - - private void updateList() { - int max = 0, last = -1; - - StringBuilder sql = - new StringBuilder(Query.select(new CountProperty()).from(Task.TABLE).toString()) - .append(" WHERE "); - - for (CriterionInstance instance : criteria) { - String value = instance.getValueFromCriterion(); - if (value == null && instance.criterion.sql != null && instance.criterion.sql.contains("?")) { - value = ""; - } - - switch (instance.type) { - case CriterionInstance.TYPE_ADD: - sql.append("OR "); - break; - case CriterionInstance.TYPE_SUBTRACT: - sql.append("AND NOT "); - break; - case CriterionInstance.TYPE_INTERSECT: - sql.append("AND "); - break; - } - - // special code for all tasks universe - if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) { - sql.append(TaskCriteria.activeAndVisible()).append(' '); - } else { - String subSql = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value)); - subSql = PermaSql.replacePlaceholdersForQuery(subSql); - sql.append(Task.ID).append(" IN (").append(subSql).append(") "); - } - - try (Cursor cursor = database.query(sql.toString(), null)) { - cursor.moveToNext(); - instance.start = last == -1 ? cursor.getInt(0) : last; - instance.end = cursor.getInt(0); - last = instance.end; - max = Math.max(max, last); - } - } - - for (CriterionInstance instance : criteria) { - instance.max = max; - } - - adapter.submitList(criteria); - } - - private String getValue(CriterionInstance instance) { - String value = instance.getValueFromCriterion(); - if (value == null && instance.criterion.sql != null && instance.criterion.sql.contains("?")) { - value = ""; - } - return value; - } - - private String getSql() { - StringBuilder sql = new StringBuilder(" WHERE "); - for (CriterionInstance instance : criteria) { - String value = getValue(instance); - - switch (instance.type) { - case CriterionInstance.TYPE_ADD: - sql.append("OR "); - break; - case CriterionInstance.TYPE_SUBTRACT: - sql.append("AND NOT "); - break; - case CriterionInstance.TYPE_INTERSECT: - sql.append("AND "); - break; - } - - // special code for all tasks universe - if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) { - sql.append(TaskCriteria.activeAndVisible()).append(' '); - } else { - String subSql = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value)); - sql.append(Task.ID).append(" IN (").append(subSql).append(") "); - } - } - return sql.toString(); - } - - private Map getValues() { - Map values = new HashMap<>(); - for (CriterionInstance instance : criteria) { - String value = getValue(instance); - - if (instance.criterion.valuesForNewTasks != null - && instance.type == CriterionInstance.TYPE_INTERSECT) { - for (Entry entry : instance.criterion.valuesForNewTasks.entrySet()) { - values.put( - entry.getKey().replace("?", value), entry.getValue().toString().replace("?", value)); - } - } - } - return values; - } -} diff --git a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt new file mode 100644 index 000000000..a63891757 --- /dev/null +++ b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt @@ -0,0 +1,390 @@ +package org.tasks.activities + +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.MenuItem +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import android.widget.FrameLayout +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.OnClick +import butterknife.OnTextChanged +import com.google.android.material.button.MaterialButtonToggleGroup +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import com.todoroo.andlib.data.Property.CountProperty +import com.todoroo.andlib.sql.Query +import com.todoroo.andlib.sql.UnaryCriterion +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.astrid.activity.MainActivity +import com.todoroo.astrid.activity.TaskListFragment +import com.todoroo.astrid.api.* +import com.todoroo.astrid.core.CriterionInstance +import com.todoroo.astrid.core.CustomFilterAdapter +import com.todoroo.astrid.core.CustomFilterItemTouchHelper +import com.todoroo.astrid.dao.Database +import com.todoroo.astrid.dao.TaskDao.TaskCriteria.activeAndVisible +import com.todoroo.astrid.data.Task +import org.tasks.R +import org.tasks.Strings +import org.tasks.data.Filter +import org.tasks.data.FilterDao +import org.tasks.filters.FilterCriteriaProvider +import org.tasks.injection.ActivityComponent +import org.tasks.locale.Locale +import java.util.* +import javax.inject.Inject +import kotlin.math.max + +class FilterSettingsActivity : BaseListSettingsActivity() { + @Inject lateinit var filterDao: FilterDao + @Inject lateinit var locale: Locale + @Inject lateinit var database: Database + @Inject lateinit var filterCriteriaProvider: FilterCriteriaProvider + + @BindView(R.id.name) + lateinit var name: TextInputEditText + + @BindView(R.id.name_layout) + lateinit var nameLayout: TextInputLayout + + @BindView(R.id.recycler_view) + lateinit var recyclerView: RecyclerView + + @BindView(R.id.fab) + lateinit var fab: ExtendedFloatingActionButton + + private var filter: CustomFilter? = null + private lateinit var adapter: CustomFilterAdapter + private lateinit var criteria: MutableList + + override fun onCreate(savedInstanceState: Bundle?) { + filter = intent.getParcelableExtra(TOKEN_FILTER) + super.onCreate(savedInstanceState) + if (savedInstanceState == null && filter != null) { + selectedColor = filter!!.tint + selectedIcon = filter!!.icon + name.setText(filter!!.listingTitle) + } + when { + savedInstanceState != null -> { + criteria = CriterionInstance.fromString( + filterCriteriaProvider, savedInstanceState.getString(EXTRA_CRITERIA)) + } + filter != null -> { + criteria = CriterionInstance.fromString(filterCriteriaProvider, filter!!.criterion) + } + intent.hasExtra(EXTRA_CRITERIA) -> { + name.setText(intent.getStringExtra(EXTRA_TITLE)) + criteria = CriterionInstance.fromString( + filterCriteriaProvider, intent.getStringExtra(EXTRA_CRITERIA)) + } + else -> { + val instance = CriterionInstance() + instance.criterion = filterCriteriaProvider.startingUniverse + instance.type = CriterionInstance.TYPE_UNIVERSE + criteria = mutableListOf(instance) + } + } + adapter = CustomFilterAdapter(criteria, locale) { replaceId: String -> onClick(replaceId) } + recyclerView.layoutManager = LinearLayoutManager(this) + recyclerView.adapter = adapter + ItemTouchHelper( + CustomFilterItemTouchHelper(this, this::onMove, this::onDelete, this::updateList)) + .attachToRecyclerView(recyclerView) + fab.isExtended = isNew || adapter.itemCount <= 1 + if (isNew) { + toolbar.inflateMenu(R.menu.menu_help) + } + updateList() + updateTheme() + } + + private fun onDelete(index: Int) { + criteria.removeAt(index) + updateList() + return + } + + private fun onMove(from: Int, to: Int) { + val criterion = criteria.removeAt(from) + criteria.add(to, criterion) + adapter.notifyItemMoved(from, to) + return + } + + private fun onClick(replaceId: String) { + val criterionInstance = criteria.find { it.id == replaceId }!! + val view = layoutInflater.inflate(R.layout.dialog_custom_filter_row_edit, recyclerView, false) + val group: MaterialButtonToggleGroup = view.findViewById(R.id.button_toggle) + val selected = getSelected(criterionInstance) + group.check(selected) + dialogBuilder + .newDialog(criterionInstance.titleFromCriterion) + .setView(view) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + criterionInstance.type = getType(group.checkedButtonId) + updateList() + } + .setNeutralButton(R.string.help) { _, _ -> help() } + .show() + return + } + + private fun getSelected(instance: CriterionInstance): Int { + return when (instance.type) { + CriterionInstance.TYPE_ADD -> R.id.button_or + CriterionInstance.TYPE_SUBTRACT -> R.id.button_not + else -> R.id.button_and + } + } + + private fun getType(selected: Int): Int { + return when (selected) { + R.id.button_or -> CriterionInstance.TYPE_ADD + R.id.button_not -> CriterionInstance.TYPE_SUBTRACT + else -> CriterionInstance.TYPE_INTERSECT + } + } + + @OnClick(R.id.fab) + fun addCriteria() { + AndroidUtilities.hideKeyboard(this) + fab.shrink() + val all = filterCriteriaProvider.all + val names = all.map(CustomFilterCriterion::getName) + dialogBuilder.newDialog() + .setItems(names) { dialog: DialogInterface, which: Int -> + val instance = CriterionInstance() + instance.criterion = all[which] + showOptionsFor(instance, Runnable { + criteria.add(instance) + updateList() + }) + dialog.dismiss() + } + .show() + } + + /** Show options menu for the given criterioninstance */ + private fun showOptionsFor(item: CriterionInstance, onComplete: Runnable?) { + val dialog = dialogBuilder.newDialog(item.criterion.name) + if (item.criterion is MultipleSelectCriterion) { + val multiSelectCriterion = item.criterion as MultipleSelectCriterion + val titles = multiSelectCriterion.entryTitles + val listener = DialogInterface.OnClickListener { _: DialogInterface?, which: Int -> + item.selectedIndex = which + onComplete?.run() + } + dialog.setItems(titles, listener) + } else if (item.criterion is TextInputCriterion) { + val textInCriterion = item.criterion as TextInputCriterion + val frameLayout = FrameLayout(this) + frameLayout.setPadding(10, 0, 10, 0) + val editText = EditText(this) + editText.setText(item.selectedText) + editText.hint = textInCriterion.hint + frameLayout.addView( + editText, + FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)) + dialog + .setView(frameLayout) + .setPositiveButton(android.R.string.ok) { _, _ -> + item.selectedText = editText.text.toString() + onComplete?.run() + } + } + dialog.show() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(EXTRA_CRITERIA, CriterionInstance.serialize(criteria)) + } + + override fun isNew(): Boolean { + return filter == null + } + + override fun getToolbarTitle(): String { + return if (isNew) getString(R.string.FLA_new_filter) else filter!!.listingTitle + } + + @OnTextChanged(R.id.name) + fun onTextChanged() { + nameLayout.error = null + } + + override fun inject(component: ActivityComponent) = component.inject(this) + + override fun save() { + val newName = newName + if (Strings.isNullOrEmpty(newName)) { + nameLayout.error = getString(R.string.name_cannot_be_empty) + return + } + if (hasChanges()) { + val f = Filter() + f.title = newName + f.setColor(selectedColor) + f.setIcon(selectedIcon) + f.values = AndroidUtilities.mapToSerializedString(values) + f.criterion = CriterionInstance.serialize(criteria) + f.setSql(sql) + if (isNew) { + f.id = filterDao.insert(f) + } else { + f.id = filter!!.id + filterDao.update(f) + } + setResult( + Activity.RESULT_OK, + Intent(TaskListFragment.ACTION_RELOAD) + .putExtra(MainActivity.OPEN_FILTER, CustomFilter(f))) + } + finish() + } + + private val newName: String + get() = name.text.toString().trim { it <= ' ' } + + override fun hasChanges(): Boolean { + return if (isNew) { + (!Strings.isNullOrEmpty(newName) + || selectedColor != 0 || selectedIcon != -1 || criteria.size > 1) + } else newName != filter!!.listingTitle + || selectedColor != filter!!.tint || selectedIcon != filter!!.icon || CriterionInstance.serialize(criteria) != filter!!.criterion + || values != filter!!.valuesForNewTasks + || sql != filter!!.originalSqlQuery + } + + override fun finish() { + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(name.windowToken, 0) + super.finish() + } + + override fun getLayout(): Int { + return R.layout.filter_settings_activity + } + + override fun delete() { + filterDao.delete(filter!!.id) + setResult( + Activity.RESULT_OK, Intent(TaskListFragment.ACTION_DELETED).putExtra(TOKEN_FILTER, filter)) + finish() + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + return if (item.itemId == R.id.menu_help) { + help() + true + } else { + super.onMenuItemClick(item) + } + } + + private fun help() { + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://tasks.org/filters"))) + } + + private fun updateList() { + var max = 0 + var last = -1 + val sql = StringBuilder(Query.select(CountProperty()).from(Task.TABLE).toString()) + .append(" WHERE ") + for (instance in criteria) { + var value = instance.valueFromCriterion + if (value == null && instance.criterion.sql != null && instance.criterion.sql.contains("?")) { + value = "" + } + when (instance.type) { + CriterionInstance.TYPE_ADD -> sql.append("OR ") + CriterionInstance.TYPE_SUBTRACT -> sql.append("AND NOT ") + CriterionInstance.TYPE_INTERSECT -> sql.append("AND ") + } + + // special code for all tasks universe + if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) { + sql.append(activeAndVisible()).append(' ') + } else { + var subSql: String? = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value)) + subSql = PermaSql.replacePlaceholdersForQuery(subSql) + sql.append(Task.ID).append(" IN (").append(subSql).append(") ") + } + database.query(sql.toString(), null).use { cursor -> + cursor.moveToNext() + instance.start = if (last == -1) cursor.getInt(0) else last + instance.end = cursor.getInt(0) + last = instance.end + max = max(max, last) + } + } + for (instance in criteria) { + instance.max = max + } + adapter.submitList(criteria) + } + + private fun getValue(instance: CriterionInstance): String? { + var value = instance.valueFromCriterion + if (value == null && instance.criterion.sql != null && instance.criterion.sql.contains("?")) { + value = "" + } + return value + } + + // special code for all tasks universe + private val sql: String + get() { + val sql = StringBuilder(" WHERE ") + for (instance in criteria) { + val value = getValue(instance) + when (instance.type) { + CriterionInstance.TYPE_ADD -> sql.append("OR ") + CriterionInstance.TYPE_SUBTRACT -> sql.append("AND NOT ") + CriterionInstance.TYPE_INTERSECT -> sql.append("AND ") + } + + // special code for all tasks universe + if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) { + sql.append(activeAndVisible()).append(' ') + } else { + val subSql = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value)) + sql.append(Task.ID).append(" IN (").append(subSql).append(") ") + } + } + return sql.toString() + } + + private val values: Map + get() { + val values: MutableMap = HashMap() + for (instance in criteria) { + val value = getValue(instance) + if (instance.criterion.valuesForNewTasks != null + && instance.type == CriterionInstance.TYPE_INTERSECT) { + for ((key, value1) in instance.criterion.valuesForNewTasks) { + values[key.replace("?", value!!)] = value1.toString().replace("?", value) + } + } + } + return values + } + + companion object { + const val TOKEN_FILTER = "token_filter" + const val EXTRA_TITLE = "extra_title" + const val EXTRA_CRITERIA = "extra_criteria" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt b/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt index 1bae9f890..99cccd00c 100644 --- a/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt +++ b/app/src/main/java/org/tasks/dialogs/ColorPalettePicker.kt @@ -12,7 +12,6 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import butterknife.ButterKnife -import org.tasks.Callback import org.tasks.R import org.tasks.billing.Inventory import org.tasks.billing.PurchaseActivity @@ -88,10 +87,7 @@ class ColorPalettePicker : InjectingDialogFragment() { Palette.WIDGET -> colorProvider.getWidgetColors() } - val iconPickerAdapter = ColorPickerAdapter( - context as Activity, - inventory, - Callback { index: Int -> onSelected(index) }) + val iconPickerAdapter = ColorPickerAdapter(context as Activity, inventory, this::onSelected) recyclerView.layoutManager = IconLayoutManager(context) recyclerView.adapter = iconPickerAdapter iconPickerAdapter.submitList(colors) diff --git a/app/src/main/java/org/tasks/dialogs/ColorPickerAdapter.kt b/app/src/main/java/org/tasks/dialogs/ColorPickerAdapter.kt index e25b0145e..aa5c75854 100644 --- a/app/src/main/java/org/tasks/dialogs/ColorPickerAdapter.kt +++ b/app/src/main/java/org/tasks/dialogs/ColorPickerAdapter.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter -import org.tasks.Callback import org.tasks.R import org.tasks.billing.Inventory import org.tasks.dialogs.ColorPalettePicker.Pickable @@ -12,7 +11,7 @@ import org.tasks.dialogs.ColorPalettePicker.Pickable class ColorPickerAdapter( private val activity: Activity, private val inventory: Inventory, - private val onSelected: Callback + private val onSelected: (Int) -> Unit ) : ListAdapter(DiffCallback()) { enum class Palette { diff --git a/app/src/main/java/org/tasks/filters/FilterProvider.java b/app/src/main/java/org/tasks/filters/FilterProvider.java deleted file mode 100644 index 331ed403d..000000000 --- a/app/src/main/java/org/tasks/filters/FilterProvider.java +++ /dev/null @@ -1,382 +0,0 @@ -package org.tasks.filters; - -import static com.google.common.collect.Iterables.concat; -import static com.google.common.collect.Iterables.filter; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Lists.transform; -import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread; -import static com.todoroo.andlib.utility.DateUtilities.now; -import static java.util.Collections.emptyList; -import static org.tasks.Strings.isNullOrEmpty; -import static org.tasks.caldav.CaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT; -import static org.tasks.ui.NavigationDrawerFragment.REQUEST_DONATE; -import static org.tasks.ui.NavigationDrawerFragment.REQUEST_PURCHASE; -import static org.tasks.ui.NavigationDrawerFragment.REQUEST_SETTINGS; - -import android.content.Context; -import android.content.Intent; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.api.FilterListItem; -import com.todoroo.astrid.core.BuiltInFilterExposer; -import com.todoroo.astrid.core.CustomFilterExposer; -import com.todoroo.astrid.timers.TimerFilterExposer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import javax.inject.Inject; -import org.tasks.BuildConfig; -import org.tasks.Function; -import org.tasks.R; -import org.tasks.activities.GoogleTaskListSettingsActivity; -import org.tasks.activities.TagSettingsActivity; -import org.tasks.billing.Inventory; -import org.tasks.caldav.CaldavCalendarSettingsActivity; -import org.tasks.data.CaldavAccount; -import org.tasks.data.CaldavDao; -import org.tasks.data.GoogleTaskAccount; -import org.tasks.data.GoogleTaskListDao; -import org.tasks.data.LocationDao; -import org.tasks.data.TagDataDao; -import org.tasks.etesync.EteSyncCalendarSettingsActivity; -import org.tasks.filters.NavigationDrawerSubheader.SubheaderType; -import org.tasks.injection.ForApplication; -import org.tasks.location.LocationPickerActivity; -import org.tasks.preferences.HelpAndFeedback; -import org.tasks.preferences.MainPreferences; -import org.tasks.preferences.Preferences; -import org.tasks.ui.NavigationDrawerFragment; - -public class FilterProvider { - - private final Context context; - private final Inventory inventory; - private final BuiltInFilterExposer builtInFilterExposer; - private final TimerFilterExposer timerFilterExposer; - private final CustomFilterExposer customFilterExposer; - private final TagDataDao tagDataDao; - private final GoogleTaskListDao googleTaskListDao; - private final CaldavDao caldavDao; - private final Preferences preferences; - private final LocationDao locationDao; - - @Inject - public FilterProvider( - @ForApplication Context context, - Inventory inventory, - BuiltInFilterExposer builtInFilterExposer, - TimerFilterExposer timerFilterExposer, - CustomFilterExposer customFilterExposer, - TagDataDao tagDataDao, - GoogleTaskListDao googleTaskListDao, - CaldavDao caldavDao, - Preferences preferences, - LocationDao locationDao) { - this.context = context; - this.inventory = inventory; - this.builtInFilterExposer = builtInFilterExposer; - this.timerFilterExposer = timerFilterExposer; - this.customFilterExposer = customFilterExposer; - this.tagDataDao = tagDataDao; - this.googleTaskListDao = googleTaskListDao; - this.caldavDao = caldavDao; - this.preferences = preferences; - this.locationDao = locationDao; - } - - public List getRemoteListPickerItems() { - assertNotMainThread(); - - List items = new ArrayList<>(); - - Filter item = new Filter(context.getString(R.string.dont_sync), null); - item.icon = R.drawable.ic_outline_cloud_off_24px; - items.add(item); - - for (Map.Entry> filters : getGoogleTaskFilters()) { - GoogleTaskAccount account = filters.getKey(); - items.addAll( - getSubmenu( - account.getAccount(), - !isNullOrEmpty(account.getError()), - filters.getValue(), - true, - account.isCollapsed(), - SubheaderType.GOOGLE_TASKS, - account.getId())); - } - - for (Map.Entry> filters : getCaldavFilters()) { - CaldavAccount account = filters.getKey(); - items.addAll( - getSubmenu( - account.getName(), - !isNullOrEmpty(account.getError()), - filters.getValue(), - true, - account.isCollapsed(), - SubheaderType.CALDAV, - account.getId())); - } - - return items; - } - - private void addFilters(List items, boolean navigationDrawer) { - if (!preferences.getBoolean(R.string.p_filters_enabled, true)) { - return; - } - - items.addAll(getSubmenu(R.string.filters, R.string.p_collapse_filters, this::getFilters)); - - if (navigationDrawer && !preferences.getBoolean(R.string.p_collapse_filters, false)) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.add_filter), - R.drawable.ic_outline_add_24px, - NavigationDrawerFragment.REQUEST_NEW_FILTER)); - } - } - - private void addTags(List items, boolean navigationDrawer) { - if (!preferences.getBoolean(R.string.p_tags_enabled, true)) { - return; - } - - boolean collapsed = preferences.getBoolean(R.string.p_collapse_tags, false); - Iterable filters = collapsed ? emptyList() : tagDataDao.getTagFilters(now()); - if (preferences.getBoolean(R.string.p_tags_hide_unused, false)) { - filters = filter(filters, f -> f.count > 0); - } - List tags = newArrayList(Iterables.transform(filters, TagFilters::toTagFilter)); - Collections.sort(tags, new AlphanumComparator<>(AlphanumComparator.FILTER)); - - items.addAll( - getSubmenu( - context.getString(R.string.tags), - false, - tags, - false, - collapsed, - SubheaderType.PREFERENCE, - R.string.p_collapse_tags)); - - if (navigationDrawer && !collapsed) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.new_tag), - R.drawable.ic_outline_add_24px, - new Intent(context, TagSettingsActivity.class), - NavigationDrawerFragment.REQUEST_NEW_LIST)); - } - } - - private void addPlaces(List items, boolean navigationDrawer) { - if (!preferences.getBoolean(R.string.p_places_enabled, true)) { - return; - } - - boolean collapsed = preferences.getBoolean(R.string.p_collapse_locations, false); - Iterable filters = collapsed ? emptyList() : locationDao.getPlaceFilters(now()); - if (preferences.getBoolean(R.string.p_places_hide_unused, false)) { - filters = filter(filters, f -> f.count > 0); - } - - items.addAll( - getSubmenu( - context.getString(R.string.places), - false, - newArrayList(Iterables.transform(filters, LocationFilters::toLocationFilter)), - false, - collapsed, - SubheaderType.PREFERENCE, - R.string.p_collapse_locations)); - - if (navigationDrawer && !collapsed) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.add_place), - R.drawable.ic_outline_add_24px, - new Intent(context, LocationPickerActivity.class), - NavigationDrawerFragment.REQUEST_NEW_PLACE)); - } - } - - public List getItems(boolean navigationDrawer) { - assertNotMainThread(); - - List items = new ArrayList<>(); - - items.add(builtInFilterExposer.getMyTasksFilter()); - - addFilters(items, navigationDrawer); - - addTags(items, navigationDrawer); - - addPlaces(items, navigationDrawer); - - for (Map.Entry> filters : getGoogleTaskFilters()) { - GoogleTaskAccount account = filters.getKey(); - items.addAll( - getSubmenu( - account.getAccount(), - !isNullOrEmpty(account.getError()), - filters.getValue(), - !navigationDrawer, - account.isCollapsed(), - SubheaderType.GOOGLE_TASKS, - account.getId())); - - if (navigationDrawer && !account.isCollapsed()) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.new_list), - R.drawable.ic_outline_add_24px, - new Intent(context, GoogleTaskListSettingsActivity.class) - .putExtra(GoogleTaskListSettingsActivity.EXTRA_ACCOUNT, account), - NavigationDrawerFragment.REQUEST_NEW_LIST)); - } - } - - for (Map.Entry> filters : getCaldavFilters()) { - CaldavAccount account = filters.getKey(); - items.addAll( - getSubmenu( - account.getName(), - !isNullOrEmpty(account.getError()), - filters.getValue(), - !navigationDrawer, - account.isCollapsed(), - SubheaderType.CALDAV, - account.getId())); - - if (navigationDrawer && !account.isCollapsed()) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.new_list), - R.drawable.ic_outline_add_24px, - new Intent( - context, - account.isCaldavAccount() - ? CaldavCalendarSettingsActivity.class - : EteSyncCalendarSettingsActivity.class) - .putExtra(EXTRA_CALDAV_ACCOUNT, account), - NavigationDrawerFragment.REQUEST_NEW_LIST)); - } - } - - if (navigationDrawer) { - items.add(new NavigationDrawerSeparator()); - - //noinspection ConstantConditions - if (BuildConfig.FLAVOR.equals("generic")) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.TLA_menu_donate), - R.drawable.ic_outline_attach_money_24px, - REQUEST_DONATE)); - } else if (!inventory.hasPro()) { - items.add( - new NavigationDrawerAction( - context.getString(R.string.name_your_price), - R.drawable.ic_outline_attach_money_24px, - REQUEST_PURCHASE)); - } - - items.add( - new NavigationDrawerAction( - context.getString(R.string.TLA_menu_settings), - R.drawable.ic_outline_settings_24px, - new Intent(context, MainPreferences.class), - REQUEST_SETTINGS)); - - items.add( - new NavigationDrawerAction( - context.getString(R.string.help_and_feedback), - R.drawable.ic_outline_help_outline_24px, - new Intent(context, HelpAndFeedback.class), - 0)); - } - - return items; - } - - private List getFilters() { - ArrayList filters = new ArrayList<>(builtInFilterExposer.getFilters()); - Filter filter = timerFilterExposer.getFilters(); - if (filter != null) { - filters.add(filter); - } - filters.addAll(customFilterExposer.getFilters()); - return filters; - } - - private Set>> getGoogleTaskFilters() { - List accounts = googleTaskListDao.getAccounts(); - LinkedHashMap> filters = new LinkedHashMap<>(); - for (GoogleTaskAccount account : accounts) { - filters.put( - account, - account.isCollapsed() - ? emptyList() - : new ArrayList<>( - transform( - googleTaskListDao.getGoogleTaskFilters(account.getAccount(), now()), - GoogleTaskFilters::toGtasksFilter))); - } - for (Map.Entry> entry : filters.entrySet()) { - Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER)); - } - return filters.entrySet(); - } - - private Set>> getCaldavFilters() { - List accounts = caldavDao.getAccounts(); - LinkedHashMap> filters = new LinkedHashMap<>(); - for (CaldavAccount account : accounts) { - filters.put( - account, - account.isCollapsed() - ? emptyList() - : new ArrayList<>( - transform( - caldavDao.getCaldavFilters(account.getUuid(), now()), - CaldavFilters::toCaldavFilter))); - } - for (Map.Entry> entry : filters.entrySet()) { - Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER)); - } - return filters.entrySet(); - } - - private List getSubmenu(int title, int prefId, Function> getFilters) { - boolean collapsed = preferences.getBoolean(prefId, false); - return newArrayList( - concat( - ImmutableList.of( - new NavigationDrawerSubheader( - context.getString(title), false, collapsed, SubheaderType.PREFERENCE, prefId)), - collapsed ? emptyList() : getFilters.call())); - } - - private List getSubmenu( - String title, - boolean error, - List filters, - boolean hideIfEmpty, - boolean collapsed, - SubheaderType type, - long id) { - return hideIfEmpty && filters.isEmpty() && !collapsed - ? ImmutableList.of() - : newArrayList( - concat( - ImmutableList.of(new NavigationDrawerSubheader(title, error, collapsed, type, id)), - filters)); - } -} diff --git a/app/src/main/java/org/tasks/filters/FilterProvider.kt b/app/src/main/java/org/tasks/filters/FilterProvider.kt new file mode 100644 index 000000000..e736610f7 --- /dev/null +++ b/app/src/main/java/org/tasks/filters/FilterProvider.kt @@ -0,0 +1,298 @@ +package org.tasks.filters + +import android.content.Context +import android.content.Intent +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.api.FilterListItem +import com.todoroo.astrid.core.BuiltInFilterExposer +import com.todoroo.astrid.core.CustomFilterExposer +import com.todoroo.astrid.timers.TimerFilterExposer +import org.tasks.BuildConfig +import org.tasks.R +import org.tasks.Strings +import org.tasks.activities.GoogleTaskListSettingsActivity +import org.tasks.activities.TagSettingsActivity +import org.tasks.billing.Inventory +import org.tasks.caldav.BaseCaldavCalendarSettingsActivity +import org.tasks.caldav.CaldavCalendarSettingsActivity +import org.tasks.data.* +import org.tasks.etesync.EteSyncCalendarSettingsActivity +import org.tasks.filters.NavigationDrawerSubheader.SubheaderType +import org.tasks.injection.ForApplication +import org.tasks.location.LocationPickerActivity +import org.tasks.preferences.HelpAndFeedback +import org.tasks.preferences.MainPreferences +import org.tasks.preferences.Preferences +import org.tasks.ui.NavigationDrawerFragment +import java.util.* +import javax.inject.Inject + +class FilterProvider @Inject constructor( + @param:ForApplication private val context: Context, + private val inventory: Inventory, + private val builtInFilterExposer: BuiltInFilterExposer, + private val timerFilterExposer: TimerFilterExposer, + private val customFilterExposer: CustomFilterExposer, + private val tagDataDao: TagDataDao, + private val googleTaskListDao: GoogleTaskListDao, + private val caldavDao: CaldavDao, + private val preferences: Preferences, + private val locationDao: LocationDao) { + + val remoteListPickerItems: List + get() { + AndroidUtilities.assertNotMainThread() + val items: MutableList = ArrayList() + val item = Filter(context.getString(R.string.dont_sync), null) + item.icon = R.drawable.ic_outline_cloud_off_24px + items.add(item) + for ((account, value) in googleTaskFilters) { + items.addAll( + getSubmenu( + account.account, + !Strings.isNullOrEmpty(account.error), + value, + true, + account.isCollapsed, + SubheaderType.GOOGLE_TASKS, + account.id)) + } + for ((account, value) in caldavFilters) { + items.addAll( + getSubmenu( + account.name, + !Strings.isNullOrEmpty(account.error), + value, + true, + account.isCollapsed, + SubheaderType.CALDAV, + account.id)) + } + return items + } + + private fun addFilters(items: MutableList, navigationDrawer: Boolean) { + if (!preferences.getBoolean(R.string.p_filters_enabled, true)) { + return + } + items.addAll(getSubmenu(R.string.filters, R.string.p_collapse_filters) { filters }) + if (navigationDrawer && !preferences.getBoolean(R.string.p_collapse_filters, false)) { + items.add( + NavigationDrawerAction( + context.getString(R.string.add_filter), + R.drawable.ic_outline_add_24px, + NavigationDrawerFragment.REQUEST_NEW_FILTER)) + } + } + + private fun addTags(items: MutableList, navigationDrawer: Boolean) { + if (!preferences.getBoolean(R.string.p_tags_enabled, true)) { + return + } + val collapsed = preferences.getBoolean(R.string.p_collapse_tags, false) + var filters = if (collapsed) emptyList() else tagDataDao.getTagFilters(DateUtilities.now()) + if (preferences.getBoolean(R.string.p_tags_hide_unused, false)) { + filters = filters.filter { it.count > 0 } + } + val tags = filters.map(TagFilters::toTagFilter) + Collections.sort(tags, AlphanumComparator(AlphanumComparator.FILTER)) + items.addAll( + getSubmenu( + context.getString(R.string.tags), + false, + tags, + false, + collapsed, + SubheaderType.PREFERENCE, + R.string.p_collapse_tags.toLong())) + if (navigationDrawer && !collapsed) { + items.add( + NavigationDrawerAction( + context.getString(R.string.new_tag), + R.drawable.ic_outline_add_24px, + Intent(context, TagSettingsActivity::class.java), + NavigationDrawerFragment.REQUEST_NEW_LIST)) + } + } + + private fun addPlaces(items: MutableList, navigationDrawer: Boolean) { + if (!preferences.getBoolean(R.string.p_places_enabled, true)) { + return + } + val collapsed = preferences.getBoolean(R.string.p_collapse_locations, false) + var filters = if (collapsed) emptyList() else locationDao.getPlaceFilters(DateUtilities.now()) + if (preferences.getBoolean(R.string.p_places_hide_unused, false)) { + filters = filters.filter { it.count > 0 } + } + items.addAll( + getSubmenu( + context.getString(R.string.places), + false, + filters.map(LocationFilters::toLocationFilter), + false, + collapsed, + SubheaderType.PREFERENCE, + R.string.p_collapse_locations.toLong())) + if (navigationDrawer && !collapsed) { + items.add( + NavigationDrawerAction( + context.getString(R.string.add_place), + R.drawable.ic_outline_add_24px, + Intent(context, LocationPickerActivity::class.java), + NavigationDrawerFragment.REQUEST_NEW_PLACE)) + } + } + + fun getItems(navigationDrawer: Boolean): List { + AndroidUtilities.assertNotMainThread() + val items: MutableList = ArrayList() + items.add(builtInFilterExposer.myTasksFilter) + addFilters(items, navigationDrawer) + addTags(items, navigationDrawer) + addPlaces(items, navigationDrawer) + for ((account, value) in googleTaskFilters) { + items.addAll( + getSubmenu( + account.account, + !Strings.isNullOrEmpty(account.error), + value, + !navigationDrawer, + account.isCollapsed, + SubheaderType.GOOGLE_TASKS, + account.id)) + if (navigationDrawer && !account.isCollapsed) { + items.add( + NavigationDrawerAction( + context.getString(R.string.new_list), + R.drawable.ic_outline_add_24px, + Intent(context, GoogleTaskListSettingsActivity::class.java) + .putExtra(GoogleTaskListSettingsActivity.EXTRA_ACCOUNT, account), + NavigationDrawerFragment.REQUEST_NEW_LIST)) + } + } + for ((account, value) in caldavFilters) { + items.addAll( + getSubmenu( + account.name, + !Strings.isNullOrEmpty(account.error), + value, + !navigationDrawer, + account.isCollapsed, + SubheaderType.CALDAV, + account.id)) + if (navigationDrawer && !account.isCollapsed) { + items.add( + NavigationDrawerAction( + context.getString(R.string.new_list), + R.drawable.ic_outline_add_24px, + Intent( + context, + if (account.isCaldavAccount) CaldavCalendarSettingsActivity::class.java else EteSyncCalendarSettingsActivity::class.java) + .putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT, account), + NavigationDrawerFragment.REQUEST_NEW_LIST)) + } + } + if (navigationDrawer) { + items.add(NavigationDrawerSeparator()) + @Suppress("ConstantConditionIf") + if (BuildConfig.FLAVOR == "generic") { + items.add( + NavigationDrawerAction( + context.getString(R.string.TLA_menu_donate), + R.drawable.ic_outline_attach_money_24px, + NavigationDrawerFragment.REQUEST_DONATE)) + } else if (!inventory.hasPro()) { + items.add( + NavigationDrawerAction( + context.getString(R.string.name_your_price), + R.drawable.ic_outline_attach_money_24px, + NavigationDrawerFragment.REQUEST_PURCHASE)) + } + items.add( + NavigationDrawerAction( + context.getString(R.string.TLA_menu_settings), + R.drawable.ic_outline_settings_24px, + Intent(context, MainPreferences::class.java), + NavigationDrawerFragment.REQUEST_SETTINGS)) + items.add( + NavigationDrawerAction( + context.getString(R.string.help_and_feedback), + R.drawable.ic_outline_help_outline_24px, + Intent(context, HelpAndFeedback::class.java), + 0)) + } + return items + } + + private val filters: List + get() { + val filters = ArrayList(builtInFilterExposer.filters) + val filter = timerFilterExposer.filters + if (filter != null) { + filters.add(filter) + } + filters.addAll(customFilterExposer.filters) + return filters + } + + private val googleTaskFilters: Set>> + get() { + val accounts = googleTaskListDao.getAccounts() + val filters = LinkedHashMap>() + for (account in accounts) { + filters[account] = if (account.isCollapsed) { + emptyList() + } else { + googleTaskListDao + .getGoogleTaskFilters(account.account!!, DateUtilities.now()) + .map(GoogleTaskFilters::toGtasksFilter) + } + } + for ((_, value) in filters) { + Collections.sort(value, AlphanumComparator(AlphanumComparator.FILTER)) + } + return filters.entries + } + + private val caldavFilters: Set>> + get() { + val accounts = caldavDao.getAccounts() + val filters = LinkedHashMap>() + for (account in accounts) { + filters[account] = if (account.isCollapsed) { + emptyList() + } else { + caldavDao + .getCaldavFilters(account.uuid!!, DateUtilities.now()) + .map(CaldavFilters::toCaldavFilter) + } + } + for ((_, value) in filters) { + Collections.sort(value, AlphanumComparator(AlphanumComparator.FILTER)) + } + return filters.entries + } + + private fun getSubmenu(title: Int, prefId: Int, getFilters: () -> List): List { + val collapsed = preferences.getBoolean(prefId, false) + val subheader = NavigationDrawerSubheader(context.getString(title), false, collapsed, SubheaderType.PREFERENCE, prefId.toLong()) + return listOf(subheader).plus(if (collapsed) emptyList() else getFilters.invoke()) + } + + private fun getSubmenu( + title: String?, + error: Boolean, + filters: List, + hideIfEmpty: Boolean, + collapsed: Boolean, + type: SubheaderType, + id: Long): List { + return if (hideIfEmpty && filters.isEmpty() && !collapsed) { + listOf() + } else { + listOf(NavigationDrawerSubheader(title, error, collapsed, type, id)).plus(filters) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/injection/InjectingApplication.kt b/app/src/main/java/org/tasks/injection/InjectingApplication.kt index a9f088a88..d8c06cd86 100644 --- a/app/src/main/java/org/tasks/injection/InjectingApplication.kt +++ b/app/src/main/java/org/tasks/injection/InjectingApplication.kt @@ -13,5 +13,5 @@ abstract class InjectingApplication : Application() { inject(component) } - protected abstract fun inject(component: ApplicationComponent?) + protected abstract fun inject(component: ApplicationComponent) } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/themes/ColorProvider.kt b/app/src/main/java/org/tasks/themes/ColorProvider.kt index e623e01e2..dee409635 100644 --- a/app/src/main/java/org/tasks/themes/ColorProvider.kt +++ b/app/src/main/java/org/tasks/themes/ColorProvider.kt @@ -7,7 +7,7 @@ import org.tasks.injection.ForActivity import org.tasks.preferences.Preferences import javax.inject.Inject -class ColorProvider @Inject constructor(@ForActivity private val context: Context, preferences: Preferences) { +class ColorProvider @Inject constructor(@param:ForActivity private val context: Context, preferences: Preferences) { companion object { const val BLUE_500 = -14575885 diff --git a/app/src/main/java/org/tasks/ui/CheckBoxProvider.kt b/app/src/main/java/org/tasks/ui/CheckBoxProvider.kt index 821f0fb1b..b5549f03f 100644 --- a/app/src/main/java/org/tasks/ui/CheckBoxProvider.kt +++ b/app/src/main/java/org/tasks/ui/CheckBoxProvider.kt @@ -12,7 +12,7 @@ import org.tasks.themes.ColorProvider import org.tasks.themes.DrawableUtil import javax.inject.Inject -class CheckBoxProvider @Inject constructor(@ForActivity private val context: Context, private val colorProvider: ColorProvider) { +class CheckBoxProvider @Inject constructor(@param:ForActivity private val context: Context, private val colorProvider: ColorProvider) { fun getCheckBox(task: Task) = getCheckBox(task.isCompleted, task.isRecurring, task.priority!!)