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 b74c22aff..f4f8c7f7d 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 @@ -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.observe 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.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.ExploreListEventListener 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.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource @@ -65,7 +65,9 @@ class ExploreFragment : override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) { 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) { adapter = exploreAdapter setHasFixedSize(true) @@ -103,15 +105,14 @@ class ExploreFragment : override fun onClick(v: View) { 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_bookmarks -> BookmarksActivity.newIntent(v.context) R.id.button_more -> SuggestionsActivity.newIntent(v.context) - R.id.button_favourites -> FavouriteCategoriesActivity.newIntent(v.context) - //R.id.button_random -> { - // viewModel.openRandom() - // return - //} + R.id.button_downloads -> DownloadsActivity.newIntent(v.context) + R.id.button_random -> { + viewModel.openRandom() + return + } else -> return } 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 504cbdf7e..96e6dd2c7 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 @@ -3,6 +3,8 @@ package org.koitharu.kotatsu.explore.ui import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow 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.parsers.model.Manga 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 private const val TIP_SUGGESTIONS = "suggestions" @@ -39,6 +42,7 @@ private const val TIP_SUGGESTIONS = "suggestions" @HiltViewModel class ExploreViewModel @Inject constructor( private val settings: AppSettings, + private val suggestionRepository: SuggestionRepository, private val exploreRepository: ExploreRepository, ) : BaseViewModel() { @@ -51,6 +55,12 @@ class ExploreViewModel @Inject constructor( val onOpenManga = MutableEventFlow() val onActionDone = MutableEventFlow() val onShowSuggestionsTip = MutableEventFlow() + private val isRandomLoading = MutableStateFlow(false) + private val recommendationDeferred = viewModelScope.async(Dispatchers.Default) { + runCatchingCancellable { + suggestionRepository.getRandom() + }.getOrNull() + } val content: StateFlow> = isLoading.flatMapLatest { loading -> if (loading) { @@ -69,9 +79,17 @@ class ExploreViewModel @Inject constructor( } fun openRandom() { - launchLoadingJob(Dispatchers.Default) { - val manga = exploreRepository.findRandomManga(tagsLimit = 8) - onOpenManga.call(manga) + if (isRandomLoading.value) { + return + } + 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) } - private fun createContentFlow() = settings.observe() - .filter { - it == AppSettings.KEY_SOURCES_HIDDEN || - it == AppSettings.KEY_SOURCES_ORDER || - it == AppSettings.KEY_SUGGESTIONS - } - .onStart { emit("") } - .map { settings.getMangaSources(includeHidden = false) } - .combine(isGrid) { content, grid -> buildList(content, grid) } + private fun createContentFlow() = combine( + observeSources(), + isGrid, + isRandomLoading, + ) { content, grid, randomLoading -> + val recommendation = recommendationDeferred.await() + buildList(content, recommendation, grid, randomLoading) + } - private fun buildList(sources: List, isGrid: Boolean): List { + private fun buildList( + sources: List, + recommendation: Manga?, + isGrid: Boolean, + randomLoading: Boolean, + ): List { val result = ArrayList(sources.size + 4) - result += ExploreButtons() - result += ListHeader(R.string.suggestions, 0, null) - result += RecommendationsItem( - Manga( - 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 + result += ExploreButtons(randomLoading) + if (recommendation != null) { + result += ListHeader(R.string.suggestions, 0, null) + result += RecommendationsItem(recommendation) + } if (sources.isNotEmpty()) { result += ListHeader(R.string.remote_sources, R.string.manage, null) sources.mapTo(result) { MangaSourceItem(it, isGrid) } @@ -141,8 +147,17 @@ class ExploreViewModel @Inject constructor( 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( - ExploreButtons(), + ExploreButtons(isRandomLoading.value), LoadingState, ) } 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 ca5942f9a..12bfe9fe6 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 @@ -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.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.model.Manga class ExploreAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, listener: ExploreListEventListener, clickListener: OnListItemClickListener, + mangaClickListener: OnListItemClickListener, ) : BaseListAdapter() { init { delegatesManager .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_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner)) .addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner)) 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 966d5f94c..f07227e84 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 @@ -1,7 +1,9 @@ package org.koitharu.kotatsu.explore.ui.adapter +import android.graphics.Color import android.view.View import androidx.lifecycle.LifecycleOwner +import androidx.swiperefreshlayout.widget.CircularProgressDrawable import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding 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.util.ext.disposeImageRequest 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.resolveDp import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding 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.RecommendationsItem 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( clickListener: View.OnClickListener, @@ -29,26 +35,37 @@ fun exploreButtonsAD( ) { binding.buttonBookmarks.setOnClickListener(clickListener) - binding.buttonHistory.setOnClickListener(clickListener) + binding.buttonDownloads.setOnClickListener(clickListener) binding.buttonLocal.setOnClickListener(clickListener) - //binding.buttonSuggestions.setOnClickListener(clickListener) - binding.buttonFavourites.setOnClickListener(clickListener) - //binding.buttonRandom.setOnClickListener(clickListener) + binding.buttonRandom.setOnClickListener(clickListener) - //bind { - // binding.buttonSuggestions.isVisible = item.isSuggestionsEnabled - //} + bind { + 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( coil: ImageLoader, clickListener: View.OnClickListener, + itemClickListener: OnListItemClickListener, lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) } + { layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) }, ) { binding.buttonMore.setOnClickListener(clickListener) + binding.root.setOnClickListener { v -> + itemClickListener.onItemClick(item.manga, v) + } bind { binding.textViewTitle.text = item.manga.title diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt index 90b224b65..0e4aa9c83 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/model/ExploreButtons.kt @@ -2,7 +2,9 @@ package org.koitharu.kotatsu.explore.ui.model import org.koitharu.kotatsu.list.ui.model.ListModel -class ExploreButtons : ListModel { +class ExploreButtons( + val isRandomLoading: Boolean, +) : ListModel { override fun areItemsTheSame(other: ListModel): Boolean { return other is ExploreButtons @@ -10,10 +12,14 @@ class ExploreButtons : ListModel { override fun equals(other: Any?): Boolean { 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 { - return javaClass.hashCode() + return isRandomLoading.hashCode() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt index 23e703b59..1759239d2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt @@ -10,7 +10,6 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.core.util.ext.mapItems import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.suggestions.data.SuggestionEntity -import org.koitharu.kotatsu.suggestions.data.SuggestionWithManga import javax.inject.Inject class SuggestionRepository @Inject constructor( @@ -29,8 +28,10 @@ class SuggestionRepository @Inject constructor( } } - suspend fun getRandom(): SuggestionWithManga? { - return db.suggestionDao.getRandom() + suspend fun getRandom(): Manga? { + return db.suggestionDao.getRandom()?.let { + it.manga.toManga(it.tags.toMangaTags()) + } } suspend fun clear() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt index cb34ab395..5579069d6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt @@ -72,7 +72,6 @@ class FeedFragment : addMenuProvider( FeedMenuProvider( binding.recyclerView, - (activity as? BottomNavOwner)?.bottomNav, viewModel, ), ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt index 88d888039..14fffb485 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedMenuProvider.kt @@ -8,12 +8,10 @@ import android.view.View import androidx.core.view.MenuProvider import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.dialog.CheckBoxAlertDialog -import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.tracker.work.TrackWorker class FeedMenuProvider( private val snackbarHost: View, - private val anchorView: View?, private val viewModel: FeedViewModel, ) : MenuProvider { @@ -43,12 +41,6 @@ class FeedMenuProvider( true } - R.id.action_settings -> { - val intent = SettingsActivity.newTrackerSettingsIntent(context) - context.startActivity(intent) - true - } - else -> false } } diff --git a/app/src/main/res/layout/item_explore_buttons.xml b/app/src/main/res/layout/item_explore_buttons.xml index 34aa9b3bd..e55b90d35 100644 --- a/app/src/main/res/layout/item_explore_buttons.xml +++ b/app/src/main/res/layout/item_explore_buttons.xml @@ -7,22 +7,6 @@ android:paddingHorizontal="8dp" android:paddingVertical="4dp"> - - - - + + + + - - - \ No newline at end of file +