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

@ -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<TaskContainer> = ArrayList()
private var collapsed = HashSet<Long>()
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
}

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

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

@ -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_dividers">widget-show-dividers-</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_lists">widget-show-lists-</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_position">widget-due-date-position-</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_default_list">default_remote_list</string>

@ -122,6 +122,11 @@
android:key="@string/p_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

Loading…
Cancel
Save