diff --git a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt index b01f68bd0..dcd9a2c08 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt @@ -5,15 +5,16 @@ */ package com.todoroo.astrid.adapter -import com.todoroo.astrid.core.SortHelper.SORT_DUE -import com.todoroo.astrid.core.SortHelper.SORT_IMPORTANCE +import com.todoroo.astrid.core.SortHelper.* import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.data.Task +import com.todoroo.astrid.data.Task.Companion.HIDE_UNTIL_SPECIFIC_DAY import org.tasks.BuildConfig import org.tasks.LocalBroadcastManager import org.tasks.data.* import org.tasks.date.DateTimeUtils.toAppleEpoch import org.tasks.date.DateTimeUtils.toDateTime +import org.tasks.time.DateTimeUtils.millisOfDay import java.util.* import kotlin.collections.HashSet @@ -207,15 +208,16 @@ open class TaskAdapter( 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 task.setDueDateAdjustingHideUntil(when { 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) }) 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) { when { task.isGoogleTask -> changeGoogleTaskParent(task, null) diff --git a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java index fa7447854..768415573 100644 --- a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java +++ b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java @@ -32,6 +32,7 @@ public class SortHelper { public static final int SORT_CREATED = 5; public static final int SORT_GTASKS = 6; 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 @SuppressLint("DefaultLocale") @@ -40,6 +41,8 @@ public class SortHelper { private static final String ADJUSTED_DUE_DATE = "(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)); /** 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 + " END)+importance"); 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: order = Order.asc( @@ -126,6 +136,8 @@ public class SortHelper { switch (sortType) { case SORT_DUE: return "tasks.dueDate"; + case SORT_START: + return "tasks.hideUntil"; case SORT_IMPORTANCE: return "tasks.importance"; case SORT_MODIFIED: @@ -149,6 +161,11 @@ public class SortHelper { + ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate") + " END)+tasks.importance AS sort_duedate"; 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: 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; @@ -189,6 +206,9 @@ public class SortHelper { case SORT_DUE: order = Order.asc("sort_duedate"); break; + case SORT_START: + order = Order.asc("sort_startdate"); + break; case SORT_IMPORTANCE: order = Order.asc("sort_importance"); break; diff --git a/app/src/main/java/org/tasks/dialogs/SortDialog.java b/app/src/main/java/org/tasks/dialogs/SortDialog.java index d5a09f68b..8cf452cb1 100644 --- a/app/src/main/java/org/tasks/dialogs/SortDialog.java +++ b/app/src/main/java/org/tasks/dialogs/SortDialog.java @@ -86,6 +86,7 @@ public class SortDialog extends DialogFragment { } 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_importance)); items.add(getString(R.string.SSD_sort_alpha)); @@ -173,16 +174,18 @@ public class SortDialog extends DialogFragment { switch (sortMode) { case SortHelper.SORT_AUTO: return 1; - case SortHelper.SORT_DUE: + case SortHelper.SORT_START: return 2; - case SortHelper.SORT_IMPORTANCE: + case SortHelper.SORT_DUE: return 3; - case SortHelper.SORT_ALPHA: + case SortHelper.SORT_IMPORTANCE: return 4; - case SortHelper.SORT_MODIFIED: + case SortHelper.SORT_ALPHA: return 5; - case SortHelper.SORT_CREATED: + case SortHelper.SORT_MODIFIED: return 6; + case SortHelper.SORT_CREATED: + return 7; } Timber.e("Invalid sort mode: %s", sortMode); @@ -194,14 +197,16 @@ public class SortDialog extends DialogFragment { case 1: return SortHelper.SORT_AUTO; case 2: - return SortHelper.SORT_DUE; + return SortHelper.SORT_START; case 3: - return SortHelper.SORT_IMPORTANCE; + return SortHelper.SORT_DUE; case 4: - return SortHelper.SORT_ALPHA; + return SortHelper.SORT_IMPORTANCE; case 5: - return SortHelper.SORT_MODIFIED; + return SortHelper.SORT_ALPHA; case 6: + return SortHelper.SORT_MODIFIED; + case 7: return SortHelper.SORT_CREATED; } 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 6e3c6a26f..9ae16b4e1 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/ScrollableWidget.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/ScrollableWidget.kt @@ -203,6 +203,7 @@ class ScrollableWidget : InjectingPreferenceFragment() { } else { when (widgetPreferences.sortMode) { SORT_DUE -> R.string.SSD_sort_due + SORT_START -> R.string.SSD_sort_start SORT_IMPORTANCE -> R.string.SSD_sort_importance SORT_ALPHA -> R.string.SSD_sort_alpha SORT_MODIFIED -> R.string.SSD_sort_modified diff --git a/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt b/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt index 120dda2b7..bab6a903c 100644 --- a/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt +++ b/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt @@ -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.setTextColor( 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 = 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_IMPORTANCE -> + context.getString(priorityToString(group.toInt())) + group == 0L -> context.getString(when (sortMode) { + SortHelper.SORT_DUE -> R.string.no_due_date + SortHelper.SORT_START -> R.string.no_start_date + else -> R.string.no_date }) sortMode == SortHelper.SORT_CREATED -> context.getString(R.string.sort_created_group, getDateString(group, alwaysDisplayFullDate)) diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt index 27ae0ba81..1a341aed3 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt @@ -6,7 +6,6 @@ import androidx.recyclerview.widget.RecyclerView import com.todoroo.astrid.activity.TaskListFragment import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapterDataSource -import com.todoroo.astrid.core.SortHelper.SORT_DUE import org.tasks.data.TaskContainer import org.tasks.preferences.Preferences @@ -22,13 +21,13 @@ abstract class TaskListRecyclerAdapter internal constructor( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val filter = taskList.getFilter() - val sortByDueDate = filter.supportsSorting() - && preferences.sortMode == SORT_DUE + val groupsEnabled = filter.supportsSorting() && !(filter.supportsManualSort() && preferences.isManualSort) && !(filter.supportsAstridSorting() && preferences.isAstridSort) val task = getItem(position) 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 val indent = adapter.getIndent(task) task.setIndent(indent) diff --git a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt index f24eb0882..995b8224f 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt +++ b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.kt @@ -15,6 +15,8 @@ import butterknife.OnLongClick import com.google.android.material.chip.ChipGroup import com.todoroo.andlib.utility.DateUtilities 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 org.tasks.R import org.tasks.data.TaskContainer @@ -132,13 +134,13 @@ class TaskViewHolder internal constructor( 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 indent = task.indent nameView.text = task.title setupTitleAndCheckbox() - setupDueDate(sortByDueDate) - setupChips(filter) + setupDueDate(sortMode == SORT_DUE) + setupChips(filter, sortMode == SORT_START) if (preferences.getBoolean(R.string.p_show_description, true)) { description.text = task.notes description.visibility = if (task.hasNotes()) View.VISIBLE else View.GONE @@ -201,8 +203,8 @@ class TaskViewHolder internal constructor( } } - private fun setupChips(filter: Filter) { - val chips = chipProvider.getChips(filter, indent > 0, task) + private fun setupChips(filter: Filter, sortByStartDate: Boolean) { + val chips = chipProvider.getChips(filter, indent > 0, task, sortByStartDate) if (chips.isEmpty()) { chipGroup.visibility = View.GONE } else { diff --git a/app/src/main/java/org/tasks/ui/ChipProvider.kt b/app/src/main/java/org/tasks/ui/ChipProvider.kt index cd7cd222d..4e36a017d 100644 --- a/app/src/main/java/org/tasks/ui/ChipProvider.kt +++ b/app/src/main/java/org/tasks/ui/ChipProvider.kt @@ -11,11 +11,13 @@ import com.todoroo.astrid.api.CaldavFilter import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.GtasksFilter import com.todoroo.astrid.api.TagFilter +import com.todoroo.astrid.data.Task import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.billing.Inventory import org.tasks.data.TagData import org.tasks.data.TaskContainer +import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.filters.PlaceFilter import org.tasks.locale.Locale import org.tasks.preferences.Preferences @@ -48,23 +50,24 @@ class ChipProvider @Inject constructor( 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) - apply( - chip, - R.drawable.ic_pending_actions_24px, - DateUtilities.getRelativeDateTime( - activity, - task.startDate, - locale.locale, - if (compact) FormatStyle.SHORT else FormatStyle.MEDIUM, - false, - false - ), - 0, - showText = true, - showIcon = true - ) + val text = if (timeOnly) { + task.startDate + .takeIf { Task.hasDueTime(it) } + ?.let { DateUtilities.getTimeString(activity, task.startDate.toDateTime()) } + ?: return null + } else { + DateUtilities.getRelativeDateTime( + activity, + task.startDate, + locale.locale, + if (compact) FormatStyle.SHORT else FormatStyle.MEDIUM, + false, + false + ) + } + apply(chip, R.drawable.ic_pending_actions_24px, text, 0, showText = true, showIcon = true) return chip } @@ -82,14 +85,14 @@ class ChipProvider @Inject constructor( return chip } - fun getChips(filter: Filter?, isSubtask: Boolean, task: TaskContainer): List { + fun getChips(filter: Filter?, isSubtask: Boolean, task: TaskContainer, sortByStartDate: Boolean): List { AndroidUtilities.assertMainThread() val chips = ArrayList() if (task.hasChildren() && preferences.showSubtaskChip) { chips.add(newSubtaskChip(task, !showText)) } 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) { val location = task.getLocation() diff --git a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt index 894d34221..238a1c028 100644 --- a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt +++ b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.kt @@ -150,6 +150,10 @@ internal class ScrollableViewsFactory( && sortGroup > 0 && DateTimeUtils.newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) { 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 { textColorSecondary } @@ -169,10 +173,10 @@ internal class ScrollableViewsFactory( private fun getHeader(sortMode: Int, group: Long): String = 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 + group == 0L -> context.getString(when (sortMode) { + SortHelper.SORT_DUE -> R.string.no_due_date + SortHelper.SORT_START -> R.string.no_start_date + else -> R.string.no_date }) sortMode == SortHelper.SORT_CREATED -> context.getString(R.string.sort_created_group, getDateString(group)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4bbf89dd2..ccc68c2c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,7 @@ File %1$s contained %2$s.\n\n 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 Smart sort By title + By start date By due date By priority By last modified