From 38d4274ece36cd4aa517428619246ff8986416dd Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 28 Oct 2022 07:56:29 +0300 Subject: [PATCH] Shelf settings --- app/src/main/AndroidManifest.xml | 3 + .../kotatsu/core/prefs/AppSettings.kt | 20 ++-- .../kotatsu/shelf/ui/ShelfMenuProvider.kt | 4 +- .../kotatsu/shelf/ui/ShelfViewModel.kt | 49 +++++---- .../kotatsu/shelf/ui/config/ShelfConfigAD.kt | 51 --------- .../shelf/ui/config/ShelfConfigAdapter.kt | 42 -------- .../shelf/ui/config/ShelfConfigSheet.kt | 53 --------- .../shelf/ui/config/ShelfConfigViewModel.kt | 74 ------------- .../shelf/ui/config/ShelfSettingsActivity.kt | 101 ++++++++++++++++++ .../shelf/ui/config/ShelfSettingsAdapter.kt | 41 +++++++ .../config/ShelfSettingsAdapterDelegates.kt | 75 +++++++++++++ ...nfigModel.kt => ShelfSettingsItemModel.kt} | 6 +- .../shelf/ui/config/ShelfSettingsListener.kt | 10 ++ .../shelf/ui/config/ShelfSettingsViewModel.kt | 101 ++++++++++++++++++ .../res/layout/activity_shelf_settings.xml | 36 +++++++ .../layout/item_shelf_section_draggable.xml | 36 +++++++ 16 files changed, 450 insertions(+), 252 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAD.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAdapter.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigSheet.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigViewModel.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsActivity.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt rename app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/{ShelfConfigModel.kt => ShelfSettingsItemModel.kt} (91%) create mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsListener.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsViewModel.kt create mode 100644 app/src/main/res/layout/activity_shelf_settings.xml create mode 100644 app/src/main/res/layout/item_shelf_section_draggable.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3faa30c69..1183591b1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -139,6 +139,9 @@ + get() = Collections.unmodifiableSet(remoteSources) - var shelfSections: Set + var shelfSections: List get() { - val raw = prefs.getStringSet(KEY_SHELF_SECTIONS, null) - if (raw == null) { - return EnumSet.allOf(ShelfSection::class.java) + val raw = prefs.getString(KEY_SHELF_SECTIONS, null) + val values = enumValues() + if (raw.isNullOrEmpty()) { + return values.toList() } - return raw.mapTo(EnumSet.noneOf(ShelfSection::class.java)) { ShelfSection.valueOf(it) } + return raw.split('|') + .mapNotNull { values.getOrNull(it.toIntOrNull() ?: -1) } + .distinct() } set(value) { - val raw = value.mapToSet { it.name } - prefs.edit { putStringSet(KEY_SHELF_SECTIONS, raw) } + val raw = value.joinToString("|") { it.ordinal.toString() } + prefs.edit { putString(KEY_SHELF_SECTIONS, raw) } } var listMode: ListMode @@ -352,7 +354,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_READER_TAPS_LTR = "reader_taps_ltr" const val KEY_LOCAL_LIST_ORDER = "local_order" const val KEY_WEBTOON_ZOOM = "webtoon_zoom" - const val KEY_SHELF_SECTIONS = "shelf_sections" + const val KEY_SHELF_SECTIONS = "shelf_sections_2" // About const val KEY_APP_UPDATE = "app_update" diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfMenuProvider.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfMenuProvider.kt index 8b6f5dfeb..d00601406 100644 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfMenuProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfMenuProvider.kt @@ -10,7 +10,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener import org.koitharu.kotatsu.local.ui.ImportDialogFragment -import org.koitharu.kotatsu.shelf.ui.config.ShelfConfigSheet +import org.koitharu.kotatsu.shelf.ui.config.ShelfSettingsActivity import org.koitharu.kotatsu.shelf.ui.config.size.ShelfSizeBottomSheet import org.koitharu.kotatsu.utils.ext.startOfDay import java.util.Date @@ -45,7 +45,7 @@ class ShelfMenuProvider( } R.id.action_categories -> { - ShelfConfigSheet.show(fragmentManager) + context.startActivity(ShelfSettingsActivity.newIntent(context)) true } diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt index 53ca50f88..a56d848ea 100644 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/ShelfViewModel.kt @@ -135,22 +135,18 @@ class ShelfViewModel @Inject constructor( private suspend fun mapList( content: ShelfContent, - sections: Set, + sections: List, isNetworkAvailable: Boolean, ): List { val result = ArrayList(content.favourites.keys.size + 3) if (isNetworkAvailable) { - if (content.history.isNotEmpty() && ShelfSection.HISTORY in sections) { - mapHistory(result, content.history) - } - if (content.local.isNotEmpty() && ShelfSection.LOCAL in sections) { - mapLocal(result, content.local) - } - if (content.updated.isNotEmpty() && ShelfSection.UPDATED in sections) { - mapUpdated(result, content.updated) - } - if (content.favourites.isNotEmpty() && ShelfSection.FAVORITES in sections) { - mapFavourites(result, content.favourites) + for (section in sections) { + when (section) { + ShelfSection.HISTORY -> mapHistory(result, content.history) + ShelfSection.LOCAL -> mapLocal(result, content.local) + ShelfSection.UPDATED -> mapUpdated(result, content.updated) + ShelfSection.FAVORITES -> mapFavourites(result, content.favourites) + } } } else { result += EmptyHint( @@ -159,12 +155,17 @@ class ShelfViewModel @Inject constructor( textSecondary = R.string.network_unavailable_hint, actionStringRes = R.string.manage, ) - val offlineHistory = content.history.filter { it.manga.source == MangaSource.LOCAL } - if (offlineHistory.isNotEmpty() && ShelfSection.HISTORY in sections) { - mapHistory(result, offlineHistory) - } - if (content.local.isNotEmpty() && ShelfSection.LOCAL in sections) { - mapLocal(result, content.local) + for (section in sections) { + when (section) { + ShelfSection.HISTORY -> mapHistory( + result, + content.history.filter { it.manga.source == MangaSource.LOCAL }, + ) + + ShelfSection.LOCAL -> mapLocal(result, content.local) + ShelfSection.UPDATED -> Unit + ShelfSection.FAVORITES -> Unit + } } } if (result.isEmpty()) { @@ -187,6 +188,9 @@ class ShelfViewModel @Inject constructor( destination: MutableList, list: List, ) { + if (list.isEmpty()) { + return + } val showPercent = settings.isReadingIndicatorsEnabled destination += ShelfSectionModel.History( items = list.map { (manga, history) -> @@ -202,6 +206,9 @@ class ShelfViewModel @Inject constructor( destination: MutableList, updated: Map, ) { + if (updated.isEmpty()) { + return + } val showPercent = settings.isReadingIndicatorsEnabled destination += ShelfSectionModel.Updated( items = updated.map { (manga, counter) -> @@ -216,6 +223,9 @@ class ShelfViewModel @Inject constructor( destination: MutableList, local: List, ) { + if (local.isEmpty()) { + return + } destination += ShelfSectionModel.Local( items = local.toUi(ListMode.GRID, this), showAllButtonText = R.string.show_all, @@ -226,6 +236,9 @@ class ShelfViewModel @Inject constructor( destination: MutableList, favourites: Map>, ) { + if (favourites.isEmpty()) { + return + } for ((category, list) in favourites) { if (list.isNotEmpty()) { destination += ShelfSectionModel.Favourites( diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAD.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAD.kt deleted file mode 100644 index fae0f67f0..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAD.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.koitharu.kotatsu.shelf.ui.config - -import androidx.core.view.updatePaddingRelative -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter -import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding -import org.koitharu.kotatsu.shelf.domain.ShelfSection - -fun shelfSectionAD( - listener: OnListItemClickListener, -) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) }, -) { - - val eventListener = AdapterDelegateClickListenerAdapter(this, listener) - itemView.setOnClickListener(eventListener) - - bind { - binding.root.setText(item.section.titleResId) - binding.root.isChecked = item.isChecked - } -} - -fun shelfCategoryAD( - listener: OnListItemClickListener, -) = - adapterDelegateViewBinding( - { layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) }, - ) { - val eventListener = AdapterDelegateClickListenerAdapter(this, listener) - itemView.setOnClickListener(eventListener) - binding.root.updatePaddingRelative( - start = binding.root.paddingStart * 2, - end = binding.root.paddingStart, - ) - - bind { - binding.root.text = item.title - binding.root.isChecked = item.isChecked - } - } - -private val ShelfSection.titleResId: Int - get() = when (this) { - ShelfSection.HISTORY -> R.string.history - ShelfSection.LOCAL -> R.string.local_storage - ShelfSection.UPDATED -> R.string.updated - ShelfSection.FAVORITES -> R.string.favourites - } diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAdapter.kt deleted file mode 100644 index 7b8417830..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigAdapter.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.koitharu.kotatsu.shelf.ui.config - -import androidx.recyclerview.widget.DiffUtil -import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter -import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener - -class ShelfConfigAdapter( - listener: OnListItemClickListener, -) : AsyncListDifferDelegationAdapter(DiffCallback()) { - - init { - delegatesManager.addDelegate(shelfCategoryAD(listener)) - .addDelegate(shelfSectionAD(listener)) - } - - class DiffCallback : DiffUtil.ItemCallback() { - - override fun areItemsTheSame(oldItem: ShelfConfigModel, newItem: ShelfConfigModel): Boolean { - return when { - oldItem is ShelfConfigModel.Section && newItem is ShelfConfigModel.Section -> { - oldItem.section == newItem.section - } - - oldItem is ShelfConfigModel.FavouriteCategory && newItem is ShelfConfigModel.FavouriteCategory -> { - oldItem.id == newItem.id - } - - else -> false - } - } - - override fun areContentsTheSame(oldItem: ShelfConfigModel, newItem: ShelfConfigModel): Boolean { - return oldItem == newItem - } - - override fun getChangePayload(oldItem: ShelfConfigModel, newItem: ShelfConfigModel): Any? { - return if (oldItem.isChecked == newItem.isChecked) { - super.getChangePayload(oldItem, newItem) - } else Unit - } - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigSheet.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigSheet.kt deleted file mode 100644 index 24fa59482..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigSheet.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.koitharu.kotatsu.shelf.ui.config - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.viewModels -import dagger.hilt.android.AndroidEntryPoint -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.BaseBottomSheet -import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.databinding.SheetBaseBinding - -@AndroidEntryPoint -class ShelfConfigSheet : - BaseBottomSheet(), - OnListItemClickListener, - View.OnClickListener { - - private val viewModel by viewModels() - - override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetBaseBinding { - return SheetBaseBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.headerBar.setTitle(R.string.settings) - binding.buttonDone.isVisible = true - binding.buttonDone.setOnClickListener(this) - val adapter = ShelfConfigAdapter(this) - binding.recyclerView.adapter = adapter - - viewModel.content.observe(viewLifecycleOwner) { adapter.items = it } - } - - override fun onItemClick(item: ShelfConfigModel, view: View) { - viewModel.toggleItem(item) - } - - override fun onClick(v: View?) { - dismiss() - } - - companion object { - - private const val TAG = "ShelfCategoriesConfigSheet" - - fun show(fm: FragmentManager) = ShelfConfigSheet().show(fm, TAG) - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigViewModel.kt deleted file mode 100644 index bd80a7976..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigViewModel.kt +++ /dev/null @@ -1,74 +0,0 @@ -package org.koitharu.kotatsu.shelf.ui.config - -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.combine -import org.koitharu.kotatsu.base.ui.BaseViewModel -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.core.prefs.observeAsFlow -import org.koitharu.kotatsu.favourites.domain.FavouritesRepository -import org.koitharu.kotatsu.shelf.domain.ShelfSection -import org.koitharu.kotatsu.utils.asFlowLiveData -import javax.inject.Inject - -@HiltViewModel -class ShelfConfigViewModel @Inject constructor( - private val favouritesRepository: FavouritesRepository, - private val settings: AppSettings, -) : BaseViewModel() { - - val content = combine( - settings.observeAsFlow(AppSettings.KEY_SHELF_SECTIONS) { shelfSections }, - favouritesRepository.observeCategories(), - ) { sections, categories -> - buildList(sections, categories) - }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) - - private var updateJob: Job? = null - - fun toggleItem(item: ShelfConfigModel) { - val prevJob = updateJob - updateJob = launchJob(Dispatchers.Default) { - prevJob?.join() - when (item) { - is ShelfConfigModel.FavouriteCategory -> { - favouritesRepository.updateCategory(item.id, !item.isChecked) - } - - is ShelfConfigModel.Section -> { - val sections = settings.shelfSections - settings.shelfSections = if (item.isChecked) { - if (sections.size > 1) { - sections - item.section - } else { - return@launchJob - } - } else { - sections + item.section - } - } - } - } - } - - private fun buildList(sections: Set, categories: List): List { - val result = ArrayList() - for (section in ShelfSection.values()) { - val isEnabled = section in sections - result.add(ShelfConfigModel.Section(section, isEnabled)) - if (section == ShelfSection.FAVORITES && isEnabled) { - categories.mapTo(result) { - ShelfConfigModel.FavouriteCategory( - id = it.id, - title = it.title, - isChecked = it.isVisibleInLibrary, - ) - } - } - } - return result - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsActivity.kt new file mode 100644 index 000000000..ef28b06a5 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsActivity.kt @@ -0,0 +1,101 @@ +package org.koitharu.kotatsu.shelf.ui.config + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.activity.viewModels +import androidx.core.graphics.Insets +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.databinding.ActivityShelfSettingsBinding + +@AndroidEntryPoint +class ShelfSettingsActivity : + BaseActivity(), + View.OnClickListener, ShelfSettingsListener { + + private val viewModel by viewModels() + private lateinit var reorderHelper: ItemTouchHelper + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityShelfSettingsBinding.inflate(layoutInflater)) + supportActionBar?.run { + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(com.google.android.material.R.drawable.abc_ic_clear_material) + } + binding.buttonDone.setOnClickListener(this) + val settingsAdapter = ShelfSettingsAdapter(this) + with(binding.recyclerView) { + setHasFixedSize(true) + adapter = settingsAdapter + reorderHelper = ItemTouchHelper(SectionsReorderCallback()).also { + it.attachToRecyclerView(this) + } + } + + + viewModel.content.observe(this) { settingsAdapter.items = it } + } + + override fun onItemCheckedChanged(item: ShelfSettingsItemModel, isChecked: Boolean) { + viewModel.setItemChecked(item, isChecked) + } + + override fun onDragHandleTouch(holder: RecyclerView.ViewHolder) { + reorderHelper.startDrag(holder) + } + + override fun onClick(v: View?) { + finishAfterTransition() + } + + override fun onWindowInsetsChanged(insets: Insets) { + binding.root.updatePadding( + left = insets.left, + right = insets.right, + ) + binding.recyclerView.updatePadding( + bottom = insets.bottom, + ) + binding.toolbar.updateLayoutParams { + topMargin = insets.top + } + } + + private inner class SectionsReorderCallback : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.DOWN or ItemTouchHelper.UP, + 0, + ) { + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder, + ): Boolean = viewHolder.itemViewType == target.itemViewType && viewModel.reorderSections( + viewHolder.bindingAdapterPosition, + target.bindingAdapterPosition, + ) + + override fun canDropOver( + recyclerView: RecyclerView, + current: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder, + ): Boolean = current.itemViewType == target.itemViewType + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit + + override fun isLongPressDragEnabled() = false + } + + companion object { + + fun newIntent(context: Context) = Intent(context, ShelfSettingsActivity::class.java) + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt new file mode 100644 index 000000000..19aaa81cf --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapter.kt @@ -0,0 +1,41 @@ +package org.koitharu.kotatsu.shelf.ui.config + +import androidx.recyclerview.widget.DiffUtil +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter + +class ShelfSettingsAdapter( + listener: ShelfSettingsListener, +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(shelfCategoryAD(listener)) + .addDelegate(shelfSectionAD(listener)) + } + + class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: ShelfSettingsItemModel, newItem: ShelfSettingsItemModel): Boolean { + return when { + oldItem is ShelfSettingsItemModel.Section && newItem is ShelfSettingsItemModel.Section -> { + oldItem.section == newItem.section + } + + oldItem is ShelfSettingsItemModel.FavouriteCategory && newItem is ShelfSettingsItemModel.FavouriteCategory -> { + oldItem.id == newItem.id + } + + else -> false + } + } + + override fun areContentsTheSame(oldItem: ShelfSettingsItemModel, newItem: ShelfSettingsItemModel): Boolean { + return oldItem == newItem + } + + override fun getChangePayload(oldItem: ShelfSettingsItemModel, newItem: ShelfSettingsItemModel): Any? { + return if (oldItem.isChecked == newItem.isChecked) { + super.getChangePayload(oldItem, newItem) + } else Unit + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt new file mode 100644 index 000000000..973391190 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsAdapterDelegates.kt @@ -0,0 +1,75 @@ +package org.koitharu.kotatsu.shelf.ui.config + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.View +import android.widget.CompoundButton +import androidx.core.view.updatePaddingRelative +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding +import org.koitharu.kotatsu.databinding.ItemShelfSectionDraggableBinding +import org.koitharu.kotatsu.shelf.domain.ShelfSection + +@SuppressLint("ClickableViewAccessibility") +fun shelfSectionAD( + listener: ShelfSettingsListener, +) = + adapterDelegateViewBinding( + { layoutInflater, parent -> ItemShelfSectionDraggableBinding.inflate(layoutInflater, parent, false) }, + ) { + + val eventListener = object : + View.OnTouchListener, + CompoundButton.OnCheckedChangeListener { + + override fun onTouch(v: View?, event: MotionEvent): Boolean { + return if (event.actionMasked == MotionEvent.ACTION_DOWN) { + listener.onDragHandleTouch(this@adapterDelegateViewBinding) + true + } else { + false + } + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + listener.onItemCheckedChanged(item, isChecked) + } + } + + binding.switchToggle.setOnCheckedChangeListener(eventListener) + binding.imageViewHandle.setOnTouchListener(eventListener) + + bind { + binding.textViewTitle.setText(item.section.titleResId) + binding.switchToggle.isChecked = item.isChecked + } + } + +fun shelfCategoryAD( + listener: ShelfSettingsListener, +) = + adapterDelegateViewBinding( + { layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) }, + ) { + itemView.setOnClickListener { + listener.onItemCheckedChanged(item, !item.isChecked) + } + binding.root.updatePaddingRelative( + start = binding.root.paddingStart * 2, + end = binding.root.paddingStart, + ) + + bind { + binding.root.text = item.title + binding.root.isChecked = item.isChecked + } + } + +private val ShelfSection.titleResId: Int + get() = when (this) { + ShelfSection.HISTORY -> R.string.history + ShelfSection.LOCAL -> R.string.local_storage + ShelfSection.UPDATED -> R.string.updated + ShelfSection.FAVORITES -> R.string.favourites + } diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigModel.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt similarity index 91% rename from app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigModel.kt rename to app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt index 996840a06..e75f329de 100644 --- a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfConfigModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsItemModel.kt @@ -3,14 +3,14 @@ package org.koitharu.kotatsu.shelf.ui.config import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.shelf.domain.ShelfSection -sealed interface ShelfConfigModel : ListModel { +sealed interface ShelfSettingsItemModel : ListModel { val isChecked: Boolean class Section( val section: ShelfSection, override val isChecked: Boolean, - ) : ShelfConfigModel { + ) : ShelfSettingsItemModel { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -35,7 +35,7 @@ sealed interface ShelfConfigModel : ListModel { val id: Long, val title: String, override val isChecked: Boolean, - ) : ShelfConfigModel { + ) : ShelfSettingsItemModel { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsListener.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsListener.kt new file mode 100644 index 000000000..b8fa749a1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsListener.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.shelf.ui.config + +import androidx.recyclerview.widget.RecyclerView + +interface ShelfSettingsListener { + + fun onItemCheckedChanged(item: ShelfSettingsItemModel, isChecked: Boolean) + + fun onDragHandleTouch(holder: RecyclerView.ViewHolder) +} diff --git a/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsViewModel.kt new file mode 100644 index 000000000..15323d9b8 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shelf/ui/config/ShelfSettingsViewModel.kt @@ -0,0 +1,101 @@ +package org.koitharu.kotatsu.shelf.ui.config + +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.combine +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.model.FavouriteCategory +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.observeAsFlow +import org.koitharu.kotatsu.favourites.domain.FavouritesRepository +import org.koitharu.kotatsu.shelf.domain.ShelfSection +import org.koitharu.kotatsu.utils.asFlowLiveData +import org.koitharu.kotatsu.utils.ext.move +import javax.inject.Inject + +@HiltViewModel +class ShelfSettingsViewModel @Inject constructor( + private val favouritesRepository: FavouritesRepository, + private val settings: AppSettings, +) : BaseViewModel() { + + val content = combine( + settings.observeAsFlow(AppSettings.KEY_SHELF_SECTIONS) { shelfSections }, + favouritesRepository.observeCategories(), + ) { sections, categories -> + buildList(sections, categories) + }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) + + private var updateJob: Job? = null + + fun setItemChecked(item: ShelfSettingsItemModel, isChecked: Boolean) { + val prevJob = updateJob + updateJob = launchJob(Dispatchers.Default) { + prevJob?.join() + when (item) { + is ShelfSettingsItemModel.FavouriteCategory -> { + favouritesRepository.updateCategory(item.id, isChecked) + } + + is ShelfSettingsItemModel.Section -> { + val sections = settings.shelfSections + settings.shelfSections = if (isChecked) { + sections + item.section + } else { + if (sections.size > 1) { + sections - item.section + } else { + return@launchJob + } + } + } + } + } + } + + fun reorderSections(oldPos: Int, newPos: Int): Boolean { + val snapshot = content.value?.toMutableList() ?: return false + snapshot.move(oldPos, newPos) + settings.shelfSections = snapshot.sections() + return true + } + + private fun buildList( + sections: List, + categories: List + ): List { + val result = ArrayList() + val sectionsList = ShelfSection.values().toMutableList() + for (section in sections) { + sectionsList.remove(section) + result.addSection(section, true, categories) + } + for (section in sectionsList) { + result.addSection(section, false, categories) + } + return result + } + + private fun MutableList.addSection( + section: ShelfSection, + isEnabled: Boolean, + favouriteCategories: List, + ) { + add(ShelfSettingsItemModel.Section(section, isEnabled)) + if (isEnabled && section == ShelfSection.FAVORITES) { + favouriteCategories.mapTo(this) { + ShelfSettingsItemModel.FavouriteCategory( + id = it.id, + title = it.title, + isChecked = it.isVisibleInLibrary, + ) + } + } + } + + private fun List.sections(): List { + return mapNotNull { (it as? ShelfSettingsItemModel.Section)?.takeIf { x -> x.isChecked }?.section } + } +} diff --git a/app/src/main/res/layout/activity_shelf_settings.xml b/app/src/main/res/layout/activity_shelf_settings.xml new file mode 100644 index 000000000..a4c9781a8 --- /dev/null +++ b/app/src/main/res/layout/activity_shelf_settings.xml @@ -0,0 +1,36 @@ + + + + + +