Refactor sources settings list
This commit is contained in:
@@ -92,7 +92,6 @@ class KotatsuApp : Application() {
|
||||
.detectFragmentReuse()
|
||||
.detectWrongFragmentContainer()
|
||||
.detectRetainInstanceUsage()
|
||||
.detectTargetFragmentUsage()
|
||||
.detectSetUserVisibleHint()
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ object MangaProviderFactory {
|
||||
fun getSources(settings: AppSettings, includeHidden: Boolean): List<MangaSource> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<T, E, B : ViewBinding> 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)
|
||||
}
|
||||
@@ -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<TextView>(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?
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ class SettingsActivity : BaseActivity<ActivitySettingsBinding>(),
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onPreferenceStartFragment(
|
||||
caller: PreferenceFragmentCompat,
|
||||
pref: Preference
|
||||
|
||||
@@ -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()) }
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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<MangaSource, Boolean, ItemSourceConfigBinding>(
|
||||
ItemSourceConfigBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
) {
|
||||
|
||||
override fun onBind(data: MangaSource, extra: Boolean) {
|
||||
binding.textViewTitle.text = data.title
|
||||
binding.switchToggle.isChecked = extra
|
||||
}
|
||||
}
|
||||
@@ -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<MangaSource>,
|
||||
) : RecyclerView.Adapter<SourceViewHolder>() {
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<FragmentSettingsSourcesBinding>(),
|
||||
OnListItemClickListener<MangaSource> {
|
||||
SourceConfigListener {
|
||||
|
||||
private lateinit var reorderHelper: ItemTouchHelper
|
||||
private val viewModel by viewModel<SourcesSettingsViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -39,11 +42,16 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
|
||||
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<FragmentSettingsSourcesBinding>(),
|
||||
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<FragmentSettingsSourcesBinding>(),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<List<SourceConfigItem>>(emptyList())
|
||||
private val expandedGroups = HashSet<String?>()
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
|
||||
class SourceConfigAdapter(
|
||||
listener: SourceConfigListener,
|
||||
) : AsyncListDifferDelegationAdapter<SourceConfigItem>(
|
||||
SourceConfigDiffCallback(),
|
||||
sourceConfigHeaderDelegate(listener),
|
||||
sourceConfigItemDelegate(listener),
|
||||
)
|
||||
@@ -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<SourceConfigItem.LocaleHeader, SourceConfigItem, ItemExpandableBinding>(
|
||||
{ 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<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>(
|
||||
{ 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
||||
class SourceConfigDiffCallback : DiffUtil.ItemCallback<SourceConfigItem>() {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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<Locale> {
|
||||
val list = ArrayList<Locale>(size())
|
||||
@@ -26,4 +25,8 @@ inline fun <R, C : MutableCollection<in R>> LocaleListCompat.mapTo(
|
||||
|
||||
inline fun <T> LocaleListCompat.map(block: (Locale) -> T): List<T> {
|
||||
return mapTo(ArrayList(size()), block)
|
||||
}
|
||||
|
||||
inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
|
||||
return mapTo(LinkedHashSet(size()), block)
|
||||
}
|
||||
5
app/src/main/res/drawable/ic_expand_collapse.xml
Normal file
5
app/src/main/res/drawable/ic_expand_collapse.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/ic_expand_less" android:state_checked="true" />
|
||||
<item android:drawable="@drawable/ic_expand_more" android:state_checked="false" />
|
||||
</selector>
|
||||
5
app/src/main/res/drawable/ic_expand_less.xml
Normal file
5
app/src/main/res/drawable/ic_expand_less.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_expand_more.xml
Normal file
11
app/src/main/res/drawable/ic_expand_more.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z" />
|
||||
</vector>
|
||||
15
app/src/main/res/layout/item_expandable.xml
Normal file
15
app/src/main/res/layout/item_expandable.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:drawablePadding="12dp"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
app:drawableEndCompat="@drawable/ic_expand_collapse"
|
||||
app:drawableTint="?android:textColorPrimary"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
Reference in New Issue
Block a user