diff --git a/app/src/main/java/io/legado/app/data/dao/DictRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/DictRuleDao.kt index dc6438e03..0d6786d69 100644 --- a/app/src/main/java/io/legado/app/data/dao/DictRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/DictRuleDao.kt @@ -5,6 +5,8 @@ import androidx.room.Delete import androidx.room.Query import androidx.room.Upsert import io.legado.app.data.entities.DictRule +import kotlinx.coroutines.flow.Flow + @Dao interface DictRuleDao { @@ -15,6 +17,9 @@ interface DictRuleDao { @get:Query("select * from dictRules where enabled = 1") val enabled: List + @Query("select * from dictRules where enabled = 1") + fun flowAll(): Flow> + @Upsert fun upsert(vararg dictRule: DictRule) diff --git a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleActivity.kt b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleActivity.kt index 5c4f2c53d..137f55ad6 100644 --- a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleActivity.kt @@ -1,17 +1,112 @@ package io.legado.app.ui.dict.rule import android.os.Bundle +import android.view.MenuItem import androidx.activity.viewModels +import androidx.appcompat.widget.PopupMenu +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.data.appDb +import io.legado.app.data.entities.DictRule import io.legado.app.databinding.ActivityDictRuleBinding +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.SelectActionBar +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.viewbindingdelegate.viewBinding +import kotlinx.coroutines.launch -class DictRuleActivity : VMBaseActivity() { +class DictRuleActivity : VMBaseActivity(), + PopupMenu.OnMenuItemClickListener, + SelectActionBar.CallBack, + DictRuleAdapter.CallBack { override val viewModel by viewModels() override val binding by viewBinding(ActivityDictRuleBinding::inflate) - override fun onActivityCreated(savedInstanceState: Bundle?) { + private val adapter by lazy { DictRuleAdapter(this, this) } + override fun onActivityCreated(savedInstanceState: Bundle?) { + initRecyclerView() + initSelectActionView() + observeDictRuleData() } + + private fun initRecyclerView() { + binding.recyclerView.setEdgeEffectColor(primaryColor) + binding.recyclerView.layoutManager = LinearLayoutManager(this) + binding.recyclerView.adapter = adapter + binding.recyclerView.addItemDecoration(VerticalDivider(this)) + val itemTouchCallback = ItemTouchCallback(adapter) + itemTouchCallback.isCanDrag = true + val dragSelectTouchHelper: DragSelectTouchHelper = + DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50) + dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView) + // When this page is opened, it is in selection mode + dragSelectTouchHelper.activeSlideSelect() + + // Note: need judge selection first, so add ItemTouchHelper after it. + ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView) + } + + private fun initSelectActionView() { + binding.selectActionBar.setMainActionText(R.string.delete) + binding.selectActionBar.inflateMenu(R.menu.replace_rule_sel) + binding.selectActionBar.setOnMenuItemClickListener(this) + binding.selectActionBar.setCallBack(this) + } + + private fun observeDictRuleData() { + launch { + appDb.dictRuleDao.flowAll().collect { + adapter.setItems(it, adapter.diffItemCallBack) + } + } + } + + override fun selectAll(selectAll: Boolean) { + TODO("Not yet implemented") + } + + override fun revertSelection() { + TODO("Not yet implemented") + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + TODO("Not yet implemented") + } + + override fun update(vararg rule: DictRule) { + TODO("Not yet implemented") + } + + override fun delete(rule: DictRule) { + TODO("Not yet implemented") + } + + override fun edit(rule: DictRule) { + TODO("Not yet implemented") + } + + override fun toTop(rule: DictRule) { + TODO("Not yet implemented") + } + + override fun toBottom(rule: DictRule) { + TODO("Not yet implemented") + } + + override fun upOrder() { + TODO("Not yet implemented") + } + + override fun upCountView() { + TODO("Not yet implemented") + } + + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleAdapter.kt b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleAdapter.kt new file mode 100644 index 000000000..46874855c --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleAdapter.kt @@ -0,0 +1,226 @@ +package io.legado.app.ui.dict.rule + +import android.content.Context +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.PopupMenu +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.legado.app.R +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.RecyclerAdapter +import io.legado.app.data.entities.DictRule +import io.legado.app.databinding.ItemReplaceRuleBinding +import io.legado.app.lib.theme.backgroundColor +import io.legado.app.ui.widget.recycler.DragSelectTouchHelper +import io.legado.app.ui.widget.recycler.ItemTouchCallback +import io.legado.app.utils.ColorUtils + + +class DictRuleAdapter(context: Context, var callBack: CallBack) : + RecyclerAdapter(context), + ItemTouchCallback.Callback { + + private val selected = linkedSetOf() + + val selection: List + get() { + return getItems().filter { + selected.contains(it) + } + } + + val diffItemCallBack = object : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: DictRule, newItem: DictRule): Boolean { + return oldItem.name == newItem.name + } + + override fun areContentsTheSame(oldItem: DictRule, newItem: DictRule): Boolean { + if (oldItem.name != newItem.name) { + return false + } + if (oldItem.enabled != newItem.enabled) { + return false + } + return true + } + + override fun getChangePayload(oldItem: DictRule, newItem: DictRule): Any? { + val payload = Bundle() + if (oldItem.name != newItem.name) { + payload.putBoolean("upName", true) + } + if (oldItem.enabled != newItem.enabled) { + payload.putBoolean("enabled", newItem.enabled) + } + if (payload.isEmpty) { + return null + } + return payload + } + } + + fun selectAll() { + getItems().forEach { + selected.add(it) + } + notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null))) + callBack.upCountView() + } + + fun revertSelection() { + getItems().forEach { + if (selected.contains(it)) { + selected.remove(it) + } else { + selected.add(it) + } + } + notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null))) + callBack.upCountView() + } + + override fun getViewBinding(parent: ViewGroup): ItemReplaceRuleBinding { + return ItemReplaceRuleBinding.inflate(inflater, parent, false) + } + + override fun onCurrentListChanged() { + callBack.upCountView() + } + + override fun convert( + holder: ItemViewHolder, + binding: ItemReplaceRuleBinding, + item: DictRule, + payloads: MutableList + ) { + binding.run { + val bundle = payloads.getOrNull(0) as? Bundle + if (bundle == null) { + root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f)) + cbName.text = item.name + swtEnabled.isChecked = item.enabled + cbName.isChecked = selected.contains(item) + } else { + bundle.keySet().map { + when (it) { + "selected" -> cbName.isChecked = selected.contains(item) + "upName" -> cbName.text = item.name + "enabled" -> swtEnabled.isChecked = item.enabled + } + } + } + } + } + + override fun registerListener(holder: ItemViewHolder, binding: ItemReplaceRuleBinding) { + binding.apply { + swtEnabled.setOnCheckedChangeListener { buttonView, isChecked -> + if (buttonView.isPressed) { + getItem(holder.layoutPosition)?.let { + it.enabled = isChecked + callBack.update(it) + } + } + } + ivEdit.setOnClickListener { + getItem(holder.layoutPosition)?.let { + callBack.edit(it) + } + } + cbName.setOnClickListener { + getItem(holder.layoutPosition)?.let { + if (cbName.isChecked) { + selected.add(it) + } else { + selected.remove(it) + } + } + callBack.upCountView() + } + ivMenuMore.setOnClickListener { + showMenu(ivMenuMore, holder.layoutPosition) + } + } + } + + private fun showMenu(view: View, position: Int) { + val item = getItem(position) ?: return + val popupMenu = PopupMenu(context, view) + popupMenu.inflate(R.menu.replace_rule_item) + popupMenu.setOnMenuItemClickListener { menuItem -> + when (menuItem.itemId) { + R.id.menu_top -> callBack.toTop(item) + R.id.menu_bottom -> callBack.toBottom(item) + R.id.menu_del -> callBack.delete(item) + } + true + } + popupMenu.show() + } + + override fun swap(srcPosition: Int, targetPosition: Int): Boolean { + val srcItem = getItem(srcPosition) + val targetItem = getItem(targetPosition) + if (srcItem != null && targetItem != null) { + if (srcItem.sortNumber == targetItem.sortNumber) { + callBack.upOrder() + } else { + val srcOrder = srcItem.sortNumber + srcItem.sortNumber = targetItem.sortNumber + targetItem.sortNumber = srcOrder + movedItems.add(srcItem) + movedItems.add(targetItem) + } + } + swapItem(srcPosition, targetPosition) + return true + } + + private val movedItems = linkedSetOf() + + override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { + if (movedItems.isNotEmpty()) { + callBack.update(*movedItems.toTypedArray()) + movedItems.clear() + } + } + + val dragSelectCallback: DragSelectTouchHelper.Callback = + object : DragSelectTouchHelper.AdvanceCallback(Mode.ToggleAndReverse) { + override fun currentSelectedId(): MutableSet { + return selected + } + + override fun getItemId(position: Int): DictRule { + return getItem(position)!! + } + + override fun updateSelectState(position: Int, isSelected: Boolean): Boolean { + getItem(position)?.let { + if (isSelected) { + selected.add(it) + } else { + selected.remove(it) + } + notifyItemChanged(position, bundleOf(Pair("selected", null))) + callBack.upCountView() + return true + } + return false + } + } + + interface CallBack { + fun update(vararg rule: DictRule) + fun delete(rule: DictRule) + fun edit(rule: DictRule) + fun toTop(rule: DictRule) + fun toBottom(rule: DictRule) + fun upOrder() + fun upCountView() + } +} diff --git a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleViewModel.kt index a99e69091..e8bdbda43 100644 --- a/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/dict/rule/DictRuleViewModel.kt @@ -2,5 +2,22 @@ package io.legado.app.ui.dict.rule import android.app.Application import io.legado.app.base.BaseViewModel +import io.legado.app.data.appDb +import io.legado.app.data.entities.DictRule -class DictRuleViewModel(application: Application) : BaseViewModel(application) \ No newline at end of file +class DictRuleViewModel(application: Application) : BaseViewModel(application) { + + + fun upsert(vararg dictRule: DictRule) { + execute { + appDb.dictRuleDao.upsert(*dictRule) + } + } + + fun delete(vararg dictRule: DictRule) { + execute { + appDb.dictRuleDao.delete(*dictRule) + } + } + +} \ No newline at end of file