Add sort 'By start date'

pull/1294/head
Alex Baker 5 years ago
parent 7f5cbf372a
commit dbf4d6fdf7

@ -5,15 +5,16 @@
*/ */
package com.todoroo.astrid.adapter package com.todoroo.astrid.adapter
import com.todoroo.astrid.core.SortHelper.SORT_DUE import com.todoroo.astrid.core.SortHelper.*
import com.todoroo.astrid.core.SortHelper.SORT_IMPORTANCE
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.data.* import org.tasks.data.*
import org.tasks.date.DateTimeUtils.toAppleEpoch import org.tasks.date.DateTimeUtils.toAppleEpoch
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.time.DateTimeUtils.millisOfDay
import java.util.* import java.util.*
import kotlin.collections.HashSet import kotlin.collections.HashSet
@ -207,15 +208,16 @@ open class TaskAdapter(
taskDao.save(t) taskDao.save(t)
} }
} }
SORT_DUE -> applyDate(task.task, dataSource.nearestHeader(if (pos == 0) 1 else pos)) SORT_DUE -> applyDueDate(task.task, dataSource.nearestHeader(if (pos == 0) 1 else pos))
SORT_START -> applyStartDate(task.task, dataSource.nearestHeader(if (pos == 0) 1 else pos))
} }
} }
private suspend fun applyDate(task: Task, date: Long) { private suspend fun applyDueDate(task: Task, date: Long) {
val original = task.dueDate val original = task.dueDate
task.setDueDateAdjustingHideUntil(when { task.setDueDateAdjustingHideUntil(when {
date == 0L -> 0L date == 0L -> 0L
task.hasDueTime() -> date.toDateTime().withMillisOfDay(task.dueDate.toDateTime().millisOfDay).millis task.hasDueTime() -> date.toDateTime().withMillisOfDay(original.millisOfDay()).millis
else -> Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, date) else -> Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, date)
}) })
if (original != task.dueDate) { if (original != task.dueDate) {
@ -223,6 +225,18 @@ open class TaskAdapter(
} }
} }
private suspend fun applyStartDate(task: Task, date: Long) {
val original = task.hideUntil
task.hideUntil = when {
date == 0L -> 0L
task.hasHideUntilDate() -> date.toDateTime().withMillisOfDay(original.millisOfDay()).millis
else -> task.createHideUntil(HIDE_UNTIL_SPECIFIC_DAY, date)
}
if (original != task.hideUntil) {
taskDao.save(task)
}
}
private suspend fun moveToTopLevel(task: TaskContainer) { private suspend fun moveToTopLevel(task: TaskContainer) {
when { when {
task.isGoogleTask -> changeGoogleTaskParent(task, null) task.isGoogleTask -> changeGoogleTaskParent(task, null)

@ -32,6 +32,7 @@ public class SortHelper {
public static final int SORT_CREATED = 5; public static final int SORT_CREATED = 5;
public static final int SORT_GTASKS = 6; public static final int SORT_GTASKS = 6;
public static final int SORT_CALDAV = 7; public static final int SORT_CALDAV = 7;
public static final int SORT_START = 8;
public static final long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT public static final long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
@ -40,6 +41,8 @@ public class SortHelper {
private static final String ADJUSTED_DUE_DATE = private static final String ADJUSTED_DUE_DATE =
"(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)"; "(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)";
private static final String ADJUSTED_START_DATE =
"(CASE WHEN (hideUntil / 1000) % 60 > 0 THEN hideUntil ELSE (hideUntil + 86399000) END)";
private static final Order ORDER_TITLE = Order.asc(Functions.upper(Task.TITLE)); private static final Order ORDER_TITLE = Order.asc(Functions.upper(Task.TITLE));
/** Takes a SQL query, and if there isn't already an order, creates an order. */ /** Takes a SQL query, and if there isn't already an order, creates an order. */
@ -90,6 +93,13 @@ public class SortHelper {
+ ADJUSTED_DUE_DATE + ADJUSTED_DUE_DATE
+ " END)+importance"); + " END)+importance");
break; break;
case SORT_START:
order =
Order.asc(
"(CASE WHEN (hideUntil=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_START_DATE
+ " END)+importance");
break;
case SORT_IMPORTANCE: case SORT_IMPORTANCE:
order = order =
Order.asc( Order.asc(
@ -126,6 +136,8 @@ public class SortHelper {
switch (sortType) { switch (sortType) {
case SORT_DUE: case SORT_DUE:
return "tasks.dueDate"; return "tasks.dueDate";
case SORT_START:
return "tasks.hideUntil";
case SORT_IMPORTANCE: case SORT_IMPORTANCE:
return "tasks.importance"; return "tasks.importance";
case SORT_MODIFIED: case SORT_MODIFIED:
@ -149,6 +161,11 @@ public class SortHelper {
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate") + ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ " END)+tasks.importance AS sort_duedate"; + " END)+tasks.importance AS sort_duedate";
break; break;
case SORT_START:
select = "(CASE WHEN (tasks.hideUntil=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_START_DATE.replace("hideUntil", "tasks.hideUntil")
+ " END)+tasks.importance AS sort_startdate";
break;
case SORT_IMPORTANCE: case SORT_IMPORTANCE:
select = "tasks.importance*(strftime('%s','now')*1000)+(CASE WHEN (tasks.dueDate=0) THEN (strftime('%s','now')*1000) ELSE tasks.dueDate END) AS sort_importance"; select = "tasks.importance*(strftime('%s','now')*1000)+(CASE WHEN (tasks.dueDate=0) THEN (strftime('%s','now')*1000) ELSE tasks.dueDate END) AS sort_importance";
break; break;
@ -189,6 +206,9 @@ public class SortHelper {
case SORT_DUE: case SORT_DUE:
order = Order.asc("sort_duedate"); order = Order.asc("sort_duedate");
break; break;
case SORT_START:
order = Order.asc("sort_startdate");
break;
case SORT_IMPORTANCE: case SORT_IMPORTANCE:
order = Order.asc("sort_importance"); order = Order.asc("sort_importance");
break; break;

@ -86,6 +86,7 @@ public class SortDialog extends DialogFragment {
} }
items.add(getString(R.string.SSD_sort_auto)); items.add(getString(R.string.SSD_sort_auto));
items.add(getString(R.string.SSD_sort_start));
items.add(getString(R.string.SSD_sort_due)); items.add(getString(R.string.SSD_sort_due));
items.add(getString(R.string.SSD_sort_importance)); items.add(getString(R.string.SSD_sort_importance));
items.add(getString(R.string.SSD_sort_alpha)); items.add(getString(R.string.SSD_sort_alpha));
@ -173,16 +174,18 @@ public class SortDialog extends DialogFragment {
switch (sortMode) { switch (sortMode) {
case SortHelper.SORT_AUTO: case SortHelper.SORT_AUTO:
return 1; return 1;
case SortHelper.SORT_DUE: case SortHelper.SORT_START:
return 2; return 2;
case SortHelper.SORT_IMPORTANCE: case SortHelper.SORT_DUE:
return 3; return 3;
case SortHelper.SORT_ALPHA: case SortHelper.SORT_IMPORTANCE:
return 4; return 4;
case SortHelper.SORT_MODIFIED: case SortHelper.SORT_ALPHA:
return 5; return 5;
case SortHelper.SORT_CREATED: case SortHelper.SORT_MODIFIED:
return 6; return 6;
case SortHelper.SORT_CREATED:
return 7;
} }
Timber.e("Invalid sort mode: %s", sortMode); Timber.e("Invalid sort mode: %s", sortMode);
@ -194,14 +197,16 @@ public class SortDialog extends DialogFragment {
case 1: case 1:
return SortHelper.SORT_AUTO; return SortHelper.SORT_AUTO;
case 2: case 2:
return SortHelper.SORT_DUE; return SortHelper.SORT_START;
case 3: case 3:
return SortHelper.SORT_IMPORTANCE; return SortHelper.SORT_DUE;
case 4: case 4:
return SortHelper.SORT_ALPHA; return SortHelper.SORT_IMPORTANCE;
case 5: case 5:
return SortHelper.SORT_MODIFIED; return SortHelper.SORT_ALPHA;
case 6: case 6:
return SortHelper.SORT_MODIFIED;
case 7:
return SortHelper.SORT_CREATED; return SortHelper.SORT_CREATED;
} }

@ -203,6 +203,7 @@ class ScrollableWidget : InjectingPreferenceFragment() {
} else { } else {
when (widgetPreferences.sortMode) { when (widgetPreferences.sortMode) {
SORT_DUE -> R.string.SSD_sort_due SORT_DUE -> R.string.SSD_sort_due
SORT_START -> R.string.SSD_sort_start
SORT_IMPORTANCE -> R.string.SSD_sort_importance SORT_IMPORTANCE -> R.string.SSD_sort_importance
SORT_ALPHA -> R.string.SSD_sort_alpha SORT_ALPHA -> R.string.SSD_sort_alpha
SORT_MODIFIED -> R.string.SSD_sort_modified SORT_MODIFIED -> R.string.SSD_sort_modified

@ -34,17 +34,24 @@ class HeaderViewHolder(
this.header.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, if (section.collapsed) R.drawable.ic_keyboard_arrow_down_black_18dp else R.drawable.ic_keyboard_arrow_up_black_18dp, 0) this.header.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, if (section.collapsed) R.drawable.ic_keyboard_arrow_down_black_18dp else R.drawable.ic_keyboard_arrow_up_black_18dp, 0)
this.header.setTextColor( this.header.setTextColor(
context.getColor( context.getColor(
if (sortMode == SortHelper.SORT_DUE && sortGroup > 0 && newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) R.color.overdue else R.color.text_secondary)) if ((sortMode == SortHelper.SORT_DUE || sortMode == SortHelper.SORT_START)
&& sortGroup > 0
&& newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) {
R.color.overdue
} else {
R.color.text_secondary
}))
} }
} }
private fun getHeader(sortMode: Int, alwaysDisplayFullDate: Boolean, group: Long): String = private fun getHeader(sortMode: Int, alwaysDisplayFullDate: Boolean, group: Long): String =
when { when {
sortMode == SortHelper.SORT_IMPORTANCE -> context.getString(priorityToString(group.toInt())) sortMode == SortHelper.SORT_IMPORTANCE ->
group == 0L -> context.getString(if (sortMode == SortHelper.SORT_DUE) { context.getString(priorityToString(group.toInt()))
R.string.no_due_date group == 0L -> context.getString(when (sortMode) {
} else { SortHelper.SORT_DUE -> R.string.no_due_date
R.string.no_date SortHelper.SORT_START -> R.string.no_start_date
else -> R.string.no_date
}) })
sortMode == SortHelper.SORT_CREATED -> sortMode == SortHelper.SORT_CREATED ->
context.getString(R.string.sort_created_group, getDateString(group, alwaysDisplayFullDate)) context.getString(R.string.sort_created_group, getDateString(group, alwaysDisplayFullDate))

@ -6,7 +6,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.todoroo.astrid.activity.TaskListFragment import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.adapter.TaskAdapterDataSource import com.todoroo.astrid.adapter.TaskAdapterDataSource
import com.todoroo.astrid.core.SortHelper.SORT_DUE
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
@ -22,13 +21,13 @@ abstract class TaskListRecyclerAdapter internal constructor(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val filter = taskList.getFilter() val filter = taskList.getFilter()
val sortByDueDate = filter.supportsSorting() val groupsEnabled = filter.supportsSorting()
&& preferences.sortMode == SORT_DUE
&& !(filter.supportsManualSort() && preferences.isManualSort) && !(filter.supportsManualSort() && preferences.isManualSort)
&& !(filter.supportsAstridSorting() && preferences.isAstridSort) && !(filter.supportsAstridSorting() && preferences.isAstridSort)
val task = getItem(position) val task = getItem(position)
if (task != null) { if (task != null) {
(holder as TaskViewHolder).bindView(task, filter, sortByDueDate) (holder as TaskViewHolder)
.bindView(task, filter, if (groupsEnabled) preferences.sortMode else -1)
holder.moving = false holder.moving = false
val indent = adapter.getIndent(task) val indent = adapter.getIndent(task)
task.setIndent(indent) task.setIndent(indent)

@ -15,6 +15,8 @@ import butterknife.OnLongClick
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
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.SORT_DUE
import com.todoroo.astrid.core.SortHelper.SORT_START
import com.todoroo.astrid.ui.CheckableImageView import com.todoroo.astrid.ui.CheckableImageView
import org.tasks.R import org.tasks.R
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
@ -132,13 +134,13 @@ class TaskViewHolder internal constructor(
private fun getIndentSize(indent: Int) = (indent * shiftSize).roundToInt() private fun getIndentSize(indent: Int) = (indent * shiftSize).roundToInt()
fun bindView(task: TaskContainer, filter: Filter, sortByDueDate: Boolean) { fun bindView(task: TaskContainer, filter: Filter, sortMode: Int) {
this.task = task this.task = task
indent = task.indent indent = task.indent
nameView.text = task.title nameView.text = task.title
setupTitleAndCheckbox() setupTitleAndCheckbox()
setupDueDate(sortByDueDate) setupDueDate(sortMode == SORT_DUE)
setupChips(filter) setupChips(filter, sortMode == SORT_START)
if (preferences.getBoolean(R.string.p_show_description, true)) { if (preferences.getBoolean(R.string.p_show_description, true)) {
description.text = task.notes description.text = task.notes
description.visibility = if (task.hasNotes()) View.VISIBLE else View.GONE description.visibility = if (task.hasNotes()) View.VISIBLE else View.GONE
@ -201,8 +203,8 @@ class TaskViewHolder internal constructor(
} }
} }
private fun setupChips(filter: Filter) { private fun setupChips(filter: Filter, sortByStartDate: Boolean) {
val chips = chipProvider.getChips(filter, indent > 0, task) val chips = chipProvider.getChips(filter, indent > 0, task, sortByStartDate)
if (chips.isEmpty()) { if (chips.isEmpty()) {
chipGroup.visibility = View.GONE chipGroup.visibility = View.GONE
} else { } else {

@ -11,11 +11,13 @@ import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter import com.todoroo.astrid.api.TagFilter
import com.todoroo.astrid.data.Task
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.data.TagData import org.tasks.data.TagData
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.filters.PlaceFilter import org.tasks.filters.PlaceFilter
import org.tasks.locale.Locale import org.tasks.locale.Locale
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
@ -48,11 +50,14 @@ class ChipProvider @Inject constructor(
showIcon = appearance != 1 showIcon = appearance != 1
} }
private fun newStartDateChip(task: TaskContainer, compact: Boolean): Chip { private fun newStartDateChip(task: TaskContainer, compact: Boolean, timeOnly: Boolean): Chip? {
val chip = newChip(task) val chip = newChip(task)
apply( val text = if (timeOnly) {
chip, task.startDate
R.drawable.ic_pending_actions_24px, .takeIf { Task.hasDueTime(it) }
?.let { DateUtilities.getTimeString(activity, task.startDate.toDateTime()) }
?: return null
} else {
DateUtilities.getRelativeDateTime( DateUtilities.getRelativeDateTime(
activity, activity,
task.startDate, task.startDate,
@ -60,11 +65,9 @@ class ChipProvider @Inject constructor(
if (compact) FormatStyle.SHORT else FormatStyle.MEDIUM, if (compact) FormatStyle.SHORT else FormatStyle.MEDIUM,
false, false,
false false
),
0,
showText = true,
showIcon = true
) )
}
apply(chip, R.drawable.ic_pending_actions_24px, text, 0, showText = true, showIcon = true)
return chip return chip
} }
@ -82,14 +85,14 @@ class ChipProvider @Inject constructor(
return chip return chip
} }
fun getChips(filter: Filter?, isSubtask: Boolean, task: TaskContainer): List<Chip> { fun getChips(filter: Filter?, isSubtask: Boolean, task: TaskContainer, sortByStartDate: Boolean): List<Chip> {
AndroidUtilities.assertMainThread() AndroidUtilities.assertMainThread()
val chips = ArrayList<Chip>() val chips = ArrayList<Chip>()
if (task.hasChildren() && preferences.showSubtaskChip) { if (task.hasChildren() && preferences.showSubtaskChip) {
chips.add(newSubtaskChip(task, !showText)) chips.add(newSubtaskChip(task, !showText))
} }
if (task.isHidden && preferences.showStartDateChip) { if (task.isHidden && preferences.showStartDateChip) {
chips.add(newStartDateChip(task, !showText)) newStartDateChip(task, !showText, sortByStartDate)?.let(chips::add)
} }
if (task.hasLocation() && filter !is PlaceFilter && preferences.showPlaceChip) { if (task.hasLocation() && filter !is PlaceFilter && preferences.showPlaceChip) {
val location = task.getLocation() val location = task.getLocation()

@ -150,6 +150,10 @@ internal class ScrollableViewsFactory(
&& sortGroup > 0 && sortGroup > 0
&& DateTimeUtils.newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) { && DateTimeUtils.newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) {
context.getColor(R.color.overdue) context.getColor(R.color.overdue)
} else if (sortMode == SortHelper.SORT_START
&& sortGroup > 0
&& DateTimeUtils.newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) {
context.getColor(R.color.overdue)
} else { } else {
textColorSecondary textColorSecondary
} }
@ -169,10 +173,10 @@ internal class ScrollableViewsFactory(
private fun getHeader(sortMode: Int, group: Long): String = when { private fun getHeader(sortMode: Int, group: Long): String = when {
sortMode == SortHelper.SORT_IMPORTANCE -> context.getString(priorityToString(group.toInt())) sortMode == SortHelper.SORT_IMPORTANCE -> context.getString(priorityToString(group.toInt()))
group == 0L -> context.getString(if (sortMode == SortHelper.SORT_DUE) { group == 0L -> context.getString(when (sortMode) {
R.string.no_due_date SortHelper.SORT_DUE -> R.string.no_due_date
} else { SortHelper.SORT_START -> R.string.no_start_date
R.string.no_date else -> R.string.no_date
}) })
sortMode == SortHelper.SORT_CREATED -> sortMode == SortHelper.SORT_CREATED ->
context.getString(R.string.sort_created_group, getDateString(group)) context.getString(R.string.sort_created_group, getDateString(group))

@ -38,6 +38,7 @@ File %1$s contained %2$s.\n\n
<string name="astrid_sort_order_summary">Enable Astrid\'s manual sort mode for \'My Tasks\', \'Today\', and tags. This sort mode will be replaced by \'My order\' in a future update</string> <string name="astrid_sort_order_summary">Enable Astrid\'s manual sort mode for \'My Tasks\', \'Today\', and tags. This sort mode will be replaced by \'My order\' in a future update</string>
<string name="SSD_sort_auto">Smart sort</string> <string name="SSD_sort_auto">Smart sort</string>
<string name="SSD_sort_alpha">By title</string> <string name="SSD_sort_alpha">By title</string>
<string name="SSD_sort_start">By start date</string>
<string name="SSD_sort_due">By due date</string> <string name="SSD_sort_due">By due date</string>
<string name="SSD_sort_importance">By priority</string> <string name="SSD_sort_importance">By priority</string>
<string name="SSD_sort_modified">By last modified</string> <string name="SSD_sort_modified">By last modified</string>

Loading…
Cancel
Save