Merge Voyager screens (#8656)
* Merge Voyager screens * cleanups (cherry picked from commit 3d66eaea8373b6ab5d8e8423be227e0452cb0743) # Conflicts: # app/src/main/java/eu/kanade/presentation/components/MangaBottomActionMenu.kt # app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesFilterController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt # app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt # app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt # app/src/main/res/layout/main_activity.xml
This commit is contained in:
parent
07c7ec972d
commit
726626f2c5
@ -264,15 +264,12 @@ dependencies {
|
|||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
implementation(libs.insetter)
|
||||||
implementation(libs.markwon)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.cascade)
|
implementation(libs.cascade)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.wheelpicker)
|
implementation(libs.wheelpicker)
|
||||||
|
|
||||||
// Conductor
|
|
||||||
implementation(libs.conductor)
|
|
||||||
|
|
||||||
// FlowBinding
|
// FlowBinding
|
||||||
implementation(libs.flowbinding.android)
|
implementation(libs.flowbinding.android)
|
||||||
|
|
||||||
@ -294,9 +291,6 @@ dependencies {
|
|||||||
implementation(libs.leakcanary.plumber)
|
implementation(libs.leakcanary.plumber)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
// Changelog
|
|
||||||
implementation(sylibs.changelog)
|
|
||||||
|
|
||||||
// Text distance (EH)
|
// Text distance (EH)
|
||||||
implementation (sylibs.simularity)
|
implementation (sylibs.simularity)
|
||||||
|
|
||||||
@ -350,6 +344,7 @@ tasks {
|
|||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.components
|
|||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@ -16,10 +17,10 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.BookmarkAdd
|
import androidx.compose.material.icons.outlined.BookmarkAdd
|
||||||
@ -100,7 +101,11 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues())
|
.padding(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.only(WindowInsetsSides.Bottom)
|
||||||
|
.asPaddingValues(),
|
||||||
|
)
|
||||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
) {
|
) {
|
||||||
if (onBookmarkClicked != null) {
|
if (onBookmarkClicked != null) {
|
||||||
@ -218,11 +223,11 @@ private fun RowScope.Button(
|
|||||||
fun LibraryBottomActionMenu(
|
fun LibraryBottomActionMenu(
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onChangeCategoryClicked: (() -> Unit)?,
|
onChangeCategoryClicked: () -> Unit,
|
||||||
onMarkAsReadClicked: (() -> Unit)?,
|
onMarkAsReadClicked: () -> Unit,
|
||||||
onMarkAsUnreadClicked: (() -> Unit)?,
|
onMarkAsUnreadClicked: () -> Unit,
|
||||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||||
onDeleteClicked: (() -> Unit)?,
|
onDeleteClicked: () -> Unit,
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickCleanTitles: (() -> Unit)?,
|
onClickCleanTitles: (() -> Unit)?,
|
||||||
onClickMigrate: (() -> Unit)?,
|
onClickMigrate: (() -> Unit)?,
|
||||||
@ -231,8 +236,8 @@ fun LibraryBottomActionMenu(
|
|||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = expandVertically(expandFrom = Alignment.Bottom),
|
enter = expandVertically(animationSpec = tween(delayMillis = 300)),
|
||||||
exit = shrinkVertically(shrinkTowards = Alignment.Bottom),
|
exit = shrinkVertically(animationSpec = tween()),
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
Surface(
|
Surface(
|
||||||
@ -260,18 +265,19 @@ fun LibraryBottomActionMenu(
|
|||||||
// SY <--
|
// SY <--
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.navigationBarsPadding()
|
.windowInsetsPadding(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.only(WindowInsetsSides.Bottom),
|
||||||
|
)
|
||||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
) {
|
) {
|
||||||
if (onChangeCategoryClicked != null) {
|
Button(
|
||||||
Button(
|
title = stringResource(R.string.action_move_category),
|
||||||
title = stringResource(R.string.action_move_category),
|
icon = Icons.Outlined.Label,
|
||||||
icon = Icons.Outlined.Label,
|
toConfirm = confirm[0],
|
||||||
toConfirm = confirm[0],
|
onLongClick = { onLongClickItem(0) },
|
||||||
onLongClick = { onLongClickItem(0) },
|
onClick = onChangeCategoryClicked,
|
||||||
onClick = onChangeCategoryClicked,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
if (onDownloadClicked != null) {
|
if (onDownloadClicked != null) {
|
||||||
var downloadExpanded by remember { mutableStateOf(false) }
|
var downloadExpanded by remember { mutableStateOf(false) }
|
||||||
Button(
|
Button(
|
||||||
@ -290,15 +296,13 @@ fun LibraryBottomActionMenu(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onDeleteClicked != null) {
|
Button(
|
||||||
Button(
|
title = stringResource(R.string.action_delete),
|
||||||
title = stringResource(R.string.action_delete),
|
icon = Icons.Outlined.Delete,
|
||||||
icon = Icons.Outlined.Delete,
|
toConfirm = confirm[4],
|
||||||
toConfirm = confirm[4],
|
onLongClick = { onLongClickItem(4) },
|
||||||
onLongClick = { onLongClickItem(4) },
|
onClick = onDeleteClicked,
|
||||||
onClick = onDeleteClicked,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
// SY -->
|
// SY -->
|
||||||
if (onMarkAsReadClicked != null) {
|
if (onMarkAsReadClicked != null) {
|
||||||
Button(
|
Button(
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
|
import androidx.compose.material3.contentColorFor
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M3 Navbar with no horizontal spacer
|
||||||
|
*
|
||||||
|
* @see [androidx.compose.material3.NavigationBar]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NavigationBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
containerColor: Color = NavigationBarDefaults.containerColor,
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
|
||||||
|
tonalElevation: Dp = NavigationBarDefaults.Elevation,
|
||||||
|
windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
|
||||||
|
content: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Surface(
|
||||||
|
color = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.windowInsetsPadding(windowInsets)
|
||||||
|
.height(80.dp)
|
||||||
|
.selectableGroup(),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material3.NavigationRailDefaults
|
||||||
|
import androidx.compose.material3.contentColorFor
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center-aligned M3 Navigation rail
|
||||||
|
*
|
||||||
|
* @see [androidx.compose.material3.NavigationRail]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NavigationRail(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
containerColor: Color = NavigationRailDefaults.ContainerColor,
|
||||||
|
contentColor: Color = contentColorFor(containerColor),
|
||||||
|
header: @Composable (ColumnScope.() -> Unit)? = null,
|
||||||
|
windowInsets: WindowInsets = NavigationRailDefaults.windowInsets,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Surface(
|
||||||
|
color = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
modifier = modifier,
|
||||||
|
tonalElevation = 3.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.windowInsetsPadding(windowInsets)
|
||||||
|
.widthIn(min = 80.dp)
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.selectableGroup(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically),
|
||||||
|
) {
|
||||||
|
if (header != null) {
|
||||||
|
header()
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.MutableWindowInsets
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.exclude
|
||||||
|
import androidx.compose.foundation.layout.withConsumedWindowInsets
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
import androidx.compose.material3.ScaffoldDefaults
|
||||||
@ -31,6 +34,7 @@ import androidx.compose.material3.rememberTopAppBarState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@ -67,6 +71,7 @@ import kotlin.math.max
|
|||||||
* * Remove height constraint for expanded app bar
|
* * Remove height constraint for expanded app bar
|
||||||
* * Also take account of fab height when providing inner padding
|
* * Also take account of fab height when providing inner padding
|
||||||
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
|
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
|
||||||
|
* * Handle consumed window insets
|
||||||
*
|
*
|
||||||
* @param modifier the [Modifier] to be applied to this scaffold
|
* @param modifier the [Modifier] to be applied to this scaffold
|
||||||
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
||||||
@ -103,9 +108,12 @@ fun Scaffold(
|
|||||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
// Tachiyomi: Handle consumed window insets
|
||||||
|
val remainingWindowInsets = remember { MutableWindowInsets() }
|
||||||
androidx.compose.material3.Surface(
|
androidx.compose.material3.Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
||||||
|
.withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) }
|
||||||
.then(modifier),
|
.then(modifier),
|
||||||
color = containerColor,
|
color = containerColor,
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
@ -116,7 +124,7 @@ fun Scaffold(
|
|||||||
bottomBar = bottomBar,
|
bottomBar = bottomBar,
|
||||||
content = content,
|
content = content,
|
||||||
snackbar = snackbarHost,
|
snackbar = snackbarHost,
|
||||||
contentWindowInsets = contentWindowInsets,
|
contentWindowInsets = remainingWindowInsets,
|
||||||
fab = floatingActionButton,
|
fab = floatingActionButton,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -88,9 +87,7 @@ fun TabbedScreen(
|
|||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
) { page ->
|
) { page ->
|
||||||
tabs[page].content(
|
tabs[page].content(
|
||||||
TachiyomiBottomNavigationView.withBottomNavPadding(
|
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||||
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
|
||||||
),
|
|
||||||
snackbarHostState,
|
snackbarHostState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -21,7 +20,6 @@ import eu.kanade.presentation.history.components.HistoryContent
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryState
|
import eu.kanade.tachiyomi.ui.history.HistoryState
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -55,7 +53,6 @@ fun HistoryScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
state.list.let {
|
state.list.let {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package eu.kanade.presentation.more
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.CloudOff
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
import androidx.compose.material.icons.outlined.GetApp
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
@ -32,8 +29,7 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MoreScreen(
|
fun MoreScreen(
|
||||||
@ -60,10 +56,7 @@ fun MoreScreen(
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier.systemBarsPadding(),
|
||||||
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(
|
|
||||||
WindowInsets.navigationBars.asPaddingValues(),
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
if (isFDroid) {
|
if (isFDroid) {
|
||||||
item {
|
item {
|
||||||
@ -209,7 +202,7 @@ fun MoreScreen(
|
|||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_help),
|
title = stringResource(R.string.label_help),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onPreferenceClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
144
app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt
Normal file
144
app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.OpenInNew
|
||||||
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import com.halilibo.richtext.markdown.Markdown
|
||||||
|
import com.halilibo.richtext.ui.RichTextStyle
|
||||||
|
import com.halilibo.richtext.ui.material3.Material3RichText
|
||||||
|
import com.halilibo.richtext.ui.string.RichTextStringStyle
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.util.padding
|
||||||
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewUpdateScreen(
|
||||||
|
versionName: String,
|
||||||
|
changelogInfo: String,
|
||||||
|
onOpenInBrowser: () -> Unit,
|
||||||
|
onRejectUpdate: () -> Unit,
|
||||||
|
onAcceptUpdate: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
val strokeWidth = Dp.Hairline
|
||||||
|
val borderColor = MaterialTheme.colorScheme.outline
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.drawBehind {
|
||||||
|
drawLine(
|
||||||
|
borderColor,
|
||||||
|
Offset(0f, 0f),
|
||||||
|
Offset(size.width, 0f),
|
||||||
|
strokeWidth.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.windowInsetsPadding(NavigationBarDefaults.windowInsets)
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onAcceptUpdate,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.update_check_confirm))
|
||||||
|
}
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onRejectUpdate,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_not_now))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
// Status bar scrim
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(2f)
|
||||||
|
.secondaryItemAlpha()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(paddingValues.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(top = 48.dp)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.NewReleases,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = MaterialTheme.padding.small)
|
||||||
|
.size(48.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.update_check_notification_update_available),
|
||||||
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = versionName,
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
|
||||||
|
Material3RichText(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = MaterialTheme.padding.large),
|
||||||
|
style = RichTextStyle(
|
||||||
|
stringStyle = RichTextStringStyle(
|
||||||
|
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Markdown(content = changelogInfo)
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = onOpenInBrowser,
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.update_check_open))
|
||||||
|
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
|
||||||
|
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,11 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -19,7 +23,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.LinkIcon
|
import eu.kanade.presentation.components.LinkIcon
|
||||||
@ -29,13 +32,11 @@ import eu.kanade.presentation.more.LogoHeader
|
|||||||
import eu.kanade.presentation.more.about.LicensesScreen
|
import eu.kanade.presentation.more.about.LicensesScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||||
import eu.kanade.tachiyomi.ui.main.WhatsNewDialogController
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
|
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
@ -63,7 +64,10 @@ object AboutScreen : Screen {
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val handleBack = LocalBackPress.current
|
val handleBack = LocalBackPress.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
|
// SY -->
|
||||||
|
var showWhatsNewDialog by remember { mutableStateOf(false) }
|
||||||
|
// SY <--
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
@ -98,7 +102,15 @@ object AboutScreen : Screen {
|
|||||||
title = stringResource(R.string.check_for_updates),
|
title = stringResource(R.string.check_for_updates),
|
||||||
onPreferenceClick = {
|
onPreferenceClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
checkVersion(context, router)
|
checkVersion(context) { result ->
|
||||||
|
val updateScreen = NewUpdateScreen(
|
||||||
|
versionName = result.release.version,
|
||||||
|
changelogInfo = result.release.info,
|
||||||
|
releaseLink = result.release.releaseLink,
|
||||||
|
downloadLink = result.release.getDownloadLink(),
|
||||||
|
)
|
||||||
|
navigator.push(updateScreen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -109,7 +121,7 @@ object AboutScreen : Screen {
|
|||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.whats_new),
|
title = stringResource(R.string.whats_new),
|
||||||
// SY -->
|
// SY -->
|
||||||
onPreferenceClick = { WhatsNewDialogController().showDialog(router) },
|
onPreferenceClick = { showWhatsNewDialog = true },
|
||||||
// SY <--
|
// SY <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -179,19 +191,25 @@ object AboutScreen : Screen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
if (showWhatsNewDialog) {
|
||||||
|
WhatsNewDialog(onDismissRequest = { showWhatsNewDialog = false })
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks version and shows a user prompt if an update is available.
|
* Checks version and shows a user prompt if an update is available.
|
||||||
*/
|
*/
|
||||||
private suspend fun checkVersion(context: Context, router: Router) {
|
private suspend fun checkVersion(context: Context, onAvailableUpdate: (AppUpdateResult.NewUpdate) -> Unit) {
|
||||||
val updateChecker = AppUpdateChecker()
|
val updateChecker = AppUpdateChecker()
|
||||||
withUIContext {
|
withUIContext {
|
||||||
context.toast(R.string.update_check_look_for_updates)
|
context.toast(R.string.update_check_look_for_updates)
|
||||||
try {
|
try {
|
||||||
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
|
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
|
||||||
is AppUpdateResult.NewUpdate -> {
|
is AppUpdateResult.NewUpdate -> {
|
||||||
NewUpdateDialogController(result).showDialog(router)
|
onAvailableUpdate(result)
|
||||||
}
|
}
|
||||||
is AppUpdateResult.NoNewUpdate -> {
|
is AppUpdateResult.NoNewUpdate -> {
|
||||||
context.toast(R.string.update_check_no_new_updates)
|
context.toast(R.string.update_check_no_new_updates)
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import exh.log.xLogE
|
||||||
|
import exh.uconfig.EHConfigurator
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
|
||||||
|
val unsortedPreferences = remember {
|
||||||
|
Injekt.get<UnsortedPreferences>()
|
||||||
|
}
|
||||||
|
var warnDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
var configureDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
var configureFailedDialogOpen by remember { mutableStateOf<Exception?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(run) {
|
||||||
|
if (run) {
|
||||||
|
if (unsortedPreferences.exhShowSettingsUploadWarning().get()) {
|
||||||
|
warnDialogOpen = true
|
||||||
|
} else {
|
||||||
|
configureDialogOpen = true
|
||||||
|
}
|
||||||
|
onRunning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnDialogOpen) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { warnDialogOpen = false },
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = false,
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
),
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
unsortedPreferences.exhShowSettingsUploadWarning().set(false)
|
||||||
|
configureDialogOpen = true
|
||||||
|
warnDialogOpen = false
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.settings_profile_note))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.settings_profile_note_message))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (configureDialogOpen) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
withContext(Dispatchers.IO + NonCancellable) {
|
||||||
|
try {
|
||||||
|
EHConfigurator(context).configureAll()
|
||||||
|
launchUI {
|
||||||
|
context.toast(R.string.eh_settings_successfully_uploaded)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
configureFailedDialogOpen = e
|
||||||
|
xLogE("Configuration error!", e)
|
||||||
|
} finally {
|
||||||
|
configureDialogOpen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = false,
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
),
|
||||||
|
confirmButton = {},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.eh_settings_uploading_to_server))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.eh_settings_uploading_to_server_message))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (configureFailedDialogOpen != null) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { configureFailedDialogOpen = null },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { configureFailedDialogOpen = null }) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.eh_settings_configuration_failed))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.eh_settings_configuration_failed_message, configureFailedDialogOpen?.message.orEmpty()))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,6 @@ import eu.kanade.domain.manga.interactor.GetAllManga
|
|||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
import eu.kanade.domain.manga.repository.MangaRepository
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
@ -71,7 +70,6 @@ import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
|
|||||||
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
|
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
|
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||||
@ -82,7 +80,7 @@ import eu.kanade.tachiyomi.util.system.logcat
|
|||||||
import eu.kanade.tachiyomi.util.system.powerManager
|
import eu.kanade.tachiyomi.util.system.powerManager
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.debug.SettingsDebugController
|
import exh.debug.SettingsDebugScreen
|
||||||
import exh.log.EHLogLevel
|
import exh.log.EHLogLevel
|
||||||
import exh.pref.DelegateSourcePreferences
|
import exh.pref.DelegateSourcePreferences
|
||||||
import exh.source.BlacklistedSources
|
import exh.source.BlacklistedSources
|
||||||
@ -673,7 +671,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun getDeveloperToolsGroup(): Preference.PreferenceGroup {
|
private fun getDeveloperToolsGroup(): Preference.PreferenceGroup {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
||||||
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||||
val delegateSourcePreferences = remember { Injekt.get<DelegateSourcePreferences>() }
|
val delegateSourcePreferences = remember { Injekt.get<DelegateSourcePreferences>() }
|
||||||
@ -729,7 +727,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
HtmlCompat.fromHtml(context.getString(R.string.open_debug_menu_summary), HtmlCompat.FROM_HTML_MODE_COMPACT)
|
HtmlCompat.fromHtml(context.getString(R.string.open_debug_menu_summary), HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||||
.toAnnotatedString()
|
.toAnnotatedString()
|
||||||
},
|
},
|
||||||
onClick = { router.pushController(SettingsDebugController()) },
|
onClick = { navigator.push(SettingsDebugScreen()) },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -9,19 +9,18 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.pluralStringResource
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
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.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.category.repos.RepoScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.repos.RepoController
|
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryController
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -48,13 +47,13 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
title = stringResource(R.string.label_sources),
|
title = stringResource(R.string.label_sources),
|
||||||
preferenceItems = listOf(
|
preferenceItems = listOf(
|
||||||
kotlin.run {
|
kotlin.run {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val count by sourcePreferences.sourcesTabCategories().collectAsState()
|
val count by sourcePreferences.sourcesTabCategories().collectAsState()
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.action_edit_categories),
|
title = stringResource(R.string.action_edit_categories),
|
||||||
subtitle = pluralStringResource(R.plurals.num_categories, count.size, count.size),
|
subtitle = pluralStringResource(R.plurals.num_categories, count.size, count.size),
|
||||||
onClick = {
|
onClick = {
|
||||||
router.pushController(SourceCategoryController())
|
navigator.push(SourceCategoryScreen())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -99,13 +98,13 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
kotlin.run {
|
kotlin.run {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val count by unsortedPreferences.extensionRepos().collectAsState()
|
val count by unsortedPreferences.extensionRepos().collectAsState()
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.action_edit_repos),
|
title = stringResource(R.string.action_edit_repos),
|
||||||
subtitle = pluralStringResource(R.plurals.num_repos, count.size, count.size),
|
subtitle = pluralStringResource(R.plurals.num_repos, count.size, count.size),
|
||||||
onClick = {
|
onClick = {
|
||||||
router.pushController(RepoController())
|
navigator.push(RepoScreen())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -46,13 +46,11 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
|
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
|
||||||
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
|
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
|
||||||
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
|
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
|
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
|
||||||
@ -68,7 +66,6 @@ import exh.eh.EHentaiUpdateWorkerConstants
|
|||||||
import exh.eh.EHentaiUpdaterStats
|
import exh.eh.EHentaiUpdaterStats
|
||||||
import exh.favorites.FavoritesIntroDialog
|
import exh.favorites.FavoritesIntroDialog
|
||||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||||
import exh.uconfig.WarnConfigureDialogController
|
|
||||||
import exh.ui.login.EhLoginActivity
|
import exh.ui.login.EhLoginActivity
|
||||||
import exh.util.nullIfBlank
|
import exh.util.nullIfBlank
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@ -126,18 +123,18 @@ object SettingsEhScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val openWarnConfigureDialogController = {
|
|
||||||
WarnConfigureDialogController.uploadSettings(router)
|
|
||||||
}
|
|
||||||
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() }
|
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() }
|
||||||
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
|
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
|
||||||
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
|
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
|
||||||
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
|
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
|
||||||
val exhentaiEnabled by unsortedPreferences.enableExhentai().collectAsState()
|
val exhentaiEnabled by unsortedPreferences.enableExhentai().collectAsState()
|
||||||
|
var runConfigureDialog by remember { mutableStateOf(false) }
|
||||||
|
val openWarnConfigureDialogController = { runConfigureDialog = true }
|
||||||
|
|
||||||
Reconfigure(unsortedPreferences, openWarnConfigureDialogController)
|
Reconfigure(unsortedPreferences, openWarnConfigureDialogController)
|
||||||
|
|
||||||
|
ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false })
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
stringResource(R.string.ehentai_prefs_account_settings),
|
stringResource(R.string.ehentai_prefs_account_settings),
|
||||||
|
@ -26,15 +26,14 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.collectAsState
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesController
|
import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -98,7 +97,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
// SY -->
|
// SY -->
|
||||||
kotlin.run {
|
kotlin.run {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.action_edit_biometric_lock_times),
|
title = stringResource(R.string.action_edit_biometric_lock_times),
|
||||||
@ -108,7 +107,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
count.size,
|
count.size,
|
||||||
),
|
),
|
||||||
onClick = {
|
onClick = {
|
||||||
router.pushController(BiometricTimesController())
|
navigator.push(BiometricTimesScreen())
|
||||||
},
|
},
|
||||||
enabled = useAuth,
|
enabled = useAuth,
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.presentation.components.Divider
|
||||||
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import nl.adaptivity.xmlutil.AndroidXmlReader
|
||||||
|
import nl.adaptivity.xmlutil.serialization.XML
|
||||||
|
import nl.adaptivity.xmlutil.serialization.XmlSerialName
|
||||||
|
import nl.adaptivity.xmlutil.serialization.XmlValue
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WhatsNewDialog(onDismissRequest: () -> Unit) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(R.string.whats_new))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val changelog by produceState<List<DisplayChangelog>?>(initialValue = null) {
|
||||||
|
value = withIOContext {
|
||||||
|
XML.decodeFromReader<Changelog>(
|
||||||
|
AndroidXmlReader(
|
||||||
|
context.resources.openRawResource(
|
||||||
|
if (isPreviewBuildType) R.raw.changelog_debug else R.raw.changelog_release,
|
||||||
|
).bufferedReader(),
|
||||||
|
),
|
||||||
|
).toDisplayChangelog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changelog != null) {
|
||||||
|
LazyColumn(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
items(changelog.orEmpty()) { changelog ->
|
||||||
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.changelog_version, changelog.version),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
Divider(Modifier.padding(vertical = 8.dp))
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
changelog.changelog.forEach {
|
||||||
|
Text(text = it, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DisplayChangelog(
|
||||||
|
val version: String,
|
||||||
|
val changelog: List<AnnotatedString>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("changelog", "", "")
|
||||||
|
data class Changelog(
|
||||||
|
val bulletedList: Boolean,
|
||||||
|
val changelogs: List<ChangelogVersion>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("changelogversion", "", "")
|
||||||
|
data class ChangelogVersion(
|
||||||
|
val versionName: String,
|
||||||
|
val changeDate: String,
|
||||||
|
val text: List<ChangelogText>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@XmlSerialName("changelogtext", "", "")
|
||||||
|
data class ChangelogText(
|
||||||
|
@XmlValue(true) val value: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val bullet = "\u2022"
|
||||||
|
|
||||||
|
fun Changelog.toDisplayChangelog(): List<DisplayChangelog> {
|
||||||
|
val prefix = if (bulletedList) bullet + "\t\t" else ""
|
||||||
|
return changelogs.map { version ->
|
||||||
|
DisplayChangelog(
|
||||||
|
version = version.versionName,
|
||||||
|
changelog = version.text.mapIndexed { index, changelogText ->
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(prefix)
|
||||||
|
var inBBCode = false
|
||||||
|
var isEscape = false
|
||||||
|
changelogText.value.forEachIndexed { charIndex, c ->
|
||||||
|
if (!inBBCode && c == '[') {
|
||||||
|
inBBCode = true
|
||||||
|
} else if (inBBCode && c == ']') {
|
||||||
|
inBBCode = false
|
||||||
|
isEscape = false
|
||||||
|
} else if (inBBCode && c == '/') {
|
||||||
|
isEscape = true
|
||||||
|
} else if (inBBCode && c == 'b') {
|
||||||
|
if (isEscape) {
|
||||||
|
try {
|
||||||
|
pop()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
throw Exception("Exception on ${version.versionName}:$index:$charIndex", e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
append(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,6 @@ import androidx.compose.material.icons.outlined.Refresh
|
|||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@ -36,7 +35,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesState
|
import eu.kanade.tachiyomi.ui.updates.UpdatesState
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@ -87,7 +85,6 @@ fun UpdateScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package eu.kanade.presentation.util
|
package eu.kanade.presentation.util
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
/**
|
|
||||||
* For interop with Conductor
|
|
||||||
*/
|
|
||||||
val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf { null }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For invoking back press to the parent activity
|
* For invoking back press to the parent activity
|
||||||
@ -17,3 +13,12 @@ val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf
|
|||||||
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
||||||
|
|
||||||
val LocalNavigatorContentPadding: ProvidableCompositionLocal<PaddingValues> = compositionLocalOf { PaddingValues() }
|
val LocalNavigatorContentPadding: ProvidableCompositionLocal<PaddingValues> = compositionLocalOf { PaddingValues() }
|
||||||
|
|
||||||
|
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
|
||||||
|
suspend fun onReselect(navigator: Navigator) {}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
@Composable
|
||||||
|
fun isEnabled(): Boolean = true
|
||||||
|
// SY <--
|
||||||
|
}
|
||||||
|
@ -23,8 +23,8 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
@ -457,7 +457,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
val newIntent =
|
val newIntent =
|
||||||
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
.putExtra(MangaController.MANGA_EXTRA, manga.id)
|
.putExtra(Constants.MANGA_EXTRA, manga.id)
|
||||||
.putExtra("notificationId", manga.id.hashCode())
|
.putExtra("notificationId", manga.id.hashCode())
|
||||||
.putExtra("groupId", groupId)
|
.putExtra("groupId", groupId)
|
||||||
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
@ -48,7 +48,7 @@ import eu.kanade.domain.manga.model.MangaCover
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
@ -136,7 +136,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||||||
) {
|
) {
|
||||||
val intent = Intent(LocalContext.current, MainActivity::class.java).apply {
|
val intent = Intent(LocalContext.current, MainActivity::class.java).apply {
|
||||||
action = MainActivity.SHORTCUT_MANGA
|
action = MainActivity.SHORTCUT_MANGA
|
||||||
putExtra(MangaController.MANGA_EXTRA, mangaId)
|
putExtra(Constants.MANGA_EXTRA, mangaId)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.changehandler
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorSet
|
|
||||||
import android.animation.ObjectAnimator
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
|
||||||
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler
|
|
||||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An [AnimatorChangeHandler] that will remove the from view and fade in the to view
|
|
||||||
*/
|
|
||||||
class OneWayFadeChangeHandler : FadeChangeHandler {
|
|
||||||
constructor()
|
|
||||||
constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush)
|
|
||||||
constructor(duration: Long) : super(duration)
|
|
||||||
constructor(duration: Long, removesFromViewOnPush: Boolean) : super(
|
|
||||||
duration,
|
|
||||||
removesFromViewOnPush,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getAnimator(container: ViewGroup, from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator {
|
|
||||||
val animator = AnimatorSet()
|
|
||||||
if (to != null) {
|
|
||||||
val start: Float = if (toAddedToContainer) 0F else to.alpha
|
|
||||||
animator.play(ObjectAnimator.ofFloat(to, View.ALPHA, start, 1f))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from != null && (!isPush || removesFromViewOnPush())) {
|
|
||||||
from.alpha = 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
return animator
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun copy(): ControllerChangeHandler {
|
|
||||||
return OneWayFadeChangeHandler(animationDuration, removesFromViewOnPush())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.viewbinding.ViewBinding
|
|
||||||
import com.bluelinelabs.conductor.Controller
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.view.hideKeyboard
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
|
|
||||||
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) : Controller(bundle) {
|
|
||||||
|
|
||||||
protected lateinit var binding: VB
|
|
||||||
private set
|
|
||||||
|
|
||||||
lateinit var viewScope: CoroutineScope
|
|
||||||
|
|
||||||
init {
|
|
||||||
retainViewMode = RetainViewMode.RETAIN_DETACH
|
|
||||||
|
|
||||||
addLifecycleListener(
|
|
||||||
object : LifecycleListener() {
|
|
||||||
override fun postCreateView(controller: Controller, view: View) {
|
|
||||||
onViewCreated(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun preCreateView(controller: Controller) {
|
|
||||||
viewScope = MainScope()
|
|
||||||
logcat { "Create view for ${controller.instance()}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun preAttach(controller: Controller, view: View) {
|
|
||||||
logcat { "Attach view for ${controller.instance()}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun preDetach(controller: Controller, view: View) {
|
|
||||||
logcat { "Detach view for ${controller.instance()}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun preDestroyView(controller: Controller, view: View) {
|
|
||||||
viewScope.cancel()
|
|
||||||
logcat { "Destroy view for ${controller.instance()}" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun createBinding(inflater: LayoutInflater): VB
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
|
|
||||||
binding = createBinding(inflater)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun onViewCreated(view: View) {}
|
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
|
||||||
view?.hideKeyboard()
|
|
||||||
|
|
||||||
if (type.isEnter) {
|
|
||||||
setTitle()
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onChangeStarted(handler, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getTitle(): String? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTitle(title: String? = null) {
|
|
||||||
(activity as? AppCompatActivity)?.supportActionBar?.title = title ?: getTitle()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Controller.instance(): String {
|
|
||||||
return "${javaClass.simpleName}@${Integer.toHexString(hashCode())}"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.OnBackPressedDispatcherOwner
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
|
|
||||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic Compose controller without a presenter.
|
|
||||||
*/
|
|
||||||
abstract class BasicFullComposeController(bundle: Bundle? = null) :
|
|
||||||
BaseController<ComposeControllerBinding>(bundle),
|
|
||||||
ComposeContentController {
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater) =
|
|
||||||
ComposeControllerBinding.inflate(inflater)
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
binding.root.apply {
|
|
||||||
setComposeContent {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
ComposeContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let Compose view handle this
|
|
||||||
override fun handleBack(): Boolean {
|
|
||||||
val dispatcher = (activity as? OnBackPressedDispatcherOwner)?.onBackPressedDispatcher ?: return false
|
|
||||||
return if (dispatcher.hasEnabledCallbacks()) {
|
|
||||||
dispatcher.onBackPressed()
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ComposeContentController {
|
|
||||||
@Composable fun ComposeContent()
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.bluelinelabs.conductor.Controller
|
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
|
||||||
import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler
|
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
||||||
|
|
||||||
fun Router.setRoot(controller: Controller, id: Int) {
|
|
||||||
setRoot(controller.withFadeTransaction().tag(id.toString()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Router.pushController(controller: Controller) {
|
|
||||||
pushController(controller.withFadeTransaction())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Controller.withFadeTransaction(): RouterTransaction {
|
|
||||||
return RouterTransaction.with(this)
|
|
||||||
.pushChangeHandler(OneWayFadeChangeHandler())
|
|
||||||
.popChangeHandler(OneWayFadeChangeHandler())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Controller.openInBrowser(url: String) {
|
|
||||||
activity?.openInBrowser(url.toUri())
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import com.bluelinelabs.conductor.Controller
|
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
|
||||||
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A controller that displays a dialog window, floating on top of its activity's window.
|
|
||||||
* This is a wrapper over [Dialog] object like [android.app.DialogFragment].
|
|
||||||
*
|
|
||||||
* Implementations should override this class and implement [.onCreateDialog] to create a custom dialog, such as an [android.app.AlertDialog]
|
|
||||||
*/
|
|
||||||
abstract class DialogController : Controller {
|
|
||||||
|
|
||||||
protected var dialog: Dialog? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
private var dismissed = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience constructor for use when no arguments are needed.
|
|
||||||
*/
|
|
||||||
protected constructor() : super(null)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor that takes arguments that need to be retained across restarts.
|
|
||||||
*
|
|
||||||
* @param args Any arguments that need to be retained.
|
|
||||||
*/
|
|
||||||
protected constructor(args: Bundle?) : super(args)
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
|
|
||||||
dialog = onCreateDialog(savedViewState)
|
|
||||||
dialog!!.setOwnerActivity(activity!!)
|
|
||||||
dialog!!.setOnDismissListener { dismissDialog() }
|
|
||||||
if (savedViewState != null) {
|
|
||||||
val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG)
|
|
||||||
if (dialogState != null) {
|
|
||||||
dialog!!.onRestoreInstanceState(dialogState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return View(activity) // stub view
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveViewState(view: View, outState: Bundle) {
|
|
||||||
super.onSaveViewState(view, outState)
|
|
||||||
val dialogState = dialog!!.onSaveInstanceState()
|
|
||||||
outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(view: View) {
|
|
||||||
super.onAttach(view)
|
|
||||||
dialog!!.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach(view: View) {
|
|
||||||
super.onDetach(view)
|
|
||||||
dialog!!.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
super.onDestroyView(view)
|
|
||||||
dialog!!.setOnDismissListener(null)
|
|
||||||
dialog!!.dismiss()
|
|
||||||
dialog = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the dialog, create a transaction and pushing the controller.
|
|
||||||
* @param router The router on which the transaction will be applied
|
|
||||||
*/
|
|
||||||
open fun showDialog(router: Router) {
|
|
||||||
showDialog(router, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the dialog, create a transaction and pushing the controller.
|
|
||||||
* @param router The router on which the transaction will be applied
|
|
||||||
* @param tag The tag for this controller
|
|
||||||
*/
|
|
||||||
fun showDialog(router: Router, tag: String?) {
|
|
||||||
dismissed = false
|
|
||||||
router.pushController(
|
|
||||||
RouterTransaction.with(this)
|
|
||||||
.pushChangeHandler(SimpleSwapChangeHandler(false))
|
|
||||||
.popChangeHandler(SimpleSwapChangeHandler(false))
|
|
||||||
.tag(tag),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismiss the dialog and pop this controller
|
|
||||||
*/
|
|
||||||
fun dismissDialog() {
|
|
||||||
if (dismissed) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
router.popController(this)
|
|
||||||
dismissed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build your own custom Dialog container such as an [android.app.AlertDialog]
|
|
||||||
*
|
|
||||||
* @param savedViewState A bundle for the view's state, which would have been created in [.onSaveViewState] or `null` if no saved state exists.
|
|
||||||
* @return Return a new Dialog instance to be displayed by the Controller
|
|
||||||
*/
|
|
||||||
protected abstract fun onCreateDialog(savedViewState: Bundle?): Dialog
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val SAVED_DIALOG_STATE_TAG = "android:savedDialogState"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
interface RootController
|
|
@ -1,27 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
|
|
||||||
class BrowseController : BasicFullComposeController, RootController {
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(bundle: Bundle? = null) : this(bundle?.getBoolean(TO_EXTENSIONS_EXTRA) ?: false)
|
|
||||||
|
|
||||||
constructor(toExtensions: Boolean = false) : super(
|
|
||||||
bundleOf(TO_EXTENSIONS_EXTRA to toExtensions),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val toExtensions = args.getBoolean(TO_EXTENSIONS_EXTRA, false)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = BrowseScreen(toExtensions = toExtensions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val TO_EXTENSIONS_EXTRA = "to_extensions"
|
|
@ -1,18 +1,24 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
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.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.core.prefs.asState
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.TabbedScreen
|
import eu.kanade.presentation.components.TabbedScreen
|
||||||
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
||||||
@ -24,9 +30,21 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
data class BrowseScreen(
|
data class BrowseTab(
|
||||||
private val toExtensions: Boolean,
|
private val toExtensions: Boolean = false,
|
||||||
) : Screen {
|
) : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 3u,
|
||||||
|
title = stringResource(R.string.browse),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||||
import eu.kanade.presentation.browse.FeedAddDialog
|
import eu.kanade.presentation.browse.FeedAddDialog
|
||||||
@ -17,17 +18,15 @@ import eu.kanade.presentation.browse.FeedDeleteConfirmDialog
|
|||||||
import eu.kanade.presentation.browse.FeedScreen
|
import eu.kanade.presentation.browse.FeedScreen
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.TabContent
|
import eu.kanade.presentation.components.TabContent
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Screen.feedTab(): TabContent {
|
fun Screen.feedTab(): TabContent {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { FeedScreenModel() }
|
val screenModel = rememberScreenModel { FeedScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -48,25 +47,25 @@ fun Screen.feedTab(): TabContent {
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickSavedSearch = { savedSearch, source ->
|
onClickSavedSearch = { savedSearch, source ->
|
||||||
screenModel.sourcePreferences.lastUsedSource().set(savedSearch.source)
|
screenModel.sourcePreferences.lastUsedSource().set(savedSearch.source)
|
||||||
router.pushController(
|
navigator.push(
|
||||||
BrowseSourceController(
|
BrowseSourceScreen(
|
||||||
source,
|
source.id,
|
||||||
savedSearch = savedSearch.id,
|
savedSearch = savedSearch.id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClickSource = { source ->
|
onClickSource = { source ->
|
||||||
screenModel.sourcePreferences.lastUsedSource().set(source.id)
|
screenModel.sourcePreferences.lastUsedSource().set(source.id)
|
||||||
router.pushController(
|
navigator.push(
|
||||||
BrowseSourceController(
|
BrowseSourceScreen(
|
||||||
source,
|
source.id,
|
||||||
GetRemoteManga.QUERY_LATEST,
|
GetRemoteManga.QUERY_LATEST,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClickDelete = screenModel::openDeleteDialog,
|
onClickDelete = screenModel::openDeleteDialog,
|
||||||
onClickManga = { manga ->
|
onClickManga = { manga ->
|
||||||
router.pushController(MangaController(manga.id, true))
|
navigator.push(MangaScreen(manga.id, true))
|
||||||
},
|
},
|
||||||
getMangaState = { manga, source -> screenModel.getManga(initialManga = manga, source = source) },
|
getMangaState = { manga, source -> screenModel.getManga(initialManga = manga, source = source) },
|
||||||
)
|
)
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.design
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig
|
|
||||||
|
|
||||||
class PreMigrationController(bundle: Bundle? = null) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(mangaIds: List<Long>) : this(
|
|
||||||
bundleOf(
|
|
||||||
MANGA_IDS_EXTRA to mangaIds.toLongArray(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = PreMigrationScreen(config.toList()))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MANGA_IDS_EXTRA = "manga_ids"
|
|
||||||
|
|
||||||
fun navigateToMigration(skipPre: Boolean, router: Router, mangaIds: List<Long>) {
|
|
||||||
router.pushController(
|
|
||||||
if (skipPre) {
|
|
||||||
MigrationListController(
|
|
||||||
MigrationProcedureConfig(mangaIds, null),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
PreMigrationController(mangaIds)
|
|
||||||
}.withFadeTransaction().tag(MigrationListController.TAG),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -45,7 +45,6 @@ import eu.kanade.presentation.components.AppBar
|
|||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
import eu.kanade.presentation.components.OverflowMenu
|
import eu.kanade.presentation.components.OverflowMenu
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.databinding.PreMigrationListBinding
|
import eu.kanade.tachiyomi.databinding.PreMigrationListBinding
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
||||||
@ -58,7 +57,6 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val screenModel = rememberScreenModel { PreMigrationScreenModel() }
|
val screenModel = rememberScreenModel { PreMigrationScreenModel() }
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
var fabExpanded by remember { mutableStateOf(true) }
|
var fabExpanded by remember { mutableStateOf(true) }
|
||||||
val items by screenModel.state.collectAsState()
|
val items by screenModel.state.collectAsState()
|
||||||
@ -99,12 +97,7 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen {
|
|||||||
topBar = {
|
topBar = {
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.select_sources),
|
title = stringResource(R.string.select_sources),
|
||||||
navigateUp = {
|
navigateUp = navigator::pop,
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
else -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { screenModel.massSelect(false) }) {
|
IconButton(onClick = { screenModel.massSelect(false) }) {
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
|
||||||
|
|
||||||
class MigrationListController(bundle: Bundle? = null) :
|
|
||||||
BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(config: MigrationProcedureConfig) : this(
|
|
||||||
bundleOf(
|
|
||||||
CONFIG_EXTRA to config,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val config = args.getSerializableCompat<MigrationProcedureConfig>(CONFIG_EXTRA)!!
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MigrationListScreen(config))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val CONFIG_EXTRA = "config_extra"
|
|
||||||
const val TAG = "migration_list"
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,14 +15,9 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
|||||||
import eu.kanade.presentation.browse.MigrationListScreen
|
import eu.kanade.presentation.browse.MigrationListScreen
|
||||||
import eu.kanade.presentation.browse.components.MigrationExitDialog
|
import eu.kanade.presentation.browse.components.MigrationExitDialog
|
||||||
import eu.kanade.presentation.browse.components.MigrationMangaDialog
|
import eu.kanade.presentation.browse.components.MigrationMangaDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.changehandler.OneWayFadeChangeHandler
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -39,7 +34,6 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
|||||||
val unfinishedCount by screenModel.unfinishedCount.collectAsState()
|
val unfinishedCount by screenModel.unfinishedCount.collectAsState()
|
||||||
val dialog by screenModel.dialog.collectAsState()
|
val dialog by screenModel.dialog.collectAsState()
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(items) {
|
LaunchedEffect(items) {
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
@ -52,11 +46,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (!screenModel.hideNotFound) {
|
if (!screenModel.hideNotFound) {
|
||||||
if (navigator.canPop) {
|
navigator.pop()
|
||||||
navigator.pop()
|
|
||||||
} else {
|
|
||||||
router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,56 +61,29 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
|||||||
|
|
||||||
LaunchedEffect(screenModel) {
|
LaunchedEffect(screenModel) {
|
||||||
screenModel.navigateOut.collect {
|
screenModel.navigateOut.collect {
|
||||||
if (navigator.canPop) {
|
if (items.size == 1) {
|
||||||
if (items.size == 1) {
|
val hasDetails = navigator.items.any { it is MangaScreen }
|
||||||
val hasDetails = navigator.items.any { it is MangaScreen }
|
if (hasDetails) {
|
||||||
if (hasDetails) {
|
val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let {
|
||||||
val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let {
|
screenModel.getManga(it.id)
|
||||||
screenModel.getManga(it.id)
|
|
||||||
}
|
|
||||||
withUIContext {
|
|
||||||
if (manga != null) {
|
|
||||||
val newStack = navigator.items.filter {
|
|
||||||
it !is MangaScreen &&
|
|
||||||
it !is MigrationListScreen &&
|
|
||||||
it !is PreMigrationScreen
|
|
||||||
} + MangaScreen(manga.id)
|
|
||||||
navigator replaceAll newStack.first()
|
|
||||||
navigator.push(newStack.drop(1))
|
|
||||||
} else {
|
|
||||||
navigator.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
withUIContext {
|
withUIContext {
|
||||||
navigator.pop()
|
if (manga != null) {
|
||||||
|
val newStack = navigator.items.filter {
|
||||||
|
it !is MangaScreen &&
|
||||||
|
it !is MigrationListScreen &&
|
||||||
|
it !is PreMigrationScreen
|
||||||
|
} + MangaScreen(manga.id)
|
||||||
|
navigator replaceAll newStack.first()
|
||||||
|
navigator.push(newStack.drop(1))
|
||||||
|
} else {
|
||||||
|
navigator.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (items.size == 1) {
|
withUIContext {
|
||||||
val hasDetails = router.backstack.any { it.controller is MangaController }
|
navigator.pop()
|
||||||
if (hasDetails) {
|
|
||||||
val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let {
|
|
||||||
screenModel.getManga(it.id)
|
|
||||||
}
|
|
||||||
withUIContext {
|
|
||||||
if (manga != null) {
|
|
||||||
val newStack = router.backstack.filter {
|
|
||||||
it.controller !is MangaController &&
|
|
||||||
it.controller !is MigrationListController &&
|
|
||||||
it.controller !is PreMigrationController
|
|
||||||
} + MangaController(manga.id).withFadeTransaction()
|
|
||||||
router.setBackstack(newStack, OneWayFadeChangeHandler())
|
|
||||||
} else {
|
|
||||||
router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withUIContext {
|
|
||||||
router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,13 +129,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
|||||||
MigrationListScreenModel.Dialog.MigrationExitDialog -> {
|
MigrationListScreenModel.Dialog.MigrationExitDialog -> {
|
||||||
MigrationExitDialog(
|
MigrationExitDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
exitMigration = {
|
exitMigration = navigator::pop,
|
||||||
if (navigator.canPop) {
|
|
||||||
navigator.pop()
|
|
||||||
} else {
|
|
||||||
router.popCurrentController()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
null -> Unit
|
null -> Unit
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.search
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
|
||||||
|
|
||||||
class SourceSearchController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(manga: Manga, source: CatalogueSource, searchQuery: String? = null) : this(
|
|
||||||
bundleOf(
|
|
||||||
SOURCE_ID_KEY to source.id,
|
|
||||||
MANGA_KEY to manga,
|
|
||||||
SEARCH_QUERY_KEY to searchQuery,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private var oldManga: Manga = args.getSerializableCompat(MANGA_KEY)!!
|
|
||||||
private val sourceId = args.getLong(SOURCE_ID_KEY)
|
|
||||||
private val query = args.getString(SEARCH_QUERY_KEY)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = SourceSearchScreen(oldManga, sourceId, query))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val MANGA_KEY = "oldManga"
|
|
||||||
private const val SOURCE_ID_KEY = "sourceId"
|
|
||||||
private const val SEARCH_QUERY_KEY = "searchQuery"
|
|
@ -20,13 +20,12 @@ import eu.kanade.presentation.browse.BrowseSourceContent
|
|||||||
import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton
|
import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
|
|
||||||
data class SourceSearchScreen(
|
data class SourceSearchScreen(
|
||||||
private val oldManga: Manga,
|
private val oldManga: Manga,
|
||||||
@ -38,7 +37,6 @@ data class SourceSearchScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
||||||
@ -46,19 +44,12 @@ data class SourceSearchScreen(
|
|||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
searchQuery = state.toolbarQuery ?: "",
|
searchQuery = state.toolbarQuery ?: "",
|
||||||
onChangeSearchQuery = screenModel::setToolbarQuery,
|
onChangeSearchQuery = screenModel::setToolbarQuery,
|
||||||
onClickCloseSearch = navigateUp,
|
onClickCloseSearch = navigator::pop,
|
||||||
onSearch = { screenModel.search(it) },
|
onSearch = { screenModel.search(it) },
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -100,7 +91,7 @@ data class SourceSearchScreen(
|
|||||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
|
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
|
||||||
onMangaClick = openMigrateDialog,
|
onMangaClick = openMigrateDialog,
|
||||||
onMangaLongClick = openMigrateDialog,
|
onMangaLongClick = openMigrateDialog,
|
||||||
@ -108,7 +99,7 @@ data class SourceSearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.filters) {
|
LaunchedEffect(state.filters) {
|
||||||
screenModel.initFilterSheet(context, router)
|
screenModel.initFilterSheet(context, navigator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class MigrationSourcesController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MigrationSourcesScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val HELP_URL = "https://tachiyomi.org/help/guides/source-migration/"
|
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class SourceFilterController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = SourcesFilterScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,10 +7,10 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.SourcesFilterScreen
|
import eu.kanade.presentation.browse.SourcesFilterScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class SourcesFilterScreen : Screen {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourcesFilterScreenModel() }
|
val screenModel = rememberScreenModel { SourcesFilterScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ class SourcesFilterScreen : Screen {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
context.toast(R.string.internal_error)
|
context.toast(R.string.internal_error)
|
||||||
router.popCurrentController()
|
navigator.pop()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ class SourcesFilterScreen : Screen {
|
|||||||
val successState = state as SourcesFilterState.Success
|
val successState = state as SourcesFilterState.Success
|
||||||
|
|
||||||
SourcesFilterScreen(
|
SourcesFilterScreen(
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickLanguage = screenModel::toggleLanguage,
|
onClickLanguage = screenModel::toggleLanguage,
|
||||||
onClickSource = screenModel::toggleSource,
|
onClickSource = screenModel::toggleSource,
|
||||||
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga.Companion.QUERY_POPULAR
|
import eu.kanade.domain.source.interactor.GetRemoteManga.Companion.QUERY_POPULAR
|
||||||
import eu.kanade.presentation.browse.SourceCategoriesDialog
|
import eu.kanade.presentation.browse.SourceCategoriesDialog
|
||||||
@ -17,14 +18,12 @@ import eu.kanade.presentation.browse.SourceOptionsDialog
|
|||||||
import eu.kanade.presentation.browse.SourcesScreen
|
import eu.kanade.presentation.browse.SourcesScreen
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.TabContent
|
import eu.kanade.presentation.components.TabContent
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen.SmartSearchConfig
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen.SmartSearchConfig
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import exh.ui.smartsearch.SmartSearchController
|
import exh.ui.smartsearch.SmartSearchScreen
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ import kotlinx.coroutines.launch
|
|||||||
fun Screen.sourcesTab(
|
fun Screen.sourcesTab(
|
||||||
smartSearchConfig: SmartSearchConfig? = null,
|
smartSearchConfig: SmartSearchConfig? = null,
|
||||||
): TabContent {
|
): TabContent {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourcesScreenModel(smartSearchConfig = smartSearchConfig) }
|
val screenModel = rememberScreenModel { SourcesScreenModel(smartSearchConfig = smartSearchConfig) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -47,12 +46,12 @@ fun Screen.sourcesTab(
|
|||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_global_search),
|
title = stringResource(R.string.action_global_search),
|
||||||
icon = Icons.Outlined.TravelExplore,
|
icon = Icons.Outlined.TravelExplore,
|
||||||
onClick = { router.pushController(GlobalSearchController()) },
|
onClick = { navigator.push(GlobalSearchScreen()) },
|
||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_filter),
|
title = stringResource(R.string.action_filter),
|
||||||
icon = Icons.Outlined.FilterList,
|
icon = Icons.Outlined.FilterList,
|
||||||
onClick = { router.pushController(SourceFilterController()) },
|
onClick = { navigator.push(SourcesFilterScreen()) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -65,13 +64,13 @@ fun Screen.sourcesTab(
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickItem = { source, query ->
|
onClickItem = { source, query ->
|
||||||
// SY -->
|
// SY -->
|
||||||
val controller = when {
|
val screen = when {
|
||||||
smartSearchConfig != null -> SmartSearchController(source.id, smartSearchConfig)
|
smartSearchConfig != null -> SmartSearchScreen(source.id, smartSearchConfig)
|
||||||
(query.isBlank() || query == QUERY_POPULAR) && screenModel.useNewSourceNavigation -> SourceFeedController(source.id)
|
(query.isBlank() || query == QUERY_POPULAR) && screenModel.useNewSourceNavigation -> SourceFeedScreen(source.id)
|
||||||
else -> BrowseSourceController(source.id, query)
|
else -> BrowseSourceScreen(source.id, query)
|
||||||
}
|
}
|
||||||
screenModel.onOpenSource(source)
|
screenModel.onOpenSource(source)
|
||||||
router.pushController(controller)
|
navigator.push(screen)
|
||||||
// SY <--
|
// SY <--
|
||||||
},
|
},
|
||||||
onClickPin = screenModel::togglePin,
|
onClickPin = screenModel::togglePin,
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.consumeAsFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class BrowseSourceController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
sourceId: Long,
|
|
||||||
query: String? = null,
|
|
||||||
// SY -->
|
|
||||||
smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
|
||||||
savedSearch: Long? = null,
|
|
||||||
filterList: String? = null,
|
|
||||||
// SY <--
|
|
||||||
) : this(
|
|
||||||
Bundle().apply {
|
|
||||||
putLong(SOURCE_ID_KEY, sourceId)
|
|
||||||
if (query != null) {
|
|
||||||
putString(SEARCH_QUERY_KEY, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
if (smartSearchConfig != null) {
|
|
||||||
putSerializable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedSearch != null) {
|
|
||||||
putLong(SAVED_SEARCH_CONFIG_KEY, savedSearch)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterList != null) {
|
|
||||||
putString(FILTERS_CONFIG_KEY, filterList)
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
source: CatalogueSource,
|
|
||||||
query: String? = null,
|
|
||||||
// SY -->
|
|
||||||
smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
|
||||||
savedSearch: Long? = null,
|
|
||||||
filterList: String? = null,
|
|
||||||
// SY <--
|
|
||||||
) : this(
|
|
||||||
source.id,
|
|
||||||
query,
|
|
||||||
smartSearchConfig,
|
|
||||||
savedSearch,
|
|
||||||
filterList,
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
source: Source,
|
|
||||||
query: String? = null,
|
|
||||||
// SY -->
|
|
||||||
smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
|
||||||
savedSearch: Long? = null,
|
|
||||||
filterList: String? = null,
|
|
||||||
// SY <--
|
|
||||||
) : this(
|
|
||||||
source.id,
|
|
||||||
query,
|
|
||||||
smartSearchConfig,
|
|
||||||
savedSearch,
|
|
||||||
filterList,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val sourceId = args.getLong(SOURCE_ID_KEY)
|
|
||||||
private val initialQuery = args.getString(SEARCH_QUERY_KEY)
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
private val filtersJson = args.getString(FILTERS_CONFIG_KEY)
|
|
||||||
private val savedSearch = args.getLong(SAVED_SEARCH_CONFIG_KEY, 0).takeUnless { it == 0L }
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
private val queryEvent = Channel<BrowseSourceScreen.SearchType>()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(
|
|
||||||
screen = BrowseSourceScreen(
|
|
||||||
sourceId = sourceId,
|
|
||||||
query = initialQuery,
|
|
||||||
// SY -->
|
|
||||||
filtersJson = filtersJson,
|
|
||||||
savedSearch = savedSearch,
|
|
||||||
// SY <--
|
|
||||||
),
|
|
||||||
) { navigator ->
|
|
||||||
CurrentScreen()
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
queryEvent.consumeAsFlow()
|
|
||||||
.collectLatest {
|
|
||||||
val screen = (navigator.lastItem as? BrowseSourceScreen)
|
|
||||||
when (it) {
|
|
||||||
is BrowseSourceScreen.SearchType.Genre -> screen?.searchGenre(it.txt)
|
|
||||||
is BrowseSourceScreen.SearchType.Text -> screen?.search(it.txt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restarts the request with a new query.
|
|
||||||
*
|
|
||||||
* @param newQuery the new query.
|
|
||||||
*/
|
|
||||||
fun searchWithQuery(newQuery: String) {
|
|
||||||
viewScope.launch { queryEvent.send(BrowseSourceScreen.SearchType.Text(newQuery)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to restart the request with a new genre-filtered query.
|
|
||||||
* If the genre name can't be found the filters,
|
|
||||||
* the standard searchWithQuery search method is used instead.
|
|
||||||
*
|
|
||||||
* @param genreName the name of the genre
|
|
||||||
*/
|
|
||||||
fun searchWithGenre(genreName: String) {
|
|
||||||
viewScope.launch { queryEvent.send(BrowseSourceScreen.SearchType.Genre(genreName)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val SOURCE_ID_KEY = "sourceId"
|
|
||||||
private const val SEARCH_QUERY_KEY = "searchQuery"
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
private const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
|
|
||||||
private const val SAVED_SEARCH_CONFIG_KEY = "savedSearch"
|
|
||||||
private const val FILTERS_CONFIG_KEY = "filters"
|
|
||||||
// SY <--
|
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -52,16 +51,15 @@ import eu.kanade.presentation.components.ChangeCategoryDialog
|
|||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.DuplicateMangaDialog
|
import eu.kanade.presentation.components.DuplicateMangaDialog
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
|
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -73,6 +71,7 @@ data class BrowseSourceScreen(
|
|||||||
// SY -->
|
// SY -->
|
||||||
private val filtersJson: String? = null,
|
private val filtersJson: String? = null,
|
||||||
private val savedSearch: Long? = null,
|
private val savedSearch: Long? = null,
|
||||||
|
private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
||||||
// SY <--
|
// SY <--
|
||||||
) : Screen {
|
) : Screen {
|
||||||
|
|
||||||
@ -80,7 +79,6 @@ data class BrowseSourceScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -109,13 +107,6 @@ data class BrowseSourceScreen(
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
@ -125,7 +116,7 @@ data class BrowseSourceScreen(
|
|||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = screenModel.displayMode,
|
||||||
onDisplayModeChange = { screenModel.displayMode = it },
|
onDisplayModeChange = { screenModel.displayMode = it },
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigator::pop,
|
||||||
onWebViewClick = onWebViewClick,
|
onWebViewClick = onWebViewClick,
|
||||||
onHelpClick = onHelpClick,
|
onHelpClick = onHelpClick,
|
||||||
onSearch = { screenModel.search(it) },
|
onSearch = { screenModel.search(it) },
|
||||||
@ -227,9 +218,9 @@ data class BrowseSourceScreen(
|
|||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
onWebViewClick = onWebViewClick,
|
onWebViewClick = onWebViewClick,
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
onLocalSourceHelpClick = onHelpClick,
|
onLocalSourceHelpClick = onHelpClick,
|
||||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
onMangaClick = { navigator.push(MangaScreen(it.id, true, smartSearchConfig)) },
|
||||||
onMangaLongClick = { manga ->
|
onMangaLongClick = { manga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||||
@ -256,7 +247,7 @@ data class BrowseSourceScreen(
|
|||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||||
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||||
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -273,9 +264,7 @@ data class BrowseSourceScreen(
|
|||||||
ChangeCategoryDialog(
|
ChangeCategoryDialog(
|
||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = {
|
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||||
router.pushController(CategoryController())
|
|
||||||
},
|
|
||||||
onConfirm = { include, _ ->
|
onConfirm = { include, _ ->
|
||||||
screenModel.changeMangaFavorite(dialog.manga)
|
screenModel.changeMangaFavorite(dialog.manga)
|
||||||
screenModel.moveMangaToCategories(dialog.manga, include)
|
screenModel.moveMangaToCategories(dialog.manga, include)
|
||||||
@ -298,10 +287,8 @@ data class BrowseSourceScreen(
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(onBack = navigateUp)
|
|
||||||
|
|
||||||
LaunchedEffect(state.filters) {
|
LaunchedEffect(state.filters) {
|
||||||
screenModel.initFilterSheet(context, router)
|
screenModel.initFilterSheet(context, navigator)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
@ -14,7 +14,7 @@ import androidx.paging.cachedIn
|
|||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.core.prefs.CheckboxState
|
import eu.kanade.core.prefs.CheckboxState
|
||||||
import eu.kanade.core.prefs.asState
|
import eu.kanade.core.prefs.asState
|
||||||
@ -454,7 +454,7 @@ open class BrowseSourceScreenModel(
|
|||||||
mutableState.update { it.copy(toolbarQuery = query) }
|
mutableState.update { it.copy(toolbarQuery = query) }
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun initFilterSheet(context: Context, router: Router) {
|
open fun initFilterSheet(context: Context, navigator: Navigator) {
|
||||||
val state = state.value
|
val state = state.value
|
||||||
/*if (state.filters.isEmpty()) {
|
/*if (state.filters.isEmpty()) {
|
||||||
return
|
return
|
||||||
@ -463,7 +463,7 @@ open class BrowseSourceScreenModel(
|
|||||||
filterSheet = SourceFilterSheet(
|
filterSheet = SourceFilterSheet(
|
||||||
context = context,
|
context = context,
|
||||||
// SY -->
|
// SY -->
|
||||||
router = router,
|
navigator = navigator,
|
||||||
source = source,
|
source = source,
|
||||||
searches = emptyList(),
|
searches = emptyList(),
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -7,7 +7,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.ConcatAdapter
|
import androidx.recyclerview.widget.ConcatAdapter
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
@ -23,7 +23,7 @@ import exh.source.getMainSource
|
|||||||
class SourceFilterSheet(
|
class SourceFilterSheet(
|
||||||
context: Context,
|
context: Context,
|
||||||
// SY -->
|
// SY -->
|
||||||
router: Router,
|
navigator: Navigator,
|
||||||
source: CatalogueSource,
|
source: CatalogueSource,
|
||||||
searches: List<EXHSavedSearch> = emptyList(),
|
searches: List<EXHSavedSearch> = emptyList(),
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -41,7 +41,7 @@ class SourceFilterSheet(
|
|||||||
// SY -->
|
// SY -->
|
||||||
searches = searches,
|
searches = searches,
|
||||||
source = source,
|
source = source,
|
||||||
router = router,
|
navigator = navigator,
|
||||||
dismissSheet = ::dismiss,
|
dismissSheet = ::dismiss,
|
||||||
// SY <--
|
// SY <--
|
||||||
)
|
)
|
||||||
@ -84,7 +84,7 @@ class SourceFilterSheet(
|
|||||||
// SY -->
|
// SY -->
|
||||||
searches: List<EXHSavedSearch> = emptyList(),
|
searches: List<EXHSavedSearch> = emptyList(),
|
||||||
source: CatalogueSource? = null,
|
source: CatalogueSource? = null,
|
||||||
router: Router? = null,
|
navigator: Navigator? = null,
|
||||||
dismissSheet: (() -> Unit)? = null,
|
dismissSheet: (() -> Unit)? = null,
|
||||||
// SY <--
|
// SY <--
|
||||||
) :
|
) :
|
||||||
@ -116,10 +116,10 @@ class SourceFilterSheet(
|
|||||||
// SY -->
|
// SY -->
|
||||||
recycler.adapter = ConcatAdapter(
|
recycler.adapter = ConcatAdapter(
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
router?.let {
|
navigator?.let {
|
||||||
source?.getMainSource<MangaDex>()
|
source?.getMainSource<MangaDex>()
|
||||||
?.let {
|
?.let {
|
||||||
MangaDexFabHeaderAdapter(router, it) {
|
MangaDexFabHeaderAdapter(navigator, it) {
|
||||||
dismissSheet?.invoke()
|
dismissSheet?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.feed
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class SourceFeedController : BasicFullComposeController {
|
|
||||||
|
|
||||||
constructor(source: CatalogueSource) : super(
|
|
||||||
bundleOf(
|
|
||||||
SOURCE_EXTRA to source.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(sourceId: Long) : super(
|
|
||||||
bundleOf(
|
|
||||||
SOURCE_EXTRA to sourceId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(bundle: Bundle) : super(bundle)
|
|
||||||
|
|
||||||
val sourceId = args.getLong(SOURCE_EXTRA)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = SourceFeedScreen(sourceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SOURCE_EXTRA = "source"
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,19 +13,16 @@ import cafe.adriel.voyager.core.screen.Screen
|
|||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||||
import eu.kanade.presentation.browse.SourceFeedScreen
|
import eu.kanade.presentation.browse.SourceFeedScreen
|
||||||
import eu.kanade.presentation.browse.components.FailedToLoadSavedSearchDialog
|
import eu.kanade.presentation.browse.components.FailedToLoadSavedSearchDialog
|
||||||
import eu.kanade.presentation.browse.components.SourceFeedAddDialog
|
import eu.kanade.presentation.browse.components.SourceFeedAddDialog
|
||||||
import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog
|
import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
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.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
@ -47,19 +44,18 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
|||||||
val screenModel = rememberScreenModel { SourceFeedScreenModel(sourceId) }
|
val screenModel = rememberScreenModel { SourceFeedScreenModel(sourceId) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
|
|
||||||
SourceFeedScreen(
|
SourceFeedScreen(
|
||||||
name = screenModel.source.name,
|
name = screenModel.source.name,
|
||||||
isLoading = state.isLoading,
|
isLoading = state.isLoading,
|
||||||
items = state.items,
|
items = state.items,
|
||||||
onFabClick = if (state.filters.isEmpty()) null else { { filterSheet?.show() } },
|
onFabClick = if (state.filters.isEmpty()) null else { { filterSheet?.show() } },
|
||||||
onClickBrowse = { onBrowseClick(router, screenModel.source) },
|
onClickBrowse = { onBrowseClick(navigator, screenModel.source) },
|
||||||
onClickLatest = { onLatestClick(router, screenModel.source) },
|
onClickLatest = { onLatestClick(navigator, screenModel.source) },
|
||||||
onClickSavedSearch = { onSavedSearchClick(router, screenModel.source, it) },
|
onClickSavedSearch = { onSavedSearchClick(navigator, screenModel.source, it) },
|
||||||
onClickDelete = screenModel::openDeleteFeed,
|
onClickDelete = screenModel::openDeleteFeed,
|
||||||
onClickManga = { onMangaClick(navigator, it) },
|
onClickManga = { onMangaClick(navigator, it) },
|
||||||
onClickSearch = { onSearchClick(router, screenModel.source, it) },
|
onClickSearch = { onSearchClick(navigator, screenModel.source, it) },
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
onSearchQueryChange = screenModel::search,
|
onSearchQueryChange = screenModel::search,
|
||||||
isIncognitoMode = screenModel.isIncognitoMode,
|
isIncognitoMode = screenModel.isIncognitoMode,
|
||||||
@ -102,7 +98,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(state.filters) {
|
LaunchedEffect(state.filters) {
|
||||||
initFilterSheet(state, screenModel, scope, context, router)
|
initFilterSheet(state, screenModel, scope, context, navigator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,28 +107,28 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
|||||||
screenModel: SourceFeedScreenModel,
|
screenModel: SourceFeedScreenModel,
|
||||||
viewScope: CoroutineScope,
|
viewScope: CoroutineScope,
|
||||||
context: Context,
|
context: Context,
|
||||||
router: Router,
|
navigator: Navigator,
|
||||||
) {
|
) {
|
||||||
val filterSerializer = FilterSerializer()
|
val filterSerializer = FilterSerializer()
|
||||||
filterSheet = SourceFilterSheet(
|
filterSheet = SourceFilterSheet(
|
||||||
context,
|
context = context,
|
||||||
// SY -->
|
// SY -->
|
||||||
router,
|
navigator = navigator,
|
||||||
screenModel.source,
|
source = screenModel.source,
|
||||||
emptyList(),
|
searches = emptyList(),
|
||||||
// SY <--
|
// SY <--
|
||||||
onFilterClicked = {
|
onFilterClicked = {
|
||||||
val allDefault = state.filters == screenModel.source.getFilterList()
|
val allDefault = state.filters == screenModel.source.getFilterList()
|
||||||
filterSheet?.dismiss()
|
filterSheet?.dismiss()
|
||||||
if (allDefault) {
|
if (allDefault) {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
router,
|
navigator,
|
||||||
screenModel.source.id,
|
screenModel.source.id,
|
||||||
state.searchQuery?.nullIfBlank(),
|
state.searchQuery?.nullIfBlank(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
router,
|
navigator,
|
||||||
screenModel.source.id,
|
screenModel.source.id,
|
||||||
state.searchQuery?.nullIfBlank(),
|
state.searchQuery?.nullIfBlank(),
|
||||||
filters = Json.encodeToString(filterSerializer.serialize(state.filters)),
|
filters = Json.encodeToString(filterSerializer.serialize(state.filters)),
|
||||||
@ -164,7 +160,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
|||||||
|
|
||||||
if (!allDefault) {
|
if (!allDefault) {
|
||||||
onBrowseClick(
|
onBrowseClick(
|
||||||
router,
|
navigator,
|
||||||
screenModel.source.id,
|
screenModel.source.id,
|
||||||
search = state.searchQuery?.nullIfBlank(),
|
search = state.searchQuery?.nullIfBlank(),
|
||||||
savedSearch = search.id,
|
savedSearch = search.id,
|
||||||
@ -192,23 +188,23 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
|||||||
navigator.push(MangaScreen(manga.id, true))
|
navigator.push(MangaScreen(manga.id, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBrowseClick(router: Router, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
fun onBrowseClick(navigator: Navigator, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||||
router.replaceTopController(BrowseSourceController(sourceId, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
|
navigator.replace(BrowseSourceScreen(sourceId, search, savedSearch = savedSearch, filtersJson = filters))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLatestClick(router: Router, source: CatalogueSource) {
|
private fun onLatestClick(navigator: Navigator, source: CatalogueSource) {
|
||||||
router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_LATEST).withFadeTransaction())
|
navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_LATEST))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBrowseClick(router: Router, source: CatalogueSource) {
|
fun onBrowseClick(navigator: Navigator, source: CatalogueSource) {
|
||||||
router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction())
|
navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_POPULAR))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSavedSearchClick(router: Router, source: CatalogueSource, savedSearch: SavedSearch) {
|
private fun onSavedSearchClick(navigator: Navigator, source: CatalogueSource, savedSearch: SavedSearch) {
|
||||||
router.replaceTopController(BrowseSourceController(source, savedSearch = savedSearch.id).withFadeTransaction())
|
navigator.replace(BrowseSourceScreen(source.id, savedSearch = savedSearch.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSearchClick(router: Router, source: CatalogueSource, query: String) {
|
private fun onSearchClick(navigator: Navigator, source: CatalogueSource, query: String) {
|
||||||
onBrowseClick(router, source.id, query.nullIfBlank())
|
onBrowseClick(navigator, source.id, query.nullIfBlank())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,6 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of [SourceFeedController]
|
|
||||||
* Function calls should be done from here. UI calls should be done from the controller.
|
|
||||||
*
|
|
||||||
* @param source the source.
|
|
||||||
*/
|
|
||||||
open class SourceFeedScreenModel(
|
open class SourceFeedScreenModel(
|
||||||
val sourceId: Long,
|
val sourceId: Long,
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class GlobalSearchController(
|
|
||||||
val searchQuery: String = "",
|
|
||||||
val extensionFilter: String = "",
|
|
||||||
) : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(
|
|
||||||
screen = GlobalSearchScreen(
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
extensionFilter = extensionFilter,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,12 +5,11 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.GlobalSearchScreen
|
import eu.kanade.presentation.browse.GlobalSearchScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
|
|
||||||
class GlobalSearchScreen(
|
class GlobalSearchScreen(
|
||||||
val searchQuery: String = "",
|
val searchQuery: String = "",
|
||||||
@ -19,7 +18,7 @@ class GlobalSearchScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
GlobalSearchScreenModel(
|
GlobalSearchScreenModel(
|
||||||
@ -31,7 +30,7 @@ class GlobalSearchScreen(
|
|||||||
|
|
||||||
GlobalSearchScreen(
|
GlobalSearchScreen(
|
||||||
state = state,
|
state = state,
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||||
onSearch = screenModel::search,
|
onSearch = screenModel::search,
|
||||||
getManga = { source, manga ->
|
getManga = { source, manga ->
|
||||||
@ -44,10 +43,10 @@ class GlobalSearchScreen(
|
|||||||
if (!screenModel.incognitoMode.get()) {
|
if (!screenModel.incognitoMode.get()) {
|
||||||
screenModel.lastUsedSourceId.set(it.id)
|
screenModel.lastUsedSourceId.set(it.id)
|
||||||
}
|
}
|
||||||
router.pushController(BrowseSourceController(it.id, state.searchQuery))
|
navigator.push(BrowseSourceScreen(it.id, state.searchQuery))
|
||||||
},
|
},
|
||||||
onClickItem = { router.pushController(MangaController(it.id, true)) },
|
onClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||||
onLongClickItem = { router.pushController(MangaController(it.id, true)) },
|
onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class CategoryController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = CategoryScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,7 +16,6 @@ import eu.kanade.presentation.category.components.CategoryCreateDialog
|
|||||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||||
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -29,7 +28,6 @@ class CategoryScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { CategoryScreenModel() }
|
val screenModel = rememberScreenModel { CategoryScreenModel() }
|
||||||
|
|
||||||
@ -49,12 +47,7 @@ class CategoryScreen : Screen {
|
|||||||
onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) },
|
||||||
onClickMoveUp = screenModel::moveUp,
|
onClickMoveUp = screenModel::moveUp,
|
||||||
onClickMoveDown = screenModel::moveDown,
|
onClickMoveDown = screenModel::moveDown,
|
||||||
navigateUp = {
|
navigateUp = navigator::pop,
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.handleBack()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category.biometric
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller to manage the lock times for the biometric lock.
|
|
||||||
*/
|
|
||||||
class BiometricTimesController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = BiometricTimesScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,12 +8,12 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
import eu.kanade.presentation.category.BiometricTimesScreen
|
import eu.kanade.presentation.category.BiometricTimesScreen
|
||||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -27,7 +27,7 @@ class BiometricTimesScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { BiometricTimesScreenModel() }
|
val screenModel = rememberScreenModel { BiometricTimesScreenModel() }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -43,7 +43,7 @@ class BiometricTimesScreen : Screen {
|
|||||||
state = successState,
|
state = successState,
|
||||||
onClickCreate = { screenModel.showDialog(BiometricTimesDialog.Create) },
|
onClickCreate = { screenModel.showDialog(BiometricTimesDialog.Create) },
|
||||||
onClickDelete = { screenModel.showDialog(BiometricTimesDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(BiometricTimesDialog.Delete(it)) },
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun showTimePicker(startTime: Duration? = null) {
|
fun showTimePicker(startTime: Duration? = null) {
|
||||||
|
@ -16,9 +16,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of [BiometricTimesController]. Used to manage the categories of the library.
|
|
||||||
*/
|
|
||||||
class BiometricTimesScreenModel(
|
class BiometricTimesScreenModel(
|
||||||
private val preferences: SecurityPreferences = Injekt.get(),
|
private val preferences: SecurityPreferences = Injekt.get(),
|
||||||
) : StateScreenModel<BiometricTimesScreenState>(BiometricTimesScreenState.Loading) {
|
) : StateScreenModel<BiometricTimesScreenState>(BiometricTimesScreenState.Loading) {
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category.genre
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller to manage the categories for the users' library.
|
|
||||||
*/
|
|
||||||
class SortTagController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = SortTagScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,11 +8,11 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -22,7 +22,7 @@ class SortTagScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SortTagScreenModel() }
|
val screenModel = rememberScreenModel { SortTagScreenModel() }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -40,7 +40,7 @@ class SortTagScreen : Screen {
|
|||||||
onClickDelete = { screenModel.showDialog(SortTagDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(SortTagDialog.Delete(it)) },
|
||||||
onClickMoveUp = screenModel::moveUp,
|
onClickMoveUp = screenModel::moveUp,
|
||||||
onClickMoveDown = screenModel::moveDown,
|
onClickMoveDown = screenModel::moveDown,
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
|
@ -17,9 +17,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of [SortTagController]. Used to manage the categories of the library.
|
|
||||||
*/
|
|
||||||
class SortTagScreenModel(
|
class SortTagScreenModel(
|
||||||
private val getSortTag: GetSortTag = Injekt.get(),
|
private val getSortTag: GetSortTag = Injekt.get(),
|
||||||
private val createSortTag: CreateSortTag = Injekt.get(),
|
private val createSortTag: CreateSortTag = Injekt.get(),
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category.repos
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller to manage the categories for the users' library.
|
|
||||||
*/
|
|
||||||
class RepoController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = RepoScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,12 +8,12 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.category.SourceRepoScreen
|
import eu.kanade.presentation.category.SourceRepoScreen
|
||||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -23,7 +23,7 @@ class RepoScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { RepoScreenModel() }
|
val screenModel = rememberScreenModel { RepoScreenModel() }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -39,7 +39,7 @@ class RepoScreen : Screen {
|
|||||||
state = successState,
|
state = successState,
|
||||||
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
||||||
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
|
@ -16,9 +16,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of [RepoController]. Used to manage the repos for the extensions.
|
|
||||||
*/
|
|
||||||
class RepoScreenModel(
|
class RepoScreenModel(
|
||||||
private val getSourceRepos: GetSourceRepos = Injekt.get(),
|
private val getSourceRepos: GetSourceRepos = Injekt.get(),
|
||||||
private val createSourceRepo: CreateSourceRepo = Injekt.get(),
|
private val createSourceRepo: CreateSourceRepo = Injekt.get(),
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category.sources
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller to manage the categories for the users' library.
|
|
||||||
*/
|
|
||||||
class SourceCategoryController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = SourceCategoryScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,13 +8,13 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.category.SourceCategoryScreen
|
import eu.kanade.presentation.category.SourceCategoryScreen
|
||||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||||
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -24,7 +24,7 @@ class SourceCategoryScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourceCategoryScreenModel() }
|
val screenModel = rememberScreenModel { SourceCategoryScreenModel() }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -41,7 +41,7 @@ class SourceCategoryScreen : Screen {
|
|||||||
onClickCreate = { screenModel.showDialog(SourceCategoryDialog.Create) },
|
onClickCreate = { screenModel.showDialog(SourceCategoryDialog.Create) },
|
||||||
onClickRename = { screenModel.showDialog(SourceCategoryDialog.Rename(it)) },
|
onClickRename = { screenModel.showDialog(SourceCategoryDialog.Rename(it)) },
|
||||||
onClickDelete = { screenModel.showDialog(SourceCategoryDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(SourceCategoryDialog.Delete(it)) },
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
|
@ -17,9 +17,6 @@ import kotlinx.coroutines.flow.update
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of [SourceCategoryController]. Used to manage the categories of the library.
|
|
||||||
*/
|
|
||||||
class SourceCategoryScreenModel(
|
class SourceCategoryScreenModel(
|
||||||
private val getSourceCategories: GetSourceCategories = Injekt.get(),
|
private val getSourceCategories: GetSourceCategories = Injekt.get(),
|
||||||
private val createSourceCategory: CreateSourceCategory = Injekt.get(),
|
private val createSourceCategory: CreateSourceCategory = Injekt.get(),
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.download
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that shows the currently active downloads.
|
|
||||||
*/
|
|
||||||
class DownloadController : BasicFullComposeController() {
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = DownloadQueueScreen)
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,6 +47,7 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
@ -54,7 +55,6 @@ import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
|||||||
import eu.kanade.presentation.components.OverflowMenu
|
import eu.kanade.presentation.components.OverflowMenu
|
||||||
import eu.kanade.presentation.components.Pill
|
import eu.kanade.presentation.components.Pill
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.databinding.DownloadListBinding
|
import eu.kanade.tachiyomi.databinding.DownloadListBinding
|
||||||
@ -66,7 +66,7 @@ object DownloadQueueScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val screenModel = rememberScreenModel { DownloadQueueScreenModel() }
|
val screenModel = rememberScreenModel { DownloadQueueScreenModel() }
|
||||||
val downloadList by screenModel.state.collectAsState()
|
val downloadList by screenModel.state.collectAsState()
|
||||||
@ -121,7 +121,7 @@ object DownloadQueueScreen : Screen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
actions = {
|
actions = {
|
||||||
if (downloadList.isNotEmpty()) {
|
if (downloadList.isNotEmpty()) {
|
||||||
OverflowMenu { closeMenu ->
|
OverflowMenu { closeMenu ->
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.history
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class HistoryController : BasicFullComposeController(), RootController {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = HistoryScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resumeLastChapterRead() {
|
|
||||||
val context = activity ?: return
|
|
||||||
viewScope.launchIO {
|
|
||||||
val chapter = Injekt.get<GetNextChapters>().await(onlyUnread = false).firstOrNull()
|
|
||||||
HistoryScreen.openChapter(context, chapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,6 +15,7 @@ import eu.kanade.domain.history.model.HistoryWithRelations
|
|||||||
import eu.kanade.presentation.history.HistoryUiModel
|
import eu.kanade.presentation.history.HistoryUiModel
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@ -76,6 +77,10 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getNextChapter(): Chapter? {
|
||||||
|
return withIOContext { getNextChapters.await(onlyUnread = false).firstOrNull() }
|
||||||
|
}
|
||||||
|
|
||||||
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
||||||
|
@ -1,34 +1,76 @@
|
|||||||
package eu.kanade.tachiyomi.ui.history
|
package eu.kanade.tachiyomi.ui.history
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.history.HistoryScreen
|
import eu.kanade.presentation.history.HistoryScreen
|
||||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
||||||
import eu.kanade.presentation.history.components.HistoryDeleteDialog
|
import eu.kanade.presentation.history.components.HistoryDeleteDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object HistoryScreen : Screen {
|
object HistoryTab : Tab {
|
||||||
|
|
||||||
private val snackbarHostState = SnackbarHostState()
|
private val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
private val resumeLastChapterReadEvent = Channel<Unit>()
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 2u,
|
||||||
|
title = stringResource(R.string.label_recent_manga),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
resumeLastChapterReadEvent.send(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
@Composable
|
||||||
|
override fun isEnabled(): Boolean {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
return remember {
|
||||||
|
Injekt.get<UiPreferences>().showNavHistory().asState(scope)
|
||||||
|
}.value
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -39,7 +81,7 @@ object HistoryScreen : Screen {
|
|||||||
incognitoMode = screenModel.isIncognitoMode,
|
incognitoMode = screenModel.isIncognitoMode,
|
||||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||||
onSearchQueryChange = screenModel::updateSearchQuery,
|
onSearchQueryChange = screenModel::updateSearchQuery,
|
||||||
onClickCover = { router.pushController(MangaController(it)) },
|
onClickCover = { navigator.push(MangaScreen(it)) },
|
||||||
onClickResume = screenModel::getNextChapterForManga,
|
onClickResume = screenModel::getNextChapterForManga,
|
||||||
onDialogChange = screenModel::setDialog,
|
onDialogChange = screenModel::setDialog,
|
||||||
)
|
)
|
||||||
@ -84,6 +126,12 @@ object HistoryScreen : Screen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
resumeLastChapterReadEvent.consumeAsFlow().collectLatest {
|
||||||
|
openChapter(context, screenModel.getNextChapter())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun openChapter(context: Context, chapter: Chapter?) {
|
suspend fun openChapter(context: Context, chapter: Chapter?) {
|
306
app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt
Normal file
306
app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.home
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.consumedWindowInsets
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Badge
|
||||||
|
import androidx.compose.material3.BadgedBox
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.NavigationRailItem
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||||
|
import eu.kanade.core.prefs.asState
|
||||||
|
import eu.kanade.core.util.fastFilter
|
||||||
|
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.NavigationBar
|
||||||
|
import eu.kanade.presentation.components.NavigationRail
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.util.Tab
|
||||||
|
import eu.kanade.presentation.util.Transition
|
||||||
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.BrowseTab
|
||||||
|
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
||||||
|
import eu.kanade.tachiyomi.ui.library.LibraryTab
|
||||||
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
|
import eu.kanade.tachiyomi.ui.more.MoreTab
|
||||||
|
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object HomeScreen : Screen {
|
||||||
|
|
||||||
|
private val librarySearchEvent = Channel<String>()
|
||||||
|
private val openTabEvent = Channel<Tab>()
|
||||||
|
private val showBottomNavEvent = Channel<Boolean>()
|
||||||
|
|
||||||
|
private val tabs = listOf(
|
||||||
|
LibraryTab,
|
||||||
|
UpdatesTab,
|
||||||
|
HistoryTab,
|
||||||
|
BrowseTab(),
|
||||||
|
MoreTab(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
// SY -->
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val alwaysShowLabel by remember {
|
||||||
|
Injekt.get<UiPreferences>().bottomBarLabels().asState(scope)
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
TabNavigator(
|
||||||
|
tab = LibraryTab,
|
||||||
|
) { tabNavigator ->
|
||||||
|
// Provide usable navigator to content screen
|
||||||
|
CompositionLocalProvider(LocalNavigator provides navigator) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
if (isTabletUi()) {
|
||||||
|
NavigationRail {
|
||||||
|
tabs
|
||||||
|
// SY -->
|
||||||
|
.fastFilter { it.isEnabled() }
|
||||||
|
// SY <--
|
||||||
|
.fastForEach {
|
||||||
|
NavigationRailItem(it/* SY --> */, alwaysShowLabel/* SY <-- */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
if (!isTabletUi()) {
|
||||||
|
val bottomNavVisible by produceState(initialValue = true) {
|
||||||
|
showBottomNavEvent.receiveAsFlow().collectLatest { value = it }
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = bottomNavVisible,
|
||||||
|
enter = expandVertically(),
|
||||||
|
exit = shrinkVertically(),
|
||||||
|
) {
|
||||||
|
NavigationBar {
|
||||||
|
tabs
|
||||||
|
// SY -->
|
||||||
|
.fastFilter { it.isEnabled() }
|
||||||
|
// SY <--
|
||||||
|
.fastForEach {
|
||||||
|
NavigationBarItem(it/* SY --> */, alwaysShowLabel/* SY <-- */)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentWindowInsets = WindowInsets(0),
|
||||||
|
) { contentPadding ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(contentPadding)
|
||||||
|
.consumedWindowInsets(contentPadding),
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = tabNavigator.current,
|
||||||
|
transitionSpec = { Transition.OneWayFade },
|
||||||
|
content = {
|
||||||
|
tabNavigator.saveableState(key = "currentTab", it) {
|
||||||
|
it.Content()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val goToLibraryTab = { tabNavigator.current = LibraryTab }
|
||||||
|
BackHandler(
|
||||||
|
enabled = tabNavigator.current != LibraryTab,
|
||||||
|
onBack = goToLibraryTab,
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
launch {
|
||||||
|
librarySearchEvent.receiveAsFlow().collectLatest {
|
||||||
|
goToLibraryTab()
|
||||||
|
LibraryTab.search(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
openTabEvent.receiveAsFlow().collectLatest {
|
||||||
|
tabNavigator.current = when (it) {
|
||||||
|
is Tab.Library -> LibraryTab
|
||||||
|
Tab.Updates -> UpdatesTab
|
||||||
|
Tab.History -> HistoryTab
|
||||||
|
is Tab.Browse -> BrowseTab(it.toExtensions)
|
||||||
|
is Tab.More -> MoreTab(it.toDownloads)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it is Tab.Library && it.mangaIdToOpen != null) {
|
||||||
|
navigator.push(MangaScreen(it.mangaIdToOpen))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RowScope.NavigationBarItem(tab: eu.kanade.presentation.util.Tab/* SY --> */, alwaysShowLabel: Boolean/* SY <-- */) {
|
||||||
|
val tabNavigator = LocalTabNavigator.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val selected = tabNavigator.current::class == tab::class
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = selected,
|
||||||
|
onClick = {
|
||||||
|
if (!selected) {
|
||||||
|
tabNavigator.current = tab
|
||||||
|
} else {
|
||||||
|
scope.launch { tab.onReselect(navigator) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = { NavigationIconItem(tab) },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = tab.options.title,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
alwaysShowLabel = /* SY --> */alwaysShowLabel, /* SY <-- */
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NavigationRailItem(tab: eu.kanade.presentation.util.Tab/* SY --> */, alwaysShowLabel: Boolean/* SY <-- */) {
|
||||||
|
val tabNavigator = LocalTabNavigator.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val selected = tabNavigator.current::class == tab::class
|
||||||
|
NavigationRailItem(
|
||||||
|
selected = selected,
|
||||||
|
onClick = {
|
||||||
|
if (!selected) {
|
||||||
|
tabNavigator.current = tab
|
||||||
|
} else {
|
||||||
|
scope.launch { tab.onReselect(navigator) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = { NavigationIconItem(tab) },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = tab.options.title,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
alwaysShowLabel = /* SY --> */alwaysShowLabel, /* SY <-- */
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NavigationIconItem(tab: eu.kanade.presentation.util.Tab) {
|
||||||
|
BadgedBox(
|
||||||
|
badge = {
|
||||||
|
when {
|
||||||
|
tab is UpdatesTab -> {
|
||||||
|
val count by produceState(initialValue = 0) {
|
||||||
|
val pref = Injekt.get<LibraryPreferences>()
|
||||||
|
combine(
|
||||||
|
pref.showUpdatesNavBadge().changes(),
|
||||||
|
pref.unreadUpdatesCount().changes(),
|
||||||
|
) { show, count -> if (show) count else 0 }
|
||||||
|
.collectLatest { value = it }
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
Badge {
|
||||||
|
val desc = pluralStringResource(
|
||||||
|
id = R.plurals.notification_chapters_generic,
|
||||||
|
count = count,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = count.toString(),
|
||||||
|
modifier = Modifier.semantics { contentDescription = desc },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BrowseTab::class.isInstance(tab) -> {
|
||||||
|
val count by produceState(initialValue = 0) {
|
||||||
|
Injekt.get<SourcePreferences>().extensionUpdatesCount().changes()
|
||||||
|
.collectLatest { value = it }
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
Badge {
|
||||||
|
val desc = pluralStringResource(
|
||||||
|
id = R.plurals.update_check_notification_ext_updates,
|
||||||
|
count = count,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = count.toString(),
|
||||||
|
modifier = Modifier.semantics { contentDescription = desc },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(painter = tab.options.icon!!, contentDescription = tab.options.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun search(query: String) {
|
||||||
|
librarySearchEvent.send(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun openTab(tab: Tab) {
|
||||||
|
openTabEvent.send(tab)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun showBottomNav(show: Boolean) {
|
||||||
|
showBottomNavEvent.send(show)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Tab {
|
||||||
|
data class Library(val mangaIdToOpen: Long? = null) : Tab()
|
||||||
|
object Updates : Tab()
|
||||||
|
object History : Tab()
|
||||||
|
data class Browse(val toExtensions: Boolean = false) : Tab()
|
||||||
|
data class More(val toDownloads: Boolean) : Tab()
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class LibraryController(
|
|
||||||
bundle: Bundle? = null,
|
|
||||||
) : BasicFullComposeController(bundle), RootController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sheet containing filter/sort/display items.
|
|
||||||
*/
|
|
||||||
private var settingsSheet: LibrarySettingsSheet? = null
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = LibraryScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
settingsSheet = LibrarySettingsSheet(router)
|
|
||||||
viewScope.launch {
|
|
||||||
LibraryScreen.openSettingsSheetEvent
|
|
||||||
.collectLatest(::showSettingsSheet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
settingsSheet?.sheetScope?.cancel()
|
|
||||||
settingsSheet = null
|
|
||||||
super.onDestroyView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showSettingsSheet(category: Category? = null) {
|
|
||||||
if (category != null) {
|
|
||||||
settingsSheet?.show(category)
|
|
||||||
} else {
|
|
||||||
viewScope.launch { LibraryScreen.requestOpenSettingsSheet() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun search(query: String) = LibraryScreen.search(query)
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
||||||
@ -31,11 +31,11 @@ import uy.kohesive.injekt.api.get
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class LibrarySettingsSheet(
|
class LibrarySettingsSheet(
|
||||||
router: Router,
|
activity: Activity,
|
||||||
private val trackManager: TrackManager = Injekt.get(),
|
private val trackManager: TrackManager = Injekt.get(),
|
||||||
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
||||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||||
) : TabbedBottomSheetDialog(router.activity!!) {
|
) : TabbedBottomSheetDialog(activity) {
|
||||||
|
|
||||||
val filters: Filter
|
val filters: Filter
|
||||||
private val sort: Sort
|
private val sort: Sort
|
||||||
@ -48,12 +48,12 @@ class LibrarySettingsSheet(
|
|||||||
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filters = Filter(router.activity!!)
|
filters = Filter(activity)
|
||||||
sort = Sort(router.activity!!)
|
sort = Sort(activity)
|
||||||
display = Display(router.activity!!)
|
display = Display(activity)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
grouping = Grouping(router.activity!!)
|
grouping = Grouping(activity)
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -21,9 +23,11 @@ import androidx.compose.ui.platform.LocalUriHandler
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.library.model.LibraryGroup
|
import eu.kanade.domain.library.model.LibraryGroup
|
||||||
@ -44,33 +48,48 @@ import eu.kanade.presentation.library.components.SyncFavoritesConfirmDialog
|
|||||||
import eu.kanade.presentation.library.components.SyncFavoritesProgressDialog
|
import eu.kanade.presentation.library.components.SyncFavoritesProgressDialog
|
||||||
import eu.kanade.presentation.library.components.SyncFavoritesWarningDialog
|
import eu.kanade.presentation.library.components.SyncFavoritesWarningDialog
|
||||||
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import exh.favorites.FavoritesSyncStatus
|
import exh.favorites.FavoritesSyncStatus
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object LibraryScreen : Screen {
|
object LibraryTab : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 0u,
|
||||||
|
title = stringResource(R.string.label_library),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
requestOpenSettingsSheet()
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
@ -127,7 +146,7 @@ object LibraryScreen : Screen {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
||||||
if (randomItem != null) {
|
if (randomItem != null) {
|
||||||
router.openManga(randomItem.libraryManga.manga.id)
|
navigator.push(MangaScreen(randomItem.libraryManga.manga.id))
|
||||||
} else {
|
} else {
|
||||||
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
|
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
|
||||||
}
|
}
|
||||||
@ -158,9 +177,9 @@ object LibraryScreen : Screen {
|
|||||||
.map { it.manga.id }
|
.map { it.manga.id }
|
||||||
screenModel.clearSelection()
|
screenModel.clearSelection()
|
||||||
if (selectedMangaIds.isNotEmpty()) {
|
if (selectedMangaIds.isNotEmpty()) {
|
||||||
PreMigrationController.navigateToMigration(
|
PreMigrationScreen.navigateToMigration(
|
||||||
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||||
router,
|
navigator,
|
||||||
selectedMangaIds,
|
selectedMangaIds,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -172,66 +191,63 @@ object LibraryScreen : Screen {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isLoading) {
|
when {
|
||||||
LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
return@Scaffold
|
state.searchQuery.isNullOrEmpty() && state.libraryCount == 0 -> {
|
||||||
}
|
val handler = LocalUriHandler.current
|
||||||
|
EmptyScreen(
|
||||||
if (state.searchQuery.isNullOrEmpty() && state.libraryCount == 0) {
|
textResource = R.string.information_empty_library,
|
||||||
val handler = LocalUriHandler.current
|
modifier = Modifier.padding(contentPadding),
|
||||||
EmptyScreen(
|
actions = listOf(
|
||||||
textResource = R.string.information_empty_library,
|
EmptyScreenAction(
|
||||||
modifier = Modifier.padding(contentPadding),
|
stringResId = R.string.getting_started_guide,
|
||||||
actions = listOf(
|
icon = Icons.Outlined.HelpOutline,
|
||||||
EmptyScreenAction(
|
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
||||||
stringResId = R.string.getting_started_guide,
|
),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
|
||||||
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
}
|
||||||
return@Scaffold
|
else -> {
|
||||||
|
LibraryContent(
|
||||||
|
categories = state.categories,
|
||||||
|
searchQuery = state.searchQuery,
|
||||||
|
selection = state.selection,
|
||||||
|
contentPadding = contentPadding,
|
||||||
|
currentPage = { screenModel.activeCategory },
|
||||||
|
isLibraryEmpty = state.libraryCount == 0,
|
||||||
|
showPageTabs = state.showCategoryTabs,
|
||||||
|
onChangeCurrentPage = { screenModel.activeCategory = it },
|
||||||
|
onMangaClicked = { navigator.push(MangaScreen(it)) },
|
||||||
|
onContinueReadingClicked = { it: LibraryManga ->
|
||||||
|
scope.launchIO {
|
||||||
|
val chapter = screenModel.getNextUnreadChapter(it.manga)
|
||||||
|
if (chapter != null) {
|
||||||
|
context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id))
|
||||||
|
} else {
|
||||||
|
snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Unit
|
||||||
|
}.takeIf { state.showMangaContinueButton },
|
||||||
|
onToggleSelection = { screenModel.toggleSelection(it) },
|
||||||
|
onToggleRangeSelection = {
|
||||||
|
screenModel.toggleRangeSelection(it)
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
|
onRefresh = onClickRefresh,
|
||||||
|
onGlobalSearchClicked = {
|
||||||
|
navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: ""))
|
||||||
|
},
|
||||||
|
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
|
||||||
|
getDisplayModeForPage = { state.categories[it].display },
|
||||||
|
getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) },
|
||||||
|
getLibraryForPage = { state.getLibraryItemsByPage(it) },
|
||||||
|
isDownloadOnly = screenModel.isDownloadOnly,
|
||||||
|
isIncognitoMode = screenModel.isIncognitoMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryContent(
|
|
||||||
categories = state.categories,
|
|
||||||
searchQuery = state.searchQuery,
|
|
||||||
selection = state.selection,
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
currentPage = { screenModel.activeCategory },
|
|
||||||
isLibraryEmpty = state.libraryCount == 0,
|
|
||||||
showPageTabs = state.showCategoryTabs,
|
|
||||||
onChangeCurrentPage = { screenModel.activeCategory = it },
|
|
||||||
onMangaClicked = { router.openManga(it) },
|
|
||||||
onContinueReadingClicked = { it: LibraryManga ->
|
|
||||||
scope.launchIO {
|
|
||||||
val chapter = screenModel.getNextUnreadChapter(it.manga)
|
|
||||||
if (chapter != null) {
|
|
||||||
context.startActivity(ReaderActivity.newIntent(context, chapter.mangaId, chapter.id))
|
|
||||||
} else {
|
|
||||||
snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}.takeIf { state.showMangaContinueButton },
|
|
||||||
onToggleSelection = { screenModel.toggleSelection(it) },
|
|
||||||
onToggleRangeSelection = {
|
|
||||||
screenModel.toggleRangeSelection(it)
|
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
|
||||||
},
|
|
||||||
onRefresh = onClickRefresh,
|
|
||||||
onGlobalSearchClicked = {
|
|
||||||
router.pushController(GlobalSearchController(screenModel.state.value.searchQuery ?: ""))
|
|
||||||
},
|
|
||||||
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
|
|
||||||
getDisplayModeForPage = { state.categories[it].display },
|
|
||||||
getColumnsForOrientation = { screenModel.getColumnsPreferenceForCurrentOrientation(it) },
|
|
||||||
getLibraryForPage = { state.getLibraryItemsByPage(it) },
|
|
||||||
isDownloadOnly = screenModel.isDownloadOnly,
|
|
||||||
isIncognitoMode = screenModel.isIncognitoMode,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val onDismissRequest = screenModel::closeDialog
|
val onDismissRequest = screenModel::closeDialog
|
||||||
@ -242,7 +258,7 @@ object LibraryScreen : Screen {
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = {
|
onEditCategories = {
|
||||||
screenModel.clearSelection()
|
screenModel.clearSelection()
|
||||||
router.pushController(CategoryController())
|
navigator.push(CategoryScreen())
|
||||||
},
|
},
|
||||||
onConfirm = { include, exclude ->
|
onConfirm = { include, exclude ->
|
||||||
screenModel.clearSelection()
|
screenModel.clearSelection()
|
||||||
@ -295,7 +311,7 @@ object LibraryScreen : Screen {
|
|||||||
SyncFavoritesProgressDialog(
|
SyncFavoritesProgressDialog(
|
||||||
status = screenModel.favoritesSync.status.collectAsState().value,
|
status = screenModel.favoritesSync.status.collectAsState().value,
|
||||||
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
|
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
|
||||||
openManga = { router.openManga(it.id) },
|
openManga = { navigator.push(MangaScreen(it.id)) },
|
||||||
)
|
)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
@ -307,11 +323,9 @@ object LibraryScreen : Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.selectionMode) {
|
LaunchedEffect(state.selectionMode) {
|
||||||
// Could perhaps be removed when navigation is in a Compose world
|
HomeScreen.showBottomNav(!state.selectionMode)
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.isLoading) {
|
LaunchedEffect(state.isLoading) {
|
||||||
if (!state.isLoading) {
|
if (!state.isLoading) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
||||||
@ -319,23 +333,19 @@ object LibraryScreen : Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch { queryEvent.collectLatest(screenModel::search) }
|
launch { queryEvent.receiveAsFlow().collect(screenModel::search) }
|
||||||
launch { requestSettingsSheetEvent.collectLatest { onClickFilter() } }
|
launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { onClickFilter() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Router.openManga(mangaId: Long) {
|
|
||||||
pushController(MangaController(mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// For invoking search from other screen
|
// For invoking search from other screen
|
||||||
private val queryEvent = MutableSharedFlow<String>(replay = 1)
|
private val queryEvent = Channel<String>()
|
||||||
fun search(query: String) = queryEvent.tryEmit(query)
|
suspend fun search(query: String) = queryEvent.send(query)
|
||||||
|
|
||||||
// For opening settings sheet in LibraryController
|
// For opening settings sheet in LibraryController
|
||||||
private val requestSettingsSheetEvent = MutableSharedFlow<Unit>()
|
private val requestSettingsSheetEvent = Channel<Unit>()
|
||||||
private val openSettingsSheetEvent_ = MutableSharedFlow<Category>()
|
private val openSettingsSheetEvent_ = Channel<Category>()
|
||||||
val openSettingsSheetEvent = openSettingsSheetEvent_.asSharedFlow()
|
val openSettingsSheetEvent = openSettingsSheetEvent_.receiveAsFlow()
|
||||||
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.emit(category)
|
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.send(category)
|
||||||
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.emit(Unit)
|
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.send(Unit)
|
||||||
}
|
}
|
@ -7,80 +7,75 @@ import android.graphics.Color
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.Menu
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
import androidx.core.splashscreen.SplashScreen
|
import androidx.core.splashscreen.SplashScreen
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bluelinelabs.conductor.Conductor
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import com.bluelinelabs.conductor.Controller
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
|
||||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
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.more.settings.screen.ConfigureExhDialog
|
||||||
|
import eu.kanade.presentation.more.settings.screen.WhatsNewDialog
|
||||||
|
import eu.kanade.presentation.util.Transition
|
||||||
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||||
import eu.kanade.tachiyomi.databinding.MainActivityBinding
|
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeContentController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
import eu.kanade.tachiyomi.ui.library.LibraryTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
|
||||||
import eu.kanade.tachiyomi.util.preference.asHotFlow
|
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.getThemeColor
|
|
||||||
import eu.kanade.tachiyomi.util.system.isTabletUi
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||||
import exh.EXHMigrations
|
import exh.EXHMigrations
|
||||||
import exh.eh.EHentaiUpdateWorker
|
import exh.eh.EHentaiUpdateWorker
|
||||||
import exh.source.BlacklistedSources
|
import exh.source.BlacklistedSources
|
||||||
import exh.source.EH_SOURCE_ID
|
import exh.source.EH_SOURCE_ID
|
||||||
import exh.source.EXH_SOURCE_ID
|
import exh.source.EXH_SOURCE_ID
|
||||||
import exh.uconfig.WarnConfigureDialogController
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.merge
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -99,24 +94,20 @@ class MainActivity : BaseActivity() {
|
|||||||
private val unsortedPreferences: UnsortedPreferences by injectLazy()
|
private val unsortedPreferences: UnsortedPreferences by injectLazy()
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
lateinit var binding: MainActivityBinding
|
|
||||||
|
|
||||||
private lateinit var router: Router
|
|
||||||
|
|
||||||
private val startScreenId = R.id.nav_library
|
|
||||||
private var isConfirmingExit: Boolean = false
|
|
||||||
private var isHandlingShortcut: Boolean = false
|
private var isHandlingShortcut: Boolean = false
|
||||||
|
|
||||||
/**
|
|
||||||
* App bar lift state for backstack
|
|
||||||
*/
|
|
||||||
private val backstackLiftState = mutableMapOf<String, Boolean>()
|
|
||||||
|
|
||||||
private val chapterCache: ChapterCache by injectLazy()
|
private val chapterCache: ChapterCache by injectLazy()
|
||||||
|
|
||||||
// To be checked by splash screen. If true then splash screen will be removed.
|
// To be checked by splash screen. If true then splash screen will be removed.
|
||||||
var ready = false
|
var ready = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sheet containing filter/sort/display items.
|
||||||
|
*/
|
||||||
|
private var settingsSheet: LibrarySettingsSheet? = null
|
||||||
|
|
||||||
|
private lateinit var navigator: Navigator
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
// Idle-until-urgent
|
// Idle-until-urgent
|
||||||
private var firstPaint = false
|
private var firstPaint = false
|
||||||
@ -134,6 +125,8 @@ class MainActivity : BaseActivity() {
|
|||||||
iuuQueue += task
|
iuuQueue += task
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var runExhConfigureDialog by mutableStateOf(false)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -164,23 +157,64 @@ class MainActivity : BaseActivity() {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
binding = MainActivityBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
||||||
if (!isTaskRoot) {
|
if (!isTaskRoot) {
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(binding.root)
|
|
||||||
setSupportActionBar(binding.toolbar)
|
|
||||||
|
|
||||||
// Draw edge-to-edge
|
// Draw edge-to-edge
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
binding.bottomNav?.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
settingsSheet = LibrarySettingsSheet(this)
|
||||||
padding()
|
LibraryTab.openSettingsSheetEvent
|
||||||
|
.onEach(::showSettingsSheet)
|
||||||
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
|
setComposeContent {
|
||||||
|
Navigator(
|
||||||
|
screen = HomeScreen,
|
||||||
|
disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = true),
|
||||||
|
) { navigator ->
|
||||||
|
if (navigator.size == 1) {
|
||||||
|
ConfirmExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(navigator) {
|
||||||
|
this@MainActivity.navigator = navigator
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckForUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
||||||
|
if (showChangelog) {
|
||||||
|
// SY -->
|
||||||
|
WhatsNewDialog(onDismissRequest = { showChangelog = false })
|
||||||
|
// SY <--
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureExhDialog(run = runExhConfigureDialog, onRunning = { runExhConfigureDialog = false })
|
||||||
}
|
}
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
@ -190,114 +224,26 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
setSplashScreenExitAnimation(splashScreen)
|
setSplashScreenExitAnimation(splashScreen)
|
||||||
|
|
||||||
nav.setOnItemSelectedListener { item ->
|
|
||||||
val id = item.itemId
|
|
||||||
|
|
||||||
val currentRoot = router.backstack.firstOrNull()
|
|
||||||
if (currentRoot?.tag()?.toIntOrNull() != id) {
|
|
||||||
when (id) {
|
|
||||||
R.id.nav_library -> router.setRoot(LibraryController(), id)
|
|
||||||
R.id.nav_updates -> router.setRoot(UpdatesController(), id)
|
|
||||||
R.id.nav_history -> router.setRoot(HistoryController(), id)
|
|
||||||
R.id.nav_browse -> router.setRoot(BrowseController(toExtensions = false), id)
|
|
||||||
R.id.nav_more -> router.setRoot(MoreController(), id)
|
|
||||||
}
|
|
||||||
} else if (!isHandlingShortcut) {
|
|
||||||
when (id) {
|
|
||||||
R.id.nav_library -> {
|
|
||||||
val controller = router.getControllerWithTag(id.toString()) as? LibraryController
|
|
||||||
controller?.showSettingsSheet()
|
|
||||||
}
|
|
||||||
R.id.nav_updates -> {
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
router.pushController(DownloadController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.nav_history -> {
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
try {
|
|
||||||
val historyController = router.backstack[0].controller as HistoryController
|
|
||||||
historyController.resumeLastChapterRead()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
toast(R.string.cant_open_last_read_chapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.nav_more -> {
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
router.pushController(SettingsMainController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val container: ViewGroup = binding.controllerContainer
|
|
||||||
router = Conductor.attachRouter(this, container, savedInstanceState)
|
|
||||||
.setPopRootControllerMode(Router.PopRootControllerMode.NEVER)
|
|
||||||
router.addChangeListener(
|
|
||||||
object : ControllerChangeHandler.ControllerChangeListener {
|
|
||||||
override fun onChangeStarted(
|
|
||||||
to: Controller?,
|
|
||||||
from: Controller?,
|
|
||||||
isPush: Boolean,
|
|
||||||
container: ViewGroup,
|
|
||||||
handler: ControllerChangeHandler,
|
|
||||||
) {
|
|
||||||
syncActivityViewWithController(to, from, isPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeCompleted(
|
|
||||||
to: Controller?,
|
|
||||||
from: Controller?,
|
|
||||||
isPush: Boolean,
|
|
||||||
container: ViewGroup,
|
|
||||||
handler: ControllerChangeHandler,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (!router.hasRootController()) {
|
|
||||||
// Set start screen
|
|
||||||
if (!handleIntentAction(intent)) {
|
|
||||||
moveToStartScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syncActivityViewWithController()
|
|
||||||
|
|
||||||
binding.toolbar.setNavigationOnClickListener {
|
|
||||||
onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
|
// Set start screen
|
||||||
|
lifecycleScope.launch { handleIntentAction(intent) }
|
||||||
|
|
||||||
// Reset Incognito Mode on relaunch
|
// Reset Incognito Mode on relaunch
|
||||||
preferences.incognitoMode().set(false)
|
preferences.incognitoMode().set(false)
|
||||||
|
|
||||||
// Show changelog prompt on update
|
|
||||||
if (didMigration) {
|
|
||||||
WhatsNewDialogController().showDialog(router)
|
|
||||||
}
|
|
||||||
// EXH <--
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
initWhenIdle {
|
initWhenIdle {
|
||||||
// Upload settings
|
// Upload settings
|
||||||
if (unsortedPreferences.enableExhentai().get() &&
|
if (unsortedPreferences.enableExhentai().get() &&
|
||||||
unsortedPreferences.exhShowSettingsUploadWarning().get()
|
unsortedPreferences.exhShowSettingsUploadWarning().get()
|
||||||
) {
|
) {
|
||||||
WarnConfigureDialogController.uploadSettings(router)
|
runExhConfigureDialog = true
|
||||||
}
|
}
|
||||||
// Scheduler uploader job if required
|
// Scheduler uploader job if required
|
||||||
|
|
||||||
EHentaiUpdateWorker.scheduleBackground(this)
|
EHentaiUpdateWorker.scheduleBackground(this)
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
} else {
|
|
||||||
// Restore selected nav item
|
|
||||||
router.backstack.firstOrNull()?.tag()?.toIntOrNull()?.let {
|
|
||||||
nav.menu.findItem(it).isChecked = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
if (!unsortedPreferences.isHentaiEnabled().get()) {
|
if (!unsortedPreferences.isHentaiEnabled().get()) {
|
||||||
@ -305,40 +251,55 @@ class MainActivity : BaseActivity() {
|
|||||||
BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID
|
BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
|
}
|
||||||
|
|
||||||
merge(libraryPreferences.showUpdatesNavBadge().changes(), libraryPreferences.unreadUpdatesCount().changes())
|
private fun showSettingsSheet(category: Category? = null) {
|
||||||
.onEach { setUnreadUpdatesBadge() }
|
if (category != null) {
|
||||||
.launchIn(lifecycleScope)
|
settingsSheet?.show(category)
|
||||||
|
} else {
|
||||||
|
lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourcePreferences.extensionUpdatesCount()
|
@Composable
|
||||||
.asHotFlow { setExtensionsBadge() }
|
private fun ConfirmExit() {
|
||||||
.launchIn(lifecycleScope)
|
val scope = rememberCoroutineScope()
|
||||||
|
val confirmExit by preferences.confirmExit().collectAsState()
|
||||||
|
var waitingConfirmation by remember { mutableStateOf(false) }
|
||||||
|
BackHandler(enabled = !waitingConfirmation && confirmExit) {
|
||||||
|
scope.launch {
|
||||||
|
waitingConfirmation = true
|
||||||
|
val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
|
||||||
|
delay(2.seconds)
|
||||||
|
toast.cancel()
|
||||||
|
waitingConfirmation = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
preferences.downloadedOnly()
|
@Composable
|
||||||
.asHotFlow { binding.downloadedOnly.isVisible = it }
|
private fun CheckForUpdate() {
|
||||||
.launchIn(lifecycleScope)
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
binding.incognitoMode.isVisible = preferences.incognitoMode().get()
|
LaunchedEffect(Unit) {
|
||||||
preferences.incognitoMode().changes()
|
// App updates
|
||||||
.drop(1)
|
if (BuildConfig.INCLUDE_UPDATER) {
|
||||||
.onEach {
|
try {
|
||||||
binding.incognitoMode.isVisible = it
|
val result = AppUpdateChecker().checkForUpdate(context)
|
||||||
|
if (result is AppUpdateResult.NewUpdate) {
|
||||||
// Close BrowseSourceController and its MangaController child when incognito mode is disabled
|
val updateScreen = NewUpdateScreen(
|
||||||
if (!it) {
|
versionName = result.release.version,
|
||||||
val fg = router.backstack.lastOrNull()?.controller
|
changelogInfo = result.release.info,
|
||||||
if (fg is BrowseSourceController || fg is MangaController && fg.fromSource) {
|
releaseLink = result.release.releaseLink,
|
||||||
router.popToRoot()
|
downloadLink = result.release.getDownloadLink(),
|
||||||
|
)
|
||||||
|
navigator.push(updateScreen)
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(lifecycleScope)
|
}
|
||||||
|
|
||||||
// SY -->
|
|
||||||
uiPreferences.bottomBarLabels()
|
|
||||||
.asHotFlow { setNavLabelVisibility() }
|
|
||||||
.launchIn(lifecycleScope)
|
|
||||||
// SY <--
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -348,16 +309,16 @@ class MainActivity : BaseActivity() {
|
|||||||
* after the animation is finished.
|
* after the animation is finished.
|
||||||
*/
|
*/
|
||||||
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
|
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
|
||||||
|
val root = findViewById<View>(android.R.id.content)
|
||||||
val setNavbarScrim = {
|
val setNavbarScrim = {
|
||||||
// Make sure navigation bar is on bottom before we modify it
|
// Make sure navigation bar is on bottom before we modify it
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(root) { _, insets ->
|
||||||
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
||||||
val elevation = binding.bottomNav?.elevation ?: 0F
|
window.setNavigationBarTransparentCompat(this@MainActivity, 3.dpToPx.toFloat())
|
||||||
window.setNavigationBarTransparentCompat(this@MainActivity, elevation)
|
|
||||||
}
|
}
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
ViewCompat.requestApplyInsets(binding.root)
|
ViewCompat.requestApplyInsets(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
|
||||||
@ -375,7 +336,7 @@ class MainActivity : BaseActivity() {
|
|||||||
duration = SPLASH_EXIT_ANIM_DURATION
|
duration = SPLASH_EXIT_ANIM_DURATION
|
||||||
addUpdateListener { va ->
|
addUpdateListener { va ->
|
||||||
val value = va.animatedValue as Float
|
val value = va.animatedValue as Float
|
||||||
binding.root.translationY = value * 16.dpToPx
|
root.translationY = value * 16.dpToPx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,69 +364,13 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
if (!handleIntentAction(intent)) {
|
val handle = runBlocking { handleIntentAction(intent) }
|
||||||
|
if (!handle) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
private suspend fun handleIntentAction(intent: Intent): Boolean {
|
||||||
super.onResume()
|
|
||||||
checkForUpdates()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkForUpdates() {
|
|
||||||
lifecycleScope.launchIO {
|
|
||||||
// App updates
|
|
||||||
if (BuildConfig.INCLUDE_UPDATER) {
|
|
||||||
try {
|
|
||||||
val result = AppUpdateChecker().checkForUpdate(this@MainActivity)
|
|
||||||
if (result is AppUpdateResult.NewUpdate) {
|
|
||||||
NewUpdateDialogController(result).showDialog(router)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extension updates
|
|
||||||
try {
|
|
||||||
ExtensionGithubApi().checkForUpdates(
|
|
||||||
this@MainActivity,
|
|
||||||
fromAvailableExtensionList = true,
|
|
||||||
)?.let { pendingUpdates ->
|
|
||||||
sourcePreferences.extensionUpdatesCount().set(pendingUpdates.size)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUnreadUpdatesBadge() {
|
|
||||||
val updates = if (libraryPreferences.showUpdatesNavBadge().get()) libraryPreferences.unreadUpdatesCount().get() else 0
|
|
||||||
if (updates > 0) {
|
|
||||||
nav.getOrCreateBadge(R.id.nav_updates).apply {
|
|
||||||
number = updates
|
|
||||||
setContentDescriptionQuantityStringsResource(R.plurals.notification_chapters_generic)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nav.removeBadge(R.id.nav_updates)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setExtensionsBadge() {
|
|
||||||
val updates = sourcePreferences.extensionUpdatesCount().get()
|
|
||||||
if (updates > 0) {
|
|
||||||
nav.getOrCreateBadge(R.id.nav_browse).apply {
|
|
||||||
number = updates
|
|
||||||
setContentDescriptionQuantityStringsResource(R.plurals.update_check_notification_ext_updates)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nav.removeBadge(R.id.nav_browse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIntentAction(intent: Intent): Boolean {
|
|
||||||
val notificationId = intent.getIntExtra("notificationId", -1)
|
val notificationId = intent.getIntExtra("notificationId", -1)
|
||||||
if (notificationId > -1) {
|
if (notificationId > -1) {
|
||||||
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
||||||
@ -474,32 +379,19 @@ class MainActivity : BaseActivity() {
|
|||||||
isHandlingShortcut = true
|
isHandlingShortcut = true
|
||||||
|
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
SHORTCUT_LIBRARY -> setSelectedNavItem(R.id.nav_library)
|
SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library())
|
||||||
SHORTCUT_RECENTLY_UPDATED -> setSelectedNavItem(R.id.nav_updates)
|
SHORTCUT_RECENTLY_UPDATED -> HomeScreen.openTab(HomeScreen.Tab.Updates)
|
||||||
SHORTCUT_RECENTLY_READ -> setSelectedNavItem(R.id.nav_history)
|
SHORTCUT_RECENTLY_READ -> HomeScreen.openTab(HomeScreen.Tab.History)
|
||||||
SHORTCUT_CATALOGUES -> setSelectedNavItem(R.id.nav_browse)
|
SHORTCUT_CATALOGUES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false))
|
||||||
SHORTCUT_EXTENSIONS -> {
|
SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true))
|
||||||
if (router.backstackSize > 1) {
|
|
||||||
router.popToRoot()
|
|
||||||
}
|
|
||||||
setSelectedNavItem(R.id.nav_browse)
|
|
||||||
router.pushController(BrowseController(toExtensions = true))
|
|
||||||
}
|
|
||||||
SHORTCUT_MANGA -> {
|
SHORTCUT_MANGA -> {
|
||||||
val extras = intent.extras ?: return false
|
val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
|
||||||
val fgController = router.backstack.lastOrNull()?.controller as? MangaController
|
navigator.popUntilRoot()
|
||||||
if (fgController?.mangaId != extras.getLong(MangaController.MANGA_EXTRA)) {
|
HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen))
|
||||||
router.popToRoot()
|
|
||||||
setSelectedNavItem(R.id.nav_library)
|
|
||||||
router.pushController(RouterTransaction.with(MangaController(extras)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SHORTCUT_DOWNLOADS -> {
|
SHORTCUT_DOWNLOADS -> {
|
||||||
if (router.backstackSize > 1) {
|
navigator.popUntilRoot()
|
||||||
router.popToRoot()
|
HomeScreen.openTab(HomeScreen.Tab.More(toDownloads = true))
|
||||||
}
|
|
||||||
setSelectedNavItem(R.id.nav_more)
|
|
||||||
router.pushController(DownloadController())
|
|
||||||
}
|
}
|
||||||
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
||||||
// If the intent match the "standard" Android search intent
|
// If the intent match the "standard" Android search intent
|
||||||
@ -508,20 +400,16 @@ class MainActivity : BaseActivity() {
|
|||||||
// Get the search query provided in extras, and if not null, perform a global search with it.
|
// Get the search query provided in extras, and if not null, perform a global search with it.
|
||||||
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
if (query != null && query.isNotEmpty()) {
|
if (query != null && query.isNotEmpty()) {
|
||||||
if (router.backstackSize > 1) {
|
navigator.popUntilRoot()
|
||||||
router.popToRoot()
|
navigator.push(GlobalSearchScreen(query))
|
||||||
}
|
|
||||||
router.pushController(GlobalSearchController(query))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
INTENT_SEARCH -> {
|
INTENT_SEARCH -> {
|
||||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||||
if (query != null && query.isNotEmpty()) {
|
if (query != null && query.isNotEmpty()) {
|
||||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
||||||
if (router.backstackSize > 1) {
|
navigator.popUntilRoot()
|
||||||
router.popToRoot()
|
navigator.push(GlobalSearchScreen(query, filter))
|
||||||
}
|
|
||||||
router.pushController(GlobalSearchController(query, filter ?: ""))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@ -535,190 +423,22 @@ class MainActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
settingsSheet?.sheetScope?.cancel()
|
||||||
|
settingsSheet = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
// Binding sometimes isn't actually instantiated yet somehow
|
|
||||||
nav?.setOnItemSelectedListener(null)
|
|
||||||
binding?.toolbar?.setNavigationOnClickListener(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (router.handleBack()) {
|
if (navigator.size == 1 &&
|
||||||
// A Router is consuming back press
|
!onBackPressedDispatcher.hasEnabledCallbacks() &&
|
||||||
return
|
libraryPreferences.autoClearChapterCache().get()
|
||||||
}
|
) {
|
||||||
val backstackSize = router.backstackSize
|
chapterCache.clear()
|
||||||
val startScreen = router.getControllerWithTag("$startScreenId")
|
|
||||||
if (backstackSize == 1 && startScreen == null) {
|
|
||||||
// Return to start screen
|
|
||||||
moveToStartScreen()
|
|
||||||
} else if (shouldHandleExitConfirmation()) {
|
|
||||||
// Exit confirmation (resets after 2 seconds)
|
|
||||||
lifecycleScope.launchUI { resetExitConfirmation() }
|
|
||||||
} else if (backstackSize == 1) {
|
|
||||||
// Regular back (i.e. closing the app)
|
|
||||||
if (libraryPreferences.autoClearChapterCache().get()) {
|
|
||||||
chapterCache.clear()
|
|
||||||
}
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
}
|
||||||
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveToStartScreen() {
|
|
||||||
setSelectedNavItem(startScreenId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportActionModeStarted(mode: ActionMode) {
|
|
||||||
binding.appbar.apply {
|
|
||||||
tag = isTransparentWhenNotLifted
|
|
||||||
isTransparentWhenNotLifted = false
|
|
||||||
}
|
|
||||||
// Color taken from m3_appbar_background
|
|
||||||
window.statusBarColor = ColorUtils.compositeColors(
|
|
||||||
getColor(R.color.m3_appbar_overlay_color),
|
|
||||||
getThemeColor(R.attr.colorSurface),
|
|
||||||
)
|
|
||||||
super.onSupportActionModeStarted(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportActionModeFinished(mode: ActionMode) {
|
|
||||||
binding.appbar.apply {
|
|
||||||
isTransparentWhenNotLifted = (tag as? Boolean) ?: false
|
|
||||||
tag = null
|
|
||||||
}
|
|
||||||
window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
|
|
||||||
super.onSupportActionModeFinished(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun resetExitConfirmation() {
|
|
||||||
isConfirmingExit = true
|
|
||||||
val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
|
|
||||||
delay(2.seconds)
|
|
||||||
toast.cancel()
|
|
||||||
isConfirmingExit = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldHandleExitConfirmation(): Boolean {
|
|
||||||
return router.backstackSize == 1 &&
|
|
||||||
router.getControllerWithTag("$startScreenId") != null &&
|
|
||||||
preferences.confirmExit().get() &&
|
|
||||||
!isConfirmingExit
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSelectedNavItem(itemId: Int) {
|
|
||||||
if (!isFinishing) {
|
|
||||||
nav.selectedItemId = itemId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun syncActivityViewWithController(
|
|
||||||
to: Controller? = null,
|
|
||||||
from: Controller? = null,
|
|
||||||
isPush: Boolean = true,
|
|
||||||
) {
|
|
||||||
var internalTo = to
|
|
||||||
|
|
||||||
if (internalTo == null) {
|
|
||||||
// Should go here when the activity is recreated and dialog controller is on top of the backstack
|
|
||||||
// Then we'll assume the top controller is the parent controller of this dialog
|
|
||||||
val backstack = router.backstack
|
|
||||||
internalTo = backstack.lastOrNull()?.controller
|
|
||||||
if (internalTo is DialogController) {
|
|
||||||
internalTo = backstack.getOrNull(backstack.size - 2)?.controller ?: return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Ignore changes for normal transactions
|
|
||||||
if (from is DialogController || internalTo is DialogController) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(router.backstackSize != 1)
|
|
||||||
|
|
||||||
// Always show appbar again when changing controllers
|
|
||||||
binding.appbar.setExpanded(true)
|
|
||||||
|
|
||||||
if ((from == null || from is RootController) && internalTo !is RootController) {
|
|
||||||
showNav(false)
|
|
||||||
}
|
|
||||||
if (internalTo is RootController) {
|
|
||||||
// Always show bottom nav again when returning to a RootController
|
|
||||||
showNav(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val isComposeController = internalTo is ComposeContentController
|
|
||||||
binding.appbar.isVisible = !isComposeController
|
|
||||||
binding.controllerContainer.enableScrollingBehavior(!isComposeController)
|
|
||||||
|
|
||||||
if (!isTabletUi()) {
|
|
||||||
// Save lift state
|
|
||||||
if (isPush) {
|
|
||||||
if (router.backstackSize > 1) {
|
|
||||||
// Save lift state
|
|
||||||
from?.let {
|
|
||||||
backstackLiftState[it.instanceId] = binding.appbar.isLifted
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
backstackLiftState.clear()
|
|
||||||
}
|
|
||||||
binding.appbar.isLifted = false
|
|
||||||
} else {
|
|
||||||
internalTo?.let {
|
|
||||||
binding.appbar.isLifted = backstackLiftState.getOrElse(it.instanceId) { false }
|
|
||||||
}
|
|
||||||
from?.let {
|
|
||||||
backstackLiftState.remove(it.instanceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showNav(visible: Boolean) {
|
|
||||||
showBottomNav(visible)
|
|
||||||
showSideNav(visible)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also used from some controllers to swap bottom nav with action toolbar
|
|
||||||
fun showBottomNav(visible: Boolean) {
|
|
||||||
if (visible) {
|
|
||||||
binding.bottomNav?.slideUp()
|
|
||||||
// SY -->
|
|
||||||
binding.bottomNav?.menu?.let { updateNavMenu(it) }
|
|
||||||
// SY <--
|
|
||||||
} else {
|
|
||||||
binding.bottomNav?.slideDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSideNav(visible: Boolean) {
|
|
||||||
binding.sideNav?.isVisible = visible
|
|
||||||
// SY -->
|
|
||||||
binding.sideNav?.let { updateNavMenu(it.menu) }
|
|
||||||
// SY <--
|
|
||||||
}
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
private fun updateNavMenu(menu: Menu) {
|
|
||||||
menu.findItem(R.id.nav_updates).isVisible = uiPreferences.showNavUpdates().get()
|
|
||||||
menu.findItem(R.id.nav_history).isVisible = uiPreferences.showNavHistory().get()
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
private val nav: NavigationBarView
|
|
||||||
get() = binding.bottomNav ?: binding.sideNav!!
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
private fun setNavLabelVisibility() {
|
|
||||||
if (uiPreferences.bottomBarLabels().get()) {
|
|
||||||
nav.labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_LABELED
|
|
||||||
} else {
|
|
||||||
nav.labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_SELECTED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
registerSecureActivity(this)
|
registerSecureActivity(this)
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.main
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
|
||||||
import it.gmariotti.changelibs.library.view.ChangeLogRecyclerView
|
|
||||||
|
|
||||||
class WhatsNewDialogController : DialogController() {
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val activity = activity!!
|
|
||||||
val view = WhatsNewRecyclerView(activity)
|
|
||||||
return MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.whats_new)
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
class WhatsNewRecyclerView(context: Context) : ChangeLogRecyclerView(context) {
|
|
||||||
override fun initAttrs(attrs: AttributeSet?, defStyle: Int) {
|
|
||||||
mRowLayoutId = R.layout.changelog_row_layout
|
|
||||||
mRowHeaderLayoutId = R.layout.changelog_header_layout
|
|
||||||
mChangeLogFileResourceId = if (BuildConfig.DEBUG /* SY --> */ || isPreviewBuildType/* SY <-- */) R.raw.changelog_debug else R.raw.changelog_release
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
|
||||||
import eu.kanade.tachiyomi.util.system.getSerializableCompat
|
|
||||||
|
|
||||||
class MangaController : BasicFullComposeController {
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
mangaId: Long,
|
|
||||||
fromSource: Boolean = false,
|
|
||||||
smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
|
||||||
) : super(bundleOf(MANGA_EXTRA to mangaId, FROM_SOURCE_EXTRA to fromSource, SMART_SEARCH_CONFIG_EXTRA to smartSearchConfig))
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
constructor(redirect: MangaInfoScreenModel.EXHRedirect) : super(
|
|
||||||
bundleOf(MANGA_EXTRA to redirect.mangaId),
|
|
||||||
)
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
val mangaId: Long
|
|
||||||
get() = args.getLong(MANGA_EXTRA)
|
|
||||||
|
|
||||||
val fromSource: Boolean
|
|
||||||
get() = args.getBoolean(FROM_SOURCE_EXTRA)
|
|
||||||
|
|
||||||
// SY -->
|
|
||||||
val smartSearchConfig: SourcesScreen.SmartSearchConfig?
|
|
||||||
get() = args.getSerializableCompat(SMART_SEARCH_CONFIG_EXTRA)
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MangaScreen(mangaId, fromSource, smartSearchConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val FROM_SOURCE_EXTRA = "from_source"
|
|
||||||
const val MANGA_EXTRA = "manga"
|
|
||||||
|
|
||||||
// EXH -->
|
|
||||||
const val UPDATE_EXTRA = "update"
|
|
||||||
const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig"
|
|
||||||
// EXH <--
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
@ -29,7 +30,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
@ -47,7 +47,6 @@ import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
|||||||
import eu.kanade.presentation.manga.components.MangaCoverDialog
|
import eu.kanade.presentation.manga.components.MangaCoverDialog
|
||||||
import eu.kanade.presentation.manga.components.SelectScanlatorsDialog
|
import eu.kanade.presentation.manga.components.SelectScanlatorsDialog
|
||||||
import eu.kanade.presentation.util.LocalNavigatorContentPadding
|
import eu.kanade.presentation.util.LocalNavigatorContentPadding
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
@ -55,22 +54,16 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.isLocalOrStub
|
import eu.kanade.tachiyomi.source.isLocalOrStub
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen
|
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.merged.EditMergedSettingsDialog
|
import eu.kanade.tachiyomi.ui.manga.merged.EditMergedSettingsDialog
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
|
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
@ -78,9 +71,9 @@ import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
|||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.md.similar.MangaDexSimilarController
|
import exh.md.similar.MangaDexSimilarScreen
|
||||||
import exh.pagepreview.PagePreviewController
|
import exh.pagepreview.PagePreviewScreen
|
||||||
import exh.recs.RecommendsController
|
import exh.recs.RecommendsScreen
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.source.isMdBasedSource
|
import exh.source.isMdBasedSource
|
||||||
@ -89,12 +82,13 @@ import kotlinx.coroutines.CancellationException
|
|||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.take
|
import kotlinx.coroutines.flow.take
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MangaScreen(
|
class MangaScreen(
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val fromSource: Boolean = false,
|
val fromSource: Boolean = false,
|
||||||
private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
||||||
) : Screen {
|
) : Screen {
|
||||||
|
|
||||||
@ -103,9 +97,9 @@ class MangaScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val screenModel = rememberScreenModel { MangaInfoScreenModel(context, mangaId, fromSource, smartSearchConfig != null) }
|
val screenModel = rememberScreenModel { MangaInfoScreenModel(context, mangaId, fromSource, smartSearchConfig != null) }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -123,8 +117,8 @@ class MangaScreen(
|
|||||||
screenModel.redirectFlow
|
screenModel.redirectFlow
|
||||||
.take(1)
|
.take(1)
|
||||||
.onEach {
|
.onEach {
|
||||||
router.replaceTopController(
|
navigator.replace(
|
||||||
MangaController(it).withFadeTransaction(),
|
MangaScreen(it.mangaId),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
@ -135,12 +129,7 @@ class MangaScreen(
|
|||||||
state = successState,
|
state = successState,
|
||||||
snackbarHostState = screenModel.snackbarHostState,
|
snackbarHostState = screenModel.snackbarHostState,
|
||||||
isTabletUi = isTabletUi(),
|
isTabletUi = isTabletUi(),
|
||||||
onBackClicked = {
|
onBackClicked = navigator::pop,
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
else -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChapterClicked = { openChapter(context, it) },
|
onChapterClicked = { openChapter(context, it) },
|
||||||
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
||||||
onAddToLibraryClicked = {
|
onAddToLibraryClicked = {
|
||||||
@ -158,11 +147,11 @@ class MangaScreen(
|
|||||||
// SY <--
|
// SY <--
|
||||||
onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||||
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable },
|
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable },
|
||||||
onTagClicked = { performGenreSearch(router, navigator, it, screenModel.source!!) },
|
onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
|
||||||
onFilterButtonClicked = screenModel::showSettingsDialog,
|
onFilterButtonClicked = screenModel::showSettingsDialog,
|
||||||
onRefresh = screenModel::fetchAllFromSource,
|
onRefresh = screenModel::fetchAllFromSource,
|
||||||
onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) },
|
onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) },
|
||||||
onSearch = { query, global -> performSearch(router, navigator, query, global) },
|
onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } },
|
||||||
onCoverClicked = screenModel::showCoverDialog,
|
onCoverClicked = screenModel::showCoverDialog,
|
||||||
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||||
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
||||||
@ -171,12 +160,12 @@ class MangaScreen(
|
|||||||
onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite },
|
onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite },
|
||||||
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
|
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
|
||||||
onEditInfoClicked = screenModel::showEditMangaInfoDialog,
|
onEditInfoClicked = screenModel::showEditMangaInfoDialog,
|
||||||
onRecommendClicked = { openRecommends(context, router, screenModel.source?.getMainSource(), successState.manga) },
|
onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) },
|
||||||
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
||||||
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
||||||
onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) },
|
onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) },
|
||||||
onOpenPagePreview = { openPagePreview(context, successState.chapters.getNextUnread(successState.manga), it) },
|
onOpenPagePreview = { openPagePreview(context, successState.chapters.getNextUnread(successState.manga), it) },
|
||||||
onMorePreviewsClicked = { openMorePagePreviews(router, successState.manga) },
|
onMorePreviewsClicked = { openMorePagePreviews(navigator, successState.manga) },
|
||||||
// SY <--
|
// SY <--
|
||||||
onMultiBookmarkClicked = screenModel::bookmarkChapters,
|
onMultiBookmarkClicked = screenModel::bookmarkChapters,
|
||||||
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
|
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
|
||||||
@ -366,52 +355,29 @@ class MangaScreen(
|
|||||||
*
|
*
|
||||||
* @param query the search query to the parent controller
|
* @param query the search query to the parent controller
|
||||||
*/
|
*/
|
||||||
private fun performSearch(router: Router, navigator: Navigator, query: String, global: Boolean) {
|
private suspend fun performSearch(navigator: Navigator, query: String, global: Boolean) {
|
||||||
if (global) {
|
if (global) {
|
||||||
router.pushController(GlobalSearchController(query))
|
navigator.push(GlobalSearchScreen(query))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
if (navigator.size < 2) {
|
||||||
if (navigator.canPop) {
|
|
||||||
when (val previousScreen = navigator.items[navigator.items.size - 2]) {
|
|
||||||
is SourceFeedScreen -> {
|
|
||||||
navigator.pop()
|
|
||||||
previousScreen.onBrowseClick(router, previousScreen.sourceId, query)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
if (router.backstackSize < 2) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
when (val previousController = router.backstack[router.backstackSize - 2].controller) {
|
when (val previousController = navigator.items[navigator.size - 2]) {
|
||||||
is LibraryController -> {
|
is HomeScreen -> {
|
||||||
router.handleBack()
|
navigator.pop()
|
||||||
previousController.search(query)
|
previousController.search(query)
|
||||||
}
|
}
|
||||||
is UpdatesController,
|
is BrowseSourceScreen -> {
|
||||||
is HistoryController,
|
navigator.pop()
|
||||||
-> {
|
previousController.search(query)
|
||||||
// Manually navigate to LibraryController
|
|
||||||
router.handleBack()
|
|
||||||
(router.activity as MainActivity).setSelectedNavItem(R.id.nav_library)
|
|
||||||
val controller = router.getControllerWithTag(R.id.nav_library.toString()) as LibraryController
|
|
||||||
controller.search(query)
|
|
||||||
}
|
|
||||||
is BrowseSourceController -> {
|
|
||||||
router.handleBack()
|
|
||||||
previousController.searchWithQuery(query)
|
|
||||||
}
|
}
|
||||||
// SY -->
|
// SY -->
|
||||||
is SourceFeedController -> {
|
is SourceFeedScreen -> {
|
||||||
router.handleBack()
|
navigator.pop()
|
||||||
router.handleBack()
|
navigator.replace(BrowseSourceScreen(previousController.sourceId, query))
|
||||||
router.pushController(BrowseSourceController(previousController.sourceId, query))
|
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
@ -422,20 +388,17 @@ class MangaScreen(
|
|||||||
*
|
*
|
||||||
* @param genreName the search genre to the parent controller
|
* @param genreName the search genre to the parent controller
|
||||||
*/
|
*/
|
||||||
private fun performGenreSearch(router: Router, navigator: Navigator, genreName: String, source: Source) {
|
private suspend fun performGenreSearch(navigator: Navigator, genreName: String, source: Source) {
|
||||||
if (router.backstackSize < 2) {
|
if (navigator.size < 2) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val previousController = router.backstack[router.backstackSize - 2].controller
|
val previousController = navigator.items[navigator.size - 2]
|
||||||
|
if (previousController is BrowseSourceScreen && source is HttpSource) {
|
||||||
if (previousController is BrowseSourceController &&
|
navigator.pop()
|
||||||
source is HttpSource
|
previousController.searchGenre(genreName)
|
||||||
) {
|
|
||||||
router.handleBack()
|
|
||||||
previousController.searchWithGenre(genreName)
|
|
||||||
} else {
|
} else {
|
||||||
performSearch(router, navigator, genreName, global = false)
|
performSearch(navigator, genreName, global = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,8 +447,8 @@ class MangaScreen(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openMorePagePreviews(router: Router, manga: Manga) {
|
private fun openMorePagePreviews(navigator: Navigator, manga: Manga) {
|
||||||
router.pushController(PagePreviewController(manga.id))
|
navigator.push(PagePreviewScreen(manga.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openPagePreview(context: Context, chapter: Chapter?, page: Int) {
|
private fun openPagePreview(context: Context, chapter: Chapter?, page: Int) {
|
||||||
@ -527,7 +490,7 @@ class MangaScreen(
|
|||||||
// EXH <--
|
// EXH <--
|
||||||
|
|
||||||
// AZ -->
|
// AZ -->
|
||||||
private fun openRecommends(context: Context, router: Router, source: Source?, manga: Manga) {
|
private fun openRecommends(context: Context, navigator: Navigator, source: Source?, manga: Manga) {
|
||||||
source ?: return
|
source ?: return
|
||||||
if (source.isMdBasedSource()) {
|
if (source.isMdBasedSource()) {
|
||||||
MaterialAlertDialogBuilder(context)
|
MaterialAlertDialogBuilder(context)
|
||||||
@ -541,13 +504,13 @@ class MangaScreen(
|
|||||||
) { dialog, index ->
|
) { dialog, index ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
when (index) {
|
when (index) {
|
||||||
0 -> router.pushController(MangaDexSimilarController(manga, source as CatalogueSource))
|
0 -> navigator.push(MangaDexSimilarScreen(manga.id, source.id))
|
||||||
1 -> router.pushController(RecommendsController(manga, source as CatalogueSource))
|
1 -> navigator.push(RecommendsScreen(manga.id, source.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
} else if (source is CatalogueSource) {
|
} else if (source is CatalogueSource) {
|
||||||
router.pushController(RecommendsController(manga, source))
|
navigator.push(RecommendsScreen(manga.id, source.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// AZ <--
|
// AZ <--
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.more
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
|
|
||||||
class MoreController : BasicFullComposeController(), RootController {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MoreScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val URL_HELP = "https://tachiyomi.org/help/"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +1,39 @@
|
|||||||
package eu.kanade.tachiyomi.ui.more
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
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.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.core.prefs.asState
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.more.MoreScreen
|
import eu.kanade.presentation.more.MoreScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
import eu.kanade.tachiyomi.ui.stats.StatsScreen
|
||||||
import eu.kanade.tachiyomi.ui.stats.StatsController
|
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
|
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
|
||||||
import exh.ui.batchadd.BatchAddController
|
import exh.ui.batchadd.BatchAddScreen
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -35,11 +42,28 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object MoreScreen : Screen {
|
data class MoreTab(private val toDownloads: Boolean = false) : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_more_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 4u,
|
||||||
|
title = stringResource(R.string.label_more),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
navigator.push(SettingsScreen.toMainScreen())
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { MoreScreenModel() }
|
val screenModel = rememberScreenModel { MoreScreenModel() }
|
||||||
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
|
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
|
||||||
MoreScreen(
|
MoreScreen(
|
||||||
@ -53,16 +77,16 @@ object MoreScreen : Screen {
|
|||||||
showNavUpdates = screenModel.showNavUpdates,
|
showNavUpdates = screenModel.showNavUpdates,
|
||||||
showNavHistory = screenModel.showNavHistory,
|
showNavHistory = screenModel.showNavHistory,
|
||||||
// SY <--
|
// SY <--
|
||||||
onClickDownloadQueue = { router.pushController(DownloadController()) },
|
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
|
||||||
onClickCategories = { router.pushController(CategoryController()) },
|
onClickCategories = { navigator.push(CategoryScreen()) },
|
||||||
onClickStats = { router.pushController(StatsController()) },
|
onClickStats = { navigator.push(StatsScreen()) },
|
||||||
onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) },
|
onClickBackupAndRestore = { navigator.push(SettingsScreen.toBackupScreen()) },
|
||||||
onClickSettings = { router.pushController(SettingsMainController()) },
|
onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) },
|
||||||
onClickAbout = { router.pushController(SettingsMainController.toAboutScreen()) },
|
onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) },
|
||||||
// SY -->
|
// SY -->
|
||||||
onClickBatchAdd = { router.pushController(BatchAddController()) },
|
onClickBatchAdd = { navigator.push(BatchAddScreen()) },
|
||||||
onClickUpdates = { router.pushController(UpdatesController()) },
|
onClickUpdates = { navigator.push(UpdatesTab) },
|
||||||
onClickHistory = { router.pushController(HistoryController()) },
|
onClickHistory = { navigator.push(HistoryTab) },
|
||||||
// SY <--
|
// SY <--
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -1,62 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.more
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
|
||||||
import io.noties.markwon.Markwon
|
|
||||||
|
|
||||||
class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
|
|
||||||
|
|
||||||
constructor(update: AppUpdateResult.NewUpdate) : this(
|
|
||||||
bundleOf(
|
|
||||||
BODY_KEY to update.release.info,
|
|
||||||
VERSION_KEY to update.release.version,
|
|
||||||
RELEASE_URL_KEY to update.release.releaseLink,
|
|
||||||
DOWNLOAD_URL_KEY to update.release.getDownloadLink(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val releaseBody = args.getString(BODY_KEY)!!
|
|
||||||
.replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "")
|
|
||||||
val info = Markwon.create(activity!!).toMarkdown(releaseBody)
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.update_check_notification_update_available)
|
|
||||||
.setMessage(info)
|
|
||||||
.setPositiveButton(R.string.update_check_confirm) { _, _ ->
|
|
||||||
applicationContext?.let { context ->
|
|
||||||
// Start download
|
|
||||||
val url = args.getString(DOWNLOAD_URL_KEY)!!
|
|
||||||
val version = args.getString(VERSION_KEY)
|
|
||||||
AppUpdateService.start(context, url, version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNeutralButton(R.string.update_check_open) { _, _ ->
|
|
||||||
openInBrowser(args.getString(RELEASE_URL_KEY)!!)
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(view: View) {
|
|
||||||
super.onAttach(view)
|
|
||||||
|
|
||||||
// Make links in Markdown text clickable
|
|
||||||
(dialog?.findViewById(android.R.id.message) as? TextView)?.movementMethod =
|
|
||||||
LinkMovementMethod.getInstance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val BODY_KEY = "NewUpdateDialogController.body"
|
|
||||||
private const val VERSION_KEY = "NewUpdateDialogController.version"
|
|
||||||
private const val RELEASE_URL_KEY = "NewUpdateDialogController.release_url"
|
|
||||||
private const val DOWNLOAD_URL_KEY = "NewUpdateDialogController.download_url"
|
|
@ -0,0 +1,41 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.more.NewUpdateScreen
|
||||||
|
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
||||||
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
|
|
||||||
|
class NewUpdateScreen(
|
||||||
|
private val versionName: String,
|
||||||
|
private val changelogInfo: String,
|
||||||
|
private val releaseLink: String,
|
||||||
|
private val downloadLink: String,
|
||||||
|
) : Screen {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val context = LocalContext.current
|
||||||
|
val changelogInfoNoChecksum = remember {
|
||||||
|
changelogInfo.replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "")
|
||||||
|
}
|
||||||
|
NewUpdateScreen(
|
||||||
|
versionName = versionName,
|
||||||
|
changelogInfo = changelogInfoNoChecksum,
|
||||||
|
onOpenInBrowser = { context.openInBrowser(releaseLink) },
|
||||||
|
onRejectUpdate = navigator::pop,
|
||||||
|
onAcceptUpdate = {
|
||||||
|
AppUpdateService.start(
|
||||||
|
context = context,
|
||||||
|
url = downloadLink,
|
||||||
|
title = versionName,
|
||||||
|
)
|
||||||
|
navigator.pop()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -63,7 +63,6 @@ import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegateImpl
|
|||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl
|
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
|
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
|
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
|
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
|
||||||
@ -85,6 +84,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
|||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.preference.toggle
|
import eu.kanade.tachiyomi.util.preference.toggle
|
||||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||||
@ -458,7 +458,7 @@ class ReaderActivity :
|
|||||||
startActivity(
|
startActivity(
|
||||||
Intent(this, MainActivity::class.java).apply {
|
Intent(this, MainActivity::class.java).apply {
|
||||||
action = MainActivity.SHORTCUT_MANGA
|
action = MainActivity.SHORTCUT_MANGA
|
||||||
putExtra(MangaController.MANGA_EXTRA, id)
|
putExtra(Constants.MANGA_EXTRA, id)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class SettingsMainController(bundle: Bundle = bundleOf()) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
private val toBackupScreen = args.getBoolean(TO_BACKUP_SCREEN)
|
|
||||||
private val toAboutScreen = args.getBoolean(TO_ABOUT_SCREEN)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(
|
|
||||||
screen = when {
|
|
||||||
toBackupScreen -> SettingsScreen.toBackupScreen()
|
|
||||||
toAboutScreen -> SettingsScreen.toAboutScreen()
|
|
||||||
else -> SettingsScreen.toMainScreen()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun toBackupScreen(): SettingsMainController {
|
|
||||||
return SettingsMainController(bundleOf(TO_BACKUP_SCREEN to true))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toAboutScreen(): SettingsMainController {
|
|
||||||
return SettingsMainController(bundleOf(TO_ABOUT_SCREEN to true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val TO_BACKUP_SCREEN = "to_backup_screen"
|
|
||||||
private const val TO_ABOUT_SCREEN = "to_about_screen"
|
|
@ -13,7 +13,6 @@ import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
|
|||||||
import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
|
import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.Transition
|
import eu.kanade.presentation.util.Transition
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
|
|
||||||
@ -24,15 +23,8 @@ class SettingsScreen private constructor(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
if (!isTabletUi()) {
|
if (!isTabletUi()) {
|
||||||
val back: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.handleBack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Navigator(
|
Navigator(
|
||||||
screen = if (toBackup) {
|
screen = if (toBackup) {
|
||||||
SettingsBackupScreen
|
SettingsBackupScreen
|
||||||
@ -42,7 +34,7 @@ class SettingsScreen private constructor(
|
|||||||
SettingsMainScreen
|
SettingsMainScreen
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
CompositionLocalProvider(LocalBackPress provides back) {
|
CompositionLocalProvider(LocalBackPress provides navigator::pop) {
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = it,
|
navigator = it,
|
||||||
transition = { Transition.OneWayFade },
|
transition = { Transition.OneWayFade },
|
||||||
@ -62,7 +54,7 @@ class SettingsScreen private constructor(
|
|||||||
) {
|
) {
|
||||||
TwoPanelBox(
|
TwoPanelBox(
|
||||||
startContent = {
|
startContent = {
|
||||||
CompositionLocalProvider(LocalBackPress provides router::popCurrentController) {
|
CompositionLocalProvider(LocalBackPress provides navigator::pop) {
|
||||||
SettingsMainScreen.Content(twoPane = true)
|
SettingsMainScreen.Content(twoPane = true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.stats
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class StatsController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = StatsScreen())
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,18 +3,17 @@ package eu.kanade.tachiyomi.ui.stats
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenContent
|
import eu.kanade.presentation.more.stats.StatsScreenContent
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
class StatsScreen : Screen {
|
class StatsScreen : Screen {
|
||||||
@ -23,8 +22,7 @@ class StatsScreen : Screen {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { StatsScreenModel() }
|
val screenModel = rememberScreenModel { StatsScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -38,7 +36,7 @@ class StatsScreen : Screen {
|
|||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_stats),
|
title = stringResource(R.string.label_stats),
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.updates
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
|
|
||||||
class UpdatesController : BasicFullComposeController(), RootController {
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = UpdatesScreen)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +1,70 @@
|
|||||||
package eu.kanade.tachiyomi.ui.updates
|
package eu.kanade.tachiyomi.ui.updates
|
||||||
|
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import eu.kanade.core.prefs.asState
|
||||||
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.updates.UpdateScreen
|
import eu.kanade.presentation.updates.UpdateScreen
|
||||||
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
object UpdatesTab : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 1u,
|
||||||
|
title = stringResource(R.string.label_recent_updates),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
navigator.push(DownloadQueueScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
@Composable
|
||||||
|
override fun isEnabled(): Boolean {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
return remember {
|
||||||
|
Injekt.get<UiPreferences>().showNavHistory().asState(scope)
|
||||||
|
}.value
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
|
||||||
object UpdatesScreen : Screen {
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -34,7 +75,7 @@ object UpdatesScreen : Screen {
|
|||||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||||
lastUpdated = screenModel.lastUpdated,
|
lastUpdated = screenModel.lastUpdated,
|
||||||
relativeTime = screenModel.relativeTime,
|
relativeTime = screenModel.relativeTime,
|
||||||
onClickCover = { item -> router.pushController(MangaController(item.update.mangaId)) },
|
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },
|
||||||
onSelectAll = screenModel::toggleAllSelection,
|
onSelectAll = screenModel::toggleAllSelection,
|
||||||
onInvertSelection = screenModel::invertSelection,
|
onInvertSelection = screenModel::invertSelection,
|
||||||
onUpdateLibrary = screenModel::updateLibrary,
|
onUpdateLibrary = screenModel::updateLibrary,
|
||||||
@ -77,8 +118,9 @@ object UpdatesScreen : Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.selectionMode) {
|
LaunchedEffect(state.selectionMode) {
|
||||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
HomeScreen.showBottomNav(!state.selectionMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.isLoading) {
|
LaunchedEffect(state.isLoading) {
|
||||||
if (!state.isLoading) {
|
if (!state.isLoading) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
7
app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt
Normal file
7
app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val URL_HELP = "https://tachiyomi.org/help/"
|
||||||
|
|
||||||
|
const val MANGA_EXTRA = "manga"
|
||||||
|
}
|
@ -1,196 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.animation.TimeInterpolator
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.ViewPropertyAnimator
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.max
|
|
||||||
import androidx.customview.view.AbsSavedState
|
|
||||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
|
||||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
|
||||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class TachiyomiBottomNavigationView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = R.attr.bottomNavigationStyle,
|
|
||||||
defStyleRes: Int = R.style.Widget_Design_BottomNavigationView,
|
|
||||||
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) {
|
|
||||||
|
|
||||||
private var currentAnimator: ViewPropertyAnimator? = null
|
|
||||||
|
|
||||||
private var currentState = STATE_UP
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(): Parcelable {
|
|
||||||
val superState = super.onSaveInstanceState()
|
|
||||||
return SavedState(superState).also {
|
|
||||||
it.currentState = currentState
|
|
||||||
it.translationY = translationY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
|
||||||
if (state is SavedState) {
|
|
||||||
super.onRestoreInstanceState(state.superState)
|
|
||||||
super.setTranslationY(state.translationY)
|
|
||||||
currentState = state.currentState
|
|
||||||
} else {
|
|
||||||
super.onRestoreInstanceState(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTranslationY(translationY: Float) {
|
|
||||||
// Disallow translation change when state down
|
|
||||||
if (currentState == STATE_DOWN) return
|
|
||||||
super.setTranslationY(translationY)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
|
||||||
bottomNavPadding = h.pxToDp.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows this view up.
|
|
||||||
*/
|
|
||||||
fun slideUp() = post {
|
|
||||||
currentAnimator?.cancel()
|
|
||||||
clearAnimation()
|
|
||||||
|
|
||||||
currentState = STATE_UP
|
|
||||||
animateTranslation(
|
|
||||||
0F,
|
|
||||||
SLIDE_UP_ANIMATION_DURATION,
|
|
||||||
LinearOutSlowInInterpolator(),
|
|
||||||
)
|
|
||||||
bottomNavPadding = height.pxToDp.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
|
|
||||||
*/
|
|
||||||
fun slideDown() = post {
|
|
||||||
currentAnimator?.cancel()
|
|
||||||
clearAnimation()
|
|
||||||
|
|
||||||
currentState = STATE_DOWN
|
|
||||||
animateTranslation(
|
|
||||||
height.toFloat(),
|
|
||||||
SLIDE_DOWN_ANIMATION_DURATION,
|
|
||||||
FastOutLinearInInterpolator(),
|
|
||||||
)
|
|
||||||
bottomNavPadding = 0.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {
|
|
||||||
currentAnimator = animate()
|
|
||||||
.translationY(targetY)
|
|
||||||
.setInterpolator(interpolator)
|
|
||||||
.setDuration(duration)
|
|
||||||
.applySystemAnimatorScale(context)
|
|
||||||
.setListener(
|
|
||||||
object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
currentAnimator = null
|
|
||||||
postInvalidate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class SavedState : AbsSavedState {
|
|
||||||
var currentState = STATE_UP
|
|
||||||
var translationY = 0F
|
|
||||||
|
|
||||||
constructor(superState: Parcelable) : super(superState)
|
|
||||||
|
|
||||||
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
|
|
||||||
currentState = source.readInt()
|
|
||||||
translationY = source.readFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
|
||||||
super.writeToParcel(out, flags)
|
|
||||||
out.writeInt(currentState)
|
|
||||||
out.writeFloat(translationY)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmField
|
|
||||||
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
|
|
||||||
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
|
|
||||||
return SavedState(source, loader)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createFromParcel(source: Parcel): SavedState {
|
|
||||||
return SavedState(source, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<SavedState> {
|
|
||||||
return newArray(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val STATE_DOWN = 1
|
|
||||||
private const val STATE_UP = 2
|
|
||||||
|
|
||||||
private const val SLIDE_UP_ANIMATION_DURATION = 225L
|
|
||||||
private const val SLIDE_DOWN_ANIMATION_DURATION = 175L
|
|
||||||
|
|
||||||
private var bottomNavPadding by mutableStateOf(0.dp)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges [bottomNavPadding] to the origin's [PaddingValues] bottom side.
|
|
||||||
*/
|
|
||||||
@ReadOnlyComposable
|
|
||||||
@Composable
|
|
||||||
fun withBottomNavPadding(origin: PaddingValues = PaddingValues()): PaddingValues {
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
|
||||||
return PaddingValues(
|
|
||||||
start = origin.calculateStartPadding(layoutDirection),
|
|
||||||
top = origin.calculateTopPadding(),
|
|
||||||
end = origin.calculateEndPadding(layoutDirection),
|
|
||||||
bottom = max(origin.calculateBottomPadding(), bottomNavPadding),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see withBottomNavPadding
|
|
||||||
*/
|
|
||||||
@ReadOnlyComposable
|
|
||||||
@Composable
|
|
||||||
fun withBottomNavInset(origin: WindowInsets): WindowInsets {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
|
||||||
return WindowInsets(
|
|
||||||
left = origin.getLeft(density, layoutDirection),
|
|
||||||
top = origin.getTop(density),
|
|
||||||
right = origin.getRight(density, layoutDirection),
|
|
||||||
bottom = max(origin.getBottom(density), with(density) { bottomNavPadding.roundToPx() }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ChangeHandlerFrameLayout] with the ability to draw behind the header sibling in [CoordinatorLayout].
|
|
||||||
* The layout behavior of this view is set to [TachiyomiScrollingViewBehavior] and should not be changed.
|
|
||||||
*/
|
|
||||||
class TachiyomiChangeHandlerFrameLayout(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet,
|
|
||||||
) : ChangeHandlerFrameLayout(context, attrs), CoordinatorLayout.AttachedBehavior {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, this view will draw behind the header sibling.
|
|
||||||
*
|
|
||||||
* @see TachiyomiScrollingViewBehavior.shouldHeaderOverlap
|
|
||||||
*/
|
|
||||||
var overlapHeader = false
|
|
||||||
set(value) {
|
|
||||||
if (field != value) {
|
|
||||||
field = value
|
|
||||||
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = behavior.apply {
|
|
||||||
shouldHeaderOverlap = value
|
|
||||||
}
|
|
||||||
if (!value) {
|
|
||||||
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
|
|
||||||
translationY = 0F
|
|
||||||
}
|
|
||||||
forceLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableScrollingBehavior(enable: Boolean) {
|
|
||||||
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = if (enable) {
|
|
||||||
behavior.apply {
|
|
||||||
shouldHeaderOverlap = overlapHeader
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (!enable) {
|
|
||||||
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
|
|
||||||
translationY = 0F
|
|
||||||
}
|
|
||||||
forceLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBehavior() = TachiyomiScrollingViewBehavior()
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package exh.debug
|
package exh.debug
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
@ -30,6 +29,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
@ -43,6 +43,9 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
@ -53,8 +56,8 @@ import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
|||||||
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.presentation.util.topSmallPaddingValues
|
import eu.kanade.presentation.util.topSmallPaddingValues
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import exh.util.capitalize
|
import exh.util.capitalize
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -63,12 +66,17 @@ import kotlin.reflect.KFunction
|
|||||||
import kotlin.reflect.KVisibility
|
import kotlin.reflect.KVisibility
|
||||||
import kotlin.reflect.full.declaredFunctions
|
import kotlin.reflect.full.declaredFunctions
|
||||||
|
|
||||||
class SettingsDebugController : BasicFullComposeController() {
|
class SettingsDebugScreen : Screen {
|
||||||
|
|
||||||
data class DebugToggle(val name: String, val pref: PreferenceMutableState<Boolean>, val default: Boolean)
|
data class DebugToggle(val name: String, val pref: PreferenceMutableState<Boolean>, val default: Boolean)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun Content() {
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose { navigator.pop() }
|
||||||
|
}
|
||||||
val functions by produceState<List<Pair<KFunction<*>, String>>?>(initialValue = null) {
|
val functions by produceState<List<Pair<KFunction<*>, String>>?>(initialValue = null) {
|
||||||
value = withContext(Dispatchers.Default) {
|
value = withContext(Dispatchers.Default) {
|
||||||
DebugFunctions::class.declaredFunctions.filter {
|
DebugFunctions::class.declaredFunctions.filter {
|
||||||
@ -82,7 +90,7 @@ class SettingsDebugController : BasicFullComposeController() {
|
|||||||
}
|
}
|
||||||
val toggles by produceState(initialValue = emptyList()) {
|
val toggles by produceState(initialValue = emptyList()) {
|
||||||
value = withContext(Dispatchers.Default) {
|
value = withContext(Dispatchers.Default) {
|
||||||
DebugToggles.values().map { DebugToggle(it.name, it.asPref(viewScope), it.default) }
|
DebugToggles.values().map { DebugToggle(it.name, it.asPref(scope), it.default) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@ -96,7 +104,7 @@ class SettingsDebugController : BasicFullComposeController() {
|
|||||||
Crossfade(functions == null) {
|
Crossfade(functions == null) {
|
||||||
when (it) {
|
when (it) {
|
||||||
true -> LoadingScreen()
|
true -> LoadingScreen()
|
||||||
false -> FunctionList(paddingValues, functions.orEmpty(), toggles)
|
false -> FunctionList(paddingValues, functions.orEmpty(), toggles, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,8 +115,8 @@ class SettingsDebugController : BasicFullComposeController() {
|
|||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
functions: List<Pair<KFunction<*>, String>>,
|
functions: List<Pair<KFunction<*>, String>>,
|
||||||
toggles: List<DebugToggle>,
|
toggles: List<DebugToggle>,
|
||||||
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
var running by remember { mutableStateOf(false) }
|
var running by remember { mutableStateOf(false) }
|
||||||
var result by remember { mutableStateOf<Pair<String, String>?>(null) }
|
var result by remember { mutableStateOf<Pair<String, String>?>(null) }
|
||||||
@ -227,9 +235,4 @@ class SettingsDebugController : BasicFullComposeController() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityStopped(activity: Activity) {
|
|
||||||
super.onActivityStopped(activity)
|
|
||||||
router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -4,17 +4,16 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.kanade.tachiyomi.databinding.SourceFilterMangadexHeaderBinding
|
import eu.kanade.tachiyomi.databinding.SourceFilterMangadexHeaderBinding
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
import exh.md.follows.MangaDexFollowsController
|
import exh.md.follows.MangaDexFollowsScreen
|
||||||
|
|
||||||
class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource, val onClick: () -> Unit) :
|
class MangaDexFabHeaderAdapter(val navigator: Navigator, val source: CatalogueSource, val onClick: () -> Unit) :
|
||||||
RecyclerView.Adapter<MangaDexFabHeaderAdapter.SavedSearchesViewHolder>() {
|
RecyclerView.Adapter<MangaDexFabHeaderAdapter.SavedSearchesViewHolder>() {
|
||||||
|
|
||||||
private lateinit var binding: SourceFilterMangadexHeaderBinding
|
private lateinit var binding: SourceFilterMangadexHeaderBinding
|
||||||
@ -33,7 +32,7 @@ class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource,
|
|||||||
inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||||
fun bind() {
|
fun bind() {
|
||||||
binding.mangadexFollows.setOnClickListener {
|
binding.mangadexFollows.setOnClickListener {
|
||||||
router.replaceTopController(MangaDexFollowsController(source).withFadeTransaction())
|
navigator.replace(MangaDexFollowsScreen(source.id))
|
||||||
onClick()
|
onClick()
|
||||||
}
|
}
|
||||||
binding.mangadexRandom.setOnClickListener {
|
binding.mangadexRandom.setOnClickListener {
|
||||||
@ -41,11 +40,11 @@ class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource,
|
|||||||
val randomMangaUrl = withIOContext {
|
val randomMangaUrl = withIOContext {
|
||||||
(source as? RandomMangaSource)?.fetchRandomMangaUrl()
|
(source as? RandomMangaSource)?.fetchRandomMangaUrl()
|
||||||
}
|
}
|
||||||
router.replaceTopController(
|
navigator.replace(
|
||||||
BrowseSourceController(
|
BrowseSourceScreen(
|
||||||
source,
|
source.id,
|
||||||
"id:$randomMangaUrl",
|
"id:$randomMangaUrl",
|
||||||
).withFadeTransaction(),
|
),
|
||||||
)
|
)
|
||||||
onClick()
|
onClick()
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package exh.md.follows
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController].
|
|
||||||
*/
|
|
||||||
class MangaDexFollowsController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(source: CatalogueSource) : this(
|
|
||||||
bundleOf(
|
|
||||||
SOURCE_ID_KEY to source.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val sourceId = args.getLong(SOURCE_ID_KEY)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MangaDexFollowsScreen(sourceId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val SOURCE_ID_KEY = "source_id"
|
|
@ -1,6 +1,5 @@
|
|||||||
package exh.md.follows
|
package exh.md.follows
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -23,19 +22,16 @@ import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
|||||||
import eu.kanade.presentation.components.ChangeCategoryDialog
|
import eu.kanade.presentation.components.ChangeCategoryDialog
|
||||||
import eu.kanade.presentation.components.DuplicateMangaDialog
|
import eu.kanade.presentation.components.DuplicateMangaDialog
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
|
|
||||||
class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
@ -44,20 +40,13 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
|||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
BrowseSourceSimpleToolbar(
|
BrowseSourceSimpleToolbar(
|
||||||
title = stringResource(R.string.mangadex_follows),
|
title = stringResource(R.string.mangadex_follows),
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = screenModel.displayMode,
|
||||||
onDisplayModeChange = { screenModel.displayMode = it },
|
onDisplayModeChange = { screenModel.displayMode = it },
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigator::pop,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -82,7 +71,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
|||||||
onWebViewClick = null,
|
onWebViewClick = null,
|
||||||
onHelpClick = null,
|
onHelpClick = null,
|
||||||
onLocalSourceHelpClick = null,
|
onLocalSourceHelpClick = null,
|
||||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
onMangaClick = { navigator.push(MangaScreen(it.id, true)) },
|
||||||
onMangaLongClick = { manga ->
|
onMangaLongClick = { manga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||||
@ -109,7 +98,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
|||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||||
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||||
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -127,7 +116,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
|||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = {
|
onEditCategories = {
|
||||||
router.pushController(CategoryController())
|
navigator.push(CategoryScreen())
|
||||||
},
|
},
|
||||||
onConfirm = { include, _ ->
|
onConfirm = { include, _ ->
|
||||||
screenModel.changeMangaFavorite(dialog.manga)
|
screenModel.changeMangaFavorite(dialog.manga)
|
||||||
@ -137,7 +126,5 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
|||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(onBack = navigateUp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package exh.md.follows
|
package exh.md.follows
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
@ -22,7 +22,7 @@ class MangaDexFollowsScreenModel(sourceId: Long) : BrowseSourceScreenModel(sourc
|
|||||||
return map { it to metadata }
|
return map { it to metadata }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initFilterSheet(context: Context, router: Router) {
|
override fun initFilterSheet(context: Context, navigator: Navigator) {
|
||||||
// No-op: we don't allow filtering in recs
|
// No-op: we don't allow filtering in recs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package exh.md.similar
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController].
|
|
||||||
*/
|
|
||||||
class MangaDexSimilarController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(manga: Manga, source: CatalogueSource) : this(
|
|
||||||
bundleOf(
|
|
||||||
MANGA_ID to manga.id,
|
|
||||||
SOURCE_ID_KEY to source.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val mangaId = args.getLong(MANGA_ID)
|
|
||||||
val sourceId = args.getLong(SOURCE_ID_KEY)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MangaDexSimilarScreen(mangaId, sourceId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val MANGA_ID = "manga_id"
|
|
||||||
private const val SOURCE_ID_KEY = "source_id"
|
|
@ -17,10 +17,8 @@ import eu.kanade.domain.manga.model.Manga
|
|||||||
import eu.kanade.presentation.browse.BrowseSourceContent
|
import eu.kanade.presentation.browse.BrowseSourceContent
|
||||||
import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar
|
import eu.kanade.presentation.browse.components.BrowseSourceSimpleToolbar
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
|
|
||||||
class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen {
|
class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen {
|
||||||
|
|
||||||
@ -28,26 +26,18 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val screenModel = rememberScreenModel { MangaDexSimilarScreenModel(mangaId, sourceId) }
|
val screenModel = rememberScreenModel { MangaDexSimilarScreenModel(mangaId, sourceId) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val onMangaClick: (Manga) -> Unit = {
|
val onMangaClick: (Manga) -> Unit = {
|
||||||
router.pushController(MangaController(it.id, true))
|
navigator.push(MangaScreen(it.id, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
BrowseSourceSimpleToolbar(
|
BrowseSourceSimpleToolbar(
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigator::pop,
|
||||||
title = stringResource(R.string.similar, screenModel.manga.title),
|
title = stringResource(R.string.similar, screenModel.manga.title),
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = screenModel.displayMode,
|
||||||
onDisplayModeChange = { screenModel.displayMode = it },
|
onDisplayModeChange = { screenModel.displayMode = it },
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package exh.md.similar
|
package exh.md.similar
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import eu.kanade.domain.manga.interactor.GetManga
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||||
@ -16,9 +16,6 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
/**
|
|
||||||
* Presenter of [MangaDexSimilarController]. Inherit BrowseCataloguePresenter.
|
|
||||||
*/
|
|
||||||
class MangaDexSimilarScreenModel(
|
class MangaDexSimilarScreenModel(
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
@ -35,7 +32,7 @@ class MangaDexSimilarScreenModel(
|
|||||||
return map { it to metadata }
|
return map { it to metadata }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initFilterSheet(context: Context, router: Router) {
|
override fun initFilterSheet(context: Context, navigator: Navigator) {
|
||||||
// No-op: we don't allow filtering in recs
|
// No-op: we don't allow filtering in recs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package exh.pagepreview
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class PagePreviewController : BasicFullComposeController {
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(bundle: Bundle? = null) : super(bundle)
|
|
||||||
|
|
||||||
constructor(mangaId: Long) : super(
|
|
||||||
bundleOf(MANGA_ID to mangaId),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = PagePreviewScreen(args.getLong(MANGA_ID, -1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MANGA_ID = "manga_id"
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,8 +7,8 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import exh.pagepreview.components.PagePreviewScreen
|
import exh.pagepreview.components.PagePreviewScreen
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ class PagePreviewScreen(private val mangaId: Long) : Screen {
|
|||||||
val screenModel = rememberScreenModel { PagePreviewScreenModel(mangaId) }
|
val screenModel = rememberScreenModel { PagePreviewScreenModel(mangaId) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
PagePreviewScreen(
|
PagePreviewScreen(
|
||||||
state = state,
|
state = state,
|
||||||
pageDialogOpen = screenModel.pageDialogOpen,
|
pageDialogOpen = screenModel.pageDialogOpen,
|
||||||
@ -27,7 +27,7 @@ class PagePreviewScreen(private val mangaId: Long) : Screen {
|
|||||||
onOpenPage = { openPage(context, state, it) },
|
onOpenPage = { openPage(context, state, it) },
|
||||||
onOpenPageDialog = { screenModel.pageDialogOpen = true },
|
onOpenPageDialog = { screenModel.pageDialogOpen = true },
|
||||||
onDismissPageDialog = { screenModel.pageDialogOpen = false },
|
onDismissPageDialog = { screenModel.pageDialogOpen = false },
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package exh.recs
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.manga.model.Manga
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that shows the latest manga from the catalogue. Inherit [BrowseSourceController].
|
|
||||||
*/
|
|
||||||
class RecommendsController(bundle: Bundle) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
constructor(manga: Manga, source: CatalogueSource) : this(
|
|
||||||
bundleOf(
|
|
||||||
MANGA_ID to manga.id,
|
|
||||||
SOURCE_ID_KEY to source.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val mangaId = args.getLong(MANGA_ID)
|
|
||||||
val sourceId = args.getLong(SOURCE_ID_KEY)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = RecommendsScreen(mangaId, sourceId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val MANGA_ID = "manga_id"
|
|
||||||
private const val SOURCE_ID_KEY = "source_id"
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user