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