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