From 17519db44e8d8697a23ecb73b0f43d9065bba8d7 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 6 Jan 2022 17:26:16 +0200 Subject: [PATCH] Refactor sources settings list --- .../java/org/koitharu/kotatsu/KotatsuApp.kt | 1 - .../base/domain/MangaProviderFactory.kt | 2 +- .../kotatsu/base/ui/list/BaseViewHolder.kt | 28 ------ .../ui/list/decor/SectionItemDecoration.kt | 96 ------------------- .../kotatsu/settings/SettingsActivity.kt | 1 - .../kotatsu/settings/SettingsModule.kt | 2 + .../settings/onboard/OnboardViewModel.kt | 7 +- .../settings/sources/SourceViewHolder.kt | 18 ---- .../settings/sources/SourcesAdapter.kt | 69 ------------- .../sources/SourcesReorderCallback.kt | 24 ----- .../sources/SourcesSettingsFragment.kt | 81 ++++++++++------ .../sources/SourcesSettingsViewModel.kt | 59 ++++++++++++ .../sources/adapter/SourceConfigAdapter.kt | 11 +++ .../adapter/SourceConfigAdapterDelegates.kt | 61 ++++++++++++ .../adapter/SourceConfigDiffCallback.kt | 33 +++++++ .../sources/adapter/SourceConfigItem.kt | 17 ++++ .../sources/adapter/SourceConfigListener.kt | 14 +++ .../koitharu/kotatsu/utils/ext/LocaleExt.kt | 5 +- .../main/res/drawable/ic_expand_collapse.xml | 5 + app/src/main/res/drawable/ic_expand_less.xml | 5 + app/src/main/res/drawable/ic_expand_more.xml | 11 +++ app/src/main/res/layout/item_expandable.xml | 15 +++ 22 files changed, 293 insertions(+), 272 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigItem.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigListener.kt create mode 100644 app/src/main/res/drawable/ic_expand_collapse.xml create mode 100644 app/src/main/res/drawable/ic_expand_less.xml create mode 100644 app/src/main/res/drawable/ic_expand_more.xml create mode 100644 app/src/main/res/layout/item_expandable.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index 9c555ae18..b86d1ab0b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -92,7 +92,6 @@ class KotatsuApp : Application() { .detectFragmentReuse() .detectWrongFragmentContainer() .detectRetainInstanceUsage() - .detectTargetFragmentUsage() .detectSetUserVisibleHint() .build() } diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt index 12771e5ea..0223031d9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaProviderFactory.kt @@ -8,7 +8,6 @@ object MangaProviderFactory { fun getSources(settings: AppSettings, includeHidden: Boolean): List { val list = MangaSource.values().toList() - MangaSource.LOCAL val order = settings.sourcesOrder - val hidden = settings.hiddenSources val sorted = list.sortedBy { x -> val e = order.indexOf(x.ordinal) if (e == -1) order.size + x.ordinal else e @@ -16,6 +15,7 @@ object MangaProviderFactory { return if (includeHidden) { sorted } else { + val hidden = settings.hiddenSources sorted.filterNot { x -> x.name in hidden } diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt deleted file mode 100644 index b878524ca..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseViewHolder.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.koitharu.kotatsu.base.ui.list - -import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding -import org.koin.core.component.KoinComponent - -@Deprecated("") -abstract class BaseViewHolder protected constructor(val binding: B) : - RecyclerView.ViewHolder(binding.root), KoinComponent { - - var boundData: T? = null - private set - - val context get() = itemView.context!! - - fun bind(data: T, extra: E) { - boundData = data - onBind(data, extra) - } - - fun requireData(): T { - return boundData ?: throw IllegalStateException("Calling requireData() before bind()") - } - - open fun onRecycled() = Unit - - abstract fun onBind(data: T, extra: E) -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt deleted file mode 100644 index d8181e5c7..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SectionItemDecoration.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.koitharu.kotatsu.base.ui.list.decor - -import android.graphics.Canvas -import android.graphics.Rect -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.core.view.children -import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.utils.ext.inflate -import kotlin.math.max - -/** - * https://github.com/paetztm/recycler_view_headers - */ -class SectionItemDecoration( - private val isSticky: Boolean, - private val callback: Callback -) : RecyclerView.ItemDecoration() { - - private var headerView: TextView? = null - private var headerOffset: Int = 0 - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - if (headerOffset == 0) { - headerOffset = parent.resources.getDimensionPixelSize(R.dimen.header_height) - } - val pos = parent.getChildAdapterPosition(view) - outRect.set(0, if (callback.isSection(pos)) headerOffset else 0, 0, 0) - } - - override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDrawOver(c, parent, state) - val textView = headerView ?: parent.inflate(R.layout.item_filter_header).also { - headerView = it - } - fixLayoutSize(textView, parent) - - for (child in parent.children) { - val pos = parent.getChildAdapterPosition(child) - if (callback.isSection(pos)) { - textView.text = callback.getSectionTitle(pos) ?: continue - c.save() - if (isSticky) { - c.translate( - 0f, - max(0f, (child.top - textView.height).toFloat()) - ) - } else { - c.translate( - 0f, - (child.top - textView.height).toFloat() - ) - } - textView.draw(c) - c.restore() - } - } - } - - /** - * Measures the header view to make sure its size is greater than 0 and will be drawn - * https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations - */ - private fun fixLayoutSize(view: View, parent: ViewGroup) { - val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY) - val heightSpec = - View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED) - - val childWidth = ViewGroup.getChildMeasureSpec( - widthSpec, - parent.paddingLeft + parent.paddingRight, - view.layoutParams.width - ) - val childHeight = ViewGroup.getChildMeasureSpec( - heightSpec, - parent.paddingTop + parent.paddingBottom, - view.layoutParams.height - ) - view.measure(childWidth, childHeight) - view.layout(0, 0, view.measuredWidth, view.measuredHeight) - } - - interface Callback { - - fun isSection(position: Int): Boolean - - fun getSectionTitle(position: Int): CharSequence? - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt index 5382630a7..c7cdef0d4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt @@ -30,7 +30,6 @@ class SettingsActivity : BaseActivity(), } } - @Suppress("DEPRECATION") override fun onPreferenceStartFragment( caller: PreferenceFragmentCompat, pref: Preference diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt index 24fe705d3..6d9f543c4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt @@ -11,6 +11,7 @@ import org.koitharu.kotatsu.settings.backup.BackupViewModel import org.koitharu.kotatsu.settings.backup.RestoreViewModel import org.koitharu.kotatsu.settings.onboard.OnboardViewModel import org.koitharu.kotatsu.settings.protect.ProtectSetupViewModel +import org.koitharu.kotatsu.settings.sources.SourcesSettingsViewModel val settingsModule get() = module { @@ -25,4 +26,5 @@ val settingsModule } viewModel { ProtectSetupViewModel(get()) } viewModel { OnboardViewModel(get()) } + viewModel { SourcesSettingsViewModel(get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt index b0f2a4ee4..44ddad5bc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt @@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.settings.onboard.model.SourceLocale import org.koitharu.kotatsu.utils.ext.map import org.koitharu.kotatsu.utils.ext.mapToSet +import org.koitharu.kotatsu.utils.ext.toTitleCase import java.util.* class OnboardViewModel( @@ -27,9 +28,9 @@ class OnboardViewModel( init { if (settings.isSourcesSelected) { - selectedLocales.removeAll(settings.hiddenSources.map { x -> MangaSource.valueOf(x).locale }) + selectedLocales.removeAll(settings.hiddenSources.mapToSet { x -> MangaSource.valueOf(x).locale }) } else { - val deviceLocales = LocaleListCompat.getDefault().map { x -> + val deviceLocales = LocaleListCompat.getDefault().mapToSet { x -> x.language } selectedLocales.retainAll(deviceLocales) @@ -64,7 +65,7 @@ class OnboardViewModel( } else null SourceLocale( key = key, - title = locale?.getDisplayLanguage(locale)?.capitalize(locale), + title = locale?.getDisplayLanguage(locale)?.toTitleCase(locale), isChecked = key in selectedLocales ) }.sortedWith(SourceLocaleComparator()) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt deleted file mode 100644 index 1660f9bd7..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourceViewHolder.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.koitharu.kotatsu.settings.sources - -import android.view.LayoutInflater -import android.view.ViewGroup -import org.koitharu.kotatsu.base.ui.list.BaseViewHolder -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding - -class SourceViewHolder(parent: ViewGroup) : - BaseViewHolder( - ItemSourceConfigBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) { - - override fun onBind(data: MangaSource, extra: Boolean) { - binding.textViewTitle.text = data.title - binding.switchToggle.isChecked = extra - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt deleted file mode 100644 index 739f35032..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesAdapter.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.koitharu.kotatsu.settings.sources - -import android.annotation.SuppressLint -import android.view.MotionEvent -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.base.domain.MangaProviderFactory -import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.utils.ext.mapToSet - -class SourcesAdapter( - private val settings: AppSettings, - private val onItemClickListener: OnListItemClickListener, -) : RecyclerView.Adapter() { - - private val dataSet = - MangaProviderFactory.getSources(settings, includeHidden = true).toMutableList() - private val hiddenItems = settings.hiddenSources.mapNotNull { - runCatching { - MangaSource.valueOf(it) - }.getOrNull() - }.toMutableSet() - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ) = SourceViewHolder(parent).also(::onViewHolderCreated) - - override fun getItemCount() = dataSet.size - - override fun onBindViewHolder(holder: SourceViewHolder, position: Int) { - val item = dataSet[position] - holder.bind(item, !hiddenItems.contains(item)) - } - - @SuppressLint("ClickableViewAccessibility") - private fun onViewHolderCreated(holder: SourceViewHolder) { - holder.binding.switchToggle.setOnCheckedChangeListener { _, it -> - if (it) { - hiddenItems.remove(holder.requireData()) - } else { - hiddenItems.add(holder.requireData()) - } - settings.hiddenSources = hiddenItems.mapToSet { x -> x.name } - } - holder.binding.imageViewConfig.setOnClickListener { v -> - onItemClickListener.onItemClick(holder.requireData(), v) - } - holder.binding.imageViewHandle.setOnTouchListener { v, event -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - onItemClickListener.onItemLongClick( - holder.requireData(), - holder.itemView - ) - } else { - false - } - } - } - - fun moveItem(oldPos: Int, newPos: Int) { - val item = dataSet.removeAt(oldPos) - dataSet.add(newPos, item) - notifyItemMoved(oldPos, newPos) - settings.sourcesOrder = dataSet.map { it.ordinal } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt deleted file mode 100644 index 066a43a6f..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesReorderCallback.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.koitharu.kotatsu.settings.sources - -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.RecyclerView - -class SourcesReorderCallback : - ItemTouchHelper.SimpleCallback(ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0) { - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean { - val adapter = recyclerView.adapter as? SourcesAdapter ?: return false - val oldPos = viewHolder.bindingAdapterPosition - val newPos = target.bindingAdapterPosition - adapter.moveItem(oldPos, newPos) - return true - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit - - override fun isLongPressDragEnabled() = false -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt index ac2d96c79..df04d68f8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt @@ -1,25 +1,28 @@ package org.koitharu.kotatsu.settings.sources import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.core.graphics.Insets import androidx.core.view.updatePadding import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import org.koin.android.ext.android.get +import org.koin.androidx.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment -import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding import org.koitharu.kotatsu.settings.SettingsActivity -import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment +import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter +import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigItem +import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener class SourcesSettingsFragment : BaseFragment(), - OnListItemClickListener { + SourceConfigListener { private lateinit var reorderHelper: ItemTouchHelper + private val viewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,11 +42,16 @@ class SourcesSettingsFragment : BaseFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val sourcesAdapter = SourceConfigAdapter(this) with(binding.recyclerView) { + setHasFixedSize(true) addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL)) - adapter = SourcesAdapter(get(), this@SourcesSettingsFragment) + adapter = sourcesAdapter reorderHelper.attachToRecyclerView(this) } + viewModel.items.observe(viewLifecycleOwner) { + sourcesAdapter.items = it + } } override fun onDestroyView() { @@ -51,22 +59,6 @@ class SourcesSettingsFragment : BaseFragment(), super.onDestroyView() } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - // TODO handle changes in dialog - // inflater.inflate(R.menu.opt_sources, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when(item.itemId) { - R.id.action_languages -> { - OnboardDialogFragment.show(parentFragmentManager) - true - } - else -> super.onOptionsItemSelected(item) - } - } - override fun onWindowInsetsChanged(insets: Insets) { binding.recyclerView.updatePadding( bottom = insets.bottom, @@ -75,14 +67,43 @@ class SourcesSettingsFragment : BaseFragment(), ) } - override fun onItemClick(item: MangaSource, view: View) { - (activity as? SettingsActivity)?.openMangaSourceSettings(item) + override fun onItemSettingsClick(item: SourceConfigItem.SourceItem) { + (activity as? SettingsActivity)?.openMangaSourceSettings(item.source) } - override fun onItemLongClick(item: MangaSource, view: View): Boolean { - reorderHelper.startDrag( - binding.recyclerView.findContainingViewHolder(view) ?: return false - ) - return true + override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) { + viewModel.setEnabled(item.source, isEnabled) + } + + override fun onDragHandleTouch(holder: RecyclerView.ViewHolder) { + reorderHelper.startDrag(holder) + } + + override fun onHeaderClick(header: SourceConfigItem.LocaleHeader) { + viewModel.expandOrCollapse(header.localeId) + } + + private inner class SourcesReorderCallback : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.DOWN or ItemTouchHelper.UP, + 0, + ) { + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + if (viewHolder.itemViewType != target.itemViewType) { + return false + } + val oldPos = viewHolder.bindingAdapterPosition + val newPos = target.bindingAdapterPosition + viewModel.reorderSources(oldPos, newPos) + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit + + override fun isLongPressDragEnabled() = false } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt new file mode 100644 index 000000000..b9eb5af40 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt @@ -0,0 +1,59 @@ +package org.koitharu.kotatsu.settings.sources + +import androidx.lifecycle.MutableLiveData +import org.koitharu.kotatsu.base.domain.MangaProviderFactory +import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigItem +import java.util.* + +class SourcesSettingsViewModel( + private val settings: AppSettings, +) : BaseViewModel() { + + val items = MutableLiveData>(emptyList()) + private val expandedGroups = HashSet() + + init { + buildList() + } + + fun reorderSources(oldPos: Int, newPos: Int) { + val snapshot = items.value?.toMutableList() ?: return + Collections.swap(snapshot, oldPos, newPos) + settings.sourcesOrder = snapshot.mapNotNull { + (it as? SourceConfigItem.SourceItem)?.source?.ordinal + } + buildList() + } + + fun setEnabled(source: MangaSource, isEnabled: Boolean) { + settings.hiddenSources = if (isEnabled) { + settings.hiddenSources - source.name + } else { + settings.hiddenSources + source.name + } + buildList() + } + + fun expandOrCollapse(headerId: String?) { + if (headerId in expandedGroups) { + expandedGroups.remove(headerId) + } else { + expandedGroups.add(headerId) + } + buildList() + } + + private fun buildList() { + val sources = MangaProviderFactory.getSources(settings, includeHidden = true) + val hiddenSources = settings.hiddenSources + items.value = sources.map { + SourceConfigItem.SourceItem( + source = it, + isEnabled = it.name !in hiddenSources, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt new file mode 100644 index 000000000..7577c89f0 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.settings.sources.adapter + +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter + +class SourceConfigAdapter( + listener: SourceConfigListener, +) : AsyncListDifferDelegationAdapter( + SourceConfigDiffCallback(), + sourceConfigHeaderDelegate(listener), + sourceConfigItemDelegate(listener), +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt new file mode 100644 index 000000000..16f143a69 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -0,0 +1,61 @@ +package org.koitharu.kotatsu.settings.sources.adapter + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.View +import android.widget.CompoundButton +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.databinding.ItemExpandableBinding +import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding + +fun sourceConfigHeaderDelegate( + listener: SourceConfigListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemExpandableBinding.inflate(layoutInflater, parent, false) } +) { + + binding.root.setOnClickListener { + listener.onHeaderClick(item) + } + + bind { + binding.root.text = item.title ?: getString(R.string.other) + binding.root.isChecked = item.isExpanded + } +} + +@SuppressLint("ClickableViewAccessibility") +fun sourceConfigItemDelegate( + listener: SourceConfigListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) } +) { + + val eventListener = object : View.OnClickListener, View.OnTouchListener, + CompoundButton.OnCheckedChangeListener { + override fun onClick(v: View?) = listener.onItemSettingsClick(item) + + 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.onItemEnabledChanged(item, isChecked) + } + } + + binding.imageViewConfig.setOnClickListener(eventListener) + binding.switchToggle.setOnCheckedChangeListener(eventListener) + binding.imageViewHandle.setOnTouchListener(eventListener) + + bind { + binding.textViewTitle.text = item.source.title + binding.switchToggle.isChecked = item.isEnabled + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt new file mode 100644 index 000000000..211ae2395 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.settings.sources.adapter + +import androidx.recyclerview.widget.DiffUtil + +class SourceConfigDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: SourceConfigItem, newItem: SourceConfigItem): Boolean { + return when { + oldItem.javaClass != newItem.javaClass -> false + oldItem is SourceConfigItem.LocaleHeader && newItem is SourceConfigItem.LocaleHeader -> { + oldItem.localeId == newItem.localeId + } + oldItem is SourceConfigItem.SourceItem && newItem is SourceConfigItem.SourceItem -> { + oldItem.source == newItem.source + } + else -> false + } + } + + override fun areContentsTheSame(oldItem: SourceConfigItem, newItem: SourceConfigItem): Boolean { + return when { + oldItem is SourceConfigItem.LocaleHeader && newItem is SourceConfigItem.LocaleHeader -> { + oldItem.title == newItem.title && oldItem.isExpanded == newItem.isExpanded + } + oldItem is SourceConfigItem.SourceItem && newItem is SourceConfigItem.SourceItem -> { + oldItem.isEnabled == newItem.isEnabled + } + else -> false + } + } + + override fun getChangePayload(oldItem: SourceConfigItem, newItem: SourceConfigItem) = Unit +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigItem.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigItem.kt new file mode 100644 index 000000000..b565add2f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigItem.kt @@ -0,0 +1,17 @@ +package org.koitharu.kotatsu.settings.sources.adapter + +import org.koitharu.kotatsu.core.model.MangaSource + +sealed interface SourceConfigItem { + + data class LocaleHeader( + val localeId: String?, + val title: String?, + val isExpanded: Boolean, + ) : SourceConfigItem + + data class SourceItem( + val source: MangaSource, + val isEnabled: Boolean, + ) : SourceConfigItem +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigListener.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigListener.kt new file mode 100644 index 000000000..b34c0e759 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigListener.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.settings.sources.adapter + +import androidx.recyclerview.widget.RecyclerView + +interface SourceConfigListener { + + fun onItemSettingsClick(item: SourceConfigItem.SourceItem) + + fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) + + fun onDragHandleTouch(holder: RecyclerView.ViewHolder) + + fun onHeaderClick(header: SourceConfigItem.LocaleHeader) +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt index 1ad4cc003..00ff31c91 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt @@ -2,7 +2,6 @@ package org.koitharu.kotatsu.utils.ext import androidx.core.os.LocaleListCompat import java.util.* -import kotlin.collections.ArrayList fun LocaleListCompat.toList(): List { val list = ArrayList(size()) @@ -26,4 +25,8 @@ inline fun > LocaleListCompat.mapTo( inline fun LocaleListCompat.map(block: (Locale) -> T): List { return mapTo(ArrayList(size()), block) +} + +inline fun LocaleListCompat.mapToSet(block: (Locale) -> T): Set { + return mapTo(LinkedHashSet(size()), block) } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_expand_collapse.xml b/app/src/main/res/drawable/ic_expand_collapse.xml new file mode 100644 index 000000000..a5848a628 --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_collapse.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_expand_less.xml b/app/src/main/res/drawable/ic_expand_less.xml new file mode 100644 index 000000000..8b5d44e5f --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_less.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_expand_more.xml b/app/src/main/res/drawable/ic_expand_more.xml new file mode 100644 index 000000000..bc6e8295b --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_more.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/item_expandable.xml b/app/src/main/res/layout/item_expandable.xml new file mode 100644 index 000000000..8d071b055 --- /dev/null +++ b/app/src/main/res/layout/item_expandable.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file