Drag and drop to rearrange drawer

pull/1020/head
Alex Baker 4 years ago
parent e297ed4bd2
commit 745c17fbc5

@ -284,6 +284,8 @@
android:taskAffinity="" android:taskAffinity=""
android:theme="@style/TranslucentDialog"/> android:theme="@style/TranslucentDialog"/>
<activity android:name=".activities.NavigationDrawerCustomization" />
<activity android:name=".activities.TagSettingsActivity"/> <activity android:name=".activities.TagSettingsActivity"/>
<activity android:name=".activities.FilterSettingsActivity"/> <activity android:name=".activities.FilterSettingsActivity"/>

@ -38,6 +38,8 @@ class FilterViewHolder internal constructor(
@BindView(R.id.size) @BindView(R.id.size)
lateinit var size: TextView lateinit var size: TextView
lateinit var filter: FilterListItem
init { init {
ButterKnife.bind(this, itemView) ButterKnife.bind(this, itemView)
if (navigationDrawer) { if (navigationDrawer) {
@ -45,7 +47,12 @@ class FilterViewHolder internal constructor(
} }
} }
fun setMoving(moving: Boolean) {
itemView.isSelected = moving
}
fun bind(filter: FilterListItem, selected: Boolean, count: Int?) { fun bind(filter: FilterListItem, selected: Boolean, count: Int?) {
this.filter = filter
if (navigationDrawer) { if (navigationDrawer) {
itemView.isSelected = selected itemView.isSelected = selected
} else { } else {

@ -9,12 +9,14 @@ import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.FilterListItem import com.todoroo.astrid.api.FilterListItem
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.subjects.PublishSubject
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.activities.DragAndDropDiffer
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao import org.tasks.data.GoogleTaskDao
@ -22,6 +24,7 @@ import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.locale.Locale import org.tasks.locale.Locale
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider import org.tasks.themes.ColorProvider
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.max import kotlin.math.max
@ -34,11 +37,15 @@ class NavigationDrawerAdapter @Inject constructor(
private val googleTaskDao: GoogleTaskDao, private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
private val localBroadcastManager: LocalBroadcastManager) private val localBroadcastManager: LocalBroadcastManager)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() { : RecyclerView.Adapter<RecyclerView.ViewHolder>(), DragAndDropDiffer<FilterListItem, MutableList<FilterListItem>> {
private lateinit var onClick: (FilterListItem?) -> Unit private lateinit var onClick: (FilterListItem?) -> Unit
private var selected: Filter? = null private var selected: Filter? = null
private val differ = AsyncListDiffer(this, DiffCallback()) override val disposables = CompositeDisposable()
override val publishSubject = PublishSubject.create<MutableList<FilterListItem>>()
override val updates: Queue<Pair<MutableList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList()
override var items = initializeDiffer(ArrayList())
override var dragging = false
fun setOnClick(onClick: (FilterListItem?) -> Unit) { fun setOnClick(onClick: (FilterListItem?) -> Unit) {
this.onClick = onClick this.onClick = onClick
@ -54,7 +61,7 @@ class NavigationDrawerAdapter @Inject constructor(
override fun getItemId(position: Int) = position.toLong() override fun getItemId(position: Int) = position.toLong()
override fun getItemCount() = differ.currentList.size override fun getItemCount() = items.size
fun setSelected(selected: Filter?) { fun setSelected(selected: Filter?) {
this.selected = selected this.selected = selected
@ -90,17 +97,38 @@ class NavigationDrawerAdapter @Inject constructor(
override fun getItemViewType(position: Int) = getItem(position).itemType.ordinal override fun getItemViewType(position: Int) = getItem(position).itemType.ordinal
private fun getItem(position: Int) = differ.currentList[position] private fun getItem(position: Int) = items[position]
fun submitList(filterListItems: List<FilterListItem>) = differ.submitList(filterListItems) override fun transform(list: List<FilterListItem>) = list.toMutableList()
private class DiffCallback : DiffUtil.ItemCallback<FilterListItem>() { override fun diff(last: MutableList<FilterListItem>, next: MutableList<FilterListItem>) =
override fun areItemsTheSame(old: FilterListItem, new: FilterListItem) = old.areItemsTheSame(new) DiffUtil.calculateDiff(DiffCallback(last, next))
override fun areContentsTheSame(old: FilterListItem, new: FilterListItem) = old.areContentsTheSame(new) private class DiffCallback(val old: List<FilterListItem>, val new: List<FilterListItem>) : DiffUtil.Callback() {
override fun getOldListSize() = old.size
override fun getNewListSize() = new.size
override fun areItemsTheSame(oldPosition: Int, newPosition: Int) =
old[oldPosition].areItemsTheSame(new[newPosition])
override fun areContentsTheSame(oldPosition: Int, newPosition: Int) =
old[oldPosition].areContentsTheSame(new[newPosition])
} }
companion object { companion object {
private const val TOKEN_SELECTED = "token_selected" private const val TOKEN_SELECTED = "token_selected"
} }
override fun onChanged(position: Int, count: Int, payload: Any?) =
notifyItemRangeChanged(position, count, payload)
override fun onMoved(fromPosition: Int, toPosition: Int) =
notifyItemMoved(fromPosition, toPosition)
override fun onInserted(position: Int, count: Int) =
notifyItemRangeInserted(position, count)
override fun onRemoved(position: Int, count: Int) =
notifyItemRangeRemoved(position, count)
} }

@ -74,6 +74,10 @@ public class CaldavFilter extends Filter {
return calendar.getUuid(); return calendar.getUuid();
} }
public String getAccount() {
return calendar.getAccount();
}
public CaldavCalendar getCalendar() { public CaldavCalendar getCalendar() {
return calendar; return calendar;
} }

@ -73,7 +73,8 @@ public abstract class FilterListItem implements Parcelable {
return Objects.equals(listingTitle, other.listingTitle) return Objects.equals(listingTitle, other.listingTitle)
&& icon == other.icon && icon == other.icon
&& tint == other.tint && tint == other.tint
&& count == other.count; && count == other.count
&& order == other.order;
} }
@Override @Override

@ -71,6 +71,10 @@ public class GtasksFilter extends Filter {
return list.getId(); return list.getId();
} }
public String getAccount() {
return list.getAccount();
}
public GoogleTaskList getList() { public GoogleTaskList getList() {
return list; return list;
} }

@ -0,0 +1,66 @@
package org.tasks.activities
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import com.todoroo.andlib.utility.AndroidUtilities
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import java.util.*
interface DragAndDropDiffer<T, R> : ListUpdateCallback {
val publishSubject: PublishSubject<R>
val updates: Queue<Pair<R, DiffUtil.DiffResult?>>
val disposables: CompositeDisposable
var items: R
var dragging: Boolean
fun submitList(list: List<T>) {
disposables.add(
Single.fromCallable { transform(list) }
.subscribeOn(Schedulers.computation())
.subscribe(publishSubject::onNext))
}
fun calculateDiff(last: Pair<R, DiffUtil.DiffResult?>, next: R): Pair<R, DiffUtil.DiffResult?> {
AndroidUtilities.assertNotMainThread()
return Pair(next, diff(last.first!!, next))
}
fun applyDiff(update: Pair<R, DiffUtil.DiffResult?>) {
AndroidUtilities.assertMainThread()
updates.add(update)
if (!dragging) {
drainQueue()
}
}
fun drainQueue() {
AndroidUtilities.assertMainThread()
var update = updates.poll()
while (update != null) {
items = update.first
update.second?.dispatchUpdatesTo(this as ListUpdateCallback)
update = updates.poll()
}
}
fun initializeDiffer(list: List<T>): R {
val initial = transform(list)
disposables.add(publishSubject
.observeOn(Schedulers.computation())
.scan(Pair(initial, null), { last: Pair<R, DiffUtil.DiffResult?>, next: R ->
calculateDiff(last, next)
})
.skip(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::applyDiff))
return initial
}
fun transform(list: List<T>): R
fun diff(last: R, next: R): DiffUtil.DiffResult
}

@ -244,8 +244,11 @@ class FilterSettingsActivity : BaseListSettingsActivity() {
if (isNew) { if (isNew) {
f.id = filterDao.insert(f) f.id = filterDao.insert(f)
} else { } else {
f.id = filter!!.id filter?.let {
filterDao.update(f) f.id = it.id
f.order = it.order
filterDao.update(f)
}
} }
setResult( setResult(
Activity.RESULT_OK, Activity.RESULT_OK,

@ -0,0 +1,266 @@
package org.tasks.activities
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.*
import androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.todoroo.astrid.adapter.FilterViewHolder
import com.todoroo.astrid.adapter.NavigationDrawerAdapter
import com.todoroo.astrid.api.*
import com.todoroo.astrid.api.FilterListItem.Type.ITEM
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.data.*
import org.tasks.databinding.ActivityTagOrganizerBinding
import org.tasks.dialogs.NewFilterDialog.Companion.newFilterDialog
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerAction
import org.tasks.filters.PlaceFilter
import org.tasks.injection.ActivityComponent
import org.tasks.injection.ThemedInjectingAppCompatActivity
import org.tasks.preferences.Preferences
import org.tasks.ui.NavigationDrawerFragment.Companion.REQUEST_NEW_FILTER
import javax.inject.Inject
class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var filterProvider: FilterProvider
@Inject lateinit var adapter: NavigationDrawerAdapter
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var preferences: Preferences
@Inject lateinit var tagDataDao: TagDataDao
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var filterDao: FilterDao
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var locationDao: LocationDao
private lateinit var binding: ActivityTagOrganizerBinding
private lateinit var toolbar: Toolbar
private var disposables: CompositeDisposable? = null
private val refreshReceiver = RefreshReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTagOrganizerBinding.inflate(layoutInflater)
setContentView(binding.root)
toolbar = binding.toolbar.toolbar
toolbar.title = getString(R.string.manage_lists)
toolbar.navigationIcon = getDrawable(R.drawable.ic_outline_arrow_back_24px)
toolbar.setNavigationOnClickListener { finish() }
toolbar.setOnMenuItemClickListener(this)
toolbar.inflateMenu(R.menu.menu_nav_drawer_customization)
themeColor.apply(toolbar)
themeColor.applyToSystemBars(this)
adapter.setOnClick(this::onClick)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback())
itemTouchHelper.attachToRecyclerView(binding.recyclerView)
}
override fun onPause() {
super.onPause()
localBroadcastManager.unregisterReceiver(refreshReceiver)
}
override fun onStart() {
super.onStart()
disposables = CompositeDisposable()
}
override fun onStop() {
super.onStop()
disposables?.dispose()
}
override fun onResume() {
super.onResume()
localBroadcastManager.registerRefreshListReceiver(refreshReceiver)
updateFilters()
}
private fun updateFilters() =
disposables?.add(
Single.fromCallable {
filterProvider.drawerCustomizationItems.apply {
forEach { f -> f.count = 0 }
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter::submitList))
private fun onClick(item: FilterListItem?) {
if (item is NavigationDrawerAction) {
when (item.requestCode) {
REQUEST_NEW_FILTER ->
newFilterDialog().show(supportFragmentManager, FRAG_TAG_NEW_FILTER)
else -> startActivity(item.intent)
}
} else {
when (item) {
is GtasksFilter ->
Intent(this, GoogleTaskListSettingsActivity::class.java)
.putExtra(GoogleTaskListSettingsActivity.EXTRA_STORE_DATA, item.list)
.apply(this::startActivity)
is CaldavFilter ->
caldavDao.getAccountByUuid(item.account)?.let {
Intent(this, it.listSettingsClass())
.putExtra(BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_CALENDAR, item.calendar)
.apply(this::startActivity)
}
is CustomFilter ->
Intent(this, FilterSettingsActivity::class.java)
.putExtra(FilterSettingsActivity.TOKEN_FILTER, item)
.apply(this::startActivity)
is TagFilter ->
Intent(this, TagSettingsActivity::class.java)
.putExtra(TagSettingsActivity.EXTRA_TAG_DATA, item.tagData)
.apply(this::startActivity)
is PlaceFilter ->
Intent(this, PlaceSettingsActivity::class.java)
.putExtra(PlaceSettingsActivity.EXTRA_PLACE, item.place as Parcelable)
.apply(this::startActivity)
}
}
}
override fun inject(component: ActivityComponent) = component.inject(this)
override fun onMenuItemClick(item: MenuItem): Boolean {
return if (item.itemId == R.id.reset_sort) {
filterDao.resetOrders()
caldavDao.resetOrders()
googleTaskListDao.resetOrders()
tagDataDao.resetOrders()
locationDao.resetOrders()
updateFilters()
true
} else {
false
}
}
private inner class RefreshReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action
if (LocalBroadcastManager.REFRESH == action || LocalBroadcastManager.REFRESH_LIST == action) {
updateFilters()
}
}
}
private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
private var from = -1
private var to = -1
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
if (viewHolder.itemViewType == ITEM.ordinal) ALLOW_DRAGGING else NO_MOVEMENT
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ACTION_STATE_DRAG) {
adapter.dragging = true
(viewHolder as FilterViewHolder).setMoving(true)
}
}
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
if (target !is FilterViewHolder) {
return false
}
val sourceFilter = (viewHolder as FilterViewHolder).filter
val targetFilter = target.filter
if (sourceFilter::class.java != targetFilter::class.java) {
return false
}
if (sourceFilter is GtasksFilter && targetFilter is GtasksFilter) {
if (sourceFilter.account != targetFilter.account) {
return false
}
} else if (sourceFilter is CaldavFilter && targetFilter is CaldavFilter) {
if (sourceFilter.account != targetFilter.account) {
return false
}
}
val sourcePosition = viewHolder.adapterPosition
if (from == -1) {
from = sourcePosition
}
to = target.adapterPosition
adapter.notifyItemMoved(sourcePosition, to)
return true
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
(viewHolder as FilterViewHolder).setMoving(false)
if (from != to) {
viewHolder.filter.order = to
adapter.items
.apply {
removeAt(from)
add(to, viewHolder.filter)
}
.filter(getPredicate(viewHolder.filter))
.forEachIndexed { order, filter ->
filter.order = order
setOrder(order, filter)
}
updateFilters()
}
adapter.dragging = false
from = -1
to = -1
}
private fun getPredicate(item: FilterListItem): (FilterListItem) -> Boolean = { f ->
item::class.java == f::class.java && when (item) {
is GtasksFilter -> item.account == (f as GtasksFilter).account
is CaldavFilter -> item.account == (f as CaldavFilter).account
else -> true
}
}
private fun setOrder(order: Int, filter: FilterListItem) {
when (filter) {
is GtasksFilter -> googleTaskListDao.setOrder(filter.list.id, order)
is CaldavFilter -> caldavDao.setOrder(filter.calendar.id, order)
is TagFilter -> tagDataDao.setOrder(filter.tagData.id!!, order)
is CustomFilter -> filterDao.setOrder(filter.id, order)
is PlaceFilter -> locationDao.setOrder(filter.place.id, order)
}
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
}
companion object {
private val NO_MOVEMENT = makeMovementFlags(0, 0)
private val ALLOW_DRAGGING = makeMovementFlags(UP or DOWN, 0)
private const val FRAG_TAG_NEW_FILTER = "frag_tag_new_filter"
}
}

@ -11,6 +11,7 @@ import com.todoroo.astrid.core.BuiltInFilterExposer
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.activities.GoogleTaskListSettingsActivity import org.tasks.activities.GoogleTaskListSettingsActivity
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.activities.TagSettingsActivity import org.tasks.activities.TagSettingsActivity
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
@ -45,6 +46,9 @@ class FilterProvider @Inject constructor(
val filterPickerItems: List<FilterListItem> val filterPickerItems: List<FilterListItem>
get() = getAllFilters(showCreate = false) get() = getAllFilters(showCreate = false)
val drawerCustomizationItems: List<FilterListItem>
get() = getAllFilters(showBuiltIn = false)
private fun addFilters(showCreate: Boolean, showBuiltIn: Boolean): List<FilterListItem> = private fun addFilters(showCreate: Boolean, showBuiltIn: Boolean): List<FilterListItem> =
if (!preferences.getBoolean(R.string.p_filters_enabled, true)) { if (!preferences.getBoolean(R.string.p_filters_enabled, true)) {
emptyList() emptyList()
@ -154,6 +158,11 @@ class FilterProvider @Inject constructor(
R.drawable.ic_outline_attach_money_24px, R.drawable.ic_outline_attach_money_24px,
NavigationDrawerFragment.REQUEST_PURCHASE) NavigationDrawerFragment.REQUEST_PURCHASE)
} }
.plus(NavigationDrawerAction(
context.getString(R.string.manage_lists),
R.drawable.ic_outline_edit_24px,
Intent(context, NavigationDrawerCustomization::class.java),
0))
.plus(NavigationDrawerAction( .plus(NavigationDrawerAction(
context.getString(R.string.TLA_menu_settings), context.getString(R.string.TLA_menu_settings),
R.drawable.ic_outline_settings_24px, R.drawable.ic_outline_settings_24px,

@ -77,4 +77,5 @@ interface ActivityComponent {
fun inject(activity: SyncPreferences) fun inject(activity: SyncPreferences)
fun inject(activity: PlaceSettingsActivity) fun inject(activity: PlaceSettingsActivity)
fun inject(activity: LocalListSettingsActivity) fun inject(activity: LocalListSettingsActivity)
fun inject(activity: NavigationDrawerCustomization)
} }

@ -2,23 +2,18 @@ package org.tasks.tasklist
import android.graphics.Canvas import android.graphics.Canvas
import android.view.ViewGroup import android.view.ViewGroup
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
import androidx.recyclerview.widget.ItemTouchHelper.* import androidx.recyclerview.widget.ItemTouchHelper.*
import androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags import androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.todoroo.andlib.utility.AndroidUtilities
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.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.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
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 java.util.* import java.util.*
@ -32,19 +27,26 @@ class DragAndDropRecyclerAdapter(
private val taskList: TaskListFragment, private val taskList: TaskListFragment,
tasks: List<TaskContainer>, tasks: List<TaskContainer>,
taskDao: TaskDao, taskDao: TaskDao,
preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) { preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences), DragAndDropDiffer<TaskContainer, SectionedDataSource> {
private var list: SectionedDataSource private val disableHeaders = taskList.getFilter().let {
private val publishSubject = PublishSubject.create<SectionedDataSource>() !it.supportsSorting()
private val disposables = CompositeDisposable() || !preferences.showGroupHeaders()
private val updates: Queue<Pair<SectionedDataSource, DiffUtil.DiffResult>> = LinkedList() || (it.supportsManualSort() && preferences.isManualSort)
private var dragging = false || (it.supportsAstridSorting() && preferences.isAstridSort)
private val disableHeaders: Boolean }
private val itemTouchHelper: ItemTouchHelper private val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()).apply {
attachToRecyclerView(recyclerView)
}
override val publishSubject = PublishSubject.create<SectionedDataSource>()
override val disposables = CompositeDisposable()
override val updates: Queue<Pair<SectionedDataSource, DiffUtil.DiffResult?>> = LinkedList()
override var dragging = false
override var items = initializeDiffer(tasks)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val viewType = getItemViewType(position) val viewType = getItemViewType(position)
if (viewType == 1) { if (viewType == 1) {
val headerSection = list.getSection(position) val headerSection = items.getSection(position)
(holder as HeaderViewHolder).bind(taskList.getFilter(), preferences.sortMode, headerSection) (holder as HeaderViewHolder).bind(taskList.getFilter(), preferences.sortMode, headerSection)
} else { } else {
super.onBindViewHolder(holder, position) super.onBindViewHolder(holder, position)
@ -52,9 +54,11 @@ class DragAndDropRecyclerAdapter(
} }
override val sortMode: Int override val sortMode: Int
get() = list.sortMode get() = items.sortMode
override fun getItemViewType(position: Int) = if (items.isHeader(position)) 1 else 0
override fun getItemViewType(position: Int) = if (list.isHeader(position)) 1 else 0 override fun submitList(list: List<TaskContainer>) = super.submitList(list)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = if (viewType == 1) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = if (viewType == 1) {
viewHolderFactory.newHeaderViewHolder(parent, this::toggleGroup) viewHolderFactory.newHeaderViewHolder(parent, this::toggleGroup)
@ -69,44 +73,21 @@ class DragAndDropRecyclerAdapter(
override fun dragAndDropEnabled() = taskList.getFilter().supportsSubtasks() override fun dragAndDropEnabled() = taskList.getFilter().supportsSubtasks()
override fun isHeader(position: Int): Boolean = list.isHeader(position) override fun isHeader(position: Int): Boolean = items.isHeader(position)
override fun nearestHeader(position: Int) = list.getNearestHeader(position) override fun nearestHeader(position: Int) = items.getNearestHeader(position)
override fun getItem(position: Int) = list.getItem(position) override fun getItem(position: Int) = items.getItem(position)
override fun submitList(list: List<TaskContainer>) { override fun transform(list: List<TaskContainer>): SectionedDataSource =
disposables.add( SectionedDataSource(list, disableHeaders, preferences.sortMode, adapter.getCollapsed())
Single.fromCallable { SectionedDataSource(list, disableHeaders, preferences.sortMode, adapter.getCollapsed()) }
.subscribeOn(Schedulers.computation())
.subscribe(publishSubject::onNext))
}
private fun calculateDiff(
last: Pair<SectionedDataSource, DiffUtil.DiffResult>, next: SectionedDataSource): Pair<SectionedDataSource, DiffUtil.DiffResult> {
AndroidUtilities.assertNotMainThread()
val cb = DiffCallback(last.first!!, next, adapter)
val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE)
return Pair.create(next, result)
}
private fun applyDiff(update: Pair<SectionedDataSource, DiffUtil.DiffResult>) { override fun diff(last: SectionedDataSource, next: SectionedDataSource) =
AndroidUtilities.assertMainThread() DiffUtil.calculateDiff(DiffCallback(last, next, adapter), next.size < LONG_LIST_SIZE)
updates.add(update)
if (!dragging) {
drainQueue()
}
}
private fun drainQueue() { override fun drainQueue() {
AndroidUtilities.assertMainThread()
val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState() val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState()
var update = updates.poll() super.drainQueue()
while (update != null) {
list = update.first!!
update.second!!.dispatchUpdatesTo((this as ListUpdateCallback))
update = updates.poll()
}
recyclerView.layoutManager!!.onRestoreInstanceState(recyclerViewState) recyclerView.layoutManager!!.onRestoreInstanceState(recyclerViewState)
} }
@ -115,9 +96,9 @@ class DragAndDropRecyclerAdapter(
disposables.dispose() disposables.dispose()
} }
override fun getTaskCount() = list.taskCount override fun getTaskCount() = items.taskCount
override fun getItemCount() = list.size override fun getItemCount() = items.size
private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() { private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
private var from = -1 private var from = -1
@ -162,7 +143,7 @@ class DragAndDropRecyclerAdapter(
notifyItemMoved(fromPosition, toPosition) notifyItemMoved(fromPosition, toPosition)
if (isHeader) { if (isHeader) {
val offset = if (fromPosition < toPosition) -1 else 1 val offset = if (fromPosition < toPosition) -1 else 1
list.moveSection(toPosition, offset) items.moveSection(toPosition, offset)
} }
updateIndents(source, from, to) updateIndents(source, from, to)
return true return true
@ -257,8 +238,8 @@ class DragAndDropRecyclerAdapter(
from from
} }
adapter.moved(from, to, indent) adapter.moved(from, to, indent)
val task: TaskContainer = list.removeAt(from) val task: TaskContainer = items.removeAt(from)
list.add(if (from < to) to - 1 else to, task) items.add(if (from < to) to - 1 else to, task)
taskList.loadTaskListContent() taskList.loadTaskListContent()
} }
} }
@ -268,24 +249,4 @@ class DragAndDropRecyclerAdapter(
private val NO_MOVEMENT = makeMovementFlags(0, 0) private val NO_MOVEMENT = makeMovementFlags(0, 0)
private val ALLOW_DRAGGING = makeMovementFlags(UP or DOWN or LEFT or RIGHT, 0) private val ALLOW_DRAGGING = makeMovementFlags(UP or DOWN or LEFT or RIGHT, 0)
} }
init {
val filter = taskList.getFilter()
disableHeaders = !filter.supportsSorting()
|| !preferences.showGroupHeaders()
|| (filter.supportsManualSort() && preferences.isManualSort)
|| (filter.supportsAstridSorting() && preferences.isAstridSort)
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback())
itemTouchHelper.attachToRecyclerView(recyclerView)
list = SectionedDataSource(tasks, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet())
val initial = Pair.create<SectionedDataSource, DiffUtil.DiffResult>(list, null)
disposables.add(publishSubject
.observeOn(Schedulers.computation())
.scan(initial, { last: Pair<SectionedDataSource, DiffUtil.DiffResult>, next: SectionedDataSource ->
calculateDiff(last, next)
})
.skip(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe { update: Pair<SectionedDataSource, DiffUtil.DiffResult> -> applyDiff(update) })
}
} }

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view" />
</LinearLayout>

@ -6,8 +6,7 @@
android:id="@+id/subheader_row" android:id="@+id/subheader_row"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:focusable="true" android:focusable="true"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:orientation="vertical">
<View <View
android:id="@+id/divider" android:id="@+id/divider"
@ -45,15 +44,12 @@
android:fontFamily="@string/font_fontFamily_medium" android:fontFamily="@string/font_fontFamily_medium"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_below="@id/divider" android:layout_below="@id/divider"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:drawableTint="@color/icon_tint_with_alpha" android:drawableTint="@color/icon_tint_with_alpha"
android:singleLine="true" android:singleLine="true"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textSize="14sp" android:textSize="14sp"
android:paddingStart="@dimen/keyline_first" android:padding="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />
</RelativeLayout> </RelativeLayout>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/reset_sort"
android:title="@string/reset_sort_order"
app:showAsAction="never" />
</menu>

@ -30,6 +30,7 @@ File %1$s contained %2$s.\n\n
<string name="TLA_menu_search">Search</string> <string name="TLA_menu_search">Search</string>
<string name="TLA_menu_settings">Settings</string> <string name="TLA_menu_settings">Settings</string>
<string name="TAd_actionEditTask">Edit</string> <string name="TAd_actionEditTask">Edit</string>
<string name="manage_lists">Manage lists</string>
<string name="action_call">Call</string> <string name="action_call">Call</string>
<string name="action_open">Open</string> <string name="action_open">Open</string>
<string name="SSD_sort_my_order">My order</string> <string name="SSD_sort_my_order">My order</string>
@ -634,4 +635,5 @@ File %1$s contained %2$s.\n\n
<string name="on_launch">On launch</string> <string name="on_launch">On launch</string>
<string name="open_last_viewed_list">Open last viewed list</string> <string name="open_last_viewed_list">Open last viewed list</string>
<string name="lists">Lists</string> <string name="lists">Lists</string>
<string name="reset_sort_order">Reset sort order</string>
</resources> </resources>

Loading…
Cancel
Save