Move app state banner to the very top ()

This moves the banners to the root composable and so eliminates the need to
track the app states in every screen.
This commit is contained in:
Ivan Iskandar 2022-12-09 23:20:13 +07:00 committed by GitHub
parent a61e2799db
commit d97eab0328
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 217 additions and 295 deletions

@ -182,6 +182,7 @@ dependencies {
implementation(compose.accompanist.flowlayout)
implementation(compose.accompanist.permissions)
implementation(compose.accompanist.themeadapter)
implementation(compose.accompanist.systemuicontroller)
implementation(androidx.paging.runtime)
implementation(androidx.paging.compose)

@ -63,9 +63,6 @@ fun AppBar(
actionModeCounter: Int = 0,
onCancelActionMode: () -> Unit = {},
actionModeActions: @Composable RowScope.() -> Unit = {},
// Banners
downloadedOnlyMode: Boolean = false,
incognitoMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
@ -93,8 +90,6 @@ fun AppBar(
},
isActionMode = isActionMode,
onCancelActionMode = onCancelActionMode,
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
)
}
@ -112,9 +107,6 @@ fun AppBar(
// Action mode
isActionMode: Boolean = false,
onCancelActionMode: () -> Unit = {},
// Banners
downloadedOnlyMode: Boolean = false,
incognitoMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null,
) {
@ -150,8 +142,6 @@ fun AppBar(
),
scrollBehavior = scrollBehavior,
)
AppStateBanners(downloadedOnlyMode, incognitoMode)
}
}
@ -236,8 +226,6 @@ fun SearchToolbar(
onSearch: (String) -> Unit = {},
onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) },
actions: @Composable RowScope.() -> Unit = {},
incognitoMode: Boolean = false,
downloadedOnlyMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
@ -326,8 +314,6 @@ fun SearchToolbar(
key("actions") { actions() }
},
isActionMode = false,
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
)
LaunchedEffect(searchClickCount) {

@ -3,8 +3,13 @@ package eu.kanade.presentation.components
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -37,24 +42,34 @@ fun AppStateBanners(
incognitoMode: Boolean,
modifier: Modifier = Modifier,
) {
val insets = WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
Column(modifier = modifier) {
if (downloadedOnlyMode) {
DownloadedOnlyModeBanner()
DownloadedOnlyModeBanner(
modifier = Modifier.windowInsetsPadding(insets),
)
}
if (incognitoMode) {
IncognitoModeBanner()
IncognitoModeBanner(
modifier = if (!downloadedOnlyMode) {
Modifier.windowInsetsPadding(insets)
} else {
Modifier
},
)
}
}
}
@Composable
private fun DownloadedOnlyModeBanner() {
private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
Text(
text = stringResource(R.string.label_downloaded_only),
modifier = Modifier
.background(color = MaterialTheme.colorScheme.tertiary)
.fillMaxWidth()
.padding(4.dp),
.padding(4.dp)
.then(modifier),
color = MaterialTheme.colorScheme.onTertiary,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
@ -62,13 +77,14 @@ private fun DownloadedOnlyModeBanner() {
}
@Composable
private fun IncognitoModeBanner() {
private fun IncognitoModeBanner(modifier: Modifier = Modifier) {
Text(
text = stringResource(R.string.pref_incognito_mode),
modifier = Modifier
.background(color = MaterialTheme.colorScheme.primary)
.fillMaxWidth()
.padding(4.dp),
.padding(4.dp)
.then(modifier),
color = MaterialTheme.colorScheme.onPrimary,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,

@ -29,8 +29,6 @@ fun TabbedScreen(
startIndex: Int? = null,
searchQuery: String? = null,
onChangeSearchQuery: (String?) -> Unit = {},
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
) {
val scope = rememberCoroutineScope()
val state = rememberPagerState()
@ -78,8 +76,6 @@ fun TabbedScreen(
}
}
AppStateBanners(downloadedOnlyMode, incognitoMode)
HorizontalPager(
count = tabs.size,
modifier = Modifier.fillMaxSize(),

@ -26,8 +26,6 @@ import java.util.Date
fun HistoryScreen(
state: HistoryState,
snackbarHostState: SnackbarHostState,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onSearchQueryChange: (String?) -> Unit,
onClickCover: (mangaId: Long) -> Unit,
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
@ -47,8 +45,6 @@ fun HistoryScreen(
)
}
},
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
)
},

@ -45,8 +45,6 @@ fun LibraryContent(
getDisplayModeForPage: @Composable (Int) -> LibraryDisplayMode,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: (Int) -> List<LibraryItem>,
isDownloadOnly: Boolean,
isIncognitoMode: Boolean,
) {
Column(
modifier = Modifier.padding(
@ -65,8 +63,6 @@ fun LibraryContent(
LibraryTabs(
categories = categories,
currentPageIndex = pagerState.currentPage,
isDownloadOnly = isDownloadOnly,
isIncognitoMode = isIncognitoMode,
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
) { scope.launch { pagerState.animateScrollToPage(it) } }
}

@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.TabIndicator
import eu.kanade.presentation.components.TabText
@ -17,8 +16,6 @@ import eu.kanade.presentation.components.TabText
fun LibraryTabs(
categories: List<Category>,
currentPageIndex: Int,
isDownloadOnly: Boolean,
isIncognitoMode: Boolean,
getNumberOfMangaForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit,
) {
@ -47,7 +44,5 @@ fun LibraryTabs(
}
Divider()
AppStateBanners(isDownloadOnly, isIncognitoMode)
}
}

@ -32,8 +32,6 @@ fun LibraryToolbar(
hasActiveFilters: Boolean,
selectedCount: Int,
title: LibraryToolbarTitle,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onClickUnselectAll: () -> Unit,
onClickSelectAll: () -> Unit,
onClickInvertSelection: () -> Unit,
@ -46,8 +44,6 @@ fun LibraryToolbar(
) = when {
selectedCount > 0 -> LibrarySelectionToolbar(
selectedCount = selectedCount,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
onClickUnselectAll = onClickUnselectAll,
onClickSelectAll = onClickSelectAll,
onClickInvertSelection = onClickInvertSelection,
@ -55,8 +51,6 @@ fun LibraryToolbar(
else -> LibraryRegularToolbar(
title = title,
hasFilters = hasActiveFilters,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
searchQuery = searchQuery,
onSearchQueryChange = onSearchQueryChange,
onClickFilter = onClickFilter,
@ -70,8 +64,6 @@ fun LibraryToolbar(
fun LibraryRegularToolbar(
title: LibraryToolbarTitle,
hasFilters: Boolean,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
searchQuery: String?,
onSearchQueryChange: (String?) -> Unit,
onClickFilter: () -> Unit,
@ -123,8 +115,6 @@ fun LibraryRegularToolbar(
)
}
},
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
scrollBehavior = scrollBehavior,
)
}
@ -132,8 +122,6 @@ fun LibraryRegularToolbar(
@Composable
fun LibrarySelectionToolbar(
selectedCount: Int,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onClickUnselectAll: () -> Unit,
onClickSelectAll: () -> Unit,
onClickInvertSelection: () -> Unit,
@ -150,8 +138,6 @@ fun LibrarySelectionToolbar(
},
isActionMode = true,
onCancelActionMode = onClickUnselectAll,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
)
}

@ -238,8 +238,6 @@ private fun MangaScreenSmallImpl(
titleAlphaProvider = { animatedTitleAlpha },
backgroundAlphaProvider = { animatedBgAlpha },
hasFilters = state.manga.chaptersFiltered(),
incognitoMode = state.isIncognitoMode,
downloadedOnlyMode = state.isDownloadedOnlyMode,
onBackClicked = internalOnBackPressed,
onClickFilter = onFilterClicked,
onClickShare = onShareClicked,
@ -450,8 +448,6 @@ fun MangaScreenLargeImpl(
titleAlphaProvider = { if (chapters.fastAny { it.selected }) 1f else 0f },
backgroundAlphaProvider = { 1f },
hasFilters = state.manga.chaptersFiltered(),
incognitoMode = state.isIncognitoMode,
downloadedOnlyMode = state.isDownloadedOnlyMode,
onBackClicked = internalOnBackPressed,
onClickFilter = onFilterButtonClicked,
onClickShare = onShareClicked,

@ -26,7 +26,6 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.DownloadDropdownMenu
import eu.kanade.presentation.components.OverflowMenu
import eu.kanade.presentation.manga.DownloadAction
@ -40,8 +39,6 @@ fun MangaToolbar(
titleAlphaProvider: () -> Float,
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
hasFilters: Boolean,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onBackClicked: () -> Unit,
onClickFilter: () -> Unit,
onClickShare: (() -> Unit)?,
@ -151,7 +148,5 @@ fun MangaToolbar(
.copy(alpha = if (isActionMode) 1f else backgroundAlphaProvider()),
),
)
AppStateBanners(downloadedOnlyMode, incognitoMode)
}
}

@ -1,7 +1,13 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.GetApp
@ -18,8 +24,8 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
@ -45,125 +51,125 @@ fun MoreScreen(
) {
val uriHandler = LocalUriHandler.current
ScrollbarLazyColumn(
modifier = Modifier.systemBarsPadding(),
) {
if (isFDroid) {
Scaffold(
topBar = {
Column(
modifier = Modifier.windowInsetsPadding(
WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
),
) {
if (isFDroid) {
WarningBanner(
textRes = R.string.fdroid_warning,
modifier = Modifier.clickable {
uriHandler.openUri("https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version")
},
)
}
}
},
) { contentPadding ->
ScrollbarLazyColumn(
modifier = Modifier.padding(contentPadding),
) {
item {
WarningBanner(
textRes = R.string.fdroid_warning,
modifier = Modifier.clickable {
uriHandler.openUri("https://tachiyomi.org/help/faq/#how-do-i-migrate-from-the-f-droid-version")
LogoHeader()
}
item {
SwitchPreferenceWidget(
title = stringResource(R.string.label_downloaded_only),
subtitle = stringResource(R.string.downloaded_only_summary),
icon = Icons.Outlined.CloudOff,
checked = downloadedOnly,
onCheckedChanged = onDownloadedOnlyChange,
)
}
item {
SwitchPreferenceWidget(
title = stringResource(R.string.pref_incognito_mode),
subtitle = stringResource(R.string.pref_incognito_mode_summary),
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
checked = incognitoMode,
onCheckedChanged = onIncognitoModeChange,
)
}
item { Divider() }
item {
val downloadQueueState = downloadQueueStateProvider()
TextPreferenceWidget(
title = stringResource(R.string.label_download_queue),
subtitle = when (downloadQueueState) {
DownloadQueueState.Stopped -> null
is DownloadQueueState.Paused -> {
val pending = downloadQueueState.pending
if (pending == 0) {
stringResource(R.string.paused)
} else {
"${stringResource(R.string.paused)}${
pluralStringResource(
id = R.plurals.download_queue_summary,
count = pending,
pending,
)
}"
}
}
is DownloadQueueState.Downloading -> {
val pending = downloadQueueState.pending
pluralStringResource(id = R.plurals.download_queue_summary, count = pending, pending)
}
},
icon = Icons.Outlined.GetApp,
onPreferenceClick = onClickDownloadQueue,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.categories),
icon = Icons.Outlined.Label,
onPreferenceClick = onClickCategories,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.label_stats),
icon = Icons.Outlined.QueryStats,
onPreferenceClick = onClickStats,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.label_backup),
icon = Icons.Outlined.SettingsBackupRestore,
onPreferenceClick = onClickBackupAndRestore,
)
}
item { Divider() }
item {
TextPreferenceWidget(
title = stringResource(R.string.label_settings),
icon = Icons.Outlined.Settings,
onPreferenceClick = onClickSettings,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.pref_category_about),
icon = Icons.Outlined.Info,
onPreferenceClick = onClickAbout,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.label_help),
icon = Icons.Outlined.HelpOutline,
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
)
}
}
item {
LogoHeader()
}
item {
AppStateBanners(
downloadedOnlyMode = downloadedOnly,
incognitoMode = incognitoMode,
)
}
item {
SwitchPreferenceWidget(
title = stringResource(R.string.label_downloaded_only),
subtitle = stringResource(R.string.downloaded_only_summary),
icon = Icons.Outlined.CloudOff,
checked = downloadedOnly,
onCheckedChanged = onDownloadedOnlyChange,
)
}
item {
SwitchPreferenceWidget(
title = stringResource(R.string.pref_incognito_mode),
subtitle = stringResource(R.string.pref_incognito_mode_summary),
icon = ImageVector.vectorResource(R.drawable.ic_glasses_24dp),
checked = incognitoMode,
onCheckedChanged = onIncognitoModeChange,
)
}
item { Divider() }
item {
val downloadQueueState = downloadQueueStateProvider()
TextPreferenceWidget(
title = stringResource(R.string.label_download_queue),
subtitle = when (downloadQueueState) {
DownloadQueueState.Stopped -> null
is DownloadQueueState.Paused -> {
val pending = downloadQueueState.pending
if (pending == 0) {
stringResource(R.string.paused)
} else {
"${stringResource(R.string.paused)}${
pluralStringResource(
id = R.plurals.download_queue_summary,
count = pending,
pending,
)
}"
}
}
is DownloadQueueState.Downloading -> {
val pending = downloadQueueState.pending
pluralStringResource(id = R.plurals.download_queue_summary, count = pending, pending)
}
},
icon = Icons.Outlined.GetApp,
onPreferenceClick = onClickDownloadQueue,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.categories),
icon = Icons.Outlined.Label,
onPreferenceClick = onClickCategories,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.label_stats),
icon = Icons.Outlined.QueryStats,
onPreferenceClick = onClickStats,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.label_backup),
icon = Icons.Outlined.SettingsBackupRestore,
onPreferenceClick = onClickBackupAndRestore,
)
}
item { Divider() }
item {
TextPreferenceWidget(
title = stringResource(R.string.label_settings),
icon = Icons.Outlined.Settings,
onPreferenceClick = onClickSettings,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.pref_category_about),
icon = Icons.Outlined.Info,
onPreferenceClick = onClickAbout,
)
}
item {
TextPreferenceWidget(
title = stringResource(R.string.label_help),
icon = Icons.Outlined.HelpOutline,
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
)
}
}
}

@ -43,8 +43,6 @@ import kotlin.time.Duration.Companion.seconds
fun UpdateScreen(
state: UpdatesState,
snackbarHostState: SnackbarHostState,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
lastUpdated: Long,
relativeTime: Int,
onClickCover: (UpdatesItem) -> Unit,
@ -65,8 +63,6 @@ fun UpdateScreen(
Scaffold(
topBar = { scrollBehavior ->
UpdatesAppBar(
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
onUpdateLibrary = { onUpdateLibrary() },
actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) },
@ -136,8 +132,6 @@ fun UpdateScreen(
@Composable
private fun UpdatesAppBar(
modifier: Modifier = Modifier,
incognitoMode: Boolean,
downloadedOnlyMode: Boolean,
onUpdateLibrary: () -> Unit,
// For action mode
actionModeCounter: Int,
@ -173,8 +167,6 @@ private fun UpdatesAppBar(
)
}
},
downloadedOnlyMode = downloadedOnlyMode,
incognitoMode = incognitoMode,
scrollBehavior = scrollBehavior,
)
}

@ -9,13 +9,9 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.TabOptions
import eu.kanade.core.prefs.asState
import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.components.TabbedScreen
import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R
@ -25,8 +21,6 @@ import eu.kanade.tachiyomi.ui.browse.migration.sources.migrateSourceTab
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.storage.DiskUtil
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
data class BrowseTab(
private val toExtensions: Boolean = false,
@ -47,7 +41,6 @@ data class BrowseTab(
@Composable
override fun Content() {
val context = LocalContext.current
val screenModel = rememberScreenModel { BrowseScreenModel() }
// Hoisted for extensions tab's search bar
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
@ -63,8 +56,6 @@ data class BrowseTab(
startIndex = 1.takeIf { toExtensions },
searchQuery = extensionsQuery,
onChangeSearchQuery = extensionsScreenModel::search,
incognitoMode = screenModel.isIncognitoMode,
downloadedOnlyMode = screenModel.isDownloadOnly,
)
// For local source
@ -75,10 +66,3 @@ data class BrowseTab(
}
}
}
private class BrowseScreenModel(
preferences: BasePreferences = Injekt.get(),
) : ScreenModel {
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
}

@ -43,7 +43,6 @@ import eu.kanade.domain.source.interactor.GetRemoteManga
import eu.kanade.presentation.browse.BrowseSourceContent
import eu.kanade.presentation.browse.components.BrowseSourceToolbar
import eu.kanade.presentation.browse.components.RemoveMangaDialog
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.ChangeCategoryDialog
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.DuplicateMangaDialog
@ -167,8 +166,6 @@ data class BrowseSourceScreen(
}
Divider()
AppStateBanners(screenModel.isDownloadOnly, screenModel.isIncognitoMode)
}
},
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },

@ -17,7 +17,6 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.core.prefs.CheckboxState
import eu.kanade.core.prefs.asState
import eu.kanade.core.prefs.mapAsCheckboxState
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category
@ -82,7 +81,6 @@ class BrowseSourceScreenModel(
private val sourceId: Long,
searchQuery: String?,
private val sourceManager: SourceManager = Injekt.get(),
preferences: BasePreferences = Injekt.get(),
sourcePreferences: SourcePreferences = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
@ -103,9 +101,6 @@ class BrowseSourceScreenModel(
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
val source = sourceManager.get(sourceId) as CatalogueSource
/**

@ -1,12 +1,9 @@
package eu.kanade.tachiyomi.ui.history
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.prefs.asState
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.history.interactor.GetHistory
import eu.kanade.domain.history.interactor.GetNextChapters
@ -37,15 +34,11 @@ class HistoryScreenModel(
private val getHistory: GetHistory = Injekt.get(),
private val getNextChapters: GetNextChapters = Injekt.get(),
private val removeHistory: RemoveHistory = Injekt.get(),
preferences: BasePreferences = Injekt.get(),
) : StateScreenModel<HistoryState>(HistoryState()) {
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
val events: Flow<Event> = _events.receiveAsFlow()
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
init {
coroutineScope.launch {
state.map { it.searchQuery }

@ -62,8 +62,6 @@ object HistoryTab : Tab {
HistoryScreen(
state = state,
snackbarHostState = snackbarHostState,
incognitoMode = screenModel.isIncognitoMode,
downloadedOnlyMode = screenModel.isDownloadOnly,
onSearchQueryChange = screenModel::updateSearchQuery,
onClickCover = { navigator.push(MangaScreen(it)) },
onClickResume = screenModel::getNextChapterForManga,

@ -89,9 +89,6 @@ class LibraryScreenModel(
var activeCategoryIndex: Int by libraryPreferences.lastUsedCategory().asState(coroutineScope)
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState(coroutineScope)
val isIncognitoMode: Boolean by preferences.incognitoMode().asState(coroutineScope)
init {
coroutineScope.launchIO {
combine(

@ -112,8 +112,6 @@ object LibraryTab : Tab {
hasActiveFilters = state.hasActiveFilters,
selectedCount = state.selection.size,
title = title,
incognitoMode = !tabVisible && screenModel.isIncognitoMode,
downloadedOnlyMode = !tabVisible && screenModel.isDownloadOnly,
onClickUnselectAll = screenModel::clearSelection,
onClickSelectAll = { screenModel.selectAll(screenModel.activeCategoryIndex) },
onClickInvertSelection = { screenModel.invertSelection(screenModel.activeCategoryIndex) },
@ -197,10 +195,7 @@ object LibraryTab : Tab {
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
getDisplayModeForPage = { state.categories[it].display },
getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) },
getLibraryForPage = { state.getLibraryItemsByPage(it) },
isDownloadOnly = screenModel.isDownloadOnly,
isIncognitoMode = screenModel.isIncognitoMode,
)
) { state.getLibraryItemsByPage(it) }
}
}
}

@ -10,6 +10,12 @@ import android.view.View
import android.view.Window
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -20,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.animation.doOnEnd
@ -36,12 +43,14 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.transitions.ScreenTransition
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.util.Transition
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.BuildConfig
@ -142,47 +151,73 @@ class MainActivity : BaseActivity() {
.launchIn(lifecycleScope)
setComposeContent {
Navigator(
screen = HomeScreen,
disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = true),
) { navigator ->
if (navigator.size == 1) {
ConfirmExit()
val incognito by preferences.incognitoMode().collectAsState()
val download by preferences.downloadedOnly().collectAsState()
Column {
AppStateBanners(
downloadedOnlyMode = download,
incognitoMode = incognito,
)
val systemUiController = rememberSystemUiController()
val active = incognito || download
val useDarkIcons = if (isSystemInDarkTheme()) active else !active
LaunchedEffect(systemUiController, useDarkIcons) {
systemUiController.setStatusBarColor(
color = androidx.compose.ui.graphics.Color.Transparent,
darkIcons = useDarkIcons,
)
}
LaunchedEffect(navigator) {
this@MainActivity.navigator = navigator
if (savedInstanceState == null) {
// Set start screen
handleIntentAction(intent)
// Reset Incognito Mode on relaunch
preferences.incognitoMode().set(false)
Navigator(
screen = HomeScreen,
disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = true),
) { navigator ->
if (navigator.size == 1) {
ConfirmExit()
}
}
// Shows current screen
ScreenTransition(navigator = navigator, transition = { Transition.OneWayFade })
LaunchedEffect(navigator) {
this@MainActivity.navigator = navigator
// Pop source-related screens when incognito mode is turned off
LaunchedEffect(Unit) {
preferences.incognitoMode().changes()
.drop(1)
.onEach {
if (!it) {
val currentScreen = navigator.lastItem
if (currentScreen is BrowseSourceScreen ||
(currentScreen is MangaScreen && currentScreen.fromSource)
) {
navigator.popUntilRoot()
if (savedInstanceState == null) {
// Set start screen
handleIntentAction(intent)
// Reset Incognito Mode on relaunch
preferences.incognitoMode().set(false)
}
}
// Consume insets already used by app state banners
val boxModifier = if (incognito || download) {
Modifier.consumeWindowInsets(WindowInsets.statusBars)
} else {
Modifier
}
Box(modifier = boxModifier) {
// Shows current screen
ScreenTransition(navigator = navigator, transition = { Transition.OneWayFade })
}
// Pop source-related screens when incognito mode is turned off
LaunchedEffect(Unit) {
preferences.incognitoMode().changes()
.drop(1)
.onEach {
if (!it) {
val currentScreen = navigator.lastItem
if (currentScreen is BrowseSourceScreen ||
(currentScreen is MangaScreen && currentScreen.fromSource)
) {
navigator.popUntilRoot()
}
}
}
}
.launchIn(this)
}
.launchIn(this)
}
CheckForUpdate()
CheckForUpdate()
}
}
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }

@ -10,7 +10,6 @@ import eu.kanade.core.prefs.CheckboxState
import eu.kanade.core.prefs.mapAsCheckboxState
import eu.kanade.core.util.addOrRemove
import eu.kanade.data.chapter.NoChaptersException
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category
@ -53,7 +52,6 @@ import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.toRelativeString
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.preference.asHotFlow
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.system.logcat
@ -64,7 +62,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
@ -81,7 +78,6 @@ class MangaInfoScreenModel(
val context: Context,
val mangaId: Long,
private val isFromSource: Boolean,
basePreferences: BasePreferences = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val uiPreferences: UiPreferences = Injekt.get(),
@ -130,17 +126,6 @@ class MangaInfoScreenModel(
mutableState.update { if (it is MangaScreenState.Success) func(it) else it }
}
private var incognitoMode = false
set(value) {
updateSuccessState { it.copy(isIncognitoMode = value) }
field = value
}
private var downloadedOnlyMode = false
set(value) {
updateSuccessState { it.copy(isDownloadedOnlyMode = value) }
field = value
}
init {
val toChapterItemsParams: List<Chapter>.(manga: Manga) -> List<ChapterItem> = { manga ->
toChapterItems(
@ -189,8 +174,6 @@ class MangaInfoScreenModel(
isFromSource = isFromSource,
chapters = chapters,
isRefreshingData = needRefreshInfo || needRefreshChapter,
isIncognitoMode = incognitoMode,
isDownloadedOnlyMode = downloadedOnlyMode,
dialog = null,
)
}
@ -210,14 +193,6 @@ class MangaInfoScreenModel(
// Initial loading finished
updateSuccessState { it.copy(isRefreshingData = false) }
}
basePreferences.incognitoMode()
.asHotFlow { incognitoMode = it }
.launchIn(coroutineScope)
basePreferences.downloadedOnly()
.asHotFlow { downloadedOnlyMode = it }
.launchIn(coroutineScope)
}
fun fetchAllFromSource(manualFetch: Boolean = true) {
@ -1037,8 +1012,6 @@ sealed class MangaScreenState {
val chapters: List<ChapterItem>,
val trackItems: List<TrackItem> = emptyList(),
val isRefreshingData: Boolean = false,
val isIncognitoMode: Boolean = false,
val isDownloadedOnlyMode: Boolean = false,
val dialog: MangaInfoScreenModel.Dialog? = null,
) : MangaScreenState() {

@ -11,7 +11,6 @@ import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.prefs.asState
import eu.kanade.core.util.addOrRemove
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.UpdateChapter
@ -62,16 +61,12 @@ class UpdatesScreenModel(
private val getChapter: GetChapter = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
basePreferences: BasePreferences = Injekt.get(),
uiPreferences: UiPreferences = Injekt.get(),
) : StateScreenModel<UpdatesState>(UpdatesState()) {
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
val events: Flow<Event> = _events.receiveAsFlow()
val isDownloadOnly: Boolean by basePreferences.downloadedOnly().asState(coroutineScope)
val isIncognitoMode: Boolean by basePreferences.incognitoMode().asState(coroutineScope)
val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState(coroutineScope)
val relativeTime: Int by uiPreferences.relativeTime().asState(coroutineScope)

@ -56,8 +56,6 @@ object UpdatesTab : Tab {
UpdateScreen(
state = state,
snackbarHostState = screenModel.snackbarHostState,
incognitoMode = screenModel.isIncognitoMode,
downloadedOnlyMode = screenModel.isDownloadOnly,
lastUpdated = screenModel.lastUpdated,
relativeTime = screenModel.relativeTime,
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },

@ -23,4 +23,5 @@ material-core = { module = "androidx.compose.material:material", version = "1.4.
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
accompanist-themeadapter = { module = "com.google.accompanist:accompanist-themeadapter-material3", version.ref = "accompanist" }
accompanist-themeadapter = { module = "com.google.accompanist:accompanist-themeadapter-material3", version.ref = "accompanist" }
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }