New sources tip
This commit is contained in:
@@ -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<FragmentExploreBinding>(),
|
||||
RecyclerViewOwner,
|
||||
ExploreListEventListener,
|
||||
OnListItemClickListener<MangaSourceItem> {
|
||||
OnListItemClickListener<MangaSourceItem>, 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)
|
||||
|
||||
@@ -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<MangaSource>,
|
||||
): List<ListModel> {
|
||||
val result = ArrayList<ListModel>(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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MangaSourceItem>,
|
||||
mangaClickListener: OnListItemClickListener<Manga>,
|
||||
) : BaseListAdapter<ListModel>() {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
7
app/src/main/res/drawable/divider_transparent.xml
Normal file
7
app/src/main/res/drawable/divider_transparent.xml
Normal 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>
|
||||
@@ -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">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_primary"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:singleLine="true"
|
||||
@@ -53,7 +54,6 @@
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:singleLine="true"
|
||||
|
||||
Reference in New Issue
Block a user