Update explore fragment

This commit is contained in:
Koitharu
2023-07-13 16:08:09 +03:00
parent 55ca2b8d8d
commit 44a2b6db11
10 changed files with 123 additions and 93 deletions

View File

@@ -27,13 +27,13 @@ import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver
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
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.databinding.FragmentExploreBinding import org.koitharu.kotatsu.databinding.FragmentExploreBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.list.DownloadsActivity
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter 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.favourites.ui.categories.FavouriteCategoriesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListHeader
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
@@ -65,7 +65,9 @@ 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) exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this) { manga, view ->
startActivity(DetailsActivity.newIntent(view.context, manga), scaleUpActivityOptionsOf(view))
}
with(binding.recyclerView) { with(binding.recyclerView) {
adapter = exploreAdapter adapter = exploreAdapter
setHasFixedSize(true) setHasFixedSize(true)
@@ -103,15 +105,14 @@ class ExploreFragment :
override fun onClick(v: View) { override fun onClick(v: View) {
val intent = when (v.id) { val intent = when (v.id) {
R.id.button_history -> HistoryActivity.newIntent(v.context)
R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL) R.id.button_local -> MangaListActivity.newIntent(v.context, MangaSource.LOCAL)
R.id.button_bookmarks -> BookmarksActivity.newIntent(v.context) R.id.button_bookmarks -> BookmarksActivity.newIntent(v.context)
R.id.button_more -> SuggestionsActivity.newIntent(v.context) R.id.button_more -> SuggestionsActivity.newIntent(v.context)
R.id.button_favourites -> FavouriteCategoriesActivity.newIntent(v.context) R.id.button_downloads -> DownloadsActivity.newIntent(v.context)
//R.id.button_random -> { R.id.button_random -> {
// viewModel.openRandom() viewModel.openRandom()
// return return
//} }
else -> return else -> return
} }

View File

@@ -3,6 +3,8 @@ package org.koitharu.kotatsu.explore.ui
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
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
@@ -31,7 +33,8 @@ 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.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.model.MangaState import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
import javax.inject.Inject import javax.inject.Inject
private const val TIP_SUGGESTIONS = "suggestions" private const val TIP_SUGGESTIONS = "suggestions"
@@ -39,6 +42,7 @@ private const val TIP_SUGGESTIONS = "suggestions"
@HiltViewModel @HiltViewModel
class ExploreViewModel @Inject constructor( class ExploreViewModel @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
private val suggestionRepository: SuggestionRepository,
private val exploreRepository: ExploreRepository, private val exploreRepository: ExploreRepository,
) : BaseViewModel() { ) : BaseViewModel() {
@@ -51,6 +55,12 @@ class ExploreViewModel @Inject constructor(
val onOpenManga = MutableEventFlow<Manga>() val onOpenManga = MutableEventFlow<Manga>()
val onActionDone = MutableEventFlow<ReversibleAction>() val onActionDone = MutableEventFlow<ReversibleAction>()
val onShowSuggestionsTip = MutableEventFlow<Unit>() val onShowSuggestionsTip = MutableEventFlow<Unit>()
private val isRandomLoading = MutableStateFlow(false)
private val recommendationDeferred = viewModelScope.async(Dispatchers.Default) {
runCatchingCancellable {
suggestionRepository.getRandom()
}.getOrNull()
}
val content: StateFlow<List<ListModel>> = isLoading.flatMapLatest { loading -> val content: StateFlow<List<ListModel>> = isLoading.flatMapLatest { loading ->
if (loading) { if (loading) {
@@ -69,9 +79,17 @@ class ExploreViewModel @Inject constructor(
} }
fun openRandom() { fun openRandom() {
launchLoadingJob(Dispatchers.Default) { if (isRandomLoading.value) {
val manga = exploreRepository.findRandomManga(tagsLimit = 8) return
onOpenManga.call(manga) }
launchJob(Dispatchers.Default) {
isRandomLoading.value = true
try {
val manga = exploreRepository.findRandomManga(tagsLimit = 8)
onOpenManga.call(manga)
} finally {
isRandomLoading.value = false
}
} }
} }
@@ -94,39 +112,27 @@ class ExploreViewModel @Inject constructor(
settings.closeTip(TIP_SUGGESTIONS) settings.closeTip(TIP_SUGGESTIONS)
} }
private fun createContentFlow() = settings.observe() private fun createContentFlow() = combine(
.filter { observeSources(),
it == AppSettings.KEY_SOURCES_HIDDEN || isGrid,
it == AppSettings.KEY_SOURCES_ORDER || isRandomLoading,
it == AppSettings.KEY_SUGGESTIONS ) { content, grid, randomLoading ->
} val recommendation = recommendationDeferred.await()
.onStart { emit("") } buildList(content, recommendation, grid, randomLoading)
.map { settings.getMangaSources(includeHidden = false) } }
.combine(isGrid) { content, grid -> buildList(content, grid) }
private fun buildList(sources: List<MangaSource>, isGrid: Boolean): List<ListModel> { private fun buildList(
sources: List<MangaSource>,
recommendation: Manga?,
isGrid: Boolean,
randomLoading: Boolean,
): List<ListModel> {
val result = ArrayList<ListModel>(sources.size + 4) val result = ArrayList<ListModel>(sources.size + 4)
result += ExploreButtons() result += ExploreButtons(randomLoading)
result += ListHeader(R.string.suggestions, 0, null) if (recommendation != null) {
result += RecommendationsItem( result += ListHeader(R.string.suggestions, 0, null)
Manga( result += RecommendationsItem(recommendation)
0, }
"Test",
"Test",
"Test",
"Test",
0f,
false,
"http://images.ctfassets.net/yadj1kx9rmg0/wtrHxeu3zEoEce2MokCSi/cf6f68efdcf625fdc060607df0f3baef/quwowooybuqbl6ntboz3.jpg",
emptySet(),
MangaState.ONGOING,
"Test",
"http://images.ctfassets.net/yadj1kx9rmg0/wtrHxeu3zEoEce2MokCSi/cf6f68efdcf625fdc060607df0f3baef/quwowooybuqbl6ntboz3.jpg",
"Test",
emptyList(),
MangaSource.DESUME,
),
) // TODO
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)
sources.mapTo(result) { MangaSourceItem(it, isGrid) } sources.mapTo(result) { MangaSourceItem(it, isGrid) }
@@ -141,8 +147,17 @@ class ExploreViewModel @Inject constructor(
return result return result
} }
private fun observeSources() = settings.observe()
.filter {
it == AppSettings.KEY_SOURCES_HIDDEN ||
it == AppSettings.KEY_SOURCES_ORDER ||
it == AppSettings.KEY_SUGGESTIONS
}
.onStart { emit("") }
.map { settings.getMangaSources(includeHidden = false) }
private fun getLoadingStateList() = listOf( private fun getLoadingStateList() = listOf(
ExploreButtons(), ExploreButtons(isRandomLoading.value),
LoadingState, LoadingState,
) )
} }

View File

@@ -9,18 +9,23 @@ 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.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
class ExploreAdapter( class ExploreAdapter(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
listener: ExploreListEventListener, listener: ExploreListEventListener,
clickListener: OnListItemClickListener<MangaSourceItem>, clickListener: OnListItemClickListener<MangaSourceItem>,
mangaClickListener: OnListItemClickListener<Manga>,
) : BaseListAdapter<ListModel>() { ) : BaseListAdapter<ListModel>() {
init { init {
delegatesManager delegatesManager
.addDelegate(ITEM_TYPE_BUTTONS, exploreButtonsAD(listener)) .addDelegate(ITEM_TYPE_BUTTONS, exploreButtonsAD(listener))
.addDelegate(ITEM_TYPE_RECOMMENDATION, exploreRecommendationItemAD(coil, listener, lifecycleOwner)) .addDelegate(
ITEM_TYPE_RECOMMENDATION,
exploreRecommendationItemAD(coil, listener, mangaClickListener, lifecycleOwner),
)
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener)) .addDelegate(ITEM_TYPE_HEADER, listHeaderAD(listener))
.addDelegate(ITEM_TYPE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner)) .addDelegate(ITEM_TYPE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner))
.addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner)) .addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner))

View File

@@ -1,7 +1,9 @@
package org.koitharu.kotatsu.explore.ui.adapter package org.koitharu.kotatsu.explore.ui.adapter
import android.graphics.Color
import android.view.View import android.view.View
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -11,7 +13,9 @@ 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
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
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.source import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding
@@ -21,6 +25,8 @@ import org.koitharu.kotatsu.explore.ui.model.ExploreButtons
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import com.google.android.material.R as materialR
fun exploreButtonsAD( fun exploreButtonsAD(
clickListener: View.OnClickListener, clickListener: View.OnClickListener,
@@ -29,26 +35,37 @@ fun exploreButtonsAD(
) { ) {
binding.buttonBookmarks.setOnClickListener(clickListener) binding.buttonBookmarks.setOnClickListener(clickListener)
binding.buttonHistory.setOnClickListener(clickListener) binding.buttonDownloads.setOnClickListener(clickListener)
binding.buttonLocal.setOnClickListener(clickListener) binding.buttonLocal.setOnClickListener(clickListener)
//binding.buttonSuggestions.setOnClickListener(clickListener) binding.buttonRandom.setOnClickListener(clickListener)
binding.buttonFavourites.setOnClickListener(clickListener)
//binding.buttonRandom.setOnClickListener(clickListener)
//bind { bind {
// binding.buttonSuggestions.isVisible = item.isSuggestionsEnabled if (item.isRandomLoading) {
//} val icon = CircularProgressDrawable(context)
icon.strokeWidth = context.resources.resolveDp(2f)
icon.setColorSchemeColors(context.getThemeColor(materialR.attr.colorPrimary, Color.DKGRAY))
binding.buttonRandom.icon = icon
icon.start()
} else {
binding.buttonRandom.setIconResource(R.drawable.ic_dice)
}
binding.buttonRandom.isClickable = !item.isRandomLoading
}
} }
fun exploreRecommendationItemAD( fun exploreRecommendationItemAD(
coil: ImageLoader, coil: ImageLoader,
clickListener: View.OnClickListener, clickListener: View.OnClickListener,
itemClickListener: OnListItemClickListener<Manga>,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<RecommendationsItem, ListModel, ItemRecommendationBinding>( ) = adapterDelegateViewBinding<RecommendationsItem, ListModel, ItemRecommendationBinding>(
{ layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) } { layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) },
) { ) {
binding.buttonMore.setOnClickListener(clickListener) binding.buttonMore.setOnClickListener(clickListener)
binding.root.setOnClickListener { v ->
itemClickListener.onItemClick(item.manga, v)
}
bind { bind {
binding.textViewTitle.text = item.manga.title binding.textViewTitle.text = item.manga.title

View File

@@ -2,7 +2,9 @@ package org.koitharu.kotatsu.explore.ui.model
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
class ExploreButtons : ListModel { class ExploreButtons(
val isRandomLoading: Boolean,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean { override fun areItemsTheSame(other: ListModel): Boolean {
return other is ExploreButtons return other is ExploreButtons
@@ -10,10 +12,14 @@ class ExploreButtons : ListModel {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
return javaClass == other?.javaClass if (javaClass != other?.javaClass) return false
other as ExploreButtons
return isRandomLoading == other.isRandomLoading
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return javaClass.hashCode() return isRandomLoading.hashCode()
} }
} }

View File

@@ -10,7 +10,6 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.util.ext.mapItems import org.koitharu.kotatsu.core.util.ext.mapItems
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.suggestions.data.SuggestionEntity import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
import org.koitharu.kotatsu.suggestions.data.SuggestionWithManga
import javax.inject.Inject import javax.inject.Inject
class SuggestionRepository @Inject constructor( class SuggestionRepository @Inject constructor(
@@ -29,8 +28,10 @@ class SuggestionRepository @Inject constructor(
} }
} }
suspend fun getRandom(): SuggestionWithManga? { suspend fun getRandom(): Manga? {
return db.suggestionDao.getRandom() return db.suggestionDao.getRandom()?.let {
it.manga.toManga(it.tags.toMangaTags())
}
} }
suspend fun clear() { suspend fun clear() {

View File

@@ -72,7 +72,6 @@ class FeedFragment :
addMenuProvider( addMenuProvider(
FeedMenuProvider( FeedMenuProvider(
binding.recyclerView, binding.recyclerView,
(activity as? BottomNavOwner)?.bottomNav,
viewModel, viewModel,
), ),
) )

View File

@@ -8,12 +8,10 @@ import android.view.View
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.dialog.CheckBoxAlertDialog import org.koitharu.kotatsu.core.ui.dialog.CheckBoxAlertDialog
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.tracker.work.TrackWorker
class FeedMenuProvider( class FeedMenuProvider(
private val snackbarHost: View, private val snackbarHost: View,
private val anchorView: View?,
private val viewModel: FeedViewModel, private val viewModel: FeedViewModel,
) : MenuProvider { ) : MenuProvider {
@@ -43,12 +41,6 @@ class FeedMenuProvider(
true true
} }
R.id.action_settings -> {
val intent = SettingsActivity.newTrackerSettingsIntent(context)
context.startActivity(intent)
true
}
else -> false else -> false
} }
} }

View File

@@ -7,22 +7,6 @@
android:paddingHorizontal="8dp" android:paddingHorizontal="8dp"
android:paddingVertical="4dp"> android:paddingVertical="4dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_history"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/history"
app:icon="@drawable/ic_history" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_favourites"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/favourites"
app:icon="@drawable/ic_heart_outline" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_local" android:id="@+id/button_local"
style="@style/Widget.Kotatsu.ExploreButton" style="@style/Widget.Kotatsu.ExploreButton"
@@ -39,11 +23,27 @@
android:text="@string/bookmarks" android:text="@string/bookmarks"
app:icon="@drawable/ic_bookmark" /> app:icon="@drawable/ic_bookmark" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_random"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/random"
app:icon="@drawable/ic_dice" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_downloads"
style="@style/Widget.Kotatsu.ExploreButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/downloads"
app:icon="@drawable/ic_download" />
<androidx.constraintlayout.helper.widget.Flow <androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow" android:id="@+id/flow"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:constraint_referenced_ids="button_history,button_favourites,button_local,button_bookmarks" app:constraint_referenced_ids="button_local,button_bookmarks,button_random,button_downloads"
app:flow_horizontalGap="12dp" app:flow_horizontalGap="12dp"
app:flow_maxElementsWrap="@integer/explore_buttons_columns" app:flow_maxElementsWrap="@integer/explore_buttons_columns"
app:flow_verticalGap="8dp" app:flow_verticalGap="8dp"

View File

@@ -15,10 +15,4 @@
android:title="@string/clear_feed" android:title="@string/clear_feed"
app:showAsAction="never" /> app:showAsAction="never" />
<item </menu>
android:id="@+id/action_settings"
android:orderInCategory="50"
android:title="@string/settings"
app:showAsAction="never" />
</menu>