diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt index e81600499..c351e80cf 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/domain/ExploreRepository.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.util.ext.asArrayList import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.history.data.HistoryRepository +import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.runCatchingCancellable @@ -21,19 +22,39 @@ class ExploreRepository @Inject constructor( ) { suspend fun findRandomManga(tagsLimit: Int): Manga { - val blacklistTagRegex = TagsBlacklist(settings.suggestionsTagsBlacklist, 0.4f) + val tagsBlacklist = TagsBlacklist(settings.suggestionsTagsBlacklist, 0.4f) val tags = historyRepository.getPopularTags(tagsLimit).mapNotNull { - if (it in blacklistTagRegex) null else it.title + if (it in tagsBlacklist) null else it.title } val sources = sourcesRepository.getEnabledSources() check(sources.isNotEmpty()) { "No sources available" } for (i in 0..4) { - val list = getList(sources.random(), tags, blacklistTagRegex) + val list = getList(sources.random(), tags, tagsBlacklist) val manga = list.randomOrNull() ?: continue val details = runCatchingCancellable { mangaRepositoryFactory.create(manga.source).getDetails(manga) }.getOrNull() ?: continue - if ((settings.isSuggestionsExcludeNsfw && details.isNsfw) || details in blacklistTagRegex) { + if ((settings.isSuggestionsExcludeNsfw && details.isNsfw) || details in tagsBlacklist) { + continue + } + return details + } + throw NoSuchElementException() + } + + suspend fun findRandomManga(source: MangaSource, tagsLimit: Int): Manga { + val tagsBlacklist = TagsBlacklist(settings.suggestionsTagsBlacklist, 0.4f) + val skipNsfw = settings.isSuggestionsExcludeNsfw && source.contentType != ContentType.HENTAI + val tags = historyRepository.getPopularTags(tagsLimit).mapNotNull { + if (it in tagsBlacklist) null else it.title + } + for (i in 0..4) { + val list = getList(source, tags, tagsBlacklist) + val manga = list.randomOrNull() ?: continue + val details = runCatchingCancellable { + mangaRepositoryFactory.create(manga.source).getDetails(manga) + }.getOrNull() ?: continue + if ((skipNsfw && details.isNsfw) || details in tagsBlacklist) { continue } return details diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt index 04b7ed947..0cd62a9c6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt @@ -11,6 +11,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.download.ui.worker.DownloadWorker +import org.koitharu.kotatsu.explore.domain.ExploreRepository import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.ui.model.EmptyState @@ -29,6 +30,7 @@ class LocalListViewModel @Inject constructor( downloadScheduler: DownloadWorker.Scheduler, listExtraProvider: ListExtraProvider, private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase, + exploreRepository: ExploreRepository, @LocalStorageChanges private val localStorageChanges: SharedFlow, ) : RemoteListViewModel( savedStateHandle, @@ -37,6 +39,7 @@ class LocalListViewModel @Inject constructor( settings, listExtraProvider, downloadScheduler, + exploreRepository, ), SharedPreferences.OnSharedPreferenceChangeListener { val onMangaRemoved = MutableEventFlow() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index 0c87936b3..a8b94f00e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -12,9 +12,13 @@ import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.list.ListSelectionController +import org.koitharu.kotatsu.core.ui.util.MenuInvalidator 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.withArgs import org.koitharu.kotatsu.databinding.FragmentListBinding +import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.filter.ui.FilterOwner import org.koitharu.kotatsu.filter.ui.FilterSheetFragment import org.koitharu.kotatsu.filter.ui.MangaFilter @@ -35,6 +39,10 @@ class RemoteListFragment : MangaListFragment(), FilterOwner { override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) addMenuProvider(RemoteListMenuProvider()) + viewModel.isRandomLoading.observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) + viewModel.onOpenManga.observeEvent(viewLifecycleOwner) { + startActivity(DetailsActivity.newIntent(binding.root.context, it)) + } } override fun onScrolledToEnd() { @@ -75,6 +83,11 @@ class RemoteListFragment : MangaListFragment(), FilterOwner { true } + R.id.action_random -> { + viewModel.openRandom() + true + } + R.id.action_filter -> { onFilterClick(null) true @@ -83,6 +96,11 @@ class RemoteListFragment : MangaListFragment(), FilterOwner { else -> false } + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + menu.findItem(R.id.action_random)?.isEnabled = !viewModel.isRandomLoading.value + } + override fun onQueryTextSubmit(query: String?): Boolean { if (query.isNullOrEmpty()) { return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index ef37f77fb..4988f2711 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -19,10 +19,12 @@ import kotlinx.coroutines.plus import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.download.ui.worker.DownloadWorker +import org.koitharu.kotatsu.explore.domain.ExploreRepository import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.MangaFilter import org.koitharu.kotatsu.filter.ui.model.FilterState @@ -49,14 +51,19 @@ open class RemoteListViewModel @Inject constructor( settings: AppSettings, listExtraProvider: ListExtraProvider, downloadScheduler: DownloadWorker.Scheduler, + private val exploreRepository: ExploreRepository, ) : MangaListViewModel(settings, downloadScheduler), MangaFilter by filter { val source = savedStateHandle.require(RemoteListFragment.ARG_SOURCE) + val isRandomLoading = MutableStateFlow(false) + val onOpenManga = MutableEventFlow() + private val repository = mangaRepositoryFactory.create(source) private val mangaList = MutableStateFlow?>(null) private val hasNextPage = MutableStateFlow(false) private val listError = MutableStateFlow(null) private var loadingJob: Job? = null + private var randomJob: Job? = null override val content = combine( mangaList, @@ -149,4 +156,16 @@ open class RemoteListViewModel @Inject constructor( textSecondary = 0, actionStringRes = if (canResetFilter) R.string.reset_filter else 0, ) + + fun openRandom() { + if (randomJob?.isActive == true) { + return + } + randomJob = launchLoadingJob(Dispatchers.Default) { + isRandomLoading.value = true + val manga = exploreRepository.findRandomManga(source, 16) + onOpenManga.call(manga) + isRandomLoading.value = false + } + } } diff --git a/app/src/main/res/menu/opt_list_remote.xml b/app/src/main/res/menu/opt_list_remote.xml index 82554a1e2..5dff03a7c 100644 --- a/app/src/main/res/menu/opt_list_remote.xml +++ b/app/src/main/res/menu/opt_list_remote.xml @@ -10,6 +10,12 @@ app:actionViewClass="androidx.appcompat.widget.SearchView" app:showAsAction="ifRoom|collapseActionView" /> + + - \ No newline at end of file +