feat(incognito): add toggle for incognito mode in BrowseSource & SourceFeed (#902)

* feat(incognito): add `toggle for incognito` mode in BrowseSource & SourceFeed

* fix null check
This commit is contained in:
Cuong-Tran 2025-05-11 15:52:01 +07:00 committed by GitHub
parent a862bda9d5
commit 8a2b063cd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 94 additions and 24 deletions

View file

@ -104,6 +104,7 @@ fun SourceFeedScreen(
// KMK --> // KMK -->
navigateUp: () -> Unit, navigateUp: () -> Unit,
onWebViewClick: (() -> Unit)?, onWebViewClick: (() -> Unit)?,
onToggleIncognito: () -> Unit,
onSourceSettingClick: (() -> Unit?)?, onSourceSettingClick: (() -> Unit?)?,
onSortFeedClick: (() -> Unit)?, onSortFeedClick: (() -> Unit)?,
onLongClickManga: (Manga) -> Unit, onLongClickManga: (Manga) -> Unit,
@ -145,6 +146,7 @@ fun SourceFeedScreen(
// KMK --> // KMK -->
navigateUp = navigateUp, navigateUp = navigateUp,
onWebViewClick = onWebViewClick, onWebViewClick = onWebViewClick,
onToggleIncognito = onToggleIncognito,
onSourceSettingClick = onSourceSettingClick, onSourceSettingClick = onSourceSettingClick,
onSortFeedClick = onSortFeedClick, onSortFeedClick = onSortFeedClick,
toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode,
@ -298,6 +300,7 @@ fun SourceFeedToolbar(
// KMK --> // KMK -->
navigateUp: () -> Unit, navigateUp: () -> Unit,
onWebViewClick: (() -> Unit)?, onWebViewClick: (() -> Unit)?,
onToggleIncognito: () -> Unit,
onSourceSettingClick: (() -> Unit?)?, onSourceSettingClick: (() -> Unit?)?,
onSortFeedClick: (() -> Unit)?, onSortFeedClick: (() -> Unit)?,
toggleSelectionMode: () -> Unit, toggleSelectionMode: () -> Unit,
@ -332,6 +335,15 @@ fun SourceFeedToolbar(
) )
} }
// KMK -->
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.pref_incognito_mode),
onClick = onToggleIncognito,
),
)
// KMK <--
onSortFeedClick?.let { onSortFeedClick?.let {
add( add(
AppBar.OverflowAction( AppBar.OverflowAction(

View file

@ -4,7 +4,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ViewList import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.automirrored.outlined.Help import androidx.compose.material.icons.automirrored.outlined.Help
import androidx.compose.material.icons.filled.ViewModule import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -39,6 +38,7 @@ fun BrowseSourceToolbar(
onWebViewClick: () -> Unit, onWebViewClick: () -> Unit,
onHelpClick: () -> Unit, onHelpClick: () -> Unit,
// KMK --> // KMK -->
onToggleIncognito: () -> Unit,
onSettingsClick: (() -> Unit)?, onSettingsClick: (() -> Unit)?,
// KMK <-- // KMK <--
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
@ -100,22 +100,20 @@ fun BrowseSourceToolbar(
) )
} }
} else { } else {
if (isConfigurableSource && displayMode != null) { // KMK -->
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.pref_incognito_mode),
onClick = onToggleIncognito,
),
)
// KMK <--
add( add(
AppBar.OverflowAction( AppBar.OverflowAction(
title = stringResource(MR.strings.action_web_view), title = stringResource(MR.strings.action_web_view),
onClick = onWebViewClick, onClick = onWebViewClick,
), ),
) )
} else {
add(
AppBar.Action(
title = stringResource(MR.strings.action_web_view),
icon = Icons.Outlined.Public,
onClick = onWebViewClick,
),
)
}
} }
// SY <-- // SY <--
// KMK --> // KMK -->

View file

@ -227,6 +227,7 @@ data class BrowseSourceScreen(
onWebViewClick = onWebViewClick, onWebViewClick = onWebViewClick,
onHelpClick = onHelpClick, onHelpClick = onHelpClick,
// KMK --> // KMK -->
onToggleIncognito = screenModel::toggleIncognitoMode,
onSettingsClick = { onSettingsClick = {
when { when {
screenModel.source.isEhBasedSource() && isHentaiEnabled -> screenModel.source.isEhBasedSource() && isHentaiEnabled ->

View file

@ -4,6 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.paging.Pager import androidx.paging.Pager
@ -18,18 +19,22 @@ import eu.kanade.core.preference.asState
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.interactor.GetIncognitoState import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.interactor.ToggleIncognito
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.interactor.AddTracks import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.presentation.util.ioCoroutineScope
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.metadata.RaisedSearchMetadata
import exh.source.EH_PACKAGE
import exh.source.getMainSource import exh.source.getMainSource
import exh.source.isEhBasedSource
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -72,6 +77,7 @@ import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.interactor.InsertSavedSearch import tachiyomi.domain.source.interactor.InsertSavedSearch
import tachiyomi.domain.source.model.EXHSavedSearch import tachiyomi.domain.source.model.EXHSavedSearch
import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.model.SavedSearch
import tachiyomi.domain.source.model.StubSource
import tachiyomi.domain.source.repository.SourcePagingSource import tachiyomi.domain.source.repository.SourcePagingSource
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
@ -101,6 +107,10 @@ open class BrowseSourceScreenModel(
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val addTracks: AddTracks = Injekt.get(), private val addTracks: AddTracks = Injekt.get(),
private val getIncognitoState: GetIncognitoState = Injekt.get(), private val getIncognitoState: GetIncognitoState = Injekt.get(),
// KMK -->
private val toggleIncognito: ToggleIncognito = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
// KMK <--
// SY --> // SY -->
unsortedPreferences: UnsortedPreferences = Injekt.get(), unsortedPreferences: UnsortedPreferences = Injekt.get(),
@ -124,6 +134,10 @@ open class BrowseSourceScreenModel(
private val filterSerializer = FilterSerializer() private val filterSerializer = FilterSerializer()
// SY <-- // SY <--
// KMK -->
var incognitoMode = mutableStateOf(getIncognitoState.await(source.id))
// KMK <--
init { init {
// KMK --> // KMK -->
screenModelScope.launch { screenModelScope.launch {
@ -155,10 +169,6 @@ open class BrowseSourceScreenModel(
} }
}.join() }.join()
if (!getIncognitoState.await(source.id)) {
sourcePreferences.lastUsedSource().set(source.id)
}
// SY --> // SY -->
val savedSearchId = savedSearch val savedSearchId = savedSearch
val jsonFilters = filtersJson val jsonFilters = filtersJson
@ -189,9 +199,31 @@ open class BrowseSourceScreenModel(
} }
.launchIn(screenModelScope) .launchIn(screenModelScope)
// SY <-- // SY <--
// KMK-->
getIncognitoState.subscribe(sourceId)
.onEach {
if (!it) sourcePreferences.lastUsedSource().set(source.id)
incognitoMode.value = it
}
.launchIn(screenModelScope)
// KMK <--
} }
} }
// KMK -->
fun toggleIncognitoMode() {
val packageName = when {
source is StubSource -> null
source.isEhBasedSource() -> EH_PACKAGE
else -> extensionManager.getExtensionPackage(sourceId)
}
packageName?.let {
toggleIncognito.await(it, !incognitoMode.value)
}
}
// KMK <--
/** /**
* Flow of Pager flow tied to [State.listing] * Flow of Pager flow tied to [State.listing]
*/ */

View file

@ -148,6 +148,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen() {
), ),
) )
}.takeIf { screenModel.source is HttpSource }, }.takeIf { screenModel.source is HttpSource },
onToggleIncognito = screenModel::toggleIncognitoMode,
onSourceSettingClick = { onSourceSettingClick = {
when { when {
screenModel.source.isEhBasedSource() && isHentaiEnabled -> screenModel.source.isEhBasedSource() && isHentaiEnabled ->

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
@ -11,14 +12,18 @@ import dev.icerock.moko.resources.StringResource
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.domain.source.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.interactor.GetIncognitoState import eu.kanade.domain.source.interactor.GetIncognitoState
import eu.kanade.domain.source.interactor.ToggleIncognito
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.browse.SourceFeedUI import eu.kanade.presentation.browse.SourceFeedUI
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.feed.MaxFeedItems import eu.kanade.tachiyomi.ui.browse.feed.MaxFeedItems
import exh.source.EH_PACKAGE
import exh.source.getMainSource import exh.source.getMainSource
import exh.source.isEhBasedSource
import exh.source.mangaDexSourceIds import exh.source.mangaDexSourceIds
import exh.util.nullIfBlank import exh.util.nullIfBlank
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -51,6 +56,7 @@ import tachiyomi.domain.source.interactor.ReorderFeed
import tachiyomi.domain.source.model.EXHSavedSearch import tachiyomi.domain.source.model.EXHSavedSearch
import tachiyomi.domain.source.model.FeedSavedSearch import tachiyomi.domain.source.model.FeedSavedSearch
import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.model.SavedSearch
import tachiyomi.domain.source.model.StubSource
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.kmk.KMR import tachiyomi.i18n.kmk.KMR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
@ -75,6 +81,8 @@ open class SourceFeedScreenModel(
// KMK --> // KMK -->
private val reorderFeed: ReorderFeed = Injekt.get(), private val reorderFeed: ReorderFeed = Injekt.get(),
private val getIncognitoState: GetIncognitoState = Injekt.get(), private val getIncognitoState: GetIncognitoState = Injekt.get(),
private val toggleIncognito: ToggleIncognito = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
sourcePreferences: SourcePreferences = Injekt.get(), sourcePreferences: SourcePreferences = Injekt.get(),
// KMK <-- // KMK <--
) : StateScreenModel<SourceFeedState>(SourceFeedState()) { ) : StateScreenModel<SourceFeedState>(SourceFeedState()) {
@ -87,6 +95,10 @@ open class SourceFeedScreenModel(
val startExpanded by uiPreferences.expandFilters().asState(screenModelScope) val startExpanded by uiPreferences.expandFilters().asState(screenModelScope)
// KMK -->
var incognitoMode = mutableStateOf(getIncognitoState.await(source.id))
// KMK <--
init { init {
// KMK --> // KMK -->
screenModelScope.launch { screenModelScope.launch {
@ -104,9 +116,12 @@ open class SourceFeedScreenModel(
// KMK --> // KMK -->
reloadSavedSearches() reloadSavedSearches()
if (!getIncognitoState.await(source.id)) { getIncognitoState.subscribe(sourceId)
sourcePreferences.lastUsedSource().set(source.id) .onEach {
if (!it) sourcePreferences.lastUsedSource().set(source.id)
incognitoMode.value = it
} }
.launchIn(screenModelScope)
// KMK <-- // KMK <--
getFeedSavedSearchBySourceId.subscribe(source.id) getFeedSavedSearchBySourceId.subscribe(source.id)
.onEach { .onEach {
@ -123,6 +138,17 @@ open class SourceFeedScreenModel(
} }
// KMK--> // KMK-->
fun toggleIncognitoMode() {
val packageName = when {
source is StubSource -> null
source.isEhBasedSource() -> EH_PACKAGE
else -> extensionManager.getExtensionPackage(sourceId)
}
packageName?.let {
toggleIncognito.await(it, !incognitoMode.value)
}
}
fun resetFilters() { fun resetFilters() {
val source = source val source = source
if (source !is CatalogueSource) return if (source !is CatalogueSource) return