mirror of https://github.com/tasks/tasks
More Kotlin conversions
parent
f9246a0674
commit
17a9b1467f
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue