From 3c5631578023b05a3ebe30b7cd4807f0529bd048 Mon Sep 17 00:00:00 2001 From: jainv4156 Date: Mon, 16 Sep 2024 22:25:00 +0530 Subject: [PATCH] Added multiselect delete in manageNotesType --- .../ichi2/anki/notetype/ManageNotetypes.kt | 99 ++++++++++++++++- .../anki/notetype/NoteTypeAdapterCallbacks.kt | 23 ++++ .../ichi2/anki/notetype/NotetypeAdapter.kt | 103 +++++++++++++++++- .../main/res/layout/item_manage_note_type.xml | 46 +++++--- .../src/main/res/menu/menu_manage_notes.xml | 21 ++++ AnkiDroid/src/main/res/menu/search.xml | 24 ---- 6 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypeAdapterCallbacks.kt create mode 100644 AnkiDroid/src/main/res/menu/menu_manage_notes.xml delete mode 100644 AnkiDroid/src/main/res/menu/search.xml diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt index 7d8bfc0c4cba..cdec9f06896a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt @@ -21,11 +21,13 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.Menu +import android.view.MenuItem import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.RecyclerView import anki.notetypes.copy import com.google.android.material.floatingactionbutton.FloatingActionButton @@ -52,12 +54,18 @@ import com.ichi2.utils.positiveButton import com.ichi2.utils.show import com.ichi2.utils.title -class ManageNotetypes : AnkiActivity() { +class ManageNotetypes : AnkiActivity(), NoteTypeAdapterCallbacks { private lateinit var actionBar: ActionBar private lateinit var noteTypesList: RecyclerView private var currentNotetypes: List = emptyList() + lateinit var toolbar: Toolbar + private var toDeleteList: List = emptyList() + private var isInMultiSelectMode = false + private fun getIsInMultiSelectMode(): Boolean { + return isInMultiSelectMode + } private val notetypesAdapter: NotetypesAdapter by lazy { NotetypesAdapter( this@ManageNotetypes, @@ -71,6 +79,8 @@ class ManageNotetypes : AnkiActivity() { }, onEditCards = { launchForChanges(mapOf("modelId" to it.id)) }, onRename = ::renameNotetype, + callback = this, + getIsInMultiSelectMode = ::getIsInMultiSelectMode, onDelete = ::deleteNotetype ) } @@ -90,6 +100,7 @@ class ManageNotetypes : AnkiActivity() { setTitle(R.string.model_browser_label) setContentView(R.layout.activity_manage_note_types) actionBar = enableToolbar() + toolbar = findViewById(R.id.toolbar) noteTypesList = findViewById(R.id.note_types_list).apply { adapter = notetypesAdapter } @@ -101,8 +112,7 @@ class ManageNotetypes : AnkiActivity() { } override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.search, menu) - + menuInflater.inflate(R.menu.menu_manage_notes, menu) val searchItem = menu.findItem(R.id.search_item) val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager val searchView = searchItem?.actionView as? AccessibleSearchView @@ -126,8 +136,21 @@ class ManageNotetypes : AnkiActivity() { return true } }) + + launchCatchingTask { + menu.findItem(R.id.action_delete_notes).isVisible = toDeleteList.isNotEmpty() && isInMultiSelectMode + } return true } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_delete_notes -> { + deleteSelectedNotetype() + true + } + else -> super.onOptionsItemSelected(item) + } + } @SuppressLint("CheckResult") private fun renameNotetype(manageNoteTypeUiModel: ManageNoteTypeUiModel) { @@ -168,6 +191,28 @@ class ManageNotetypes : AnkiActivity() { } } + private fun deleteSelectedNotetype() { + val selectedItems = toDeleteList + AlertDialog.Builder(this@ManageNotetypes).show { + title(R.string.model_browser_delete) + message(R.string.model_delete_warning) + positiveButton(R.string.dialog_positive_delete) { + disableMultiSelectMode() + launchCatchingTask { + withProgress { + withCol { + selectedItems.forEach { item -> + removeNotetype(item) + } + } + } + runAndRefreshAfter() + } + } + negativeButton(R.string.dialog_cancel) + } + } + private fun deleteNotetype(manageNoteTypeUiModel: ManageNoteTypeUiModel) { launchCatchingTask { val messageResourceId: Int? = if (userAcceptsSchemaChange()) { @@ -237,4 +282,52 @@ class ManageNotetypes : AnkiActivity() { else -> throw IllegalArgumentException("Unexpected value type: ${newExtra.value}") } } + + override fun enableMultiSelectMode() { + isInMultiSelectMode = true + toolbar.setNavigationOnClickListener { + disableMultiSelectMode() + } + notetypesAdapter.notifyItemRangeChanged(0, notetypesAdapter.itemCount, "payload_enable_checkbox_visibility") + } + + private fun disableMultiSelectMode() { + toDeleteList = emptyList() + isInMultiSelectMode = false + + toolbar.setNavigationOnClickListener { + finish() + } + notetypesAdapter.notifyItemRangeChanged(0, notetypesAdapter.itemCount, "payload_disable_checkbox_visibility") + invalidateMenu() + } + + override fun isToDeleteListContains(id: Long): Boolean { + return toDeleteList.contains(id) + } + + override fun setCheckBoxSelectionOnCLick(id: Long, position: Int) { + if (toDeleteList.contains(id)) { + toDeleteList = toDeleteList.minus(id) + + /** deselecting all checkbox will turnoff the multiselect mode*/ + if (toDeleteList.isEmpty()) { + disableMultiSelectMode() + } else { + notetypesAdapter.notifyItemChanged(position, "payload_deselect_checkbox") + } + } else { + toDeleteList = toDeleteList.plus(id) + notetypesAdapter.notifyItemChanged(position, "payload_select_checkbox") + } + invalidateMenu() + } + + override fun setCheckBoxSelectionOnStart(id: Long): Boolean { + return if (toDeleteList.contains(id)) { + true + } else { + false + } + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypeAdapterCallbacks.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypeAdapterCallbacks.kt new file mode 100644 index 000000000000..739872650685 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypeAdapterCallbacks.kt @@ -0,0 +1,23 @@ +/**************************************************************************************** + * Copyright (c) 2022 Vaibhav Jain * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 3 of the License, or (at your option) any later * + * version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY * + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * + * PARTICULAR PURPOSE. See the GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along with * + * this program. If not, see . * + ****************************************************************************************/ +package com.ichi2.anki.notetype + +interface NoteTypeAdapterCallbacks { + fun enableMultiSelectMode() + fun isToDeleteListContains(id: Long): Boolean + fun setCheckBoxSelectionOnCLick(id: Long, position: Int) + fun setCheckBoxSelectionOnStart(id: Long): Boolean +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NotetypeAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NotetypeAdapter.kt index 7104f0010b91..f6d136cdd2c9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NotetypeAdapter.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NotetypeAdapter.kt @@ -20,10 +20,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.CheckBox import android.widget.TextView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.button.MaterialButton import com.ichi2.anki.R private val notetypeNamesAndCountDiff = @@ -46,6 +48,8 @@ internal class NotetypesAdapter( private val onShowFields: (ManageNoteTypeUiModel) -> Unit, private val onEditCards: (ManageNoteTypeUiModel) -> Unit, private val onRename: (ManageNoteTypeUiModel) -> Unit, + private val callback: NoteTypeAdapterCallbacks, + private val getIsInMultiSelectMode: () -> Boolean, private val onDelete: (ManageNoteTypeUiModel) -> Unit ) : ListAdapter(notetypeNamesAndCountDiff) { private val layoutInflater = LayoutInflater.from(context) @@ -56,6 +60,8 @@ internal class NotetypesAdapter( onDelete = onDelete, onRename = onRename, onEditCards = onEditCards, + callback = callback, + getIsInMultiSelectMode = getIsInMultiSelectMode, onShowFields = onShowFields ) } @@ -63,31 +69,65 @@ internal class NotetypesAdapter( override fun onBindViewHolder(holder: NotetypeViewHolder, position: Int) { holder.bind(getItem(position)) } + + override fun onBindViewHolder(holder: NotetypeViewHolder, position: Int, payloads: List) { + if (payloads.isEmpty()) { + onBindViewHolder(holder, position) + } else { + for (payload in payloads) { + when (payload) { + "payload_enable_checkbox_visibility" -> { + holder.applyMultiSelectMode(getItem(position)) + } + "payload_disable_checkbox_visibility" -> { + holder.removeMultiSelectMode() + } + "payload_select_checkbox" -> { + holder.selectCheckBox() + } + "payload_deselect_checkbox" -> { + holder.deselectCheckBox() + } + } + } + } + } } internal class NotetypeViewHolder( rowView: View, - onShowFields: (ManageNoteTypeUiModel) -> Unit, + private val onShowFields: (ManageNoteTypeUiModel) -> Unit, + private val callback: NoteTypeAdapterCallbacks, onEditCards: (ManageNoteTypeUiModel) -> Unit, onRename: (ManageNoteTypeUiModel) -> Unit, + getIsInMultiSelectMode: () -> Boolean, onDelete: (ManageNoteTypeUiModel) -> Unit ) : RecyclerView.ViewHolder(rowView) { val name: TextView = rowView.findViewById(R.id.note_name) - val useCount: TextView = rowView.findViewById(R.id.note_use_count) + private val useCount: TextView = rowView.findViewById(R.id.note_use_count) private val btnDelete: Button = rowView.findViewById(R.id.note_delete) private val btnRename: Button = rowView.findViewById(R.id.note_rename) private val btnEditCards: Button = rowView.findViewById(R.id.note_edit_cards) private var mManageNoteTypeUiModel: ManageNoteTypeUiModel? = null private val resources = rowView.context.resources + private val isInMultiSelectMode = getIsInMultiSelectMode + private val selectedItemCheckbox: CheckBox = rowView.findViewById(R.id.selected_item_checkbox) + private val editCardButton: MaterialButton = rowView.findViewById(R.id.note_edit_cards) + private val renameCardButton: MaterialButton = rowView.findViewById(R.id.note_rename) + private val deleteCardButton: MaterialButton = rowView.findViewById(R.id.note_delete) init { - rowView.setOnClickListener { mManageNoteTypeUiModel?.let(onShowFields) } btnEditCards.setOnClickListener { mManageNoteTypeUiModel?.let(onEditCards) } btnDelete.setOnClickListener { mManageNoteTypeUiModel?.let(onDelete) } btnRename.setOnClickListener { mManageNoteTypeUiModel?.let(onRename) } } fun bind(manageNoteTypeUiModel: ManageNoteTypeUiModel) { + if (isInMultiSelectMode()) { + applyMultiSelectMode(manageNoteTypeUiModel) + } else { + removeMultiSelectMode() + } this.mManageNoteTypeUiModel = manageNoteTypeUiModel name.text = manageNoteTypeUiModel.name useCount.text = resources.getQuantityString( @@ -95,5 +135,62 @@ internal class NotetypeViewHolder( manageNoteTypeUiModel.useCount, manageNoteTypeUiModel.useCount ) + itemView.setOnLongClickListener { + callback.enableMultiSelectMode() + callback.setCheckBoxSelectionOnCLick(manageNoteTypeUiModel.id, bindingAdapterPosition) + true + } + } + fun applyMultiSelectMode(manageNoteTypeUiModel: ManageNoteTypeUiModel) { + showCheckBox(selectedItemCheckbox) + if (callback.setCheckBoxSelectionOnStart(manageNoteTypeUiModel.id)) { + selectCheckBox() + } else { + deselectCheckBox() + } + disableEditDeleteRenameButton() + itemView.setOnClickListener { + callback.setCheckBoxSelectionOnCLick(manageNoteTypeUiModel.id, bindingAdapterPosition) + } + } + fun removeMultiSelectMode() { + enableEditDeleteRenameButton() + hideCheckBox(selectedItemCheckbox) + selectedItemCheckbox.isChecked = false + itemView.setOnClickListener { mManageNoteTypeUiModel?.let(onShowFields) } + } + fun selectCheckBox() { + selectedItemCheckbox.isChecked = true + } + fun deselectCheckBox() { + selectedItemCheckbox.isChecked = false + } + private fun hideCheckBox(checkBox: CheckBox) { + checkBox.visibility = View.GONE + } + private fun showCheckBox(checkBox: CheckBox) { + checkBox.visibility = View.VISIBLE + } + private fun disableEditDeleteRenameButton() { + editCardButton.apply { + visibility = View.GONE + } + renameCardButton.apply { + visibility = View.GONE + } + deleteCardButton.apply { + visibility = View.GONE + } + } + private fun enableEditDeleteRenameButton() { + editCardButton.apply { + visibility = View.VISIBLE + } + renameCardButton.apply { + visibility = View.VISIBLE + } + deleteCardButton.apply { + visibility = View.VISIBLE + } } } diff --git a/AnkiDroid/src/main/res/layout/item_manage_note_type.xml b/AnkiDroid/src/main/res/layout/item_manage_note_type.xml index 00c18dda8131..8346fcba9ba2 100644 --- a/AnkiDroid/src/main/res/layout/item_manage_note_type.xml +++ b/AnkiDroid/src/main/res/layout/item_manage_note_type.xml @@ -14,23 +14,42 @@ - - + + + + + + + android:checked="false" + android:focusable="false" + android:focusableInTouchMode="false" + android:visibility="gone" + android:clickable="false" + tools:visibility="visible" /> + + app:layout_constraintHorizontal_bias="0.0" + tools:visibility="visible"/> + + + + + \ No newline at end of file diff --git a/AnkiDroid/src/main/res/menu/search.xml b/AnkiDroid/src/main/res/menu/search.xml deleted file mode 100644 index 3f3fe60e5942..000000000000 --- a/AnkiDroid/src/main/res/menu/search.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file