From 57f8f667b6a08fbd0b03301f100e4dd167bbe09a Mon Sep 17 00:00:00 2001 From: Naveen Singh Date: Sat, 20 Jul 2024 00:30:30 +0530 Subject: [PATCH] Use list adapter for checklists --- .../activities/WidgetConfigureActivity.kt | 4 +- .../notes/adapters/ChecklistAdapter.kt | 114 +++++++++--------- .../fossify/notes/adapters/OpenNoteAdapter.kt | 2 +- .../fossify/notes/adapters/WidgetAdapter.kt | 4 +- .../notes/fragments/ChecklistFragment.kt | 52 ++++---- .../org/fossify/notes/helpers/Config.kt | 2 +- .../interfaces/ChecklistItemsListener.kt | 4 +- .../org/fossify/notes/models/ChecklistItem.kt | 4 +- 8 files changed, 96 insertions(+), 90 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/notes/activities/WidgetConfigureActivity.kt b/app/src/main/kotlin/org/fossify/notes/activities/WidgetConfigureActivity.kt index 43190b15..287d07a5 100644 --- a/app/src/main/kotlin/org/fossify/notes/activities/WidgetConfigureActivity.kt +++ b/app/src/main/kotlin/org/fossify/notes/activities/WidgetConfigureActivity.kt @@ -171,10 +171,12 @@ class WidgetConfigureActivity : SimpleActivity() { } } - ChecklistAdapter(this, items, null, binding.checklistNoteView, false) {}.apply { + ChecklistAdapter(this, null, binding.checklistNoteView).apply { updateTextColor(mTextColor) binding.checklistNoteView.adapter = this + submitList(items.toList()) } + binding.textNoteView.beGone() binding.checklistNoteView.beVisible() } else { diff --git a/app/src/main/kotlin/org/fossify/notes/adapters/ChecklistAdapter.kt b/app/src/main/kotlin/org/fossify/notes/adapters/ChecklistAdapter.kt index 8d21eb9c..992dee73 100644 --- a/app/src/main/kotlin/org/fossify/notes/adapters/ChecklistAdapter.kt +++ b/app/src/main/kotlin/org/fossify/notes/adapters/ChecklistAdapter.kt @@ -7,10 +7,12 @@ import android.view.Menu import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import org.fossify.commons.activities.BaseSimpleActivity import org.fossify.commons.adapters.MyRecyclerViewAdapter +import org.fossify.commons.adapters.MyRecyclerViewListAdapter import org.fossify.commons.extensions.applyColorFilter import org.fossify.commons.extensions.beVisibleIf import org.fossify.commons.extensions.removeBit @@ -31,18 +33,19 @@ import java.util.Collections class ChecklistAdapter( activity: BaseSimpleActivity, - var items: MutableList, val listener: ChecklistItemsListener?, recyclerView: MyRecyclerView, - val showIcons: Boolean, - itemClick: (Any) -> Unit, -) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), ItemTouchHelperContract { + itemClick: (Any) -> Unit = {}, +) : MyRecyclerViewListAdapter( + activity = activity, recyclerView = recyclerView, diffUtil = ChecklistItemDiffUtil(), itemClick = itemClick +), ItemTouchHelperContract { private var touchHelper: ItemTouchHelper? = null private var startReorderDragListener: StartReorderDragListener init { setupDragListener(true) + setHasStableIds(true) touchHelper = ItemTouchHelper(ItemMoveCallback(this)) touchHelper!!.attachToRecyclerView(recyclerView) @@ -69,23 +72,21 @@ class ChecklistAdapter( } } - override fun getSelectableItemCount() = items.size + override fun getItemId(position: Int) = currentList[position].id.toLong() + + override fun getSelectableItemCount() = currentList.size override fun getIsItemSelectable(position: Int) = true - override fun getItemSelectionKey(position: Int) = items.getOrNull(position)?.id + override fun getItemSelectionKey(position: Int) = currentList.getOrNull(position)?.id - override fun getItemKeyPosition(key: Int) = items.indexOfFirst { it.id == key } + override fun getItemKeyPosition(key: Int) = currentList.indexOfFirst { it.id == key } @SuppressLint("NotifyDataSetChanged") - override fun onActionModeCreated() { - notifyDataSetChanged() - } + override fun onActionModeCreated() = notifyDataSetChanged() @SuppressLint("NotifyDataSetChanged") - override fun onActionModeDestroyed() { - notifyDataSetChanged() - } + override fun onActionModeDestroyed() = notifyDataSetChanged() override fun prepareActionMode(menu: Menu) { val selectedItems = getSelectedItems() @@ -101,7 +102,7 @@ class ChecklistAdapter( } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val item = items[position] + val item = currentList[position] holder.bindView(item, allowSingleClick = true, allowLongClick = true) { itemView, _ -> setupView(itemView, item, holder) } @@ -109,85 +110,62 @@ class ChecklistAdapter( bindViewHolder(holder) } - override fun getItemCount() = items.size - private fun renameChecklistItem() { val item = getSelectedItems().first() - RenameChecklistItemDialog(activity, item.title) { - val position = getSelectedItemPositions().first() - item.title = it - notifyItemChanged(position) + RenameChecklistItemDialog(activity, item.title) { title -> + val items = currentList.toMutableList() + items[getSelectedItemPositions().first()] = item.copy(title = title) + saveChecklist(items) finishActMode() - listener?.saveChecklist { - listener.refreshItems() - } } } private fun deleteSelection() { - val removeItems = ArrayList(selectedKeys.size) - val positions = ArrayList() - selectedKeys.forEach { - val key = it + val items = currentList.toMutableList() + val itemsToRemove = ArrayList(selectedKeys.size) + selectedKeys.forEach { key -> val position = items.indexOfFirst { it.id == key } if (position != -1) { - positions.add(position) - val favorite = getItemWithKey(key) if (favorite != null) { - removeItems.add(favorite) + itemsToRemove.add(favorite) } } } - items.removeAll(removeItems.toSet()) - positions.sortDescending() - removeSelectedItems(positions) - - listener?.saveChecklist { - if (items.isEmpty()) { - listener.refreshItems() - } - } + items.removeAll(itemsToRemove.toSet()) + saveChecklist(items) } private fun moveSelectedItemsToTop() { activity.config.sorting = SORT_BY_CUSTOM - val movedPositions = mutableListOf() + val items = currentList.toMutableList() selectedKeys.reversed().forEach { checklistId -> val position = items.indexOfFirst { it.id == checklistId } val tempItem = items[position] items.removeAt(position) - movedPositions.add(position) items.add(0, tempItem) } - movedPositions.forEach { - notifyItemMoved(it, 0) - } - listener?.saveChecklist() + saveChecklist(items) } private fun moveSelectedItemsToBottom() { activity.config.sorting = SORT_BY_CUSTOM - val movedPositions = mutableListOf() + val items = currentList.toMutableList() selectedKeys.forEach { checklistId -> val position = items.indexOfFirst { it.id == checklistId } val tempItem = items[position] items.removeAt(position) - movedPositions.add(position) items.add(items.size, tempItem) } - movedPositions.forEach { - notifyItemMoved(it, items.size - 1) - } - listener?.saveChecklist() + saveChecklist(items) } - private fun getItemWithKey(key: Int): ChecklistItem? = items.firstOrNull { it.id == key } + private fun getItemWithKey(key: Int): ChecklistItem? = currentList.firstOrNull { it.id == key } - private fun getSelectedItems() = items.filter { selectedKeys.contains(it.id) } as ArrayList + private fun getSelectedItems() = currentList.filter { selectedKeys.contains(it.id) }.toMutableList() private fun setupView(view: View, checklistItem: ChecklistItem, holder: ViewHolder) { val isSelected = selectedKeys.contains(checklistItem.id) @@ -223,6 +201,7 @@ class ChecklistAdapter( override fun onRowMoved(fromPosition: Int, toPosition: Int) { activity.config.sorting = SORT_BY_CUSTOM + val items = currentList.toMutableList() if (fromPosition < toPosition) { for (i in fromPosition until toPosition) { Collections.swap(items, i, i + 1) @@ -232,13 +211,34 @@ class ChecklistAdapter( Collections.swap(items, i, i - 1) } } - notifyItemMoved(fromPosition, toPosition) + + saveChecklist(items) } - override fun onRowSelected(myViewHolder: ViewHolder?) { + override fun onRowSelected(myViewHolder: MyRecyclerViewAdapter.ViewHolder?) {} + + override fun onRowClear(myViewHolder: MyRecyclerViewAdapter.ViewHolder?) { + saveChecklist(currentList.toList()) } - override fun onRowClear(myViewHolder: ViewHolder?) { - listener?.saveChecklist() + private fun saveChecklist(items: List) { + listener?.saveChecklist(items) { + listener.refreshItems() + } } } + +private class ChecklistItemDiffUtil : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ChecklistItem, + newItem: ChecklistItem + ) = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: ChecklistItem, + newItem: ChecklistItem + ) = oldItem.id == newItem.id + && oldItem.isDone == newItem.isDone + && oldItem.title == newItem.title + && oldItem.dateCreated == newItem.dateCreated +} diff --git a/app/src/main/kotlin/org/fossify/notes/adapters/OpenNoteAdapter.kt b/app/src/main/kotlin/org/fossify/notes/adapters/OpenNoteAdapter.kt index 20a410fd..69729e9a 100644 --- a/app/src/main/kotlin/org/fossify/notes/adapters/OpenNoteAdapter.kt +++ b/app/src/main/kotlin/org/fossify/notes/adapters/OpenNoteAdapter.kt @@ -124,7 +124,7 @@ class OpenNoteAdapter( NoteType.TYPE_CHECKLIST -> { val checklistItemType = object : TypeToken>() {}.type var items = Gson().fromJson>(getNoteStoredValue(context), checklistItemType) ?: listOf() - items = items.filter { it.title != null }.let { + items = items.let { val sorting = context.config.sorting ChecklistItem.sorting = sorting if (ChecklistItem.sorting and SORT_BY_CUSTOM == 0) { diff --git a/app/src/main/kotlin/org/fossify/notes/adapters/WidgetAdapter.kt b/app/src/main/kotlin/org/fossify/notes/adapters/WidgetAdapter.kt index e9b47694..f1ab47db 100644 --- a/app/src/main/kotlin/org/fossify/notes/adapters/WidgetAdapter.kt +++ b/app/src/main/kotlin/org/fossify/notes/adapters/WidgetAdapter.kt @@ -128,11 +128,11 @@ class WidgetAdapter(val context: Context, val intent: Intent) : RemoteViewsServi checklistItems = note!!.getNoteStoredValue(context)?.ifEmpty { "[]" }?.let { Json.decodeFromString(it) } ?: mutableListOf() // checklist title can be null only because of the glitch in upgrade to 6.6.0, remove this check in the future - checklistItems = checklistItems.filter { it.title != null }.toMutableList() as ArrayList + checklistItems = checklistItems.toMutableList() as ArrayList val sorting = context.config.sorting if (sorting and SORT_BY_CUSTOM == 0) { checklistItems.sort() - if (context.config?.moveDoneChecklistItems == true) { + if (context.config.moveDoneChecklistItems) { checklistItems.sortBy { it.isDone } } } diff --git a/app/src/main/kotlin/org/fossify/notes/fragments/ChecklistFragment.kt b/app/src/main/kotlin/org/fossify/notes/fragments/ChecklistFragment.kt index 42a75cbd..9206e62e 100644 --- a/app/src/main/kotlin/org/fossify/notes/fragments/ChecklistFragment.kt +++ b/app/src/main/kotlin/org/fossify/notes/fragments/ChecklistFragment.kt @@ -63,8 +63,7 @@ class ChecklistFragment : NoteFragment(), ChecklistItemsListener { val checklistItemType = object : TypeToken>() {}.type items = Gson().fromJson>(storedNote.getNoteStoredValue(requireActivity()), checklistItemType) ?: ArrayList(1) - // checklist title can be null only because of the glitch in upgrade to 6.6.0, remove this check in the future - items = items.filter { it.title != null }.toMutableList() as ArrayList + items = items.toMutableList() as ArrayList val sorting = config?.sorting ?: 0 if (sorting and SORT_BY_CUSTOM == 0 && config?.moveDoneChecklistItems == true) { items.sortBy { it.isDone } @@ -91,7 +90,7 @@ class ChecklistFragment : NoteFragment(), ChecklistItemsListener { ) } - saveChecklist() + saveChecklist(items) } private fun setupFragment() { @@ -173,25 +172,33 @@ class ChecklistFragment : NoteFragment(), ChecklistItemsListener { items.sortBy { it.isDone } } } - ChecklistAdapter( - activity = activity as SimpleActivity, - items = items, - listener = this, - recyclerView = binding.checklistList, - showIcons = true - ) { item -> - val clickedNote = item as ChecklistItem - clickedNote.isDone = !clickedNote.isDone - saveNote(items.indexOfFirst { it.id == clickedNote.id }) - context?.updateWidgets() - refreshItems() - }.apply { - binding.checklistList.adapter = this + var checklistAdapter = binding.checklistList.adapter as? ChecklistAdapter + if (checklistAdapter == null) { + checklistAdapter = ChecklistAdapter( + activity = activity as SimpleActivity, + listener = this, + recyclerView = binding.checklistList, + itemClick = ::toggleCompletion + ) + binding.checklistList.adapter = checklistAdapter + } + + checklistAdapter.submitList(items.toList()) + } + + private fun toggleCompletion(any: Any) { + val item = any as ChecklistItem + val index = items.indexOf(item) + if (index != -1) { + items[index] = item.copy(isDone = !item.isDone) + saveNote { + loadNoteById(noteId) + } } } - private fun saveNote(refreshIndex: Int = -1, callback: () -> Unit = {}) { + private fun saveNote(callback: () -> Unit = {}) { if (note == null) { return } @@ -205,12 +212,6 @@ class ChecklistFragment : NoteFragment(), ChecklistItemsListener { } if (note != null) { - if (refreshIndex != -1) { - binding.checklistList.post { - binding.checklistList.adapter?.notifyItemChanged(refreshIndex) - } - } - note!!.value = getChecklistItems() ensureBackgroundThread { @@ -237,7 +238,8 @@ class ChecklistFragment : NoteFragment(), ChecklistItemsListener { fun getChecklistItems() = Gson().toJson(items) - override fun saveChecklist(callback: () -> Unit) { + override fun saveChecklist(updatedItems: List, callback: () -> Unit) { + items = updatedItems.toMutableList() saveNote(callback = callback) } diff --git a/app/src/main/kotlin/org/fossify/notes/helpers/Config.kt b/app/src/main/kotlin/org/fossify/notes/helpers/Config.kt index 1a8581d2..f99d30e0 100644 --- a/app/src/main/kotlin/org/fossify/notes/helpers/Config.kt +++ b/app/src/main/kotlin/org/fossify/notes/helpers/Config.kt @@ -76,7 +76,7 @@ class Config(context: Context) : BaseConfig(context) { set(lastCreatedNoteType) = prefs.edit().putInt(LAST_CREATED_NOTE_TYPE, lastCreatedNoteType).apply() var moveDoneChecklistItems: Boolean - get() = prefs.getBoolean(MOVE_DONE_CHECKLIST_ITEMS, true) + get() = prefs.getBoolean(MOVE_DONE_CHECKLIST_ITEMS, false) set(moveDoneChecklistItems) = prefs.edit().putBoolean(MOVE_DONE_CHECKLIST_ITEMS, moveDoneChecklistItems).apply() fun getTextGravity() = when (gravity) { diff --git a/app/src/main/kotlin/org/fossify/notes/interfaces/ChecklistItemsListener.kt b/app/src/main/kotlin/org/fossify/notes/interfaces/ChecklistItemsListener.kt index 6e31739e..10780be6 100644 --- a/app/src/main/kotlin/org/fossify/notes/interfaces/ChecklistItemsListener.kt +++ b/app/src/main/kotlin/org/fossify/notes/interfaces/ChecklistItemsListener.kt @@ -1,7 +1,9 @@ package org.fossify.notes.interfaces +import org.fossify.notes.models.ChecklistItem + interface ChecklistItemsListener { fun refreshItems() - fun saveChecklist(callback: () -> Unit = {}) + fun saveChecklist(updatedItems: List, callback: () -> Unit = {}) } diff --git a/app/src/main/kotlin/org/fossify/notes/models/ChecklistItem.kt b/app/src/main/kotlin/org/fossify/notes/models/ChecklistItem.kt index 0a41dadf..c55b4648 100644 --- a/app/src/main/kotlin/org/fossify/notes/models/ChecklistItem.kt +++ b/app/src/main/kotlin/org/fossify/notes/models/ChecklistItem.kt @@ -9,8 +9,8 @@ import org.fossify.notes.helpers.CollatorBasedComparator data class ChecklistItem( val id: Int, val dateCreated: Long = 0L, - var title: String, - var isDone: Boolean + val title: String, + val isDone: Boolean ) : Comparable { companion object {