More Kotlin conversions

pull/996/head
Alex Baker 6 years ago
parent f9246a0674
commit 17a9b1467f

@ -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<Resources>) {
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<Resources> { 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<Resources> { 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)
}

@ -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)

@ -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)))
}

@ -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

@ -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<String>)
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) {

@ -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<CriterionViewHolder> implements
ListUpdateCallback {
private final Callback<String> onClick;
private final Function1<String, Unit> onClick;
private final Locale locale;
private final AsyncListDiffer<CriterionInstance> differ;
public CustomFilterAdapter(
List<CriterionInstance> objects, Locale locale, Callback<String> onClick) {
List<CriterionInstance> objects, Locale locale, Function1<String, Unit> onClick) {
this.locale = locale;
this.onClick = onClick;
differ = new AsyncListDiffer<>(this, new AsyncDifferConfig.Builder<>(new CriterionDiffCallback()).build());

@ -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<Int, Int>,
private val onDelete: Callback<Int>,
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)
}
}

@ -1,5 +0,0 @@
package org.tasks;
public interface Callback2<T1, T2> {
void call(T1 t1, T2 t2);
}

@ -1,5 +0,0 @@
package org.tasks;
public interface Function<T> {
T call();
}

@ -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<Task> 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<Notification> entries) {
List<org.tasks.notifications.Notification> 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());
}
}
}
}

@ -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<Notification>) {
val notifications: MutableList<Notification> = 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())
}
}
}
}

@ -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;
}
}

@ -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 }
}

@ -1,7 +0,0 @@
package org.tasks;
public class Strings {
public static boolean isNullOrEmpty(String string) {
return string == null || string.isEmpty();
}
}

@ -0,0 +1,6 @@
package org.tasks
object Strings {
@JvmStatic
fun isNullOrEmpty(string: String?) = string?.isEmpty() ?: true
}

@ -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> upgrader;
@Inject Lazy<WorkManager> workManager;
@Inject Lazy<RefreshScheduler> refreshScheduler;
@Inject Lazy<GeofenceApi> geofenceApi;
@Inject Lazy<BillingClient> billingClient;
@Inject Lazy<AppWidgetManager> 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);
}
}
}

@ -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<Upgrader>
@Inject lateinit var workManager: Lazy<WorkManager>
@Inject lateinit var refreshScheduler: Lazy<RefreshScheduler>
@Inject lateinit var geofenceApi: Lazy<GeofenceApi>
@Inject lateinit var billingClient: Lazy<BillingClient>
@Inject lateinit var appWidgetManager: Lazy<AppWidgetManager>
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)
}
}
}

@ -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<CriterionInstance> 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<CustomFilterCriterion> all = filterCriteriaProvider.getAll();
List<String> 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<String, Object> getValues() {
Map<String, Object> values = new HashMap<>();
for (CriterionInstance instance : criteria) {
String value = getValue(instance);
if (instance.criterion.valuesForNewTasks != null
&& instance.type == CriterionInstance.TYPE_INTERSECT) {
for (Entry<String, Object> entry : instance.criterion.valuesForNewTasks.entrySet()) {
values.put(
entry.getKey().replace("?", value), entry.getValue().toString().replace("?", value));
}
}
}
return values;
}
}

@ -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<CriterionInstance>
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<String, Any>
get() {
val values: MutableMap<String, Any> = 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"
}
}

@ -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)

@ -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<Int>
private val onSelected: (Int) -> Unit
) : ListAdapter<Pickable, IconPickerHolder>(DiffCallback()) {
enum class Palette {

@ -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<FilterListItem> getRemoteListPickerItems() {
assertNotMainThread();
List<FilterListItem> 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<GoogleTaskAccount, List<Filter>> 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<CaldavAccount, List<Filter>> 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<FilterListItem> 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<FilterListItem> items, boolean navigationDrawer) {
if (!preferences.getBoolean(R.string.p_tags_enabled, true)) {
return;
}
boolean collapsed = preferences.getBoolean(R.string.p_collapse_tags, false);
Iterable<TagFilters> filters = collapsed ? emptyList() : tagDataDao.getTagFilters(now());
if (preferences.getBoolean(R.string.p_tags_hide_unused, false)) {
filters = filter(filters, f -> f.count > 0);
}
List<Filter> 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<FilterListItem> items, boolean navigationDrawer) {
if (!preferences.getBoolean(R.string.p_places_enabled, true)) {
return;
}
boolean collapsed = preferences.getBoolean(R.string.p_collapse_locations, false);
Iterable<LocationFilters> 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<FilterListItem> getItems(boolean navigationDrawer) {
assertNotMainThread();
List<FilterListItem> items = new ArrayList<>();
items.add(builtInFilterExposer.getMyTasksFilter());
addFilters(items, navigationDrawer);
addTags(items, navigationDrawer);
addPlaces(items, navigationDrawer);
for (Map.Entry<GoogleTaskAccount, List<Filter>> 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<CaldavAccount, List<Filter>> 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<Filter> getFilters() {
ArrayList<Filter> filters = new ArrayList<>(builtInFilterExposer.getFilters());
Filter filter = timerFilterExposer.getFilters();
if (filter != null) {
filters.add(filter);
}
filters.addAll(customFilterExposer.getFilters());
return filters;
}
private Set<Entry<GoogleTaskAccount, List<Filter>>> getGoogleTaskFilters() {
List<GoogleTaskAccount> accounts = googleTaskListDao.getAccounts();
LinkedHashMap<GoogleTaskAccount, List<Filter>> 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<GoogleTaskAccount, List<Filter>> entry : filters.entrySet()) {
Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER));
}
return filters.entrySet();
}
private Set<Entry<CaldavAccount, List<Filter>>> getCaldavFilters() {
List<CaldavAccount> accounts = caldavDao.getAccounts();
LinkedHashMap<CaldavAccount, List<Filter>> 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<CaldavAccount, List<Filter>> entry : filters.entrySet()) {
Collections.sort(entry.getValue(), new AlphanumComparator<>(AlphanumComparator.FILTER));
}
return filters.entrySet();
}
private List<FilterListItem> getSubmenu(int title, int prefId, Function<List<Filter>> 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<FilterListItem> getSubmenu(
String title,
boolean error,
List<Filter> 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));
}
}

@ -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<FilterListItem>
get() {
AndroidUtilities.assertNotMainThread()
val items: MutableList<FilterListItem> = 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<FilterListItem>, 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<FilterListItem>, 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<FilterListItem>, 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<FilterListItem> {
AndroidUtilities.assertNotMainThread()
val items: MutableList<FilterListItem> = 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<Filter>
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<Map.Entry<GoogleTaskAccount, List<Filter>>>
get() {
val accounts = googleTaskListDao.getAccounts()
val filters = LinkedHashMap<GoogleTaskAccount, List<Filter>>()
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<Map.Entry<CaldavAccount, List<Filter>>>
get() {
val accounts = caldavDao.getAccounts()
val filters = LinkedHashMap<CaldavAccount, List<Filter>>()
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<Filter>): List<FilterListItem> {
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<Filter>,
hideIfEmpty: Boolean,
collapsed: Boolean,
type: SubheaderType,
id: Long): List<FilterListItem> {
return if (hideIfEmpty && filters.isEmpty() && !collapsed) {
listOf()
} else {
listOf(NavigationDrawerSubheader(title, error, collapsed, type, id)).plus(filters)
}
}
}

@ -13,5 +13,5 @@ abstract class InjectingApplication : Application() {
inject(component)
}
protected abstract fun inject(component: ApplicationComponent?)
protected abstract fun inject(component: ApplicationComponent)
}

@ -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

@ -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!!)

Loading…
Cancel
Save