Move app state banner to the very top (#8706)

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
24 changed files with 217 additions and 295 deletions

View File

@@ -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)
}

View File

@@ -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) },

View File

@@ -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
/**

View File

@@ -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 }

View File

@@ -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,

View File

@@ -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(

View File

@@ -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) }
}
}
}

View File

@@ -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) }

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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)) },