|
|
|
@ -1,6 +1,7 @@
|
|
|
|
package org.tasks.tasklist
|
|
|
|
package org.tasks.tasklist
|
|
|
|
|
|
|
|
|
|
|
|
import android.graphics.Canvas
|
|
|
|
import android.graphics.Canvas
|
|
|
|
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import androidx.core.util.Pair
|
|
|
|
import androidx.core.util.Pair
|
|
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
|
|
import androidx.recyclerview.widget.DiffUtil
|
|
|
|
import androidx.recyclerview.widget.ItemTouchHelper
|
|
|
|
import androidx.recyclerview.widget.ItemTouchHelper
|
|
|
|
@ -11,11 +12,13 @@ import com.todoroo.astrid.activity.TaskListFragment
|
|
|
|
import com.todoroo.astrid.adapter.TaskAdapter
|
|
|
|
import com.todoroo.astrid.adapter.TaskAdapter
|
|
|
|
import com.todoroo.astrid.dao.TaskDao
|
|
|
|
import com.todoroo.astrid.dao.TaskDao
|
|
|
|
import com.todoroo.astrid.utility.Flags
|
|
|
|
import com.todoroo.astrid.utility.Flags
|
|
|
|
|
|
|
|
import io.reactivex.Single
|
|
|
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
|
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
|
|
import io.reactivex.disposables.CompositeDisposable
|
|
|
|
import io.reactivex.disposables.CompositeDisposable
|
|
|
|
import io.reactivex.schedulers.Schedulers
|
|
|
|
import io.reactivex.schedulers.Schedulers
|
|
|
|
import io.reactivex.subjects.PublishSubject
|
|
|
|
import io.reactivex.subjects.PublishSubject
|
|
|
|
import org.tasks.data.TaskContainer
|
|
|
|
import org.tasks.data.TaskContainer
|
|
|
|
|
|
|
|
import org.tasks.preferences.Preferences
|
|
|
|
import java.util.*
|
|
|
|
import java.util.*
|
|
|
|
import kotlin.math.max
|
|
|
|
import kotlin.math.max
|
|
|
|
import kotlin.math.min
|
|
|
|
import kotlin.math.min
|
|
|
|
@ -25,27 +28,61 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
private val recyclerView: RecyclerView,
|
|
|
|
private val recyclerView: RecyclerView,
|
|
|
|
viewHolderFactory: ViewHolderFactory,
|
|
|
|
viewHolderFactory: ViewHolderFactory,
|
|
|
|
private val taskList: TaskListFragment,
|
|
|
|
private val taskList: TaskListFragment,
|
|
|
|
private var list: MutableList<TaskContainer>,
|
|
|
|
tasks: MutableList<TaskContainer>,
|
|
|
|
taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) {
|
|
|
|
taskDao: TaskDao,
|
|
|
|
private val publishSubject = PublishSubject.create<MutableList<TaskContainer>>()
|
|
|
|
preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) {
|
|
|
|
|
|
|
|
private var list: SectionedDataSource
|
|
|
|
|
|
|
|
private val publishSubject = PublishSubject.create<SectionedDataSource>()
|
|
|
|
private val disposables = CompositeDisposable()
|
|
|
|
private val disposables = CompositeDisposable()
|
|
|
|
private val updates: Queue<Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>> = LinkedList()
|
|
|
|
private val updates: Queue<Pair<SectionedDataSource, DiffUtil.DiffResult>> = LinkedList()
|
|
|
|
private var dragging = false
|
|
|
|
private var dragging = false
|
|
|
|
|
|
|
|
private val disableHeaders: Boolean
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
|
|
|
|
|
|
val viewType = getItemViewType(position)
|
|
|
|
|
|
|
|
if (viewType == 1) {
|
|
|
|
|
|
|
|
val headerSection = list.getSection(position)
|
|
|
|
|
|
|
|
(holder as HeaderViewHolder).bind(taskList.getFilter(), preferences.sortMode, headerSection)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
super.onBindViewHolder(holder, position)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun getItemViewType(position: Int) = if (list.isHeader(position)) 1 else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = if (viewType == 1) {
|
|
|
|
|
|
|
|
viewHolderFactory.newHeaderViewHolder(parent, this::toggleGroup)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
super.onCreateViewHolder(parent, viewType)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun toggleGroup(group: Long) {
|
|
|
|
|
|
|
|
adapter.toggleCollapsed(group)
|
|
|
|
|
|
|
|
taskList.loadTaskListContent()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun dragAndDropEnabled() = adapter.supportsParentingOrManualSort()
|
|
|
|
override fun dragAndDropEnabled() = adapter.supportsParentingOrManualSort()
|
|
|
|
|
|
|
|
|
|
|
|
override fun getItem(position: Int) = list[position]
|
|
|
|
override fun isHeader(position: Int): Boolean = list.isHeader(position)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun getItem(position: Int): TaskContainer = list.getItem(position)
|
|
|
|
|
|
|
|
|
|
|
|
override fun submitList(list: List<TaskContainer>) = publishSubject.onNext(list as MutableList<TaskContainer>)
|
|
|
|
override fun submitList(list: List<TaskContainer>) {
|
|
|
|
|
|
|
|
disposables.add(
|
|
|
|
|
|
|
|
Single.fromCallable { SectionedDataSource(list as MutableList, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet()) }
|
|
|
|
|
|
|
|
.subscribeOn(Schedulers.computation())
|
|
|
|
|
|
|
|
.subscribe(publishSubject::onNext))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun calculateDiff(
|
|
|
|
private fun calculateDiff(
|
|
|
|
last: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>, next: MutableList<TaskContainer>): Pair<MutableList<TaskContainer>, DiffUtil.DiffResult> {
|
|
|
|
last: Pair<SectionedDataSource, DiffUtil.DiffResult>, next: SectionedDataSource): Pair<SectionedDataSource, DiffUtil.DiffResult> {
|
|
|
|
AndroidUtilities.assertNotMainThread()
|
|
|
|
AndroidUtilities.assertNotMainThread()
|
|
|
|
val cb = DiffCallback(last.first!!, next, adapter)
|
|
|
|
val cb = DiffCallback(last.first!!, next, adapter)
|
|
|
|
val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE)
|
|
|
|
val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE)
|
|
|
|
return Pair.create(next, result)
|
|
|
|
return Pair.create(next, result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun applyDiff(update: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>) {
|
|
|
|
private fun applyDiff(update: Pair<SectionedDataSource, DiffUtil.DiffResult>) {
|
|
|
|
AndroidUtilities.assertMainThread()
|
|
|
|
AndroidUtilities.assertMainThread()
|
|
|
|
updates.add(update)
|
|
|
|
updates.add(update)
|
|
|
|
if (!dragging) {
|
|
|
|
if (!dragging) {
|
|
|
|
@ -67,9 +104,9 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
|
|
|
|
|
|
|
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = disposables.dispose()
|
|
|
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = disposables.dispose()
|
|
|
|
|
|
|
|
|
|
|
|
override fun getItemCount(): Int {
|
|
|
|
override fun getTaskCount() = list.taskCount
|
|
|
|
return list.size
|
|
|
|
|
|
|
|
}
|
|
|
|
override fun getItemCount() = list.size
|
|
|
|
|
|
|
|
|
|
|
|
private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
|
|
|
|
private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
|
|
|
|
private var from = -1
|
|
|
|
private var from = -1
|
|
|
|
@ -85,10 +122,14 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = if (adapter.supportsParentingOrManualSort() && adapter.numSelected == 0) {
|
|
|
|
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
|
|
|
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0)
|
|
|
|
return if (adapter.isHeader(viewHolder.adapterPosition)) {
|
|
|
|
} else {
|
|
|
|
makeMovementFlags(0, 0)
|
|
|
|
makeMovementFlags(0, 0)
|
|
|
|
} else if (adapter.supportsParentingOrManualSort() && adapter.numSelected == 0) {
|
|
|
|
|
|
|
|
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
makeMovementFlags(0, 0)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onMove(
|
|
|
|
override fun onMove(
|
|
|
|
@ -99,7 +140,8 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
val fromPosition = src.adapterPosition
|
|
|
|
val fromPosition = src.adapterPosition
|
|
|
|
val toPosition = target.adapterPosition
|
|
|
|
val toPosition = target.adapterPosition
|
|
|
|
val source = src as TaskViewHolder
|
|
|
|
val source = src as TaskViewHolder
|
|
|
|
if (!adapter.canMove(source.task, fromPosition, (target as TaskViewHolder).task, toPosition)) {
|
|
|
|
val isHeader = isHeader(toPosition)
|
|
|
|
|
|
|
|
if (!isHeader && !adapter.canMove(source.task, fromPosition, (target as TaskViewHolder).task, toPosition)) {
|
|
|
|
return false
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (from == -1) {
|
|
|
|
if (from == -1) {
|
|
|
|
@ -108,14 +150,19 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
to = toPosition
|
|
|
|
to = toPosition
|
|
|
|
notifyItemMoved(fromPosition, toPosition)
|
|
|
|
notifyItemMoved(fromPosition, toPosition)
|
|
|
|
|
|
|
|
if (isHeader) {
|
|
|
|
|
|
|
|
val offset = if (fromPosition < toPosition) -1 else 1
|
|
|
|
|
|
|
|
list.moveSection(toPosition, offset)
|
|
|
|
|
|
|
|
}
|
|
|
|
updateIndents(source, from, to)
|
|
|
|
updateIndents(source, from, to)
|
|
|
|
return true
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun updateIndents(source: TaskViewHolder?, from: Int, to: Int) {
|
|
|
|
private fun updateIndents(source: TaskViewHolder?, from: Int, to: Int) {
|
|
|
|
val task = source!!.task
|
|
|
|
val task = source!!.task
|
|
|
|
source.minIndent = if (to == 0 || to == itemCount - 1) 0 else adapter.minIndent(if (from <= to) to + 1 else to, task)
|
|
|
|
val previousIsHeader = to > 0 && isHeader(to - 1)
|
|
|
|
source.maxIndent = if (to == 0) 0 else adapter.maxIndent(if (from >= to) to - 1 else to, task)
|
|
|
|
source.minIndent = if (to == 0 || to == itemCount - 1 || previousIsHeader) 0 else adapter.minIndent(if (from <= to) to + 1 else to, task)
|
|
|
|
|
|
|
|
source.maxIndent = if (to == 0 || previousIsHeader) 0 else adapter.maxIndent(if (from >= to) to - 1 else to, task)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onChildDraw(
|
|
|
|
override fun onChildDraw(
|
|
|
|
@ -191,7 +238,14 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
throw UnsupportedOperationException()
|
|
|
|
throw UnsupportedOperationException()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun moved(from: Int, to: Int, indent: Int) {
|
|
|
|
private fun moved(fromOrig: Int, to: Int, indent: Int) {
|
|
|
|
|
|
|
|
val from = if (fromOrig == to) {
|
|
|
|
|
|
|
|
to
|
|
|
|
|
|
|
|
} else if (fromOrig > to && isHeader(fromOrig)) {
|
|
|
|
|
|
|
|
from - 1
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
from
|
|
|
|
|
|
|
|
}
|
|
|
|
adapter.moved(from, to, indent)
|
|
|
|
adapter.moved(from, to, indent)
|
|
|
|
val task: TaskContainer = list.removeAt(from)
|
|
|
|
val task: TaskContainer = list.removeAt(from)
|
|
|
|
list.add(if (from < to) to - 1 else to, task)
|
|
|
|
list.add(if (from < to) to - 1 else to, task)
|
|
|
|
@ -204,16 +258,18 @@ class DragAndDropRecyclerAdapter(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
init {
|
|
|
|
|
|
|
|
val filter = taskList.getFilter()
|
|
|
|
|
|
|
|
disableHeaders = !filter.supportsSorting() || preferences.isManualSort && filter.supportsManualSort()
|
|
|
|
ItemTouchHelper(ItemTouchHelperCallback()).attachToRecyclerView(recyclerView)
|
|
|
|
ItemTouchHelper(ItemTouchHelperCallback()).attachToRecyclerView(recyclerView)
|
|
|
|
val initial = Pair.create<MutableList<TaskContainer>, DiffUtil.DiffResult>(list, null)
|
|
|
|
list = SectionedDataSource(tasks, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet())
|
|
|
|
disposables.add(
|
|
|
|
val initial = Pair.create<SectionedDataSource, DiffUtil.DiffResult>(list, null)
|
|
|
|
publishSubject
|
|
|
|
disposables.add(publishSubject
|
|
|
|
.observeOn(Schedulers.computation())
|
|
|
|
.observeOn(Schedulers.computation())
|
|
|
|
.scan(initial, { last: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>, next: List<TaskContainer> ->
|
|
|
|
.scan(initial, { last: Pair<SectionedDataSource, DiffUtil.DiffResult>, next: SectionedDataSource ->
|
|
|
|
calculateDiff(last, next.toMutableList())
|
|
|
|
calculateDiff(last, next)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.skip(1)
|
|
|
|
.skip(1)
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
.subscribe { update: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult> -> applyDiff(update) })
|
|
|
|
.subscribe { update: Pair<SectionedDataSource, DiffUtil.DiffResult> -> applyDiff(update) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|