diff --git a/app/src/main/java/org/tasks/preferences/fragments/ScrollableWidget.kt b/app/src/main/java/org/tasks/preferences/fragments/ScrollableWidget.kt index 82381f7fb..75a50a62f 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/ScrollableWidget.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/ScrollableWidget.kt @@ -83,6 +83,7 @@ class ScrollableWidget : InjectingPreferenceFragment() { setupCheckbox(R.string.p_widget_show_lists) setupCheckbox(R.string.p_widget_show_tags) setupCheckbox(R.string.p_widget_show_full_task_title, false) + setupCheckbox(R.string.p_widget_disable_groups) val showDescription = setupCheckbox(R.string.p_widget_show_description, true) setupCheckbox(R.string.p_widget_show_full_description, false).dependency = showDescription.key setupList(R.string.p_widget_spacing) diff --git a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt index cd9499e08..91d862b7f 100644 --- a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt +++ b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt @@ -7,8 +7,10 @@ import android.graphics.Paint import android.view.View import android.widget.RemoteViews import android.widget.RemoteViewsService.RemoteViewsFactory +import androidx.annotation.StringRes import com.todoroo.andlib.utility.DateUtilities import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.core.SortHelper import com.todoroo.astrid.data.Task import com.todoroo.astrid.subtasks.SubtasksHelper import kotlinx.coroutines.runBlocking @@ -18,9 +20,11 @@ import org.tasks.data.SubtaskInfo import org.tasks.data.TaskContainer import org.tasks.data.TaskDao import org.tasks.data.TaskListQuery.getQuery +import org.tasks.date.DateTimeUtils import org.tasks.locale.Locale import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.Preferences +import org.tasks.tasklist.SectionedDataSource import org.tasks.ui.CheckBoxProvider import timber.log.Timber import java.time.format.FormatStyle @@ -55,30 +59,37 @@ internal class ScrollableViewsFactory( private var hPad = 0 private var handleDueDateClick = false private var showDividers = false + private var disableGroups = false private var showSubtasks = false private var showPlaces = false private var showLists = false private var showTags = false private var isRtl = false - private var tasks: List = ArrayList() + private var collapsed = HashSet() + private var sortMode = -1 + private var tasks = SectionedDataSource(emptyList(), false, 0, collapsed) override fun onCreate() {} override fun onDataSetChanged() { runBlocking { updateSettings() - tasks = taskDao.fetchTasks { subtasks: SubtaskInfo -> - getQuery(filter, subtasks) - } + tasks = SectionedDataSource( + taskDao.fetchTasks { getQuery(filter, it) }, + disableGroups, + sortMode, + collapsed + ) } } override fun onDestroy() {} + override fun getCount(): Int { return tasks.size } override fun getViewAt(position: Int): RemoteViews? { - return buildUpdate(position) + return if (tasks.isHeader(position)) buildHeader(position) else buildUpdate(position) } override fun getLoadingView(): RemoteViews { @@ -86,7 +97,7 @@ internal class ScrollableViewsFactory( } override fun getViewTypeCount(): Int { - return 1 + return 2 } override fun getItemId(position: Int) = getTask(position)?.id ?: 0 @@ -101,7 +112,75 @@ internal class ScrollableViewsFactory( private fun newRemoteView(): RemoteViews { return RemoteViews( - BuildConfig.APPLICATION_ID, if (isDark) R.layout.widget_row_dark else R.layout.widget_row_light) + BuildConfig.APPLICATION_ID, + if (isDark) R.layout.widget_row_dark else R.layout.widget_row_light + ) + } + + private fun buildHeader(position: Int): RemoteViews? { + val row = RemoteViews( + BuildConfig.APPLICATION_ID, + if (isDark) R.layout.widget_header_dark else R.layout.widget_header_light + ) + val section = tasks.getSection(position) + val sortGroup = section.value + val header: String? = if (filter?.supportsSorting() == true) { + getHeader(sortMode, section.value) + } else { + null + } + row.setTextViewText(R.id.header, header) + row.setImageViewResource(R.id.arrow, if (section.collapsed) { + R.drawable.ic_keyboard_arrow_down_black_18dp + } else { + R.drawable.ic_keyboard_arrow_up_black_18dp + }) + val color = if (sortMode == SortHelper.SORT_DUE + && sortGroup > 0 + && DateTimeUtils.newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) { + context.getColor(R.color.overdue) + } else { + textColorSecondary + } + row.setTextColor(R.id.header, color) + if (!showDividers) { + row.setViewVisibility(R.id.divider, View.GONE) + } + row.setOnClickFillInIntent( + R.id.row, + Intent(WidgetClickActivity.TOGGLE_GROUP) + .putExtra(WidgetClickActivity.EXTRA_WIDGET, widgetId) + .putExtra(WidgetClickActivity.EXTRA_GROUP, sortGroup) + .putExtra(WidgetClickActivity.EXTRA_COLLAPSED, !section.collapsed) + ) + return row + } + + private fun getHeader(sortMode: Int, group: Long): String { + return when { + sortMode == SortHelper.SORT_IMPORTANCE -> context.getString(priorityToString(group.toInt())) + group == 0L -> context.getString(if (sortMode == SortHelper.SORT_DUE) { + R.string.no_due_date + } else { + R.string.no_date + }) + sortMode == SortHelper.SORT_CREATED -> + context.getString(R.string.sort_created_group, getDateString(group)) + sortMode == SortHelper.SORT_MODIFIED -> + context.getString(R.string.sort_modified_group, getDateString(group)) + else -> getDateString(group, false) + } + } + + private fun getDateString(value: Long, lowercase: Boolean = true) = + DateUtilities.getRelativeDay(context, value, locale.locale, FormatStyle.FULL, lowercase) + + @StringRes + private fun priorityToString(priority: Int) = when (priority) { + 0 -> R.string.filter_high_priority + 1 -> R.string.filter_medium_priority + 2 -> R.string.filter_low_priority + else -> R.string.filter_no_priority } private fun buildUpdate(position: Int): RemoteViews? { @@ -266,10 +345,19 @@ internal class ScrollableViewsFactory( dueDateTextSize = max(10f, textSize - 2) filter = defaultFilterProvider.getFilterFromPreference(widgetPreferences.filterId) showDividers = widgetPreferences.showDividers() + disableGroups = widgetPreferences.disableGroups() showPlaces = widgetPreferences.showPlaces() showSubtasks = widgetPreferences.showSubtasks() showLists = widgetPreferences.showLists() showTags = widgetPreferences.showTags() + preferences.sortMode.takeIf { it != sortMode } + ?.let { + if (sortMode >= 0) { + widgetPreferences.collapsed = HashSet() + } + sortMode = it + } + collapsed = widgetPreferences.collapsed isRtl = locale.directionality == View.LAYOUT_DIRECTION_RTL } diff --git a/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt b/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt index 8e9a03433..d2e985a25 100644 --- a/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt +++ b/app/src/main/java/org/tasks/widget/WidgetClickActivity.kt @@ -31,7 +31,6 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler { if (action.isNullOrEmpty()) { return } - val task: Task = intent.getParcelableExtra(EXTRA_TASK)!! when (action) { COMPLETE_TASK -> { lifecycleScope.launch(NonCancellable) { @@ -64,9 +63,29 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler { .show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) } } + TOGGLE_GROUP -> { + val widgetPreferences = WidgetPreferences( + applicationContext, + preferences, + intent.getIntExtra(EXTRA_WIDGET, -1) + ) + val collapsed = widgetPreferences.collapsed + val group = intent.getLongExtra(EXTRA_GROUP, -1) + if (intent.getBooleanExtra(EXTRA_COLLAPSED, false)) { + collapsed.add(group) + } else { + collapsed.remove(group) + } + widgetPreferences.collapsed = collapsed + localBroadcastManager.broadcastRefresh() + finish() + } } } + val task: Task + get() = intent.getParcelableExtra(EXTRA_TASK)!! + override fun onDismiss() { finish() } @@ -76,9 +95,12 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler { const val EDIT_TASK = "EDIT_TASK" const val TOGGLE_SUBTASKS = "TOGGLE_SUBTASKS" const val RESCHEDULE_TASK = "RESCHEDULE_TASK" + const val TOGGLE_GROUP = "TOGGLE_GROUP" const val EXTRA_FILTER = "extra_filter" const val EXTRA_TASK = "extra_task" // $NON-NLS-1$ const val EXTRA_COLLAPSED = "extra_collapsed" + const val EXTRA_GROUP = "extra_group" + const val EXTRA_WIDGET = "extra_widget" private const val FRAG_TAG_DATE_TIME_PICKER = "frag_tag_date_time_picker" } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/widget/WidgetPreferences.java b/app/src/main/java/org/tasks/widget/WidgetPreferences.java index 8bf6c3536..e9b72a4c4 100644 --- a/app/src/main/java/org/tasks/widget/WidgetPreferences.java +++ b/app/src/main/java/org/tasks/widget/WidgetPreferences.java @@ -1,9 +1,14 @@ package org.tasks.widget; import android.content.Context; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; import com.todoroo.astrid.service.Upgrader; +import java.util.HashSet; import org.tasks.R; +import org.tasks.Strings; import org.tasks.preferences.Preferences; +import timber.log.Timber; public class WidgetPreferences { @@ -53,6 +58,10 @@ public class WidgetPreferences { return getBoolean(R.string.p_widget_show_subtasks, true); } + boolean disableGroups() { + return getBoolean(R.string.p_widget_disable_groups, false); + } + boolean showPlaces() { return getBoolean(R.string.p_widget_show_places, true); } @@ -69,6 +78,25 @@ public class WidgetPreferences { return getIntegerFromString(R.string.p_widget_due_date_position, 0); } + public void setCollapsed(HashSet collapsed) { + setString(R.string.p_widget_collapsed, Joiner.on(",").join(collapsed)); + } + + public HashSet getCollapsed() { + String value = getString(R.string.p_widget_collapsed); + HashSet collapsed = new HashSet<>(); + if (!Strings.isNullOrEmpty(value)) { + for (String entry : Splitter.on(",").split(value)) { + try { + collapsed.add(Long.parseLong(entry)); + } catch (NumberFormatException e) { + Timber.e(e); + } + } + } + return collapsed; + } + int getWidgetSpacing() { int spacing = getIntegerFromString(R.string.p_widget_spacing, 0); if (spacing == 2) { @@ -138,6 +166,7 @@ public class WidgetPreferences { } public void setFilter(String filterPreferenceValue) { + setCollapsed(new HashSet<>()); preferences.setString(getKey(R.string.p_widget_filter), filterPreferenceValue); } diff --git a/app/src/main/res/layout/widget_header_dark.xml b/app/src/main/res/layout/widget_header_dark.xml new file mode 100644 index 000000000..2656853d1 --- /dev/null +++ b/app/src/main/res/layout/widget_header_dark.xml @@ -0,0 +1,49 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/widget_header_light.xml b/app/src/main/res/layout/widget_header_light.xml new file mode 100644 index 000000000..73da3eaf4 --- /dev/null +++ b/app/src/main/res/layout/widget_header_light.xml @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index 3b86900f8..ac1482add 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -306,6 +306,7 @@ widget-show-full-task-title- widget-show-dividers- widget-show-subtasks- + widget-disable_groups- widget-show-places- widget-show-lists- widget-show-tags- @@ -321,6 +322,7 @@ widget-due-date-click- widget-due-date-position- widget-spacing- + widget-collapsed- dashclock_filter default_remote_list diff --git a/app/src/main/res/xml/preferences_widget.xml b/app/src/main/res/xml/preferences_widget.xml index d5d0a01fa..a68267ebf 100644 --- a/app/src/main/res/xml/preferences_widget.xml +++ b/app/src/main/res/xml/preferences_widget.xml @@ -122,6 +122,11 @@ android:key="@string/p_widget_show_dividers" android:title="@string/widget_show_dividers" /> + +