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 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_shelf_section_draggable.xml b/app/src/main/res/layout/item_shelf_section_draggable.xml
new file mode 100644
index 000000000..dbf7b22b8
--- /dev/null
+++ b/app/src/main/res/layout/item_shelf_section_draggable.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+