Collapsible sort groups in widget

pull/1149/head
Alex Baker 4 years ago
parent 339b4661c7
commit bcbe1cadb9

@ -83,6 +83,7 @@ class ScrollableWidget : InjectingPreferenceFragment() {
setupCheckbox(R.string.p_widget_show_lists) setupCheckbox(R.string.p_widget_show_lists)
setupCheckbox(R.string.p_widget_show_tags) setupCheckbox(R.string.p_widget_show_tags)
setupCheckbox(R.string.p_widget_show_full_task_title, false) 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) val showDescription = setupCheckbox(R.string.p_widget_show_description, true)
setupCheckbox(R.string.p_widget_show_full_description, false).dependency = showDescription.key setupCheckbox(R.string.p_widget_show_full_description, false).dependency = showDescription.key
setupList(R.string.p_widget_spacing) setupList(R.string.p_widget_spacing)

@ -7,8 +7,10 @@ import android.graphics.Paint
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.RemoteViewsService.RemoteViewsFactory import android.widget.RemoteViewsService.RemoteViewsFactory
import androidx.annotation.StringRes
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.core.SortHelper
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.subtasks.SubtasksHelper import com.todoroo.astrid.subtasks.SubtasksHelper
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -18,9 +20,11 @@ import org.tasks.data.SubtaskInfo
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.data.TaskDao import org.tasks.data.TaskDao
import org.tasks.data.TaskListQuery.getQuery import org.tasks.data.TaskListQuery.getQuery
import org.tasks.date.DateTimeUtils
import org.tasks.locale.Locale import org.tasks.locale.Locale
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.tasklist.SectionedDataSource
import org.tasks.ui.CheckBoxProvider import org.tasks.ui.CheckBoxProvider
import timber.log.Timber import timber.log.Timber
import java.time.format.FormatStyle import java.time.format.FormatStyle
@ -55,30 +59,37 @@ internal class ScrollableViewsFactory(
private var hPad = 0 private var hPad = 0
private var handleDueDateClick = false private var handleDueDateClick = false
private var showDividers = false private var showDividers = false
private var disableGroups = false
private var showSubtasks = false private var showSubtasks = false
private var showPlaces = false private var showPlaces = false
private var showLists = false private var showLists = false
private var showTags = false private var showTags = false
private var isRtl = false private var isRtl = false
private var tasks: List<TaskContainer> = ArrayList() private var collapsed = HashSet<Long>()
private var sortMode = -1
private var tasks = SectionedDataSource(emptyList(), false, 0, collapsed)
override fun onCreate() {} override fun onCreate() {}
override fun onDataSetChanged() { override fun onDataSetChanged() {
runBlocking { runBlocking {
updateSettings() updateSettings()
tasks = taskDao.fetchTasks { subtasks: SubtaskInfo -> tasks = SectionedDataSource(
getQuery(filter, subtasks) taskDao.fetchTasks { getQuery(filter, it) },
} disableGroups,
sortMode,
collapsed
)
} }
} }
override fun onDestroy() {} override fun onDestroy() {}
override fun getCount(): Int { override fun getCount(): Int {
return tasks.size return tasks.size
} }
override fun getViewAt(position: Int): RemoteViews? { override fun getViewAt(position: Int): RemoteViews? {
return buildUpdate(position) return if (tasks.isHeader(position)) buildHeader(position) else buildUpdate(position)
} }
override fun getLoadingView(): RemoteViews { override fun getLoadingView(): RemoteViews {
@ -86,7 +97,7 @@ internal class ScrollableViewsFactory(
} }
override fun getViewTypeCount(): Int { override fun getViewTypeCount(): Int {
return 1 return 2
} }
override fun getItemId(position: Int) = getTask(position)?.id ?: 0 override fun getItemId(position: Int) = getTask(position)?.id ?: 0
@ -101,7 +112,75 @@ internal class ScrollableViewsFactory(
private fun newRemoteView(): RemoteViews { private fun newRemoteView(): RemoteViews {
return 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? { private fun buildUpdate(position: Int): RemoteViews? {
@ -266,10 +345,19 @@ internal class ScrollableViewsFactory(
dueDateTextSize = max(10f, textSize - 2) dueDateTextSize = max(10f, textSize - 2)
filter = defaultFilterProvider.getFilterFromPreference(widgetPreferences.filterId) filter = defaultFilterProvider.getFilterFromPreference(widgetPreferences.filterId)
showDividers = widgetPreferences.showDividers() showDividers = widgetPreferences.showDividers()
disableGroups = widgetPreferences.disableGroups()
showPlaces = widgetPreferences.showPlaces() showPlaces = widgetPreferences.showPlaces()
showSubtasks = widgetPreferences.showSubtasks() showSubtasks = widgetPreferences.showSubtasks()
showLists = widgetPreferences.showLists() showLists = widgetPreferences.showLists()
showTags = widgetPreferences.showTags() 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 isRtl = locale.directionality == View.LAYOUT_DIRECTION_RTL
} }

@ -31,7 +31,6 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler {
if (action.isNullOrEmpty()) { if (action.isNullOrEmpty()) {
return return
} }
val task: Task = intent.getParcelableExtra(EXTRA_TASK)!!
when (action) { when (action) {
COMPLETE_TASK -> { COMPLETE_TASK -> {
lifecycleScope.launch(NonCancellable) { lifecycleScope.launch(NonCancellable) {
@ -64,9 +63,29 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler {
.show(fragmentManager, FRAG_TAG_DATE_TIME_PICKER) .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() { override fun onDismiss() {
finish() finish()
} }
@ -76,9 +95,12 @@ class WidgetClickActivity : InjectingAppCompatActivity(), OnDismissHandler {
const val EDIT_TASK = "EDIT_TASK" const val EDIT_TASK = "EDIT_TASK"
const val TOGGLE_SUBTASKS = "TOGGLE_SUBTASKS" const val TOGGLE_SUBTASKS = "TOGGLE_SUBTASKS"
const val RESCHEDULE_TASK = "RESCHEDULE_TASK" const val RESCHEDULE_TASK = "RESCHEDULE_TASK"
const val TOGGLE_GROUP = "TOGGLE_GROUP"
const val EXTRA_FILTER = "extra_filter" const val EXTRA_FILTER = "extra_filter"
const val EXTRA_TASK = "extra_task" // $NON-NLS-1$ const val EXTRA_TASK = "extra_task" // $NON-NLS-1$
const val EXTRA_COLLAPSED = "extra_collapsed" 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" private const val FRAG_TAG_DATE_TIME_PICKER = "frag_tag_date_time_picker"
} }
} }

@ -1,9 +1,14 @@
package org.tasks.widget; package org.tasks.widget;
import android.content.Context; import android.content.Context;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.todoroo.astrid.service.Upgrader; import com.todoroo.astrid.service.Upgrader;
import java.util.HashSet;
import org.tasks.R; import org.tasks.R;
import org.tasks.Strings;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class WidgetPreferences { public class WidgetPreferences {
@ -53,6 +58,10 @@ public class WidgetPreferences {
return getBoolean(R.string.p_widget_show_subtasks, true); return getBoolean(R.string.p_widget_show_subtasks, true);
} }
boolean disableGroups() {
return getBoolean(R.string.p_widget_disable_groups, false);
}
boolean showPlaces() { boolean showPlaces() {
return getBoolean(R.string.p_widget_show_places, true); 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); return getIntegerFromString(R.string.p_widget_due_date_position, 0);
} }
public void setCollapsed(HashSet<Long> collapsed) {
setString(R.string.p_widget_collapsed, Joiner.on(",").join(collapsed));
}
public HashSet<Long> getCollapsed() {
String value = getString(R.string.p_widget_collapsed);
HashSet<Long> 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 getWidgetSpacing() {
int spacing = getIntegerFromString(R.string.p_widget_spacing, 0); int spacing = getIntegerFromString(R.string.p_widget_spacing, 0);
if (spacing == 2) { if (spacing == 2) {
@ -138,6 +166,7 @@ public class WidgetPreferences {
} }
public void setFilter(String filterPreferenceValue) { public void setFilter(String filterPreferenceValue) {
setCollapsed(new HashSet<>());
preferences.setString(getKey(R.string.p_widget_filter), filterPreferenceValue); preferences.setString(getKey(R.string.p_widget_filter), filterPreferenceValue);
} }

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:background="@color/md_background_dark">
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/half_keyline_first"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingBottom="@dimen/half_keyline_first"
tools:src="@drawable/ic_keyboard_arrow_down_black_18dp"
android:tint="@color/icon_tint_dark_alpha"
android:layout_alignParentEnd="true"
tools:ignore="UseAppTint" />
<TextView
android:id="@+id/header"
style="@style/TextAppearance"
android:paddingTop="@dimen/half_keyline_first"
android:paddingBottom="@dimen/half_keyline_first"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/arrow"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:gravity="start|center_vertical"
android:textSize="@dimen/sku_details_row_text_size"
tools:textColor="@color/white_60"
tools:text="Today"
app:fontFamily="sans-serif-medium" />
<ImageView
android:id="@+id/divider"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_below="@id/header"
android:scaleType="fitXY"
android:layout_height=".5dp"
android:background="@color/white_12"
tools:ignore="ContentDescription" />
</RelativeLayout>

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/half_keyline_first"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:paddingBottom="@dimen/half_keyline_first"
tools:src="@drawable/ic_keyboard_arrow_down_black_18dp"
android:tint="@color/icon_tint_light_alpha"
android:layout_alignParentEnd="true"
tools:ignore="UseAppTint" />
<TextView
android:id="@+id/header"
style="@style/TextAppearance"
android:paddingTop="@dimen/half_keyline_first"
android:paddingBottom="@dimen/half_keyline_first"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/arrow"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:gravity="start|center_vertical"
android:textSize="@dimen/sku_details_row_text_size"
tools:textColor="@color/black_60"
tools:text="Today"
app:fontFamily="sans-serif-medium" />
<ImageView
android:id="@+id/divider"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_below="@id/header"
android:scaleType="fitXY"
android:layout_height=".5dp"
android:background="@color/black_12"
tools:ignore="ContentDescription" />
</RelativeLayout>

@ -306,6 +306,7 @@
<string name="p_widget_show_full_task_title">widget-show-full-task-title-</string> <string name="p_widget_show_full_task_title">widget-show-full-task-title-</string>
<string name="p_widget_show_dividers">widget-show-dividers-</string> <string name="p_widget_show_dividers">widget-show-dividers-</string>
<string name="p_widget_show_subtasks">widget-show-subtasks-</string> <string name="p_widget_show_subtasks">widget-show-subtasks-</string>
<string name="p_widget_disable_groups">widget-disable_groups-</string>
<string name="p_widget_show_places">widget-show-places-</string> <string name="p_widget_show_places">widget-show-places-</string>
<string name="p_widget_show_lists">widget-show-lists-</string> <string name="p_widget_show_lists">widget-show-lists-</string>
<string name="p_widget_show_tags">widget-show-tags-</string> <string name="p_widget_show_tags">widget-show-tags-</string>
@ -321,6 +322,7 @@
<string name="p_widget_due_date_click">widget-due-date-click-</string> <string name="p_widget_due_date_click">widget-due-date-click-</string>
<string name="p_widget_due_date_position">widget-due-date-position-</string> <string name="p_widget_due_date_position">widget-due-date-position-</string>
<string name="p_widget_spacing">widget-spacing-</string> <string name="p_widget_spacing">widget-spacing-</string>
<string name="p_widget_collapsed">widget-collapsed-</string>
<string name="p_dashclock_filter">dashclock_filter</string> <string name="p_dashclock_filter">dashclock_filter</string>
<string name="p_default_list">default_remote_list</string> <string name="p_default_list">default_remote_list</string>

@ -122,6 +122,11 @@
android:key="@string/p_widget_show_dividers" android:key="@string/p_widget_show_dividers"
android:title="@string/widget_show_dividers" /> android:title="@string/widget_show_dividers" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/p_widget_disable_groups"
android:title="@string/disable_sort_groups" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

Loading…
Cancel
Save