New sources tip

This commit is contained in:
Koitharu
2023-07-15 14:59:54 +03:00
parent eec750789d
commit 9e56766e9e
9 changed files with 139 additions and 10 deletions

View File

@@ -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.RecyclerViewOwner
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver 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.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent 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.adapter.ExploreListEventListener
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
import org.koitharu.kotatsu.list.ui.model.ListHeader 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.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity import org.koitharu.kotatsu.suggestions.ui.SuggestionsActivity
import javax.inject.Inject import javax.inject.Inject
@@ -47,7 +50,7 @@ class ExploreFragment :
BaseFragment<FragmentExploreBinding>(), BaseFragment<FragmentExploreBinding>(),
RecyclerViewOwner, RecyclerViewOwner,
ExploreListEventListener, ExploreListEventListener,
OnListItemClickListener<MangaSourceItem> { OnListItemClickListener<MangaSourceItem>, TipView.OnButtonClickListener {
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
@@ -65,7 +68,7 @@ class ExploreFragment :
override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) { override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState) 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)) startActivity(DetailsActivity.newIntent(view.context, manga), scaleUpActivityOptionsOf(view))
} }
with(binding.recyclerView) { with(binding.recyclerView) {
@@ -103,6 +106,18 @@ class ExploreFragment :
startActivity(SettingsActivity.newManageSourcesIntent(view.context)) 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) { override fun onClick(v: View) {
val intent = when (v.id) { val intent = when (v.id) {
R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL) R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL)

View File

@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf 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.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState 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.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
import javax.inject.Inject import javax.inject.Inject
private const val TIP_SUGGESTIONS = "suggestions"
@HiltViewModel @HiltViewModel
class ExploreViewModel @Inject constructor( class ExploreViewModel @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
@@ -116,9 +116,10 @@ class ExploreViewModel @Inject constructor(
observeSources(), observeSources(),
isGrid, isGrid,
isRandomLoading, isRandomLoading,
) { content, grid, randomLoading -> observeNewSources(),
) { content, grid, randomLoading, newSources ->
val recommendation = recommendationDeferred.await() val recommendation = recommendationDeferred.await()
buildList(content, recommendation, grid, randomLoading) buildList(content, recommendation, grid, randomLoading, newSources)
} }
private fun buildList( private fun buildList(
@@ -126,6 +127,7 @@ class ExploreViewModel @Inject constructor(
recommendation: Manga?, recommendation: Manga?,
isGrid: Boolean, isGrid: Boolean,
randomLoading: Boolean, randomLoading: Boolean,
newSources: Set<MangaSource>,
): List<ListModel> { ): List<ListModel> {
val result = ArrayList<ListModel>(sources.size + 4) val result = ArrayList<ListModel>(sources.size + 4)
result += ExploreButtons(randomLoading) result += ExploreButtons(randomLoading)
@@ -135,6 +137,16 @@ class ExploreViewModel @Inject constructor(
} }
if (sources.isNotEmpty()) { if (sources.isNotEmpty()) {
result += ListHeader(R.string.remote_sources, R.string.manage, null) 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) } sources.mapTo(result) { MangaSourceItem(it, isGrid) }
} else { } else {
result += EmptyHint( result += EmptyHint(
@@ -160,4 +172,16 @@ class ExploreViewModel @Inject constructor(
ExploreButtons(isRandomLoading.value), ExploreButtons(isRandomLoading.value),
LoadingState, 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"
}
} }

View File

@@ -4,10 +4,12 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener 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.explore.ui.model.MangaSourceItem
import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD 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.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@@ -15,6 +17,7 @@ class ExploreAdapter(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
listener: ExploreListEventListener, listener: ExploreListEventListener,
tipClickListener: TipView.OnButtonClickListener,
clickListener: OnListItemClickListener<MangaSourceItem>, clickListener: OnListItemClickListener<MangaSourceItem>,
mangaClickListener: OnListItemClickListener<Manga>, mangaClickListener: OnListItemClickListener<Manga>,
) : BaseListAdapter<ListModel>() { ) : BaseListAdapter<ListModel>() {
@@ -31,6 +34,7 @@ class ExploreAdapter(
.addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner)) .addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner))
.addDelegate(ITEM_TYPE_HINT, emptyHintAD(coil, lifecycleOwner, listener)) .addDelegate(ITEM_TYPE_HINT, emptyHintAD(coil, lifecycleOwner, listener))
.addDelegate(ITEM_TYPE_LOADING, loadingStateAD()) .addDelegate(ITEM_TYPE_LOADING, loadingStateAD())
.addDelegate(ITEM_TIP, tipAD(tipClickListener))
} }
companion object { companion object {
@@ -42,5 +46,6 @@ class ExploreAdapter(
const val ITEM_TYPE_HINT = 4 const val ITEM_TYPE_HINT = 4
const val ITEM_TYPE_LOADING = 5 const val ITEM_TYPE_LOADING = 5
const val ITEM_TYPE_RECOMMENDATION = 6 const val ITEM_TYPE_RECOMMENDATION = 6
const val ITEM_TIP = 7
} }
} }

View File

@@ -9,6 +9,7 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable 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.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest 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.newImageRequest
import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.core.util.ext.source 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.ItemExploreButtonsBinding
import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding
import org.koitharu.kotatsu.databinding.ItemExploreSourceListBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceListBinding
@@ -69,8 +71,13 @@ fun exploreRecommendationItemAD(
bind { bind {
binding.textViewTitle.text = item.manga.title 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 { 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) source(item.manga.source)
enqueueWith(coil) enqueueWith(coil)
} }

View File

@@ -7,6 +7,8 @@ class RecommendationsItem(
val manga: Manga val manga: Manga
) : ListModel { ) : ListModel {
val summary: String = manga.tags.joinToString { it.title }
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {
return other is RecommendationsItem return other is RecommendationsItem
} }

View File

@@ -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<TipModel, ListModel, ItemTip2Binding>(
{ 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)
}
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="8dp"
android:height="8dp" />
</shape>

View File

@@ -35,14 +35,15 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_weight="2" android:layout_weight="2"
android:orientation="horizontal"> android:divider="@drawable/divider_transparent"
android:orientation="horizontal"
android:showDividers="middle">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_primary" android:id="@+id/button_primary"
style="@style/Widget.Material3.Button.UnelevatedButton" style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_weight="1" android:layout_weight="1"
android:paddingHorizontal="8dp" android:paddingHorizontal="8dp"
android:singleLine="true" android:singleLine="true"
@@ -53,7 +54,6 @@
style="@style/Widget.Material3.Button.TonalButton" style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1" android:layout_weight="1"
android:paddingHorizontal="8dp" android:paddingHorizontal="8dp"
android:singleLine="true" android:singleLine="true"