Emit SectionedDataSource from TaskListViewModel

pull/2803/head
Alex Baker 2 months ago
parent d686b8c7e0
commit 1067de4183

@ -118,6 +118,7 @@ import org.tasks.preferences.Preferences
import org.tasks.sync.SyncAdapters import org.tasks.sync.SyncAdapters
import org.tasks.tags.TagPickerActivity import org.tasks.tags.TagPickerActivity
import org.tasks.tasklist.DragAndDropRecyclerAdapter import org.tasks.tasklist.DragAndDropRecyclerAdapter
import org.tasks.tasklist.SectionedDataSource
import org.tasks.tasklist.TaskViewHolder import org.tasks.tasklist.TaskViewHolder
import org.tasks.tasklist.ViewHolderFactory import org.tasks.tasklist.ViewHolderFactory
import org.tasks.themes.ColorProvider import org.tasks.themes.ColorProvider
@ -188,7 +189,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
activity?.recreate() activity?.recreate()
} }
if (data.getBooleanExtra(SortSettingsActivity.EXTRA_CHANGED_GROUP, false)) { if (data.getBooleanExtra(SortSettingsActivity.EXTRA_CHANGED_GROUP, false)) {
taskAdapter.clearCollapsed() listViewModel.clearCollapsed()
} }
listViewModel.invalidate() listViewModel.invalidate()
} }
@ -237,7 +238,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
val selectedTaskIds: List<Long> = taskAdapter.getSelected() val selectedTaskIds: List<Long> = taskAdapter.getSelected()
outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray()) outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray())
outState.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray())
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -274,7 +274,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
// set up list adapters // set up list adapters
taskAdapter = taskAdapterProvider.createTaskAdapter(filter) taskAdapter = taskAdapterProvider.createTaskAdapter(filter)
taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED))
listViewModel.setFilter(filter) listViewModel.setFilter(filter)
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false (recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.layoutManager = LinearLayoutManager(context)
@ -362,11 +361,19 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
return binding.root return binding.root
} }
private fun submitList(tasks: List<TaskContainer>) { private fun submitList(tasks: SectionedDataSource) {
if (recyclerAdapter !is DragAndDropRecyclerAdapter) { if (recyclerAdapter !is DragAndDropRecyclerAdapter) {
setAdapter( setAdapter(
DragAndDropRecyclerAdapter( 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 { } else {
recyclerAdapter?.submitList(tasks) recyclerAdapter?.submitList(tasks)
} }
@ -894,8 +901,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString())?.show() makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString())?.show()
} }
fun clearCollapsed() = taskAdapter.clearCollapsed()
override fun onCompletedTask(task: TaskContainer, newState: Boolean) { override fun onCompletedTask(task: TaskContainer, newState: Boolean) {
if (task.isReadOnly) { if (task.isReadOnly) {
return return
@ -1010,7 +1015,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
const val ACTION_RELOAD = "action_reload" const val ACTION_RELOAD = "action_reload"
const val ACTION_DELETED = "action_deleted" const val ACTION_DELETED = "action_deleted"
private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids" 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 VOICE_RECOGNITION_REQUEST_CODE = 1234
private const val EXTRA_FILTER = "extra_filter" private const val EXTRA_FILTER = "extra_filter"
private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker" private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker"

@ -34,11 +34,11 @@ class NavigationDrawerAdapter @Inject constructor(
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val subheaderClickHandler: SubheaderClickHandler, private val subheaderClickHandler: SubheaderClickHandler,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
DragAndDropDiffer<FilterListItem, MutableList<FilterListItem>> { DragAndDropDiffer<FilterListItem, ArrayList<FilterListItem>> {
private lateinit var onClick: (FilterListItem?) -> Unit private lateinit var onClick: (FilterListItem?) -> Unit
override val channel = Channel<List<FilterListItem>>(Channel.UNLIMITED) override val channel = Channel<ArrayList<FilterListItem>>(Channel.UNLIMITED)
override val updates: Queue<Pair<MutableList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList() override val updates: Queue<Pair<ArrayList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList()
override val scope: CoroutineScope = override val scope: CoroutineScope =
CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher() + Job()) CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher() + Job())
override var items = initializeDiffer(ArrayList()) override var items = initializeDiffer(ArrayList())
@ -84,9 +84,7 @@ class NavigationDrawerAdapter @Inject constructor(
private fun getItem(position: Int) = items[position] private fun getItem(position: Int) = items[position]
override fun transform(list: List<FilterListItem>) = list.toMutableList() override fun diff(last: ArrayList<FilterListItem>, next: ArrayList<FilterListItem>) =
override fun diff(last: MutableList<FilterListItem>, next: MutableList<FilterListItem>) =
DiffUtil.calculateDiff(DiffCallback(last, next)) DiffUtil.calculateDiff(DiffCallback(last, next))
private class DiffCallback(val old: List<FilterListItem>, val new: List<FilterListItem>) : DiffUtil.Callback() { private class DiffCallback(val old: List<FilterListItem>, val new: List<FilterListItem>) : DiffUtil.Callback() {

@ -22,7 +22,6 @@ import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
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.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED
import org.tasks.time.DateTimeUtils.millisOfDay import org.tasks.time.DateTimeUtils.millisOfDay
open class TaskAdapter( open class TaskAdapter(
@ -34,7 +33,6 @@ open class TaskAdapter(
private val taskMover: TaskMover, private val taskMover: TaskMover,
) { ) {
private val selected = HashSet<Long>() private val selected = HashSet<Long>()
private val collapsed = mutableSetOf(HEADER_COMPLETED)
private lateinit var dataSource: TaskAdapterDataSource private lateinit var dataSource: TaskAdapterDataSource
val count: Int val count: Int
@ -56,15 +54,6 @@ open class TaskAdapter(
fun clearSelections() = selected.clear() 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 getIndent(task: TaskContainer): Int = task.indent
open fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean { 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 fun supportsAstridSorting(): Boolean = false
open suspend fun moved(from: Int, to: Int, indent: Int) { open suspend fun moved(from: Int, to: Int, indent: Int) {

@ -13,25 +13,24 @@ import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.scan
import java.util.Queue import java.util.Queue
interface DragAndDropDiffer<T, R> : ListUpdateCallback { interface DragAndDropDiffer<T, R : List<T>> : ListUpdateCallback {
val channel: Channel<List<T>> val channel: Channel<R>
val updates: Queue<Pair<R, DiffUtil.DiffResult?>> val updates: Queue<Pair<R, DiffUtil.DiffResult?>>
var items: R var items: R
var dragging: Boolean var dragging: Boolean
val scope: CoroutineScope val scope: CoroutineScope
fun submitList(list: List<T>) { fun submitList(list: R) {
channel.trySend(list) channel.trySend(list)
} }
fun calculateDiff(last: Pair<R, DiffUtil.DiffResult?>, next: R): Pair<R, DiffUtil.DiffResult?> { fun calculateDiff(last: Pair<R, DiffUtil.DiffResult?>, next: R): Pair<R, DiffUtil.DiffResult?> {
AndroidUtilities.assertNotMainThread() AndroidUtilities.assertNotMainThread()
return Pair(next, diff(last.first!!, next)) return Pair(next, diff(last.first, next))
} }
fun applyDiff(update: Pair<R, DiffUtil.DiffResult?>) { fun applyDiff(update: Pair<R, DiffUtil.DiffResult?>) {
@ -53,11 +52,9 @@ interface DragAndDropDiffer<T, R> : ListUpdateCallback {
} }
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
fun initializeDiffer(list: List<T>): R { fun initializeDiffer(initial: R): R {
val initial = transform(list)
channel channel
.consumeAsFlow() .consumeAsFlow()
.map { transform(it) }
.scan(Pair(initial, null)) { last: Pair<R, DiffUtil.DiffResult?>, next: R -> .scan(Pair(initial, null)) { last: Pair<R, DiffUtil.DiffResult?>, next: R ->
calculateDiff(last, next) calculateDiff(last, next)
} }
@ -68,8 +65,6 @@ interface DragAndDropDiffer<T, R> : ListUpdateCallback {
return initial return initial
} }
fun transform(list: List<T>): R
fun diff(last: R, next: R): DiffUtil.DiffResult fun diff(last: R, next: R): DiffUtil.DiffResult
fun dispose() { fun dispose() {

@ -100,7 +100,7 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
private fun updateFilters() = lifecycleScope.launch { private fun updateFilters() = lifecycleScope.launch {
filterProvider filterProvider
.drawerCustomizationItems() .drawerCustomizationItems()
.let { adapter.submitList(it) } .let { adapter.submitList(ArrayList(it)) }
} }
private fun onClick(item: FilterListItem?) { private fun onClick(item: FilterListItem?) {

@ -44,6 +44,7 @@ import org.tasks.compose.SubtaskChip
import org.tasks.compose.TaskEditIcon import org.tasks.compose.TaskEditIcon
import org.tasks.compose.TaskEditRow import org.tasks.compose.TaskEditRow
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.tasklist.SectionedDataSource
import org.tasks.ui.TaskListViewModel import org.tasks.ui.TaskListViewModel
@Composable @Composable
@ -90,7 +91,11 @@ fun SubtaskRow(
} else { } else {
Spacer(modifier = Modifier.height(height = 8.dp)) Spacer(modifier = Modifier.height(height = 8.dp))
if (existingSubtasks is TaskListViewModel.TasksResults.Results) { if (existingSubtasks is TaskListViewModel.TasksResults.Results) {
existingSubtasks.tasks.forEach { task -> existingSubtasks
.tasks
.filterIsInstance<TaskListViewModel.UiItem.Task>()
.map { it.task }
.forEach { task ->
ExistingSubtaskRow( ExistingSubtaskRow(
task = task, task = task,
desaturate = desaturate, desaturate = desaturate,
@ -237,7 +242,7 @@ fun NoSubtasks() {
filter = null, filter = null,
hasParent = false, hasParent = false,
desaturate = true, desaturate = true,
existingSubtasks = TaskListViewModel.TasksResults.Results(emptyList()), existingSubtasks = TaskListViewModel.TasksResults.Results(SectionedDataSource()),
newSubtasks = emptyList(), newSubtasks = emptyList(),
openSubtask = {}, openSubtask = {},
completeExistingSubtask = { _, _ -> }, completeExistingSubtask = { _, _ -> },
@ -260,20 +265,22 @@ fun SubtasksPreview() {
hasParent = false, hasParent = false,
desaturate = true, desaturate = true,
existingSubtasks = TaskListViewModel.TasksResults.Results( existingSubtasks = TaskListViewModel.TasksResults.Results(
listOf( SectionedDataSource(
TaskContainer( tasks = listOf(
task = Task( TaskContainer(
title = "Existing subtask 1", task = Task(
priority = Task.Priority.HIGH, title = "Existing subtask 1",
priority = Task.Priority.HIGH,
),
indent = 0
), ),
indent = 0 TaskContainer(
), task = Task(
TaskContainer( title = "Existing subtask 2 with a really long title",
task = Task( priority = Task.Priority.LOW,
title = "Existing subtask 2 with a really long title", ),
priority = Task.Priority.LOW, indent = 1
), )
indent = 1
) )
) )
), ),

@ -30,7 +30,7 @@ internal class DiffCallback(
return if (isHeader) { return if (isHeader) {
old.groupMode == new.groupMode && old.getHeaderValue(oldPosition) == new.getHeaderValue(newPosition) old.groupMode == new.groupMode && old.getHeaderValue(oldPosition) == new.getHeaderValue(newPosition)
} else { } 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)) { if (new.isHeader(newPosition)) {
return old.getSection(oldPosition).collapsed == new.getSection(newPosition).collapsed return old.getSection(oldPosition).collapsed == new.getSection(newPosition).collapsed
} }
val oldItem = old.getItem(oldPosition)!! val oldItem = old.getItem(oldPosition)
val newItem = new.getItem(newPosition)!! val newItem = new.getItem(newPosition)
return !refreshDates && oldItem == newItem && oldItem.indent == adapter.getIndent(newItem) return !refreshDates && oldItem == newItem && oldItem.indent == adapter.getIndent(newItem)
} }
} }

@ -14,7 +14,6 @@ import androidx.recyclerview.widget.ItemTouchHelper.UP
import androidx.recyclerview.widget.RecyclerView 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.api.AstridOrderingFilter
import com.todoroo.astrid.utility.Flags import com.todoroo.astrid.utility.Flags
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -24,6 +23,7 @@ import kotlinx.coroutines.runBlocking
import org.tasks.activities.DragAndDropDiffer import org.tasks.activities.DragAndDropDiffer
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.ui.TaskListViewModel.UiItem
import java.util.LinkedList import java.util.LinkedList
import java.util.Queue import java.util.Queue
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -31,21 +31,18 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
class DragAndDropRecyclerAdapter( class DragAndDropRecyclerAdapter(
private val adapter: TaskAdapter, private val adapter: TaskAdapter,
private val recyclerView: RecyclerView, private val recyclerView: RecyclerView,
viewHolderFactory: ViewHolderFactory, viewHolderFactory: ViewHolderFactory,
private val taskList: TaskListFragment, private val taskList: TaskListFragment,
tasks: List<TaskContainer>, tasks: SectionedDataSource,
preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, preferences), DragAndDropDiffer<TaskContainer, SectionedDataSource> { preferences: Preferences,
private val disableHeaders = taskList.getFilter().let { private val toggleCollapsed: (Long) -> Unit,
!it.supportsSorting() ) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, preferences), DragAndDropDiffer<UiItem, SectionedDataSource> {
|| (it.supportsManualSort() && preferences.isManualSort)
|| (it is AstridOrderingFilter && preferences.isAstridSort)
}
private val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()).apply { private val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()).apply {
attachToRecyclerView(recyclerView) attachToRecyclerView(recyclerView)
} }
override val channel = Channel<List<TaskContainer>>(Channel.UNLIMITED) override val channel = Channel<SectionedDataSource>(Channel.UNLIMITED)
override val updates: Queue<Pair<SectionedDataSource, DiffUtil.DiffResult?>> = LinkedList() override val updates: Queue<Pair<SectionedDataSource, DiffUtil.DiffResult?>> = LinkedList()
override var dragging = false override var dragging = false
override val scope: CoroutineScope = override val scope: CoroutineScope =
@ -70,7 +67,7 @@ class DragAndDropRecyclerAdapter(
override fun getItemViewType(position: Int) = if (items.isHeader(position)) 1 else 0 override fun getItemViewType(position: Int) = if (items.isHeader(position)) 1 else 0
override fun submitList(list: List<TaskContainer>) { override fun submitList(list: SectionedDataSource) {
super.submitList(list) super.submitList(list)
} }
@ -81,8 +78,7 @@ class DragAndDropRecyclerAdapter(
} }
private fun toggleGroup(group: Long) { private fun toggleGroup(group: Long) {
adapter.toggleCollapsed(group) toggleCollapsed(group)
taskList.loadTaskListContent()
} }
override fun dragAndDropEnabled() = taskList.getFilter().supportsSubtasks() override fun dragAndDropEnabled() = taskList.getFilter().supportsSubtasks()
@ -93,18 +89,8 @@ class DragAndDropRecyclerAdapter(
override fun getItem(position: Int) = items.getItem(position) override fun getItem(position: Int) = items.getItem(position)
override fun transform(list: List<TaskContainer>): 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) = 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() { override fun drainQueue() {
val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState() val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState()

@ -6,6 +6,7 @@ import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.core.SortHelper import com.todoroo.astrid.core.SortHelper
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
import org.tasks.ui.TaskListViewModel.UiItem
class SectionedDataSource( class SectionedDataSource(
tasks: List<TaskContainer> = emptyList(), tasks: List<TaskContainer> = emptyList(),
@ -14,7 +15,7 @@ class SectionedDataSource(
val subtaskMode: Int = SortHelper.SORT_MANUAL, val subtaskMode: Int = SortHelper.SORT_MANUAL,
private val collapsed: Set<Long> = emptySet(), private val collapsed: Set<Long> = emptySet(),
private val completedAtBottom: Boolean = true, private val completedAtBottom: Boolean = true,
) { ): List<UiItem> {
private val tasks = tasks.toMutableList() private val tasks = tasks.toMutableList()
private val sections = if (disableHeaders || groupMode == SortHelper.GROUP_NONE) { private val sections = if (disableHeaders || groupMode == SortHelper.GROUP_NONE) {
@ -23,7 +24,7 @@ class SectionedDataSource(
getSections() 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 fun getHeaderValue(position: Int): Long = getSection(position).value
@ -48,9 +49,52 @@ class SectionedDataSource(
val taskCount: Int val taskCount: Int
get() = tasks.size get() = tasks.size
val size: Int override val size: Int
get() = tasks.size + sections.size() 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<UiItem> {
return object : Iterator<UiItem> {
private var index = 0
override fun hasNext() = index < size
override fun next(): UiItem = get(index++)
}
}
override fun listIterator(): ListIterator<UiItem> {
TODO("Not yet implemented")
}
override fun listIterator(index: Int): ListIterator<UiItem> {
TODO("Not yet implemented")
}
override fun subList(fromIndex: Int, toIndex: Int): List<UiItem> {
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<UiItem>): Boolean {
TODO("Not yet implemented")
}
override fun contains(element: UiItem): Boolean {
TODO("Not yet implemented")
}
fun getSection(position: Int): AdapterSection = sections[position] fun getSection(position: Int): AdapterSection = sections[position]
fun add(position: Int, task: TaskContainer) = tasks.add(sectionedPositionToPosition(position), task) fun add(position: Int, task: TaskContainer) = tasks.add(sectionedPositionToPosition(position), task)

@ -8,15 +8,14 @@ import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.adapter.TaskAdapterDataSource import com.todoroo.astrid.adapter.TaskAdapterDataSource
import com.todoroo.astrid.api.AstridOrderingFilter import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.core.SortHelper import com.todoroo.astrid.core.SortHelper
import org.tasks.data.TaskContainer
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
abstract class TaskListRecyclerAdapter internal constructor( abstract class TaskListRecyclerAdapter internal constructor(
private val adapter: TaskAdapter, private val adapter: TaskAdapter,
internal val viewHolderFactory: ViewHolderFactory, internal val viewHolderFactory: ViewHolderFactory,
private val taskList: TaskListFragment, private val taskList: TaskListFragment,
internal val preferences: Preferences) internal val preferences: Preferences
: RecyclerView.Adapter<RecyclerView.ViewHolder>(), ListUpdateCallback, TaskAdapterDataSource { ): RecyclerView.Adapter<RecyclerView.ViewHolder>(), ListUpdateCallback, TaskAdapterDataSource {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
= viewHolderFactory.newViewHolder(parent, taskList) = viewHolderFactory.newViewHolder(parent, taskList)
@ -51,7 +50,7 @@ abstract class TaskListRecyclerAdapter internal constructor(
abstract fun dragAndDropEnabled(): Boolean abstract fun dragAndDropEnabled(): Boolean
abstract fun submitList(list: List<TaskContainer>) abstract fun submitList(list: SectionedDataSource)
override fun onInserted(position: Int, count: Int) { override fun onInserted(position: Int, count: Int) {
notifyItemRangeInserted(position, count) notifyItemRangeInserted(position, count)

@ -7,6 +7,7 @@ import android.content.Intent
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.FilterImpl import com.todoroo.astrid.api.FilterImpl
import com.todoroo.astrid.api.SearchFilter import com.todoroo.astrid.api.SearchFilter
@ -19,7 +20,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -36,8 +36,10 @@ 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.db.QueryUtils import org.tasks.db.QueryUtils
import org.tasks.filters.MyTasksFilter
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.preferences.QueryPreferences import org.tasks.preferences.QueryPreferences
import org.tasks.tasklist.SectionedDataSource
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -53,18 +55,24 @@ class TaskListViewModel @Inject constructor(
private val firebase: Firebase, private val firebase: Firebase,
) : ViewModel() { ) : ViewModel() {
sealed class UiItem {
data class Header(val value: Long): UiItem()
data class Task(val task: TaskContainer): UiItem()
}
sealed interface TasksResults { sealed interface TasksResults {
data object Loading : TasksResults data object Loading : TasksResults
data class Results(val tasks: List<TaskContainer>) : TasksResults data class Results(val tasks: SectionedDataSource) : TasksResults
} }
data class State( data class State(
val filter: Filter? = null, val filter: Filter = MyTasksFilter(""),
val now: Long = DateUtilities.now(), val now: Long = DateUtilities.now(),
val searchQuery: String? = null, val searchQuery: String? = null,
val tasks: TasksResults = TasksResults.Loading, val tasks: TasksResults = TasksResults.Loading,
val begForSubscription: Boolean = false, val begForSubscription: Boolean = false,
val syncOngoing: Boolean = false, val syncOngoing: Boolean = false,
val collapsed: Set<Long> = setOf(SectionedDataSource.HEADER_COMPLETED),
) )
private val _state = MutableStateFlow(State()) private val _state = MutableStateFlow(State())
@ -104,7 +112,7 @@ class TaskListViewModel @Inject constructor(
} }
suspend fun getTasksToClear(): List<Long> { suspend fun getTasksToClear(): List<Long> {
val filter = _state.value.filter ?: return emptyList() val filter = _state.value.filter
val deleteFilter = FilterImpl( val deleteFilter = FilterImpl(
sql = QueryUtils.removeOrder(QueryUtils.showHiddenAndCompleted(filter.sql!!)), sql = QueryUtils.removeOrder(QueryUtils.showHiddenAndCompleted(filter.sql!!)),
) )
@ -130,13 +138,12 @@ class TaskListViewModel @Inject constructor(
localBroadcastManager.registerRefreshReceiver(refreshReceiver) localBroadcastManager.registerRefreshReceiver(refreshReceiver)
_state _state
.filter { it.filter != null }
.map { it.copy(tasks = TasksResults.Loading) } .map { it.copy(tasks = TasksResults.Loading) }
.distinctUntilChanged() .distinctUntilChanged()
.throttleLatest(333) .throttleLatest(333)
.map { .map {
val filter = when { val filter = when {
it.searchQuery == null -> it.filter!! it.searchQuery == null -> it.filter
it.searchQuery.isBlank() -> BuiltInFilterExposer.getMyTasksFilter(context.resources) it.searchQuery.isBlank() -> BuiltInFilterExposer.getMyTasksFilter(context.resources)
else -> context.createSearchQuery(it.searchQuery) else -> context.createSearchQuery(it.searchQuery)
} }
@ -144,7 +151,20 @@ class TaskListViewModel @Inject constructor(
} }
.onEach { tasks -> .onEach { tasks ->
_state.update { _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) .flowOn(Dispatchers.Default)
@ -163,6 +183,24 @@ class TaskListViewModel @Inject constructor(
localBroadcastManager.unregisterReceiver(refreshReceiver) 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 { companion object {
fun Context.createSearchQuery(query: String): Filter = fun Context.createSearchQuery(query: String): Filter =
SearchFilter(getString(R.string.FLA_search_filter, query), query) SearchFilter(getString(R.string.FLA_search_filter, query), query)

@ -92,7 +92,7 @@ internal class TasksWidgetViewFactory(
override fun getViewTypeCount(): Int = 2 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 override fun hasStableIds(): Boolean = true
@ -145,7 +145,7 @@ internal class TasksWidgetViewFactory(
private fun buildUpdate(position: Int): RemoteViews? { private fun buildUpdate(position: Int): RemoteViews? {
return try { return try {
val taskContainer = getTask(position) ?: return null val taskContainer = getTask(position)
val task = taskContainer.task val task = taskContainer.task
val textColorTitle = when { val textColorTitle = when {
task.isHidden -> onSurfaceVariant 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<String> { private suspend fun getQuery(filter: Filter): List<String> {
subtasksHelper.applySubtasksToWidgetFilter(filter, widgetPreferences) subtasksHelper.applySubtasksToWidgetFilter(filter, widgetPreferences)

Loading…
Cancel
Save