From 1067de41832c6fb92c6cbdd7af746c3d10a5464c Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Tue, 26 Mar 2024 13:39:47 -0500 Subject: [PATCH] Emit SectionedDataSource from TaskListViewModel --- .../astrid/activity/TaskListFragment.kt | 20 ++++--- .../astrid/adapter/NavigationDrawerAdapter.kt | 10 ++-- .../com/todoroo/astrid/adapter/TaskAdapter.kt | 19 ------- .../org/tasks/activities/DragAndDropDiffer.kt | 15 ++---- .../NavigationDrawerCustomization.kt | 2 +- .../java/org/tasks/compose/edit/SubtaskRow.kt | 37 +++++++------ .../java/org/tasks/tasklist/DiffCallback.kt | 6 +-- .../tasklist/DragAndDropRecyclerAdapter.kt | 40 +++++--------- .../org/tasks/tasklist/SectionedDataSource.kt | 50 ++++++++++++++++-- .../tasks/tasklist/TaskListRecyclerAdapter.kt | 7 ++- .../java/org/tasks/ui/TaskListViewModel.kt | 52 ++++++++++++++++--- .../tasks/widget/TasksWidgetViewFactory.kt | 6 +-- 12 files changed, 158 insertions(+), 106 deletions(-) diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index 264775999..d97c17850 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -118,6 +118,7 @@ import org.tasks.preferences.Preferences import org.tasks.sync.SyncAdapters import org.tasks.tags.TagPickerActivity import org.tasks.tasklist.DragAndDropRecyclerAdapter +import org.tasks.tasklist.SectionedDataSource import org.tasks.tasklist.TaskViewHolder import org.tasks.tasklist.ViewHolderFactory import org.tasks.themes.ColorProvider @@ -188,7 +189,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL activity?.recreate() } if (data.getBooleanExtra(SortSettingsActivity.EXTRA_CHANGED_GROUP, false)) { - taskAdapter.clearCollapsed() + listViewModel.clearCollapsed() } listViewModel.invalidate() } @@ -237,7 +238,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL super.onSaveInstanceState(outState) val selectedTaskIds: List = taskAdapter.getSelected() outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray()) - outState.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -274,7 +274,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL // set up list adapters taskAdapter = taskAdapterProvider.createTaskAdapter(filter) - taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED)) listViewModel.setFilter(filter) (recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false recyclerView.layoutManager = LinearLayoutManager(context) @@ -362,11 +361,19 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL return binding.root } - private fun submitList(tasks: List) { + private fun submitList(tasks: SectionedDataSource) { if (recyclerAdapter !is DragAndDropRecyclerAdapter) { setAdapter( DragAndDropRecyclerAdapter( - taskAdapter, binding.bodyStandard.recyclerView, viewHolderFactory, this, tasks, preferences)) + adapter = taskAdapter, + recyclerView = binding.bodyStandard.recyclerView, + viewHolderFactory = viewHolderFactory, + taskList = this, + tasks = tasks, + preferences = preferences, + toggleCollapsed = { listViewModel.toggleCollapsed(it) }, + ) + ) } else { recyclerAdapter?.submitList(tasks) } @@ -894,8 +901,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString())?.show() } - fun clearCollapsed() = taskAdapter.clearCollapsed() - override fun onCompletedTask(task: TaskContainer, newState: Boolean) { if (task.isReadOnly) { return @@ -1010,7 +1015,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL const val ACTION_RELOAD = "action_reload" const val ACTION_DELETED = "action_deleted" private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids" - private const val EXTRA_COLLAPSED = "extra_collapsed" private const val VOICE_RECOGNITION_REQUEST_CODE = 1234 private const val EXTRA_FILTER = "extra_filter" private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker" diff --git a/app/src/main/java/com/todoroo/astrid/adapter/NavigationDrawerAdapter.kt b/app/src/main/java/com/todoroo/astrid/adapter/NavigationDrawerAdapter.kt index 55adff9ec..89e74e7fe 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/NavigationDrawerAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/NavigationDrawerAdapter.kt @@ -34,11 +34,11 @@ class NavigationDrawerAdapter @Inject constructor( private val colorProvider: ColorProvider, private val subheaderClickHandler: SubheaderClickHandler, ) : RecyclerView.Adapter(), - DragAndDropDiffer> { + DragAndDropDiffer> { private lateinit var onClick: (FilterListItem?) -> Unit - override val channel = Channel>(Channel.UNLIMITED) - override val updates: Queue, DiffUtil.DiffResult?>> = LinkedList() + override val channel = Channel>(Channel.UNLIMITED) + override val updates: Queue, DiffUtil.DiffResult?>> = LinkedList() override val scope: CoroutineScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher() + Job()) override var items = initializeDiffer(ArrayList()) @@ -84,9 +84,7 @@ class NavigationDrawerAdapter @Inject constructor( private fun getItem(position: Int) = items[position] - override fun transform(list: List) = list.toMutableList() - - override fun diff(last: MutableList, next: MutableList) = + override fun diff(last: ArrayList, next: ArrayList) = DiffUtil.calculateDiff(DiffCallback(last, next)) private class DiffCallback(val old: List, val new: List) : DiffUtil.Callback() { 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 55da65987..23ea59d5c 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt @@ -22,7 +22,6 @@ import org.tasks.data.GoogleTaskDao import org.tasks.data.TaskContainer import org.tasks.date.DateTimeUtils.toAppleEpoch import org.tasks.date.DateTimeUtils.toDateTime -import org.tasks.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED import org.tasks.time.DateTimeUtils.millisOfDay open class TaskAdapter( @@ -34,7 +33,6 @@ open class TaskAdapter( private val taskMover: TaskMover, ) { private val selected = HashSet() - private val collapsed = mutableSetOf(HEADER_COMPLETED) private lateinit var dataSource: TaskAdapterDataSource val count: Int @@ -56,15 +54,6 @@ open class TaskAdapter( fun clearSelections() = selected.clear() - fun getCollapsed() = HashSet(collapsed) - - fun setCollapsed(groups: LongArray?) { - clearCollapsed() - groups?.toList()?.let(collapsed::addAll) - } - - fun clearCollapsed() = collapsed.retainAll(listOf(HEADER_COMPLETED)) - open fun getIndent(task: TaskContainer): Int = task.indent open fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean { @@ -125,14 +114,6 @@ open class TaskAdapter( } } - fun toggleCollapsed(group: Long) { - if (collapsed.contains(group)) { - collapsed.remove(group) - } else { - collapsed.add(group) - } - } - open fun supportsAstridSorting(): Boolean = false open suspend fun moved(from: Int, to: Int, indent: Int) { diff --git a/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt b/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt index 3242f2980..b9f90ccd5 100644 --- a/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt +++ b/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt @@ -13,25 +13,24 @@ import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.scan import java.util.Queue -interface DragAndDropDiffer : ListUpdateCallback { - val channel: Channel> +interface DragAndDropDiffer> : ListUpdateCallback { + val channel: Channel val updates: Queue> var items: R var dragging: Boolean val scope: CoroutineScope - fun submitList(list: List) { + fun submitList(list: R) { channel.trySend(list) } fun calculateDiff(last: Pair, next: R): Pair { AndroidUtilities.assertNotMainThread() - return Pair(next, diff(last.first!!, next)) + return Pair(next, diff(last.first, next)) } fun applyDiff(update: Pair) { @@ -53,11 +52,9 @@ interface DragAndDropDiffer : ListUpdateCallback { } @ExperimentalCoroutinesApi - fun initializeDiffer(list: List): R { - val initial = transform(list) + fun initializeDiffer(initial: R): R { channel .consumeAsFlow() - .map { transform(it) } .scan(Pair(initial, null)) { last: Pair, next: R -> calculateDiff(last, next) } @@ -68,8 +65,6 @@ interface DragAndDropDiffer : ListUpdateCallback { return initial } - fun transform(list: List): R - fun diff(last: R, next: R): DiffUtil.DiffResult fun dispose() { diff --git a/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt b/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt index 4a65d9918..9494da6ea 100644 --- a/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt +++ b/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt @@ -100,7 +100,7 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba private fun updateFilters() = lifecycleScope.launch { filterProvider .drawerCustomizationItems() - .let { adapter.submitList(it) } + .let { adapter.submitList(ArrayList(it)) } } private fun onClick(item: FilterListItem?) { diff --git a/app/src/main/java/org/tasks/compose/edit/SubtaskRow.kt b/app/src/main/java/org/tasks/compose/edit/SubtaskRow.kt index 1b4e5fcb0..ffabb28f5 100644 --- a/app/src/main/java/org/tasks/compose/edit/SubtaskRow.kt +++ b/app/src/main/java/org/tasks/compose/edit/SubtaskRow.kt @@ -44,6 +44,7 @@ import org.tasks.compose.SubtaskChip import org.tasks.compose.TaskEditIcon import org.tasks.compose.TaskEditRow import org.tasks.data.TaskContainer +import org.tasks.tasklist.SectionedDataSource import org.tasks.ui.TaskListViewModel @Composable @@ -90,7 +91,11 @@ fun SubtaskRow( } else { Spacer(modifier = Modifier.height(height = 8.dp)) if (existingSubtasks is TaskListViewModel.TasksResults.Results) { - existingSubtasks.tasks.forEach { task -> + existingSubtasks + .tasks + .filterIsInstance() + .map { it.task } + .forEach { task -> ExistingSubtaskRow( task = task, desaturate = desaturate, @@ -237,7 +242,7 @@ fun NoSubtasks() { filter = null, hasParent = false, desaturate = true, - existingSubtasks = TaskListViewModel.TasksResults.Results(emptyList()), + existingSubtasks = TaskListViewModel.TasksResults.Results(SectionedDataSource()), newSubtasks = emptyList(), openSubtask = {}, completeExistingSubtask = { _, _ -> }, @@ -260,20 +265,22 @@ fun SubtasksPreview() { hasParent = false, desaturate = true, existingSubtasks = TaskListViewModel.TasksResults.Results( - listOf( - TaskContainer( - task = Task( - title = "Existing subtask 1", - priority = Task.Priority.HIGH, + SectionedDataSource( + tasks = listOf( + TaskContainer( + task = Task( + title = "Existing subtask 1", + priority = Task.Priority.HIGH, + ), + indent = 0 ), - indent = 0 - ), - TaskContainer( - task = Task( - title = "Existing subtask 2 with a really long title", - priority = Task.Priority.LOW, - ), - indent = 1 + TaskContainer( + task = Task( + title = "Existing subtask 2 with a really long title", + priority = Task.Priority.LOW, + ), + indent = 1 + ) ) ) ), diff --git a/app/src/main/java/org/tasks/tasklist/DiffCallback.kt b/app/src/main/java/org/tasks/tasklist/DiffCallback.kt index 5b4f35fcb..a004fe85f 100644 --- a/app/src/main/java/org/tasks/tasklist/DiffCallback.kt +++ b/app/src/main/java/org/tasks/tasklist/DiffCallback.kt @@ -30,7 +30,7 @@ internal class DiffCallback( return if (isHeader) { old.groupMode == new.groupMode && old.getHeaderValue(oldPosition) == new.getHeaderValue(newPosition) } else { - old.getItem(oldPosition)!!.id == new.getItem(newPosition)!!.id + old.getItem(oldPosition).id == new.getItem(newPosition).id } } @@ -38,8 +38,8 @@ internal class DiffCallback( if (new.isHeader(newPosition)) { return old.getSection(oldPosition).collapsed == new.getSection(newPosition).collapsed } - val oldItem = old.getItem(oldPosition)!! - val newItem = new.getItem(newPosition)!! + val oldItem = old.getItem(oldPosition) + val newItem = new.getItem(newPosition) return !refreshDates && oldItem == newItem && oldItem.indent == adapter.getIndent(newItem) } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt index f7ff344d8..50e08b410 100644 --- a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt +++ b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt @@ -14,7 +14,6 @@ import androidx.recyclerview.widget.ItemTouchHelper.UP import androidx.recyclerview.widget.RecyclerView import com.todoroo.astrid.activity.TaskListFragment import com.todoroo.astrid.adapter.TaskAdapter -import com.todoroo.astrid.api.AstridOrderingFilter import com.todoroo.astrid.utility.Flags import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -24,6 +23,7 @@ import kotlinx.coroutines.runBlocking import org.tasks.activities.DragAndDropDiffer import org.tasks.data.TaskContainer import org.tasks.preferences.Preferences +import org.tasks.ui.TaskListViewModel.UiItem import java.util.LinkedList import java.util.Queue import java.util.concurrent.Executors @@ -31,21 +31,18 @@ import kotlin.math.max import kotlin.math.min class DragAndDropRecyclerAdapter( - private val adapter: TaskAdapter, - private val recyclerView: RecyclerView, - viewHolderFactory: ViewHolderFactory, - private val taskList: TaskListFragment, - tasks: List, - preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, preferences), DragAndDropDiffer { - private val disableHeaders = taskList.getFilter().let { - !it.supportsSorting() - || (it.supportsManualSort() && preferences.isManualSort) - || (it is AstridOrderingFilter && preferences.isAstridSort) - } + private val adapter: TaskAdapter, + private val recyclerView: RecyclerView, + viewHolderFactory: ViewHolderFactory, + private val taskList: TaskListFragment, + tasks: SectionedDataSource, + preferences: Preferences, + private val toggleCollapsed: (Long) -> Unit, +) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, preferences), DragAndDropDiffer { private val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()).apply { attachToRecyclerView(recyclerView) } - override val channel = Channel>(Channel.UNLIMITED) + override val channel = Channel(Channel.UNLIMITED) override val updates: Queue> = LinkedList() override var dragging = false override val scope: CoroutineScope = @@ -70,7 +67,7 @@ class DragAndDropRecyclerAdapter( override fun getItemViewType(position: Int) = if (items.isHeader(position)) 1 else 0 - override fun submitList(list: List) { + override fun submitList(list: SectionedDataSource) { super.submitList(list) } @@ -81,8 +78,7 @@ class DragAndDropRecyclerAdapter( } private fun toggleGroup(group: Long) { - adapter.toggleCollapsed(group) - taskList.loadTaskListContent() + toggleCollapsed(group) } override fun dragAndDropEnabled() = taskList.getFilter().supportsSubtasks() @@ -93,18 +89,8 @@ class DragAndDropRecyclerAdapter( override fun getItem(position: Int) = items.getItem(position) - override fun transform(list: List): SectionedDataSource = - SectionedDataSource( - tasks = list, - disableHeaders = disableHeaders, - groupMode = preferences.groupMode, - subtaskMode = preferences.subtaskMode, - collapsed = adapter.getCollapsed(), - completedAtBottom = preferences.completedTasksAtBottom, - ) - override fun diff(last: SectionedDataSource, next: SectionedDataSource) = - DiffUtil.calculateDiff(DiffCallback(last, next, adapter), next.size < LONG_LIST_SIZE) + DiffUtil.calculateDiff(DiffCallback(last, next, adapter), next.size < LONG_LIST_SIZE) override fun drainQueue() { val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState() diff --git a/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt b/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt index 8a4edc517..ed4cb0b7a 100644 --- a/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt +++ b/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt @@ -6,6 +6,7 @@ import com.todoroo.andlib.utility.DateUtilities.now import com.todoroo.astrid.core.SortHelper import org.tasks.data.TaskContainer import org.tasks.time.DateTimeUtils.startOfDay +import org.tasks.ui.TaskListViewModel.UiItem class SectionedDataSource( tasks: List = emptyList(), @@ -14,7 +15,7 @@ class SectionedDataSource( val subtaskMode: Int = SortHelper.SORT_MANUAL, private val collapsed: Set = emptySet(), private val completedAtBottom: Boolean = true, -) { +): List { private val tasks = tasks.toMutableList() private val sections = if (disableHeaders || groupMode == SortHelper.GROUP_NONE) { @@ -23,7 +24,7 @@ class SectionedDataSource( getSections() } - fun getItem(position: Int): TaskContainer? = tasks.getOrNull(sectionedPositionToPosition(position)) + fun getItem(position: Int): TaskContainer = tasks[sectionedPositionToPosition(position)] fun getHeaderValue(position: Int): Long = getSection(position).value @@ -48,9 +49,52 @@ class SectionedDataSource( val taskCount: Int get() = tasks.size - val size: Int + override val size: Int get() = tasks.size + sections.size() + override fun get(index: Int) = + sections[index] + ?.let { UiItem.Header(it.value) } + ?: UiItem.Task(getItem(index)) + + override fun isEmpty() = size == 0 + + override fun iterator(): Iterator { + return object : Iterator { + private var index = 0 + override fun hasNext() = index < size + override fun next(): UiItem = get(index++) + } + } + + override fun listIterator(): ListIterator { + TODO("Not yet implemented") + } + + override fun listIterator(index: Int): ListIterator { + TODO("Not yet implemented") + } + + override fun subList(fromIndex: Int, toIndex: Int): List { + TODO("Not yet implemented") + } + + override fun lastIndexOf(element: UiItem): Int { + TODO("Not yet implemented") + } + + override fun indexOf(element: UiItem): Int { + TODO("Not yet implemented") + } + + override fun containsAll(elements: Collection): Boolean { + TODO("Not yet implemented") + } + + override fun contains(element: UiItem): Boolean { + TODO("Not yet implemented") + } + fun getSection(position: Int): AdapterSection = sections[position] fun add(position: Int, task: TaskContainer) = tasks.add(sectionedPositionToPosition(position), task) diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt index 79f470695..9077fe7bb 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt @@ -8,15 +8,14 @@ import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapterDataSource import com.todoroo.astrid.api.AstridOrderingFilter import com.todoroo.astrid.core.SortHelper -import org.tasks.data.TaskContainer import org.tasks.preferences.Preferences abstract class TaskListRecyclerAdapter internal constructor( private val adapter: TaskAdapter, internal val viewHolderFactory: ViewHolderFactory, private val taskList: TaskListFragment, - internal val preferences: Preferences) - : RecyclerView.Adapter(), ListUpdateCallback, TaskAdapterDataSource { + internal val preferences: Preferences +): RecyclerView.Adapter(), ListUpdateCallback, TaskAdapterDataSource { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = viewHolderFactory.newViewHolder(parent, taskList) @@ -51,7 +50,7 @@ abstract class TaskListRecyclerAdapter internal constructor( abstract fun dragAndDropEnabled(): Boolean - abstract fun submitList(list: List) + abstract fun submitList(list: SectionedDataSource) override fun onInserted(position: Int, count: Int) { notifyItemRangeInserted(position, count) diff --git a/app/src/main/java/org/tasks/ui/TaskListViewModel.kt b/app/src/main/java/org/tasks/ui/TaskListViewModel.kt index 4686b89f4..f3a722767 100644 --- a/app/src/main/java/org/tasks/ui/TaskListViewModel.kt +++ b/app/src/main/java/org/tasks/ui/TaskListViewModel.kt @@ -7,6 +7,7 @@ import android.content.Intent import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.api.AstridOrderingFilter import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.FilterImpl import com.todoroo.astrid.api.SearchFilter @@ -19,7 +20,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -36,8 +36,10 @@ import org.tasks.data.TaskContainer import org.tasks.data.TaskDao import org.tasks.data.TaskListQuery.getQuery import org.tasks.db.QueryUtils +import org.tasks.filters.MyTasksFilter import org.tasks.preferences.Preferences import org.tasks.preferences.QueryPreferences +import org.tasks.tasklist.SectionedDataSource import javax.inject.Inject @HiltViewModel @@ -53,18 +55,24 @@ class TaskListViewModel @Inject constructor( private val firebase: Firebase, ) : ViewModel() { + sealed class UiItem { + data class Header(val value: Long): UiItem() + data class Task(val task: TaskContainer): UiItem() + } + sealed interface TasksResults { data object Loading : TasksResults - data class Results(val tasks: List) : TasksResults + data class Results(val tasks: SectionedDataSource) : TasksResults } data class State( - val filter: Filter? = null, + val filter: Filter = MyTasksFilter(""), val now: Long = DateUtilities.now(), val searchQuery: String? = null, val tasks: TasksResults = TasksResults.Loading, val begForSubscription: Boolean = false, val syncOngoing: Boolean = false, + val collapsed: Set = setOf(SectionedDataSource.HEADER_COMPLETED), ) private val _state = MutableStateFlow(State()) @@ -104,7 +112,7 @@ class TaskListViewModel @Inject constructor( } suspend fun getTasksToClear(): List { - val filter = _state.value.filter ?: return emptyList() + val filter = _state.value.filter val deleteFilter = FilterImpl( sql = QueryUtils.removeOrder(QueryUtils.showHiddenAndCompleted(filter.sql!!)), ) @@ -130,13 +138,12 @@ class TaskListViewModel @Inject constructor( localBroadcastManager.registerRefreshReceiver(refreshReceiver) _state - .filter { it.filter != null } .map { it.copy(tasks = TasksResults.Loading) } .distinctUntilChanged() .throttleLatest(333) .map { val filter = when { - it.searchQuery == null -> it.filter!! + it.searchQuery == null -> it.filter it.searchQuery.isBlank() -> BuiltInFilterExposer.getMyTasksFilter(context.resources) else -> context.createSearchQuery(it.searchQuery) } @@ -144,7 +151,20 @@ class TaskListViewModel @Inject constructor( } .onEach { tasks -> _state.update { - it.copy(tasks = TasksResults.Results(tasks)) + it.copy( + tasks = TasksResults.Results( + SectionedDataSource( + tasks = tasks, + disableHeaders = !it.filter.supportsSorting() + || (it.filter.supportsManualSort() && preferences.isManualSort) + || (it.filter is AstridOrderingFilter && preferences.isAstridSort), + groupMode = preferences.groupMode, + subtaskMode = preferences.subtaskMode, + collapsed = it.collapsed, + completedAtBottom = preferences.completedTasksAtBottom, + ) + ) + ) } } .flowOn(Dispatchers.Default) @@ -163,6 +183,24 @@ class TaskListViewModel @Inject constructor( localBroadcastManager.unregisterReceiver(refreshReceiver) } + fun clearCollapsed() { + _state.update { + it.copy(collapsed = setOf(SectionedDataSource.HEADER_COMPLETED)) + } + } + + fun toggleCollapsed(group: Long) { + _state.update { + it.copy( + collapsed = if (it.collapsed.contains(group)) { + it.collapsed.minus(group) + } else { + it.collapsed.plus(group) + } + ) + } + } + companion object { fun Context.createSearchQuery(query: String): Filter = SearchFilter(getString(R.string.FLA_search_filter, query), query) diff --git a/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt b/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt index 3d80dba5e..1a47fa254 100644 --- a/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt +++ b/app/src/main/java/org/tasks/widget/TasksWidgetViewFactory.kt @@ -92,7 +92,7 @@ internal class TasksWidgetViewFactory( override fun getViewTypeCount(): Int = 2 - override fun getItemId(position: Int) = getTask(position)?.id ?: 0 + override fun getItemId(position: Int) = getTask(position).id override fun hasStableIds(): Boolean = true @@ -145,7 +145,7 @@ internal class TasksWidgetViewFactory( private fun buildUpdate(position: Int): RemoteViews? { return try { - val taskContainer = getTask(position) ?: return null + val taskContainer = getTask(position) val task = taskContainer.task val textColorTitle = when { task.isHidden -> onSurfaceVariant @@ -258,7 +258,7 @@ internal class TasksWidgetViewFactory( } } - private fun getTask(position: Int): TaskContainer? = tasks.getItem(position) + private fun getTask(position: Int): TaskContainer = tasks.getItem(position) private suspend fun getQuery(filter: Filter): List { subtasksHelper.applySubtasksToWidgetFilter(filter, widgetPreferences)