From 9e56766e9ee2e9fe2101f5b72e7f05c16a0e1981 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 15 Jul 2023 14:59:54 +0300 Subject: [PATCH] New sources tip --- .../kotatsu/explore/ui/ExploreFragment.kt | 19 ++++++++- .../kotatsu/explore/ui/ExploreViewModel.kt | 32 ++++++++++++-- .../explore/ui/adapter/ExploreAdapter.kt | 5 +++ .../ui/adapter/ExploreAdapterDelegates.kt | 9 +++- .../explore/ui/model/RecommendationsItem.kt | 2 + .../koitharu/kotatsu/list/ui/adapter/TipAD.kt | 27 ++++++++++++ .../kotatsu/list/ui/model/TipModel.kt | 42 +++++++++++++++++++ .../main/res/drawable/divider_transparent.xml | 7 ++++ app/src/main/res/layout/view_tip.xml | 6 +-- 9 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TipAD.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/TipModel.kt create mode 100644 app/src/main/res/drawable/divider_transparent.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index f4f8c7f7d..7b082652e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver +import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent @@ -35,10 +36,12 @@ import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter import org.koitharu.kotatsu.explore.ui.adapter.ExploreListEventListener import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.list.ui.model.ListHeader +import org.koitharu.kotatsu.list.ui.model.TipModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.settings.SettingsActivity +import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity import javax.inject.Inject @@ -47,7 +50,7 @@ class ExploreFragment : BaseFragment(), RecyclerViewOwner, ExploreListEventListener, - OnListItemClickListener { + OnListItemClickListener, TipView.OnButtonClickListener { @Inject lateinit var coil: ImageLoader @@ -65,7 +68,7 @@ class ExploreFragment : override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) - exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this) { manga, view -> + exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this, this) { manga, view -> startActivity(DetailsActivity.newIntent(view.context, manga), scaleUpActivityOptionsOf(view)) } with(binding.recyclerView) { @@ -103,6 +106,18 @@ class ExploreFragment : startActivity(SettingsActivity.newManageSourcesIntent(view.context)) } + override fun onPrimaryButtonClick(tipView: TipView) { + when ((tipView.tag as? TipModel)?.key) { + ExploreViewModel.TIP_NEW_SOURCES -> NewSourcesDialogFragment.show(childFragmentManager) + } + } + + override fun onSecondaryButtonClick(tipView: TipView) { + when ((tipView.tag as? TipModel)?.key) { + ExploreViewModel.TIP_NEW_SOURCES -> TODO() + } + } + override fun onClick(v: View) { val intent = when (v.id) { R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index 96e6dd2c7..d9c10142c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -31,14 +32,13 @@ import org.koitharu.kotatsu.list.ui.model.EmptyHint import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingState +import org.koitharu.kotatsu.list.ui.model.TipModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository import javax.inject.Inject -private const val TIP_SUGGESTIONS = "suggestions" - @HiltViewModel class ExploreViewModel @Inject constructor( private val settings: AppSettings, @@ -116,9 +116,10 @@ class ExploreViewModel @Inject constructor( observeSources(), isGrid, isRandomLoading, - ) { content, grid, randomLoading -> + observeNewSources(), + ) { content, grid, randomLoading, newSources -> val recommendation = recommendationDeferred.await() - buildList(content, recommendation, grid, randomLoading) + buildList(content, recommendation, grid, randomLoading, newSources) } private fun buildList( @@ -126,6 +127,7 @@ class ExploreViewModel @Inject constructor( recommendation: Manga?, isGrid: Boolean, randomLoading: Boolean, + newSources: Set, ): List { val result = ArrayList(sources.size + 4) result += ExploreButtons(randomLoading) @@ -135,6 +137,16 @@ class ExploreViewModel @Inject constructor( } if (sources.isNotEmpty()) { result += ListHeader(R.string.remote_sources, R.string.manage, null) + if (newSources.isNotEmpty()) { + result += TipModel( + key = TIP_NEW_SOURCES, + title = R.string.new_sources_text, + text = R.string.new_sources_text, + icon = R.drawable.ic_explore_normal, + primaryButtonText = R.string.manage, + secondaryButtonText = R.string.discard, + ) + } sources.mapTo(result) { MangaSourceItem(it, isGrid) } } else { result += EmptyHint( @@ -160,4 +172,16 @@ class ExploreViewModel @Inject constructor( ExploreButtons(isRandomLoading.value), LoadingState, ) + + private fun observeNewSources() = settings.observe() + .filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN } + .onStart { emit("") } + .map { settings.newSources } + .distinctUntilChanged() + + companion object { + + private const val TIP_SUGGESTIONS = "suggestions" + const val TIP_NEW_SOURCES = "new_sources" + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt index 12bfe9fe6..522442c6a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt @@ -4,10 +4,12 @@ import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD +import org.koitharu.kotatsu.list.ui.adapter.tipAD import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga @@ -15,6 +17,7 @@ class ExploreAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, listener: ExploreListEventListener, + tipClickListener: TipView.OnButtonClickListener, clickListener: OnListItemClickListener, mangaClickListener: OnListItemClickListener, ) : BaseListAdapter() { @@ -31,6 +34,7 @@ class ExploreAdapter( .addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner)) .addDelegate(ITEM_TYPE_HINT, emptyHintAD(coil, lifecycleOwner, listener)) .addDelegate(ITEM_TYPE_LOADING, loadingStateAD()) + .addDelegate(ITEM_TIP, tipAD(tipClickListener)) } companion object { @@ -42,5 +46,6 @@ class ExploreAdapter( const val ITEM_TYPE_HINT = 4 const val ITEM_TYPE_LOADING = 5 const val ITEM_TYPE_RECOMMENDATION = 6 + const val ITEM_TIP = 7 } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index f07227e84..630be6bb8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -9,6 +9,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.ui.image.FaviconDrawable +import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.disposeImageRequest @@ -17,6 +18,7 @@ import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.core.util.ext.source +import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceListBinding @@ -69,8 +71,13 @@ fun exploreRecommendationItemAD( bind { binding.textViewTitle.text = item.manga.title - binding.textViewSubtitle.text = item.manga.title + binding.textViewSubtitle.textAndVisible = item.summary binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run { + placeholder(R.drawable.ic_placeholder) + fallback(R.drawable.ic_placeholder) + error(R.drawable.ic_error_placeholder) + allowRgb565(true) + transformations(TrimTransformation()) source(item.manga.source) enqueueWith(coil) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt index c4586ef62..c43ba785d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/RecommendationsItem.kt @@ -7,6 +7,8 @@ class RecommendationsItem( val manga: Manga ) : ListModel { + val summary: String = manga.tags.joinToString { it.title } + override fun areItemsTheSame(other: ListModel): Boolean { return other is RecommendationsItem } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TipAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TipAD.kt new file mode 100644 index 000000000..bec674344 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/TipAD.kt @@ -0,0 +1,27 @@ +package org.koitharu.kotatsu.list.ui.adapter + +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.core.ui.widgets.TipView +import org.koitharu.kotatsu.databinding.ItemTip2Binding +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.list.ui.model.TipModel + +fun tipAD( + listener: TipView.OnButtonClickListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemTip2Binding.inflate(layoutInflater, parent, false) } +) { + + binding.root.onButtonClickListener = listener + + bind { + with(binding.root) { + tag = item + setTitle(item.title) + setText(item.text) + setIcon(item.icon) + setPrimaryButtonText(item.primaryButtonText) + setSecondaryButtonText(item.secondaryButtonText) + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/TipModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/TipModel.kt new file mode 100644 index 000000000..9ff72b6cc --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/TipModel.kt @@ -0,0 +1,42 @@ +package org.koitharu.kotatsu.list.ui.model + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes + +class TipModel( + val key: String, + @StringRes val title: Int, + @StringRes val text: Int, + @DrawableRes val icon: Int, + @StringRes val primaryButtonText: Int, + @StringRes val secondaryButtonText: Int, +) : ListModel { + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is TipModel && other.key == key + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TipModel + + if (key != other.key) return false + if (title != other.title) return false + if (text != other.text) return false + if (icon != other.icon) return false + if (primaryButtonText != other.primaryButtonText) return false + return secondaryButtonText == other.secondaryButtonText + } + + override fun hashCode(): Int { + var result = key.hashCode() + result = 31 * result + title + result = 31 * result + text + result = 31 * result + icon + result = 31 * result + primaryButtonText + result = 31 * result + secondaryButtonText + return result + } +} diff --git a/app/src/main/res/drawable/divider_transparent.xml b/app/src/main/res/drawable/divider_transparent.xml new file mode 100644 index 000000000..64725fa5b --- /dev/null +++ b/app/src/main/res/drawable/divider_transparent.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/layout/view_tip.xml b/app/src/main/res/layout/view_tip.xml index dcbbe184a..3d1f97d5a 100644 --- a/app/src/main/res/layout/view_tip.xml +++ b/app/src/main/res/layout/view_tip.xml @@ -35,14 +35,15 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_weight="2" - android:orientation="horizontal"> + android:divider="@drawable/divider_transparent" + android:orientation="horizontal" + android:showDividers="middle">