Option to open random manga from source
This commit is contained in:
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.util.ext.asArrayList
|
|||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
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.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
|
||||||
@@ -21,19 +22,39 @@ class ExploreRepository @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun findRandomManga(tagsLimit: Int): Manga {
|
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 {
|
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()
|
val sources = sourcesRepository.getEnabledSources()
|
||||||
check(sources.isNotEmpty()) { "No sources available" }
|
check(sources.isNotEmpty()) { "No sources available" }
|
||||||
for (i in 0..4) {
|
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 manga = list.randomOrNull() ?: continue
|
||||||
val details = runCatchingCancellable {
|
val details = runCatchingCancellable {
|
||||||
mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
||||||
}.getOrNull() ?: continue
|
}.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
|
continue
|
||||||
}
|
}
|
||||||
return details
|
return details
|
||||||
|
|||||||
@@ -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.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
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.FilterCoordinator
|
||||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||||
@@ -29,6 +30,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
listExtraProvider: ListExtraProvider,
|
listExtraProvider: ListExtraProvider,
|
||||||
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
|
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
|
||||||
|
exploreRepository: ExploreRepository,
|
||||||
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
||||||
) : RemoteListViewModel(
|
) : RemoteListViewModel(
|
||||||
savedStateHandle,
|
savedStateHandle,
|
||||||
@@ -37,6 +39,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
settings,
|
settings,
|
||||||
listExtraProvider,
|
listExtraProvider,
|
||||||
downloadScheduler,
|
downloadScheduler,
|
||||||
|
exploreRepository,
|
||||||
), SharedPreferences.OnSharedPreferenceChangeListener {
|
), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
val onMangaRemoved = MutableEventFlow<Unit>()
|
val onMangaRemoved = MutableEventFlow<Unit>()
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ import androidx.fragment.app.viewModels
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
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.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.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.FragmentListBinding
|
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.FilterOwner
|
||||||
import org.koitharu.kotatsu.filter.ui.FilterSheetFragment
|
import org.koitharu.kotatsu.filter.ui.FilterSheetFragment
|
||||||
import org.koitharu.kotatsu.filter.ui.MangaFilter
|
import org.koitharu.kotatsu.filter.ui.MangaFilter
|
||||||
@@ -35,6 +39,10 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
|
|||||||
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
addMenuProvider(RemoteListMenuProvider())
|
addMenuProvider(RemoteListMenuProvider())
|
||||||
|
viewModel.isRandomLoading.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
|
||||||
|
viewModel.onOpenManga.observeEvent(viewLifecycleOwner) {
|
||||||
|
startActivity(DetailsActivity.newIntent(binding.root.context, it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScrolledToEnd() {
|
override fun onScrolledToEnd() {
|
||||||
@@ -75,6 +83,11 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.action_random -> {
|
||||||
|
viewModel.openRandom()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.action_filter -> {
|
R.id.action_filter -> {
|
||||||
onFilterClick(null)
|
onFilterClick(null)
|
||||||
true
|
true
|
||||||
@@ -83,6 +96,11 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
|
|||||||
else -> false
|
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 {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
if (query.isNullOrEmpty()) {
|
if (query.isNullOrEmpty()) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import kotlinx.coroutines.plus
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
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.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
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.FilterCoordinator
|
||||||
import org.koitharu.kotatsu.filter.ui.MangaFilter
|
import org.koitharu.kotatsu.filter.ui.MangaFilter
|
||||||
import org.koitharu.kotatsu.filter.ui.model.FilterState
|
import org.koitharu.kotatsu.filter.ui.model.FilterState
|
||||||
@@ -49,14 +51,19 @@ open class RemoteListViewModel @Inject constructor(
|
|||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
listExtraProvider: ListExtraProvider,
|
listExtraProvider: ListExtraProvider,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
|
private val exploreRepository: ExploreRepository,
|
||||||
) : MangaListViewModel(settings, downloadScheduler), MangaFilter by filter {
|
) : MangaListViewModel(settings, downloadScheduler), MangaFilter by filter {
|
||||||
|
|
||||||
val source = savedStateHandle.require<MangaSource>(RemoteListFragment.ARG_SOURCE)
|
val source = savedStateHandle.require<MangaSource>(RemoteListFragment.ARG_SOURCE)
|
||||||
|
val isRandomLoading = MutableStateFlow(false)
|
||||||
|
val onOpenManga = MutableEventFlow<Manga>()
|
||||||
|
|
||||||
private val repository = mangaRepositoryFactory.create(source)
|
private val repository = mangaRepositoryFactory.create(source)
|
||||||
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
||||||
private val hasNextPage = MutableStateFlow(false)
|
private val hasNextPage = MutableStateFlow(false)
|
||||||
private val listError = MutableStateFlow<Throwable?>(null)
|
private val listError = MutableStateFlow<Throwable?>(null)
|
||||||
private var loadingJob: Job? = null
|
private var loadingJob: Job? = null
|
||||||
|
private var randomJob: Job? = null
|
||||||
|
|
||||||
override val content = combine(
|
override val content = combine(
|
||||||
mangaList,
|
mangaList,
|
||||||
@@ -149,4 +156,16 @@ open class RemoteListViewModel @Inject constructor(
|
|||||||
textSecondary = 0,
|
textSecondary = 0,
|
||||||
actionStringRes = if (canResetFilter) R.string.reset_filter else 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
app:showAsAction="ifRoom|collapseActionView" />
|
app:showAsAction="ifRoom|collapseActionView" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_random"
|
||||||
|
android:icon="@drawable/ic_dice"
|
||||||
|
android:title="@string/random"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_filter"
|
android:id="@+id/action_filter"
|
||||||
android:orderInCategory="30"
|
android:orderInCategory="30"
|
||||||
|
|||||||
Reference in New Issue
Block a user