From 672a1e9b2a358134d08864e769e043660a21dc0b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 23 Feb 2023 19:47:30 +0200 Subject: [PATCH] Rework sources configuration screen --- .../base/ui/list/OnTipCloseListener.kt | 6 ++ .../base/ui/util/ReversibleActionObserver.kt | 38 ++++++++ .../kotatsu/core/prefs/AppSettings.kt | 13 +++ .../kotatsu/explore/ui/ExploreFragment.kt | 16 +--- .../newsources/NewSourcesDialogFragment.kt | 10 +- .../newsources/SourcesSelectAdapter.kt | 18 ++++ .../sources/SourcesSettingsFragment.kt | 50 ++++++++-- .../sources/SourcesSettingsViewModel.kt | 26 ++++- .../sources/adapter/SourceConfigAdapter.kt | 6 +- .../adapter/SourceConfigAdapterDelegates.kt | 94 ++++++++++++------- .../adapter/SourceConfigDiffCallback.kt | 15 ++- .../sources/adapter/SourceConfigListener.kt | 8 +- .../sources/model/SourceConfigItem.kt | 31 +++++- .../utils/image/FaviconFallbackDrawable.kt | 4 +- app/src/main/res/drawable/ic_tap_reorder.xml | 12 +++ .../res/layout/fragment_settings_sources.xml | 2 +- .../main/res/layout/item_source_config.xml | 39 ++++++-- ...e.xml => item_source_config_checkable.xml} | 37 +++----- app/src/main/res/layout/item_tip.xml | 54 +++++++++++ app/src/main/res/values/strings.xml | 4 +- 20 files changed, 370 insertions(+), 113 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/newsources/SourcesSelectAdapter.kt create mode 100644 app/src/main/res/drawable/ic_tap_reorder.xml rename app/src/main/res/layout/{item_source_config_draggable.xml => item_source_config_checkable.xml} (63%) create mode 100644 app/src/main/res/layout/item_tip.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt new file mode 100644 index 000000000..9c9721eef --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnTipCloseListener.kt @@ -0,0 +1,6 @@ +package org.koitharu.kotatsu.base.ui.list + +interface OnTipCloseListener { + + fun onCloseTip(tip: T) +} diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt new file mode 100644 index 000000000..da9504989 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleActionObserver.kt @@ -0,0 +1,38 @@ +package org.koitharu.kotatsu.base.ui.util + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.view.View +import androidx.lifecycle.Observer +import com.google.android.material.snackbar.Snackbar +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.reverseAsync +import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner + +class ReversibleActionObserver( + private val snackbarHost: View, +) : Observer { + + override fun onChanged(action: ReversibleAction?) { + if (action == null) { + return + } + val handle = action.handle + val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG + val snackbar = Snackbar.make(snackbarHost, action.stringResId, length) + if (handle != null) { + snackbar.setAction(R.string.undo) { handle.reverseAsync() } + } + (snackbarHost.context.findActivity() as? BottomNavOwner)?.let { + snackbar.anchorView = it.bottomNav + } + snackbar.show() + } + + private fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index c63db4e66..fd0518318 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -293,6 +293,18 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { return list } + fun isTipEnabled(tip: String): Boolean { + return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true + } + + fun closeTip(tip: String) { + val closedTips = prefs.getStringSet(KEY_TIPS_CLOSED, emptySet()).orEmpty() + if (tip in closedTips) { + return + } + prefs.edit { putStringSet(KEY_TIPS_CLOSED, closedTips + tip) } + } + fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) { prefs.registerOnSharedPreferenceChangeListener(listener) } @@ -380,6 +392,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_LOGS_SHARE = "logs_share" const val KEY_SOURCES_GRID = "sources_grid" const val KEY_UPDATES_UNSTABLE = "updates_unstable" + const val KEY_TIPS_CLOSED = "tips_closed" // About const val KEY_APP_UPDATE = "app_update" diff --git a/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 24a880d0e..ebcf96e26 100644 --- a/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -16,11 +16,10 @@ import coil.ImageLoader import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner -import org.koitharu.kotatsu.base.ui.util.ReversibleAction +import org.koitharu.kotatsu.base.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.base.ui.util.SpanSizeResolver import org.koitharu.kotatsu.bookmarks.ui.BookmarksActivity import org.koitharu.kotatsu.databinding.FragmentExploreBinding @@ -77,7 +76,7 @@ class ExploreFragment : } viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onOpenManga.observe(viewLifecycleOwner, ::onOpenManga) - viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) + viewModel.onActionDone.observe(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) viewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged) } @@ -145,17 +144,6 @@ class ExploreFragment : startActivity(intent) } - private fun onActionDone(action: ReversibleAction) { - val handle = action.handle - val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG - val snackbar = Snackbar.make(binding.recyclerView, action.stringResId, length) - if (handle != null) { - snackbar.setAction(R.string.undo) { handle.reverseAsync() } - } - snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav - snackbar.show() - } - private fun onGridModeChanged(isGrid: Boolean) { binding.recyclerView.layoutManager = if (isGrid) { GridLayoutManager(requireContext(), 4).also { lm -> diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesDialogFragment.kt index ac855f1e1..7c76e4d84 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesDialogFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/newsources/NewSourcesDialogFragment.kt @@ -7,17 +7,15 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentManager import androidx.fragment.app.viewModels -import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.databinding.DialogOnboardBinding -import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem +import javax.inject.Inject @AndroidEntryPoint class NewSourcesDialogFragment : @@ -36,7 +34,7 @@ class NewSourcesDialogFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val adapter = SourceConfigAdapter(this, coil, viewLifecycleOwner) + val adapter = SourcesSelectAdapter(this, coil, viewLifecycleOwner) binding.recyclerView.adapter = adapter binding.textViewTitle.setText(R.string.new_sources_text) @@ -61,10 +59,10 @@ class NewSourcesDialogFragment : viewModel.onItemEnabledChanged(item, isEnabled) } - override fun onDragHandleTouch(holder: RecyclerView.ViewHolder) = Unit - override fun onHeaderClick(header: SourceConfigItem.LocaleGroup) = Unit + override fun onCloseTip(tip: SourceConfigItem.Tip) = Unit + companion object { private const val TAG = "NewSources" diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/newsources/SourcesSelectAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/newsources/SourcesSelectAdapter.kt new file mode 100644 index 000000000..17b7aaf0d --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/newsources/SourcesSelectAdapter.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.settings.newsources + +import androidx.lifecycle.LifecycleOwner +import coil.ImageLoader +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigDiffCallback +import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener +import org.koitharu.kotatsu.settings.sources.adapter.sourceConfigItemCheckableDelegate +import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem + +class SourcesSelectAdapter( + listener: SourceConfigListener, + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, +) : AsyncListDifferDelegationAdapter( + SourceConfigDiffCallback(), + sourceConfigItemCheckableDelegate(listener, coil, lifecycleOwner), +) 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 e1962a000..a33d95486 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,7 +1,12 @@ package org.koitharu.kotatsu.settings.sources import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.core.graphics.Insets import androidx.core.view.MenuProvider @@ -11,10 +16,10 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner +import org.koitharu.kotatsu.base.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.settings.SettingsActivity @@ -24,6 +29,8 @@ import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem import org.koitharu.kotatsu.utils.ext.addMenuProvider +import org.koitharu.kotatsu.utils.ext.getItem +import javax.inject.Inject @AndroidEntryPoint class SourcesSettingsFragment : @@ -63,6 +70,7 @@ class SourcesSettingsFragment : viewModel.items.observe(viewLifecycleOwner) { sourcesAdapter.items = it } + viewModel.onActionDone.observe(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) addMenuProvider(SourcesMenuProvider()) } @@ -89,14 +97,14 @@ class SourcesSettingsFragment : viewModel.setEnabled(item.source, isEnabled) } - override fun onDragHandleTouch(holder: RecyclerView.ViewHolder) { - reorderHelper?.startDrag(holder) - } - override fun onHeaderClick(header: SourceConfigItem.LocaleGroup) { viewModel.expandOrCollapse(header.localeId) } + override fun onCloseTip(tip: SourceConfigItem.Tip) { + viewModel.onTipClosed(tip) + } + private inner class SourcesMenuProvider : MenuProvider, MenuItem.OnActionExpandListener, @@ -117,6 +125,7 @@ class SourcesSettingsFragment : viewModel.disableAll() true } + else -> false } @@ -140,7 +149,7 @@ class SourcesSettingsFragment : private inner class SourcesReorderCallback : ItemTouchHelper.SimpleCallback( ItemTouchHelper.DOWN or ItemTouchHelper.UP, - 0, + ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, ) { override fun onMove( @@ -161,8 +170,31 @@ class SourcesSettingsFragment : target.bindingAdapterPosition, ) - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit + override fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + val item = viewHolder.getItem(SourceConfigItem.SourceItem::class.java) + return if (item != null && item.isDraggable) { + super.getDragDirs(recyclerView, viewHolder) + } else { + 0 + } + } - override fun isLongPressDragEnabled() = false + override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + val item = viewHolder.getItem(SourceConfigItem.Tip::class.java) + return if (item != null) { + super.getSwipeDirs(recyclerView, viewHolder) + } else { + 0 + } + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val item = viewHolder.getItem(SourceConfigItem.Tip::class.java) + if (item != null) { + viewModel.onTipClosed(item) + } + } + + override fun isLongPressDragEnabled() = true } } 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 index 2ff8a9ff8..1641e37dc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt @@ -3,20 +3,25 @@ package org.koitharu.kotatsu.settings.sources import androidx.core.os.LocaleListCompat import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel -import java.util.* -import javax.inject.Inject import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.ReversibleHandle import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.core.model.getLocaleTitle import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem +import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.map import org.koitharu.kotatsu.utils.ext.move +import java.util.Locale +import java.util.TreeMap +import javax.inject.Inject private const val KEY_ENABLED = "!" +private const val TIP_REORDER = "src_reorder" @HiltViewModel class SourcesSettingsViewModel @Inject constructor( @@ -24,6 +29,8 @@ class SourcesSettingsViewModel @Inject constructor( ) : BaseViewModel() { val items = MutableLiveData>(emptyList()) + val onActionDone = SingleLiveEvent() + private val expandedGroups = HashSet() private var searchQuery: String? = null @@ -58,6 +65,11 @@ class SourcesSettingsViewModel @Inject constructor( } if (isEnabled) { settings.markKnownSources(setOf(source)) + } else { + val rollback = ReversibleHandle { + setEnabled(source, true) + } + onActionDone.postCall(ReversibleAction(R.string.source_disabled, rollback)) } buildList() } @@ -83,6 +95,11 @@ class SourcesSettingsViewModel @Inject constructor( buildList() } + fun onTipClosed(item: SourceConfigItem.Tip) { + settings.closeTip(item.key) + buildList() + } + private fun buildList() { val sources = settings.getMangaSources(includeHidden = true) val hiddenSources = settings.hiddenSources @@ -110,10 +127,13 @@ class SourcesSettingsViewModel @Inject constructor( it.locale } } - val result = ArrayList(sources.size + map.size + 1) + val result = ArrayList(sources.size + map.size + 2) val enabledSources = map.remove(KEY_ENABLED) if (!enabledSources.isNullOrEmpty()) { result += SourceConfigItem.Header(R.string.enabled_sources) + if (settings.isTipEnabled(TIP_REORDER)) { + result += SourceConfigItem.Tip(TIP_REORDER, R.drawable.ic_tap_reorder, R.string.sources_reorder_tip) + } enabledSources.mapTo(result) { SourceConfigItem.SourceItem( source = it, 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 index d580684be..2c6be9c77 100644 --- 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 @@ -13,7 +13,7 @@ class SourceConfigAdapter( SourceConfigDiffCallback(), sourceConfigHeaderDelegate(), sourceConfigGroupDelegate(listener), - sourceConfigItemDelegate(listener, coil, lifecycleOwner), - sourceConfigDraggableItemDelegate(listener), + sourceConfigItemDelegate2(listener, coil, lifecycleOwner), sourceConfigEmptySearchDelegate(), -) \ No newline at end of file + sourceConfigTipDelegate(listener), +) 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 index cd0471af2..7fff494bd 100644 --- 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 @@ -1,21 +1,26 @@ package org.koitharu.kotatsu.settings.sources.adapter -import android.annotation.SuppressLint -import android.view.MotionEvent import android.view.View -import android.widget.CompoundButton +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.OnTipCloseListener import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.databinding.ItemExpandableBinding import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding -import org.koitharu.kotatsu.databinding.ItemSourceConfigDraggableBinding +import org.koitharu.kotatsu.databinding.ItemSourceConfigCheckableBinding +import org.koitharu.kotatsu.databinding.ItemTipBinding import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem -import org.koitharu.kotatsu.utils.ext.* +import org.koitharu.kotatsu.utils.ext.crossfade +import org.koitharu.kotatsu.utils.ext.disposeImageRequest +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest +import org.koitharu.kotatsu.utils.ext.textAndVisible import org.koitharu.kotatsu.utils.image.FaviconFallbackDrawable fun sourceConfigHeaderDelegate() = @@ -44,13 +49,12 @@ fun sourceConfigGroupDelegate( } } -fun sourceConfigItemDelegate( +fun sourceConfigItemCheckableDelegate( listener: SourceConfigListener, coil: ImageLoader, lifecycleOwner: LifecycleOwner, -) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) }, - on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable }, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemSourceConfigCheckableBinding.inflate(layoutInflater, parent, false) }, ) { binding.switchToggle.setOnCheckedChangeListener { _, isChecked -> @@ -77,42 +81,60 @@ fun sourceConfigItemDelegate( } } -@SuppressLint("ClickableViewAccessibility") -fun sourceConfigDraggableItemDelegate( +fun sourceConfigItemDelegate2( listener: SourceConfigListener, -) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) }, - on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable }, + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, +) = 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) + val eventListener = View.OnClickListener { v -> + when (v.id) { + R.id.imageView_add -> listener.onItemEnabledChanged(item, true) + R.id.imageView_remove -> listener.onItemEnabledChanged(item, false) + R.id.imageView_config -> listener.onItemSettingsClick(item) } } - + binding.imageViewRemove.setOnClickListener(eventListener) + binding.imageViewAdd.setOnClickListener(eventListener) binding.imageViewConfig.setOnClickListener(eventListener) - binding.switchToggle.setOnCheckedChangeListener(eventListener) - binding.imageViewHandle.setOnTouchListener(eventListener) bind { binding.textViewTitle.text = item.source.title - binding.textViewDescription.text = item.summary ?: getString(R.string.various_languages) - binding.switchToggle.isChecked = item.isEnabled + binding.imageViewAdd.isGone = item.isEnabled + binding.imageViewRemove.isVisible = item.isEnabled + binding.imageViewConfig.isVisible = item.isEnabled + binding.textViewDescription.textAndVisible = item.summary + val fallbackIcon = FaviconFallbackDrawable(context, item.source.name) + binding.imageViewIcon.newImageRequest(item.source.faviconUri(), item.source)?.run { + crossfade(context) + error(fallbackIcon) + placeholder(fallbackIcon) + fallback(fallbackIcon) + lifecycle(lifecycleOwner) + enqueueWith(coil) + } + } + + onViewRecycled { + binding.imageViewIcon.disposeImageRequest() + } +} + +fun sourceConfigTipDelegate( + listener: OnTipCloseListener +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemTipBinding.inflate(layoutInflater, parent, false) }, +) { + + binding.buttonClose.setOnClickListener { + listener.onCloseTip(item) + } + + bind { + binding.imageViewIcon.setImageResource(item.iconResId) + binding.textView.setText(item.textResId) } } 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 index 8bab50c2a..9f57bca90 100644 --- 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 @@ -2,7 +2,10 @@ package org.koitharu.kotatsu.settings.sources.adapter import androidx.recyclerview.widget.DiffUtil import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem -import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.* +import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.EmptySearchResult +import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.Header +import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.LocaleGroup +import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.SourceItem class SourceConfigDiffCallback : DiffUtil.ItemCallback() { @@ -12,15 +15,23 @@ class SourceConfigDiffCallback : DiffUtil.ItemCallback() { oldItem is LocaleGroup && newItem is LocaleGroup -> { oldItem.localeId == newItem.localeId } + oldItem is SourceItem && newItem is SourceItem -> { oldItem.source == newItem.source } + oldItem is Header && newItem is Header -> { oldItem.titleResId == newItem.titleResId } + oldItem == EmptySearchResult && newItem == EmptySearchResult -> { true } + + oldItem is SourceConfigItem.Tip && newItem is SourceConfigItem.Tip -> { + oldItem.key == newItem.key + } + else -> false } } @@ -30,4 +41,4 @@ class SourceConfigDiffCallback : DiffUtil.ItemCallback() { } 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/SourceConfigListener.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigListener.kt index 8bc03a213..d8f0be9fa 100644 --- 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 @@ -1,15 +1,13 @@ package org.koitharu.kotatsu.settings.sources.adapter -import androidx.recyclerview.widget.RecyclerView +import org.koitharu.kotatsu.base.ui.list.OnTipCloseListener import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem -interface SourceConfigListener { +interface SourceConfigListener : OnTipCloseListener { fun onItemSettingsClick(item: SourceConfigItem.SourceItem) fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) - fun onDragHandleTouch(holder: RecyclerView.ViewHolder) - fun onHeaderClick(header: SourceConfigItem.LocaleGroup) -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt index 77f695002..f178bffd9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt @@ -1,6 +1,6 @@ package org.koitharu.kotatsu.settings.sources.model -import android.net.Uri +import androidx.annotation.DrawableRes import androidx.annotation.StringRes import org.koitharu.kotatsu.parsers.model.MangaSource @@ -77,5 +77,32 @@ sealed interface SourceConfigItem { } } + class Tip( + val key: String, + @DrawableRes val iconResId: Int, + @StringRes val textResId: Int, + ) : SourceConfigItem { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Tip + + if (key != other.key) return false + if (iconResId != other.iconResId) return false + if (textResId != other.textResId) return false + + return true + } + + override fun hashCode(): Int { + var result = key.hashCode() + result = 31 * result + iconResId + result = 31 * result + textResId + return result + } + } + object EmptySearchResult : SourceConfigItem -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt b/app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt index a03ce5e80..f6fdaa7df 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/image/FaviconFallbackDrawable.kt @@ -36,7 +36,7 @@ class FaviconFallbackDrawable( override fun onBoundsChange(bounds: Rect) { super.onBoundsChange(bounds) val innerWidth = bounds.width() - (paint.strokeWidth * 2f) - paint.textSize = getTextSizeForWidth(innerWidth, "100%") + paint.textSize = getTextSizeForWidth(innerWidth, letter) * 0.5f paint.getTextBounds(letter, 0, letter.length, textBounds) invalidateSelf() } @@ -64,4 +64,4 @@ class FaviconFallbackDrawable( val hue = (str.hashCode() % 360).absoluteValue.toFloat() return ColorUtils.HSLToColor(floatArrayOf(hue, 0.5f, 0.5f)) } -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/ic_tap_reorder.xml b/app/src/main/res/drawable/ic_tap_reorder.xml new file mode 100644 index 000000000..81a180b71 --- /dev/null +++ b/app/src/main/res/drawable/ic_tap_reorder.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/fragment_settings_sources.xml b/app/src/main/res/layout/fragment_settings_sources.xml index c65d18705..82802d046 100644 --- a/app/src/main/res/layout/fragment_settings_sources.xml +++ b/app/src/main/res/layout/fragment_settings_sources.xml @@ -10,4 +10,4 @@ android:orientation="vertical" android:scrollbars="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - tools:listitem="@layout/item_source_config" /> \ No newline at end of file + tools:listitem="@layout/item_source_config_checkable" /> diff --git a/app/src/main/res/layout/item_source_config.xml b/app/src/main/res/layout/item_source_config.xml index 6e16e217e..599ffbfcd 100644 --- a/app/src/main/res/layout/item_source_config.xml +++ b/app/src/main/res/layout/item_source_config.xml @@ -5,15 +5,17 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?android:windowBackground" android:gravity="center_vertical" - android:minHeight="?android:listPreferredItemHeightSmall" - android:orientation="horizontal"> + android:orientation="horizontal" + android:paddingVertical="@dimen/margin_small" + android:paddingStart="?listPreferredItemPaddingStart" + android:paddingEnd="?listPreferredItemPaddingEnd"> - + android:background="?selectableItemBackgroundBorderless" + android:contentDescription="@string/settings" + android:padding="@dimen/margin_small" + android:scaleType="center" + android:src="@drawable/ic_settings" /> + + + + diff --git a/app/src/main/res/layout/item_source_config_draggable.xml b/app/src/main/res/layout/item_source_config_checkable.xml similarity index 63% rename from app/src/main/res/layout/item_source_config_draggable.xml rename to app/src/main/res/layout/item_source_config_checkable.xml index f6ae672dd..9bb6ae9ad 100644 --- a/app/src/main/res/layout/item_source_config_draggable.xml +++ b/app/src/main/res/layout/item_source_config_checkable.xml @@ -1,26 +1,29 @@ + android:orientation="horizontal" + android:paddingVertical="@dimen/margin_small" + android:paddingStart="?listPreferredItemPaddingStart" + android:paddingEnd="?listPreferredItemPaddingEnd"> - + - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_tip.xml b/app/src/main/res/layout/item_tip.xml new file mode 100644 index 000000000..0beb8d9f9 --- /dev/null +++ b/app/src/main/res/layout/item_tip.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + +