diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 194dc7a53..731808a82 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -284,6 +284,8 @@
android:taskAffinity=""
android:theme="@style/TranslucentDialog"/>
+
+
diff --git a/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt b/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt
index f969b9153..2c942c142 100644
--- a/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt
+++ b/app/src/main/java/com/todoroo/astrid/adapter/FilterViewHolder.kt
@@ -38,6 +38,8 @@ class FilterViewHolder internal constructor(
@BindView(R.id.size)
lateinit var size: TextView
+ lateinit var filter: FilterListItem
+
init {
ButterKnife.bind(this, itemView)
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?) {
+ this.filter = filter
if (navigationDrawer) {
itemView.isSelected = selected
} else {
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 bd6969310..81c6fc4ad 100644
--- a/app/src/main/java/com/todoroo/astrid/adapter/NavigationDrawerAdapter.kt
+++ b/app/src/main/java/com/todoroo/astrid/adapter/NavigationDrawerAdapter.kt
@@ -9,12 +9,14 @@ import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
-import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.FilterListItem
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.subjects.PublishSubject
import org.tasks.LocalBroadcastManager
+import org.tasks.activities.DragAndDropDiffer
import org.tasks.billing.Inventory
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
@@ -22,6 +24,7 @@ import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.locale.Locale
import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider
+import java.util.*
import javax.inject.Inject
import kotlin.math.max
@@ -34,11 +37,15 @@ class NavigationDrawerAdapter @Inject constructor(
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
private val localBroadcastManager: LocalBroadcastManager)
- : RecyclerView.Adapter() {
+ : RecyclerView.Adapter(), DragAndDropDiffer> {
private lateinit var onClick: (FilterListItem?) -> Unit
private var selected: Filter? = null
- private val differ = AsyncListDiffer(this, DiffCallback())
+ override val disposables = CompositeDisposable()
+ override val publishSubject = PublishSubject.create>()
+ override val updates: Queue, DiffUtil.DiffResult?>> = LinkedList()
+ override var items = initializeDiffer(ArrayList())
+ override var dragging = false
fun setOnClick(onClick: (FilterListItem?) -> Unit) {
this.onClick = onClick
@@ -54,7 +61,7 @@ class NavigationDrawerAdapter @Inject constructor(
override fun getItemId(position: Int) = position.toLong()
- override fun getItemCount() = differ.currentList.size
+ override fun getItemCount() = items.size
fun setSelected(selected: Filter?) {
this.selected = selected
@@ -90,17 +97,38 @@ class NavigationDrawerAdapter @Inject constructor(
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) = differ.submitList(filterListItems)
+ override fun transform(list: List) = list.toMutableList()
- private class DiffCallback : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(old: FilterListItem, new: FilterListItem) = old.areItemsTheSame(new)
+ override fun diff(last: MutableList, next: MutableList) =
+ DiffUtil.calculateDiff(DiffCallback(last, next))
- override fun areContentsTheSame(old: FilterListItem, new: FilterListItem) = old.areContentsTheSame(new)
+ private class DiffCallback(val old: List, val new: List) : 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 {
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)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.java b/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.java
index 7db755b8e..d16b4c3fa 100644
--- a/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.java
+++ b/app/src/main/java/com/todoroo/astrid/api/CaldavFilter.java
@@ -74,6 +74,10 @@ public class CaldavFilter extends Filter {
return calendar.getUuid();
}
+ public String getAccount() {
+ return calendar.getAccount();
+ }
+
public CaldavCalendar getCalendar() {
return calendar;
}
diff --git a/app/src/main/java/com/todoroo/astrid/api/FilterListItem.java b/app/src/main/java/com/todoroo/astrid/api/FilterListItem.java
index 6bfb77ad8..5aaba9a5d 100644
--- a/app/src/main/java/com/todoroo/astrid/api/FilterListItem.java
+++ b/app/src/main/java/com/todoroo/astrid/api/FilterListItem.java
@@ -73,7 +73,8 @@ public abstract class FilterListItem implements Parcelable {
return Objects.equals(listingTitle, other.listingTitle)
&& icon == other.icon
&& tint == other.tint
- && count == other.count;
+ && count == other.count
+ && order == other.order;
}
@Override
diff --git a/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java b/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java
index 571b8f61a..8e6563077 100644
--- a/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java
+++ b/app/src/main/java/com/todoroo/astrid/api/GtasksFilter.java
@@ -71,6 +71,10 @@ public class GtasksFilter extends Filter {
return list.getId();
}
+ public String getAccount() {
+ return list.getAccount();
+ }
+
public GoogleTaskList getList() {
return list;
}
diff --git a/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt b/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt
new file mode 100644
index 000000000..e5450a99c
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/DragAndDropDiffer.kt
@@ -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 : ListUpdateCallback {
+ val publishSubject: PublishSubject
+ val updates: Queue>
+ val disposables: CompositeDisposable
+ var items: R
+ var dragging: Boolean
+
+ fun submitList(list: List) {
+ disposables.add(
+ Single.fromCallable { transform(list) }
+ .subscribeOn(Schedulers.computation())
+ .subscribe(publishSubject::onNext))
+ }
+
+ fun calculateDiff(last: Pair, next: R): Pair {
+ AndroidUtilities.assertNotMainThread()
+ return Pair(next, diff(last.first!!, next))
+ }
+
+ fun applyDiff(update: Pair) {
+ 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): R {
+ val initial = transform(list)
+ disposables.add(publishSubject
+ .observeOn(Schedulers.computation())
+ .scan(Pair(initial, null), { last: Pair, next: R ->
+ calculateDiff(last, next)
+ })
+ .skip(1)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::applyDiff))
+ return initial
+ }
+
+ fun transform(list: List): R
+
+ fun diff(last: R, next: R): DiffUtil.DiffResult
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt
index 9422ce496..d2ef1d883 100644
--- a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt
+++ b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt
@@ -244,8 +244,11 @@ class FilterSettingsActivity : BaseListSettingsActivity() {
if (isNew) {
f.id = filterDao.insert(f)
} else {
- f.id = filter!!.id
- filterDao.update(f)
+ filter?.let {
+ f.id = it.id
+ f.order = it.order
+ filterDao.update(f)
+ }
}
setResult(
Activity.RESULT_OK,
diff --git a/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt b/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt
new file mode 100644
index 000000000..556fe0356
--- /dev/null
+++ b/app/src/main/java/org/tasks/activities/NavigationDrawerCustomization.kt
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/filters/FilterProvider.kt b/app/src/main/java/org/tasks/filters/FilterProvider.kt
index b395e6dcb..8a4b66973 100644
--- a/app/src/main/java/org/tasks/filters/FilterProvider.kt
+++ b/app/src/main/java/org/tasks/filters/FilterProvider.kt
@@ -11,6 +11,7 @@ import com.todoroo.astrid.core.BuiltInFilterExposer
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.activities.GoogleTaskListSettingsActivity
+import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.activities.TagSettingsActivity
import org.tasks.billing.Inventory
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
@@ -45,6 +46,9 @@ class FilterProvider @Inject constructor(
val filterPickerItems: List
get() = getAllFilters(showCreate = false)
+ val drawerCustomizationItems: List
+ get() = getAllFilters(showBuiltIn = false)
+
private fun addFilters(showCreate: Boolean, showBuiltIn: Boolean): List =
if (!preferences.getBoolean(R.string.p_filters_enabled, true)) {
emptyList()
@@ -154,6 +158,11 @@ class FilterProvider @Inject constructor(
R.drawable.ic_outline_attach_money_24px,
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(
context.getString(R.string.TLA_menu_settings),
R.drawable.ic_outline_settings_24px,
diff --git a/app/src/main/java/org/tasks/injection/ActivityComponent.kt b/app/src/main/java/org/tasks/injection/ActivityComponent.kt
index 11cac1dc6..81e0661c3 100644
--- a/app/src/main/java/org/tasks/injection/ActivityComponent.kt
+++ b/app/src/main/java/org/tasks/injection/ActivityComponent.kt
@@ -77,4 +77,5 @@ interface ActivityComponent {
fun inject(activity: SyncPreferences)
fun inject(activity: PlaceSettingsActivity)
fun inject(activity: LocalListSettingsActivity)
+ fun inject(activity: NavigationDrawerCustomization)
}
\ 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 43663bd49..71fad203c 100644
--- a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt
+++ b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt
@@ -2,23 +2,18 @@ package org.tasks.tasklist
import android.graphics.Canvas
import android.view.ViewGroup
-import androidx.core.util.Pair
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.*
import androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags
-import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
-import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.utility.Flags
-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 org.tasks.activities.DragAndDropDiffer
import org.tasks.data.TaskContainer
import org.tasks.preferences.Preferences
import java.util.*
@@ -32,19 +27,26 @@ class DragAndDropRecyclerAdapter(
private val taskList: TaskListFragment,
tasks: List,
taskDao: TaskDao,
- preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) {
- private var list: SectionedDataSource
- private val publishSubject = PublishSubject.create()
- private val disposables = CompositeDisposable()
- private val updates: Queue> = LinkedList()
- private var dragging = false
- private val disableHeaders: Boolean
- private val itemTouchHelper: ItemTouchHelper
+ preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences), DragAndDropDiffer {
+ private val disableHeaders = taskList.getFilter().let {
+ !it.supportsSorting()
+ || !preferences.showGroupHeaders()
+ || (it.supportsManualSort() && preferences.isManualSort)
+ || (it.supportsAstridSorting() && preferences.isAstridSort)
+ }
+ private val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()).apply {
+ attachToRecyclerView(recyclerView)
+ }
+ override val publishSubject = PublishSubject.create()
+ override val disposables = CompositeDisposable()
+ override val updates: Queue> = LinkedList()
+ override var dragging = false
+ override var items = initializeDiffer(tasks)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val viewType = getItemViewType(position)
if (viewType == 1) {
- val headerSection = list.getSection(position)
+ val headerSection = items.getSection(position)
(holder as HeaderViewHolder).bind(taskList.getFilter(), preferences.sortMode, headerSection)
} else {
super.onBindViewHolder(holder, position)
@@ -52,9 +54,11 @@ class DragAndDropRecyclerAdapter(
}
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) = super.submitList(list)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = if (viewType == 1) {
viewHolderFactory.newHeaderViewHolder(parent, this::toggleGroup)
@@ -69,44 +73,21 @@ class DragAndDropRecyclerAdapter(
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) {
- disposables.add(
- Single.fromCallable { SectionedDataSource(list, disableHeaders, preferences.sortMode, adapter.getCollapsed()) }
- .subscribeOn(Schedulers.computation())
- .subscribe(publishSubject::onNext))
- }
-
- private fun calculateDiff(
- last: Pair, next: SectionedDataSource): Pair {
- AndroidUtilities.assertNotMainThread()
- val cb = DiffCallback(last.first!!, next, adapter)
- val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE)
- return Pair.create(next, result)
- }
+ override fun transform(list: List): SectionedDataSource =
+ SectionedDataSource(list, disableHeaders, preferences.sortMode, adapter.getCollapsed())
- private fun applyDiff(update: Pair) {
- AndroidUtilities.assertMainThread()
- updates.add(update)
- if (!dragging) {
- drainQueue()
- }
- }
+ override fun diff(last: SectionedDataSource, next: SectionedDataSource) =
+ DiffUtil.calculateDiff(DiffCallback(last, next, adapter), next.size < LONG_LIST_SIZE)
- private fun drainQueue() {
- AndroidUtilities.assertMainThread()
+ override fun drainQueue() {
val recyclerViewState = recyclerView.layoutManager!!.onSaveInstanceState()
- var update = updates.poll()
- while (update != null) {
- list = update.first!!
- update.second!!.dispatchUpdatesTo((this as ListUpdateCallback))
- update = updates.poll()
- }
+ super.drainQueue()
recyclerView.layoutManager!!.onRestoreInstanceState(recyclerViewState)
}
@@ -115,9 +96,9 @@ class DragAndDropRecyclerAdapter(
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 var from = -1
@@ -162,7 +143,7 @@ class DragAndDropRecyclerAdapter(
notifyItemMoved(fromPosition, toPosition)
if (isHeader) {
val offset = if (fromPosition < toPosition) -1 else 1
- list.moveSection(toPosition, offset)
+ items.moveSection(toPosition, offset)
}
updateIndents(source, from, to)
return true
@@ -257,8 +238,8 @@ class DragAndDropRecyclerAdapter(
from
}
adapter.moved(from, to, indent)
- val task: TaskContainer = list.removeAt(from)
- list.add(if (from < to) to - 1 else to, task)
+ val task: TaskContainer = items.removeAt(from)
+ items.add(if (from < to) to - 1 else to, task)
taskList.loadTaskListContent()
}
}
@@ -268,24 +249,4 @@ class DragAndDropRecyclerAdapter(
private val NO_MOVEMENT = makeMovementFlags(0, 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(list, null)
- disposables.add(publishSubject
- .observeOn(Schedulers.computation())
- .scan(initial, { last: Pair, next: SectionedDataSource ->
- calculateDiff(last, next)
- })
- .skip(1)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe { update: Pair -> applyDiff(update) })
- }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_tag_organizer.xml b/app/src/main/res/layout/activity_tag_organizer.xml
new file mode 100644
index 000000000..84d41df1f
--- /dev/null
+++ b/app/src/main/res/layout/activity_tag_organizer.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/filter_adapter_subheader.xml b/app/src/main/res/layout/filter_adapter_subheader.xml
index e4396276f..4872295e9 100644
--- a/app/src/main/res/layout/filter_adapter_subheader.xml
+++ b/app/src/main/res/layout/filter_adapter_subheader.xml
@@ -6,8 +6,7 @@
android:id="@+id/subheader_row"
android:background="?attr/selectableItemBackground"
android:focusable="true"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="wrap_content">
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_nav_drawer_customization.xml b/app/src/main/res/menu/menu_nav_drawer_customization.xml
new file mode 100644
index 000000000..f3013bca1
--- /dev/null
+++ b/app/src/main/res/menu/menu_nav_drawer_customization.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e2e3a700c..09bb91471 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -30,6 +30,7 @@ File %1$s contained %2$s.\n\n
Search
Settings
Edit
+ Manage lists
Call
Open
My order
@@ -634,4 +635,5 @@ File %1$s contained %2$s.\n\n
On launch
Open last viewed list
Lists
+ Reset sort order