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")
|
||||
}
|
||||
implementation(libs.insetter)
|
||||
implementation(libs.markwon)
|
||||
implementation(libs.bundles.richtext)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.cascade)
|
||||
implementation(libs.bundles.voyager)
|
||||
implementation(libs.wheelpicker)
|
||||
|
||||
// Conductor
|
||||
implementation(libs.conductor)
|
||||
|
||||
// FlowBinding
|
||||
implementation(libs.flowbinding.android)
|
||||
|
||||
@ -294,9 +291,6 @@ dependencies {
|
||||
implementation(libs.leakcanary.plumber)
|
||||
|
||||
// SY -->
|
||||
// Changelog
|
||||
implementation(sylibs.changelog)
|
||||
|
||||
// Text distance (EH)
|
||||
implementation (sylibs.simularity)
|
||||
|
||||
@ -350,6 +344,7 @@ tasks {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
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.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.BookmarkAdd
|
||||
@ -100,7 +101,11 @@ fun MangaBottomActionMenu(
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues())
|
||||
.padding(
|
||||
WindowInsets.navigationBars
|
||||
.only(WindowInsetsSides.Bottom)
|
||||
.asPaddingValues(),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
) {
|
||||
if (onBookmarkClicked != null) {
|
||||
@ -218,11 +223,11 @@ private fun RowScope.Button(
|
||||
fun LibraryBottomActionMenu(
|
||||
visible: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeCategoryClicked: (() -> Unit)?,
|
||||
onMarkAsReadClicked: (() -> Unit)?,
|
||||
onMarkAsUnreadClicked: (() -> Unit)?,
|
||||
onChangeCategoryClicked: () -> Unit,
|
||||
onMarkAsReadClicked: () -> Unit,
|
||||
onMarkAsUnreadClicked: () -> Unit,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onDeleteClicked: (() -> Unit)?,
|
||||
onDeleteClicked: () -> Unit,
|
||||
// SY -->
|
||||
onClickCleanTitles: (() -> Unit)?,
|
||||
onClickMigrate: (() -> Unit)?,
|
||||
@ -231,8 +236,8 @@ fun LibraryBottomActionMenu(
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = expandVertically(expandFrom = Alignment.Bottom),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Bottom),
|
||||
enter = expandVertically(animationSpec = tween(delayMillis = 300)),
|
||||
exit = shrinkVertically(animationSpec = tween()),
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
Surface(
|
||||
@ -260,18 +265,19 @@ fun LibraryBottomActionMenu(
|
||||
// SY <--
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.windowInsetsPadding(
|
||||
WindowInsets.navigationBars
|
||||
.only(WindowInsetsSides.Bottom),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||
) {
|
||||
if (onChangeCategoryClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_move_category),
|
||||
icon = Icons.Outlined.Label,
|
||||
toConfirm = confirm[0],
|
||||
onLongClick = { onLongClickItem(0) },
|
||||
onClick = onChangeCategoryClicked,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
title = stringResource(R.string.action_move_category),
|
||||
icon = Icons.Outlined.Label,
|
||||
toConfirm = confirm[0],
|
||||
onLongClick = { onLongClickItem(0) },
|
||||
onClick = onChangeCategoryClicked,
|
||||
)
|
||||
if (onDownloadClicked != null) {
|
||||
var downloadExpanded by remember { mutableStateOf(false) }
|
||||
Button(
|
||||
@ -290,15 +296,13 @@ fun LibraryBottomActionMenu(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (onDeleteClicked != null) {
|
||||
Button(
|
||||
title = stringResource(R.string.action_delete),
|
||||
icon = Icons.Outlined.Delete,
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
onClick = onDeleteClicked,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
title = stringResource(R.string.action_delete),
|
||||
icon = Icons.Outlined.Delete,
|
||||
toConfirm = confirm[4],
|
||||
onLongClick = { onLongClickItem(4) },
|
||||
onClick = onDeleteClicked,
|
||||
)
|
||||
// SY -->
|
||||
if (onMarkAsReadClicked != null) {
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.layout.MutableWindowInsets
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
@ -31,6 +34,7 @@ import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@ -67,6 +71,7 @@ import kotlin.math.max
|
||||
* * Remove height constraint for expanded app bar
|
||||
* * Also take account of fab height when providing inner padding
|
||||
* * 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 topBar top app bar of the screen, typically a [SmallTopAppBar]
|
||||
@ -103,9 +108,12 @@ fun Scaffold(
|
||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
// Tachiyomi: Handle consumed window insets
|
||||
val remainingWindowInsets = remember { MutableWindowInsets() }
|
||||
androidx.compose.material3.Surface(
|
||||
modifier = Modifier
|
||||
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
||||
.withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) }
|
||||
.then(modifier),
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
@ -116,7 +124,7 @@ fun Scaffold(
|
||||
bottomBar = bottomBar,
|
||||
content = content,
|
||||
snackbar = snackbarHost,
|
||||
contentWindowInsets = contentWindowInsets,
|
||||
contentWindowInsets = remainingWindowInsets,
|
||||
fab = floatingActionButton,
|
||||
)
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
@ -88,9 +87,7 @@ fun TabbedScreen(
|
||||
verticalAlignment = Alignment.Top,
|
||||
) { page ->
|
||||
tabs[page].content(
|
||||
TachiyomiBottomNavigationView.withBottomNavPadding(
|
||||
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||
),
|
||||
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||
snackbarHostState,
|
||||
)
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
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.ui.history.HistoryScreenModel
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryState
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
@ -55,7 +53,6 @@ fun HistoryScreen(
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
||||
) { contentPadding ->
|
||||
state.list.let {
|
||||
if (it == null) {
|
||||
|
@ -1,10 +1,7 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CloudOff
|
||||
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.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
import eu.kanade.tachiyomi.util.Constants
|
||||
|
||||
@Composable
|
||||
fun MoreScreen(
|
||||
@ -60,10 +56,7 @@ fun MoreScreen(
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(
|
||||
WindowInsets.navigationBars.asPaddingValues(),
|
||||
),
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
) {
|
||||
if (isFDroid) {
|
||||
item {
|
||||
@ -209,7 +202,7 @@ fun MoreScreen(
|
||||
TextPreferenceWidget(
|
||||
title = stringResource(R.string.label_help),
|
||||
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.outlined.Public
|
||||
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.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
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.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
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.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||
import eu.kanade.tachiyomi.ui.main.WhatsNewDialogController
|
||||
import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
|
||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
@ -63,7 +64,10 @@ object AboutScreen : Screen {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val handleBack = LocalBackPress.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val router = LocalRouter.currentOrThrow
|
||||
|
||||
// SY -->
|
||||
var showWhatsNewDialog by remember { mutableStateOf(false) }
|
||||
// SY <--
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
@ -98,7 +102,15 @@ object AboutScreen : Screen {
|
||||
title = stringResource(R.string.check_for_updates),
|
||||
onPreferenceClick = {
|
||||
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(
|
||||
title = stringResource(R.string.whats_new),
|
||||
// SY -->
|
||||
onPreferenceClick = { WhatsNewDialogController().showDialog(router) },
|
||||
onPreferenceClick = { showWhatsNewDialog = true },
|
||||
// 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.
|
||||
*/
|
||||
private suspend fun checkVersion(context: Context, router: Router) {
|
||||
private suspend fun checkVersion(context: Context, onAvailableUpdate: (AppUpdateResult.NewUpdate) -> Unit) {
|
||||
val updateChecker = AppUpdateChecker()
|
||||
withUIContext {
|
||||
context.toast(R.string.update_check_look_for_updates)
|
||||
try {
|
||||
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
|
||||
is AppUpdateResult.NewUpdate -> {
|
||||
NewUpdateDialogController(result).showDialog(router)
|
||||
onAvailableUpdate(result)
|
||||
}
|
||||
is AppUpdateResult.NoNewUpdate -> {
|
||||
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.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
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_SHECAN
|
||||
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.lang.launchNonCancellable
|
||||
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.setDefaultSettings
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.debug.SettingsDebugController
|
||||
import exh.debug.SettingsDebugScreen
|
||||
import exh.log.EHLogLevel
|
||||
import exh.pref.DelegateSourcePreferences
|
||||
import exh.source.BlacklistedSources
|
||||
@ -673,7 +671,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
@Composable
|
||||
private fun getDeveloperToolsGroup(): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
||||
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||
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)
|
||||
.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.stringResource
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.UnsortedPreferences
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.category.repos.RepoController
|
||||
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryController
|
||||
import eu.kanade.tachiyomi.ui.category.repos.RepoScreen
|
||||
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryScreen
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@ -48,13 +47,13 @@ object SettingsBrowseScreen : SearchableSettings {
|
||||
title = stringResource(R.string.label_sources),
|
||||
preferenceItems = listOf(
|
||||
kotlin.run {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by sourcePreferences.sourcesTabCategories().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.action_edit_categories),
|
||||
subtitle = pluralStringResource(R.plurals.num_categories, count.size, count.size),
|
||||
onClick = {
|
||||
router.pushController(SourceCategoryController())
|
||||
navigator.push(SourceCategoryScreen())
|
||||
},
|
||||
)
|
||||
},
|
||||
@ -99,13 +98,13 @@ object SettingsBrowseScreen : SearchableSettings {
|
||||
),
|
||||
// SY -->
|
||||
kotlin.run {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by unsortedPreferences.extensionRepos().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.action_edit_repos),
|
||||
subtitle = pluralStringResource(R.plurals.num_repos, count.size, count.size),
|
||||
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.DialogProperties
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.UnsortedPreferences
|
||||
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
|
||||
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
|
||||
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
|
||||
@ -68,7 +66,6 @@ import exh.eh.EHentaiUpdateWorkerConstants
|
||||
import exh.eh.EHentaiUpdaterStats
|
||||
import exh.favorites.FavoritesIntroDialog
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.uconfig.WarnConfigureDialogController
|
||||
import exh.ui.login.EhLoginActivity
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.serialization.decodeFromString
|
||||
@ -126,18 +123,18 @@ object SettingsEhScreen : SearchableSettings {
|
||||
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val openWarnConfigureDialogController = {
|
||||
WarnConfigureDialogController.uploadSettings(router)
|
||||
}
|
||||
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() }
|
||||
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
|
||||
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
|
||||
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
|
||||
val exhentaiEnabled by unsortedPreferences.enableExhentai().collectAsState()
|
||||
var runConfigureDialog by remember { mutableStateOf(false) }
|
||||
val openWarnConfigureDialogController = { runConfigureDialog = true }
|
||||
|
||||
Reconfigure(unsortedPreferences, openWarnConfigureDialogController)
|
||||
|
||||
ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false })
|
||||
|
||||
return listOf(
|
||||
Preference.PreferenceGroup(
|
||||
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.window.DialogProperties
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.collectAsState
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.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.isAuthenticationSupported
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@ -98,7 +97,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
),
|
||||
// SY -->
|
||||
kotlin.run {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(R.string.action_edit_biometric_lock_times),
|
||||
@ -108,7 +107,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||
count.size,
|
||||
),
|
||||
onClick = {
|
||||
router.pushController(BiometricTimesController())
|
||||
navigator.push(BiometricTimesScreen())
|
||||
},
|
||||
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.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
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.ui.updates.UpdatesItem
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesState
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@ -87,7 +85,6 @@ fun UpdateScreen(
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
||||
) { contentPadding ->
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
|
@ -1,15 +1,11 @@
|
||||
package eu.kanade.presentation.util
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import com.bluelinelabs.conductor.Router
|
||||
|
||||
/**
|
||||
* For interop with Conductor
|
||||
*/
|
||||
val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf { null }
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
|
||||
/**
|
||||
* For invoking back press to the parent activity
|
||||
@ -17,3 +13,12 @@ val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf
|
||||
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
||||
|
||||
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.source.SourceManager
|
||||
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.util.Constants
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
@ -457,7 +457,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
val newIntent =
|
||||
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
.putExtra(MangaController.MANGA_EXTRA, manga.id)
|
||||
.putExtra(Constants.MANGA_EXTRA, manga.id)
|
||||
.putExtra("notificationId", manga.id.hashCode())
|
||||
.putExtra("groupId", groupId)
|
||||
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.core.security.SecurityPreferences
|
||||
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.system.dpToPx
|
||||
import kotlinx.coroutines.MainScope
|
||||
@ -136,7 +136,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
||||
) {
|
||||
val intent = Intent(LocalContext.current, MainActivity::class.java).apply {
|
||||
action = MainActivity.SHORTCUT_MANGA
|
||||
putExtra(MangaController.MANGA_EXTRA, mangaId)
|
||||
putExtra(Constants.MANGA_EXTRA, mangaId)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
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
|
||||
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.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.domain.base.BasePreferences
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.components.TabbedScreen
|
||||
import eu.kanade.presentation.util.Tab
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||
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.api.get
|
||||
|
||||
data class BrowseScreen(
|
||||
private val toExtensions: Boolean,
|
||||
) : Screen {
|
||||
data class BrowseTab(
|
||||
private val toExtensions: Boolean = false,
|
||||
) : 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
|
||||
override fun Content() {
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.currentOrThrow
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||
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.components.AppBar
|
||||
import eu.kanade.presentation.components.TabContent
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun Screen.feedTab(): TabContent {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { FeedScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
@ -48,25 +47,25 @@ fun Screen.feedTab(): TabContent {
|
||||
contentPadding = contentPadding,
|
||||
onClickSavedSearch = { savedSearch, source ->
|
||||
screenModel.sourcePreferences.lastUsedSource().set(savedSearch.source)
|
||||
router.pushController(
|
||||
BrowseSourceController(
|
||||
source,
|
||||
navigator.push(
|
||||
BrowseSourceScreen(
|
||||
source.id,
|
||||
savedSearch = savedSearch.id,
|
||||
),
|
||||
)
|
||||
},
|
||||
onClickSource = { source ->
|
||||
screenModel.sourcePreferences.lastUsedSource().set(source.id)
|
||||
router.pushController(
|
||||
BrowseSourceController(
|
||||
source,
|
||||
navigator.push(
|
||||
BrowseSourceScreen(
|
||||
source.id,
|
||||
GetRemoteManga.QUERY_LATEST,
|
||||
),
|
||||
)
|
||||
},
|
||||
onClickDelete = screenModel::openDeleteDialog,
|
||||
onClickManga = { manga ->
|
||||
router.pushController(MangaController(manga.id, true))
|
||||
navigator.push(MangaScreen(manga.id, true))
|
||||
},
|
||||
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.OverflowMenu
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.PreMigrationListBinding
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
||||
@ -58,7 +57,6 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen {
|
||||
override fun Content() {
|
||||
val screenModel = rememberScreenModel { PreMigrationScreenModel() }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
var fabExpanded by remember { mutableStateOf(true) }
|
||||
val items by screenModel.state.collectAsState()
|
||||
@ -99,12 +97,7 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen {
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = stringResource(R.string.select_sources),
|
||||
navigateUp = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
else -> router.popCurrentController()
|
||||
}
|
||||
},
|
||||
navigateUp = navigator::pop,
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
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.components.MigrationExitDialog
|
||||
import eu.kanade.presentation.browse.components.MigrationMangaDialog
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
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.search.MigrateSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
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 dialog by screenModel.dialog.collectAsState()
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(items) {
|
||||
if (items.isEmpty()) {
|
||||
@ -52,11 +46,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
||||
),
|
||||
)
|
||||
if (!screenModel.hideNotFound) {
|
||||
if (navigator.canPop) {
|
||||
navigator.pop()
|
||||
} else {
|
||||
router.popCurrentController()
|
||||
}
|
||||
navigator.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,56 +61,29 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
||||
|
||||
LaunchedEffect(screenModel) {
|
||||
screenModel.navigateOut.collect {
|
||||
if (navigator.canPop) {
|
||||
if (items.size == 1) {
|
||||
val hasDetails = navigator.items.any { it is MangaScreen }
|
||||
if (hasDetails) {
|
||||
val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let {
|
||||
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()
|
||||
}
|
||||
}
|
||||
if (items.size == 1) {
|
||||
val hasDetails = navigator.items.any { it is MangaScreen }
|
||||
if (hasDetails) {
|
||||
val manga = (items.firstOrNull()?.searchResult?.value as? MigratingManga.SearchResult.Result)?.let {
|
||||
screenModel.getManga(it.id)
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
if (items.size == 1) {
|
||||
val hasDetails = router.backstack.any { it.controller is MangaController }
|
||||
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()
|
||||
}
|
||||
withUIContext {
|
||||
navigator.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,13 +129,7 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
|
||||
MigrationListScreenModel.Dialog.MigrationExitDialog -> {
|
||||
MigrationExitDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
exitMigration = {
|
||||
if (navigator.canPop) {
|
||||
navigator.pop()
|
||||
} else {
|
||||
router.popCurrentController()
|
||||
}
|
||||
},
|
||||
exitMigration = navigator::pop,
|
||||
)
|
||||
}
|
||||
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.components.Scaffold
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
|
||||
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.util.Constants
|
||||
|
||||
data class SourceSearchScreen(
|
||||
private val oldManga: Manga,
|
||||
@ -38,7 +37,6 @@ data class SourceSearchScreen(
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
||||
@ -46,19 +44,12 @@ data class SourceSearchScreen(
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val navigateUp: () -> Unit = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
router.backstackSize > 1 -> router.popCurrentController()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
SearchToolbar(
|
||||
searchQuery = state.toolbarQuery ?: "",
|
||||
onChangeSearchQuery = screenModel::setToolbarQuery,
|
||||
onClickCloseSearch = navigateUp,
|
||||
onClickCloseSearch = navigator::pop,
|
||||
onSearch = { screenModel.search(it) },
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
@ -100,7 +91,7 @@ data class SourceSearchScreen(
|
||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
||||
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
|
||||
onMangaClick = openMigrateDialog,
|
||||
onMangaLongClick = openMigrateDialog,
|
||||
@ -108,7 +99,7 @@ data class SourceSearchScreen(
|
||||
}
|
||||
|
||||
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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.SourcesFilterScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
|
||||
@ -18,7 +18,7 @@ class SourcesFilterScreen : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { SourcesFilterScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
@ -31,7 +31,7 @@ class SourcesFilterScreen : Screen {
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(Unit) {
|
||||
context.toast(R.string.internal_error)
|
||||
router.popCurrentController()
|
||||
navigator.pop()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -39,7 +39,7 @@ class SourcesFilterScreen : Screen {
|
||||
val successState = state as SourcesFilterState.Success
|
||||
|
||||
SourcesFilterScreen(
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
state = successState,
|
||||
onClickLanguage = screenModel::toggleLanguage,
|
||||
onClickSource = screenModel::toggleSource,
|
||||
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.currentOrThrow
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga.Companion.QUERY_POPULAR
|
||||
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.components.AppBar
|
||||
import eu.kanade.presentation.components.TabContent
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
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.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import exh.ui.smartsearch.SmartSearchController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||
import exh.ui.smartsearch.SmartSearchScreen
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@ -32,7 +31,7 @@ import kotlinx.coroutines.launch
|
||||
fun Screen.sourcesTab(
|
||||
smartSearchConfig: SmartSearchConfig? = null,
|
||||
): TabContent {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { SourcesScreenModel(smartSearchConfig = smartSearchConfig) }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
@ -47,12 +46,12 @@ fun Screen.sourcesTab(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_global_search),
|
||||
icon = Icons.Outlined.TravelExplore,
|
||||
onClick = { router.pushController(GlobalSearchController()) },
|
||||
onClick = { navigator.push(GlobalSearchScreen()) },
|
||||
),
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_filter),
|
||||
icon = Icons.Outlined.FilterList,
|
||||
onClick = { router.pushController(SourceFilterController()) },
|
||||
onClick = { navigator.push(SourcesFilterScreen()) },
|
||||
),
|
||||
)
|
||||
} else {
|
||||
@ -65,13 +64,13 @@ fun Screen.sourcesTab(
|
||||
contentPadding = contentPadding,
|
||||
onClickItem = { source, query ->
|
||||
// SY -->
|
||||
val controller = when {
|
||||
smartSearchConfig != null -> SmartSearchController(source.id, smartSearchConfig)
|
||||
(query.isBlank() || query == QUERY_POPULAR) && screenModel.useNewSourceNavigation -> SourceFeedController(source.id)
|
||||
else -> BrowseSourceController(source.id, query)
|
||||
val screen = when {
|
||||
smartSearchConfig != null -> SmartSearchScreen(source.id, smartSearchConfig)
|
||||
(query.isBlank() || query == QUERY_POPULAR) && screenModel.useNewSourceNavigation -> SourceFeedScreen(source.id)
|
||||
else -> BrowseSourceScreen(source.id, query)
|
||||
}
|
||||
screenModel.onOpenSource(source)
|
||||
router.pushController(controller)
|
||||
navigator.push(screen)
|
||||
// SY <--
|
||||
},
|
||||
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
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
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.DuplicateMangaDialog
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
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.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.Constants
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@ -73,6 +71,7 @@ data class BrowseSourceScreen(
|
||||
// SY -->
|
||||
private val filtersJson: String? = null,
|
||||
private val savedSearch: Long? = null,
|
||||
private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
||||
// SY <--
|
||||
) : Screen {
|
||||
|
||||
@ -80,7 +79,6 @@ data class BrowseSourceScreen(
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
@ -109,13 +107,6 @@ data class BrowseSourceScreen(
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
val navigateUp: () -> Unit = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
router.backstackSize > 1 -> router.popCurrentController()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||
@ -125,7 +116,7 @@ data class BrowseSourceScreen(
|
||||
source = screenModel.source,
|
||||
displayMode = screenModel.displayMode,
|
||||
onDisplayModeChange = { screenModel.displayMode = it },
|
||||
navigateUp = navigateUp,
|
||||
navigateUp = navigator::pop,
|
||||
onWebViewClick = onWebViewClick,
|
||||
onHelpClick = onHelpClick,
|
||||
onSearch = { screenModel.search(it) },
|
||||
@ -227,9 +218,9 @@ data class BrowseSourceScreen(
|
||||
snackbarHostState = snackbarHostState,
|
||||
contentPadding = paddingValues,
|
||||
onWebViewClick = onWebViewClick,
|
||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
||||
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||
onLocalSourceHelpClick = onHelpClick,
|
||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
||||
onMangaClick = { navigator.push(MangaScreen(it.id, true, smartSearchConfig)) },
|
||||
onMangaLongClick = { manga ->
|
||||
scope.launchIO {
|
||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||
@ -256,7 +247,7 @@ data class BrowseSourceScreen(
|
||||
DuplicateMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
||||
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
||||
)
|
||||
}
|
||||
@ -273,9 +264,7 @@ data class BrowseSourceScreen(
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = {
|
||||
router.pushController(CategoryController())
|
||||
},
|
||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||
onConfirm = { include, _ ->
|
||||
screenModel.changeMangaFavorite(dialog.manga)
|
||||
screenModel.moveMangaToCategories(dialog.manga, include)
|
||||
@ -298,10 +287,8 @@ data class BrowseSourceScreen(
|
||||
else -> {}
|
||||
}
|
||||
|
||||
BackHandler(onBack = navigateUp)
|
||||
|
||||
LaunchedEffect(state.filters) {
|
||||
screenModel.initFilterSheet(context, router)
|
||||
screenModel.initFilterSheet(context, navigator)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
@ -14,7 +14,7 @@ import androidx.paging.cachedIn
|
||||
import androidx.paging.map
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
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.kanade.core.prefs.CheckboxState
|
||||
import eu.kanade.core.prefs.asState
|
||||
@ -454,7 +454,7 @@ open class BrowseSourceScreenModel(
|
||||
mutableState.update { it.copy(toolbarQuery = query) }
|
||||
}
|
||||
|
||||
open fun initFilterSheet(context: Context, router: Router) {
|
||||
open fun initFilterSheet(context: Context, navigator: Navigator) {
|
||||
val state = state.value
|
||||
/*if (state.filters.isEmpty()) {
|
||||
return
|
||||
@ -463,7 +463,7 @@ open class BrowseSourceScreenModel(
|
||||
filterSheet = SourceFilterSheet(
|
||||
context = context,
|
||||
// SY -->
|
||||
router = router,
|
||||
navigator = navigator,
|
||||
source = source,
|
||||
searches = emptyList(),
|
||||
// SY <--
|
||||
|
@ -7,7 +7,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import com.google.android.material.chip.Chip
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
@ -23,7 +23,7 @@ import exh.source.getMainSource
|
||||
class SourceFilterSheet(
|
||||
context: Context,
|
||||
// SY -->
|
||||
router: Router,
|
||||
navigator: Navigator,
|
||||
source: CatalogueSource,
|
||||
searches: List<EXHSavedSearch> = emptyList(),
|
||||
// SY <--
|
||||
@ -41,7 +41,7 @@ class SourceFilterSheet(
|
||||
// SY -->
|
||||
searches = searches,
|
||||
source = source,
|
||||
router = router,
|
||||
navigator = navigator,
|
||||
dismissSheet = ::dismiss,
|
||||
// SY <--
|
||||
)
|
||||
@ -84,7 +84,7 @@ class SourceFilterSheet(
|
||||
// SY -->
|
||||
searches: List<EXHSavedSearch> = emptyList(),
|
||||
source: CatalogueSource? = null,
|
||||
router: Router? = null,
|
||||
navigator: Navigator? = null,
|
||||
dismissSheet: (() -> Unit)? = null,
|
||||
// SY <--
|
||||
) :
|
||||
@ -116,10 +116,10 @@ class SourceFilterSheet(
|
||||
// SY -->
|
||||
recycler.adapter = ConcatAdapter(
|
||||
listOfNotNull(
|
||||
router?.let {
|
||||
navigator?.let {
|
||||
source?.getMainSource<MangaDex>()
|
||||
?.let {
|
||||
MangaDexFabHeaderAdapter(router, it) {
|
||||
MangaDexFabHeaderAdapter(navigator, it) {
|
||||
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.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.source.interactor.GetRemoteManga
|
||||
import eu.kanade.presentation.browse.SourceFeedScreen
|
||||
import eu.kanade.presentation.browse.components.FailedToLoadSavedSearchDialog
|
||||
import eu.kanade.presentation.browse.components.SourceFeedAddDialog
|
||||
import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
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.browse.SourceFilterSheet
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
@ -47,19 +44,18 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
||||
val screenModel = rememberScreenModel { SourceFeedScreenModel(sourceId) }
|
||||
val state by screenModel.state.collectAsState()
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val router = LocalRouter.currentOrThrow
|
||||
|
||||
SourceFeedScreen(
|
||||
name = screenModel.source.name,
|
||||
isLoading = state.isLoading,
|
||||
items = state.items,
|
||||
onFabClick = if (state.filters.isEmpty()) null else { { filterSheet?.show() } },
|
||||
onClickBrowse = { onBrowseClick(router, screenModel.source) },
|
||||
onClickLatest = { onLatestClick(router, screenModel.source) },
|
||||
onClickSavedSearch = { onSavedSearchClick(router, screenModel.source, it) },
|
||||
onClickBrowse = { onBrowseClick(navigator, screenModel.source) },
|
||||
onClickLatest = { onLatestClick(navigator, screenModel.source) },
|
||||
onClickSavedSearch = { onSavedSearchClick(navigator, screenModel.source, it) },
|
||||
onClickDelete = screenModel::openDeleteFeed,
|
||||
onClickManga = { onMangaClick(navigator, it) },
|
||||
onClickSearch = { onSearchClick(router, screenModel.source, it) },
|
||||
onClickSearch = { onSearchClick(navigator, screenModel.source, it) },
|
||||
searchQuery = state.searchQuery,
|
||||
onSearchQueryChange = screenModel::search,
|
||||
isIncognitoMode = screenModel.isIncognitoMode,
|
||||
@ -102,7 +98,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
||||
val context = LocalContext.current
|
||||
|
||||
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,
|
||||
viewScope: CoroutineScope,
|
||||
context: Context,
|
||||
router: Router,
|
||||
navigator: Navigator,
|
||||
) {
|
||||
val filterSerializer = FilterSerializer()
|
||||
filterSheet = SourceFilterSheet(
|
||||
context,
|
||||
context = context,
|
||||
// SY -->
|
||||
router,
|
||||
screenModel.source,
|
||||
emptyList(),
|
||||
navigator = navigator,
|
||||
source = screenModel.source,
|
||||
searches = emptyList(),
|
||||
// SY <--
|
||||
onFilterClicked = {
|
||||
val allDefault = state.filters == screenModel.source.getFilterList()
|
||||
filterSheet?.dismiss()
|
||||
if (allDefault) {
|
||||
onBrowseClick(
|
||||
router,
|
||||
navigator,
|
||||
screenModel.source.id,
|
||||
state.searchQuery?.nullIfBlank(),
|
||||
)
|
||||
} else {
|
||||
onBrowseClick(
|
||||
router,
|
||||
navigator,
|
||||
screenModel.source.id,
|
||||
state.searchQuery?.nullIfBlank(),
|
||||
filters = Json.encodeToString(filterSerializer.serialize(state.filters)),
|
||||
@ -164,7 +160,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
||||
|
||||
if (!allDefault) {
|
||||
onBrowseClick(
|
||||
router,
|
||||
navigator,
|
||||
screenModel.source.id,
|
||||
search = state.searchQuery?.nullIfBlank(),
|
||||
savedSearch = search.id,
|
||||
@ -192,23 +188,23 @@ class SourceFeedScreen(val sourceId: Long) : Screen {
|
||||
navigator.push(MangaScreen(manga.id, true))
|
||||
}
|
||||
|
||||
fun onBrowseClick(router: Router, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||
router.replaceTopController(BrowseSourceController(sourceId, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
|
||||
fun onBrowseClick(navigator: Navigator, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||
navigator.replace(BrowseSourceScreen(sourceId, search, savedSearch = savedSearch, filtersJson = filters))
|
||||
}
|
||||
|
||||
private fun onLatestClick(router: Router, source: CatalogueSource) {
|
||||
router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_LATEST).withFadeTransaction())
|
||||
private fun onLatestClick(navigator: Navigator, source: CatalogueSource) {
|
||||
navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_LATEST))
|
||||
}
|
||||
|
||||
fun onBrowseClick(router: Router, source: CatalogueSource) {
|
||||
router.replaceTopController(BrowseSourceController(source, GetRemoteManga.QUERY_POPULAR).withFadeTransaction())
|
||||
fun onBrowseClick(navigator: Navigator, source: CatalogueSource) {
|
||||
navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_POPULAR))
|
||||
}
|
||||
|
||||
private fun onSavedSearchClick(router: Router, source: CatalogueSource, savedSearch: SavedSearch) {
|
||||
router.replaceTopController(BrowseSourceController(source, savedSearch = savedSearch.id).withFadeTransaction())
|
||||
private fun onSavedSearchClick(navigator: Navigator, source: CatalogueSource, savedSearch: SavedSearch) {
|
||||
navigator.replace(BrowseSourceScreen(source.id, savedSearch = savedSearch.id))
|
||||
}
|
||||
|
||||
private fun onSearchClick(router: Router, source: CatalogueSource, query: String) {
|
||||
onBrowseClick(router, source.id, query.nullIfBlank())
|
||||
private fun onSearchClick(navigator: Navigator, source: CatalogueSource, query: String) {
|
||||
onBrowseClick(navigator, source.id, query.nullIfBlank())
|
||||
}
|
||||
}
|
||||
|
@ -49,12 +49,6 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||
import java.util.concurrent.Executors
|
||||
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(
|
||||
val sourceId: Long,
|
||||
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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.GlobalSearchScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
|
||||
class GlobalSearchScreen(
|
||||
val searchQuery: String = "",
|
||||
@ -19,7 +18,7 @@ class GlobalSearchScreen(
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel {
|
||||
GlobalSearchScreenModel(
|
||||
@ -31,7 +30,7 @@ class GlobalSearchScreen(
|
||||
|
||||
GlobalSearchScreen(
|
||||
state = state,
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||
onSearch = screenModel::search,
|
||||
getManga = { source, manga ->
|
||||
@ -44,10 +43,10 @@ class GlobalSearchScreen(
|
||||
if (!screenModel.incognitoMode.get()) {
|
||||
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)) },
|
||||
onLongClickItem = { router.pushController(MangaController(it.id, true)) },
|
||||
onClickItem = { navigator.push(MangaScreen(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.CategoryRenameDialog
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@ -29,7 +28,6 @@ class CategoryScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { CategoryScreenModel() }
|
||||
|
||||
@ -49,12 +47,7 @@ class CategoryScreen : Screen {
|
||||
onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) },
|
||||
onClickMoveUp = screenModel::moveUp,
|
||||
onClickMoveDown = screenModel::moveDown,
|
||||
navigateUp = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
router.backstackSize > 1 -> router.handleBack()
|
||||
}
|
||||
},
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import eu.kanade.presentation.category.BiometricTimesScreen
|
||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@ -27,7 +27,7 @@ class BiometricTimesScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { BiometricTimesScreenModel() }
|
||||
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -43,7 +43,7 @@ class BiometricTimesScreen : Screen {
|
||||
state = successState,
|
||||
onClickCreate = { screenModel.showDialog(BiometricTimesDialog.Create) },
|
||||
onClickDelete = { screenModel.showDialog(BiometricTimesDialog.Delete(it)) },
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
fun showTimePicker(startTime: Duration? = null) {
|
||||
|
@ -16,9 +16,6 @@ import kotlinx.coroutines.flow.update
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [BiometricTimesController]. Used to manage the categories of the library.
|
||||
*/
|
||||
class BiometricTimesScreenModel(
|
||||
private val preferences: SecurityPreferences = Injekt.get(),
|
||||
) : 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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@ -22,7 +22,7 @@ class SortTagScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { SortTagScreenModel() }
|
||||
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -40,7 +40,7 @@ class SortTagScreen : Screen {
|
||||
onClickDelete = { screenModel.showDialog(SortTagDialog.Delete(it)) },
|
||||
onClickMoveUp = screenModel::moveUp,
|
||||
onClickMoveDown = screenModel::moveDown,
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
when (val dialog = successState.dialog) {
|
||||
|
@ -17,9 +17,6 @@ import kotlinx.coroutines.flow.update
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [SortTagController]. Used to manage the categories of the library.
|
||||
*/
|
||||
class SortTagScreenModel(
|
||||
private val getSortTag: GetSortTag = 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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.category.SourceRepoScreen
|
||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@ -23,7 +23,7 @@ class RepoScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { RepoScreenModel() }
|
||||
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -39,7 +39,7 @@ class RepoScreen : Screen {
|
||||
state = successState,
|
||||
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
||||
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
when (val dialog = successState.dialog) {
|
||||
|
@ -16,9 +16,6 @@ import kotlinx.coroutines.flow.update
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [RepoController]. Used to manage the repos for the extensions.
|
||||
*/
|
||||
class RepoScreenModel(
|
||||
private val getSourceRepos: GetSourceRepos = 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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.category.SourceCategoryScreen
|
||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@ -24,7 +24,7 @@ class SourceCategoryScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { SourceCategoryScreenModel() }
|
||||
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -41,7 +41,7 @@ class SourceCategoryScreen : Screen {
|
||||
onClickCreate = { screenModel.showDialog(SourceCategoryDialog.Create) },
|
||||
onClickRename = { screenModel.showDialog(SourceCategoryDialog.Rename(it)) },
|
||||
onClickDelete = { screenModel.showDialog(SourceCategoryDialog.Delete(it)) },
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
)
|
||||
|
||||
when (val dialog = successState.dialog) {
|
||||
|
@ -17,9 +17,6 @@ import kotlinx.coroutines.flow.update
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [SourceCategoryController]. Used to manage the categories of the library.
|
||||
*/
|
||||
class SourceCategoryScreenModel(
|
||||
private val getSourceCategories: GetSourceCategories = 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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
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.Pill
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||
import eu.kanade.tachiyomi.databinding.DownloadListBinding
|
||||
@ -66,7 +66,7 @@ object DownloadQueueScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val scope = rememberCoroutineScope()
|
||||
val screenModel = rememberScreenModel { DownloadQueueScreenModel() }
|
||||
val downloadList by screenModel.state.collectAsState()
|
||||
@ -121,7 +121,7 @@ object DownloadQueueScreen : Screen {
|
||||
}
|
||||
}
|
||||
},
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
actions = {
|
||||
if (downloadList.isNotEmpty()) {
|
||||
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.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
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) {
|
||||
coroutineScope.launchIO {
|
||||
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
||||
|
@ -1,34 +1,76 @@
|
||||
package eu.kanade.tachiyomi.ui.history
|
||||
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.res.stringResource
|
||||
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.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.ui.UiPreferences
|
||||
import eu.kanade.presentation.history.HistoryScreen
|
||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
||||
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.ui.base.controller.pushController
|
||||
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 kotlinx.coroutines.channels.Channel
|
||||
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 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
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -39,7 +81,7 @@ object HistoryScreen : Screen {
|
||||
incognitoMode = screenModel.isIncognitoMode,
|
||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||
onSearchQueryChange = screenModel::updateSearchQuery,
|
||||
onClickCover = { router.pushController(MangaController(it)) },
|
||||
onClickCover = { navigator.push(MangaScreen(it)) },
|
||||
onClickResume = screenModel::getNextChapterForManga,
|
||||
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?) {
|
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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
||||
@ -31,11 +31,11 @@ import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LibrarySettingsSheet(
|
||||
router: Router,
|
||||
activity: Activity,
|
||||
private val trackManager: TrackManager = Injekt.get(),
|
||||
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||
) : TabbedBottomSheetDialog(router.activity!!) {
|
||||
) : TabbedBottomSheetDialog(activity) {
|
||||
|
||||
val filters: Filter
|
||||
private val sort: Sort
|
||||
@ -48,12 +48,12 @@ class LibrarySettingsSheet(
|
||||
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
|
||||
init {
|
||||
filters = Filter(router.activity!!)
|
||||
sort = Sort(router.activity!!)
|
||||
display = Display(router.activity!!)
|
||||
filters = Filter(activity)
|
||||
sort = Sort(activity)
|
||||
display = Display(activity)
|
||||
|
||||
// SY -->
|
||||
grouping = Grouping(router.activity!!)
|
||||
grouping = Grouping(activity)
|
||||
// SY <--
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
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.util.fastAll
|
||||
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 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.category.model.Category
|
||||
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.SyncFavoritesWarningDialog
|
||||
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.data.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
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.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
||||
import exh.favorites.FavoritesSyncStatus
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
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
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
@ -127,7 +146,7 @@ object LibraryScreen : Screen {
|
||||
scope.launch {
|
||||
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
||||
if (randomItem != null) {
|
||||
router.openManga(randomItem.libraryManga.manga.id)
|
||||
navigator.push(MangaScreen(randomItem.libraryManga.manga.id))
|
||||
} else {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
|
||||
}
|
||||
@ -158,9 +177,9 @@ object LibraryScreen : Screen {
|
||||
.map { it.manga.id }
|
||||
screenModel.clearSelection()
|
||||
if (selectedMangaIds.isNotEmpty()) {
|
||||
PreMigrationController.navigateToMigration(
|
||||
PreMigrationScreen.navigateToMigration(
|
||||
Injekt.get<UnsortedPreferences>().skipPreMigration().get(),
|
||||
router,
|
||||
navigator,
|
||||
selectedMangaIds,
|
||||
)
|
||||
} else {
|
||||
@ -172,66 +191,63 @@ object LibraryScreen : Screen {
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
||||
) { contentPadding ->
|
||||
if (state.isLoading) {
|
||||
LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
return@Scaffold
|
||||
}
|
||||
|
||||
if (state.searchQuery.isNullOrEmpty() && state.libraryCount == 0) {
|
||||
val handler = LocalUriHandler.current
|
||||
EmptyScreen(
|
||||
textResource = R.string.information_empty_library,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.getting_started_guide,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
onClick = { handler.openUri("https://tachiyomi.org/help/guides/getting-started") },
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
state.searchQuery.isNullOrEmpty() && state.libraryCount == 0 -> {
|
||||
val handler = LocalUriHandler.current
|
||||
EmptyScreen(
|
||||
textResource = R.string.information_empty_library,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
actions = listOf(
|
||||
EmptyScreenAction(
|
||||
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
|
||||
@ -242,7 +258,7 @@ object LibraryScreen : Screen {
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = {
|
||||
screenModel.clearSelection()
|
||||
router.pushController(CategoryController())
|
||||
navigator.push(CategoryScreen())
|
||||
},
|
||||
onConfirm = { include, exclude ->
|
||||
screenModel.clearSelection()
|
||||
@ -295,7 +311,7 @@ object LibraryScreen : Screen {
|
||||
SyncFavoritesProgressDialog(
|
||||
status = screenModel.favoritesSync.status.collectAsState().value,
|
||||
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
|
||||
openManga = { router.openManga(it.id) },
|
||||
openManga = { navigator.push(MangaScreen(it.id)) },
|
||||
)
|
||||
// SY <--
|
||||
|
||||
@ -307,11 +323,9 @@ object LibraryScreen : Screen {
|
||||
}
|
||||
|
||||
LaunchedEffect(state.selectionMode) {
|
||||
// Could perhaps be removed when navigation is in a Compose world
|
||||
if (router.backstackSize == 1) {
|
||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
||||
}
|
||||
HomeScreen.showBottomNav(!state.selectionMode)
|
||||
}
|
||||
|
||||
LaunchedEffect(state.isLoading) {
|
||||
if (!state.isLoading) {
|
||||
(context as? MainActivity)?.ready = true
|
||||
@ -319,23 +333,19 @@ object LibraryScreen : Screen {
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch { queryEvent.collectLatest(screenModel::search) }
|
||||
launch { requestSettingsSheetEvent.collectLatest { onClickFilter() } }
|
||||
launch { queryEvent.receiveAsFlow().collect(screenModel::search) }
|
||||
launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { onClickFilter() } }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Router.openManga(mangaId: Long) {
|
||||
pushController(MangaController(mangaId))
|
||||
}
|
||||
|
||||
// For invoking search from other screen
|
||||
private val queryEvent = MutableSharedFlow<String>(replay = 1)
|
||||
fun search(query: String) = queryEvent.tryEmit(query)
|
||||
private val queryEvent = Channel<String>()
|
||||
suspend fun search(query: String) = queryEvent.send(query)
|
||||
|
||||
// For opening settings sheet in LibraryController
|
||||
private val requestSettingsSheetEvent = MutableSharedFlow<Unit>()
|
||||
private val openSettingsSheetEvent_ = MutableSharedFlow<Category>()
|
||||
val openSettingsSheetEvent = openSettingsSheetEvent_.asSharedFlow()
|
||||
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.emit(category)
|
||||
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.emit(Unit)
|
||||
private val requestSettingsSheetEvent = Channel<Unit>()
|
||||
private val openSettingsSheetEvent_ = Channel<Category>()
|
||||
val openSettingsSheetEvent = openSettingsSheetEvent_.receiveAsFlow()
|
||||
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.send(category)
|
||||
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.send(Unit)
|
||||
}
|
@ -7,80 +7,75 @@ import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.view.Menu
|
||||
import android.view.ViewGroup
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
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.graphics.ColorUtils
|
||||
import androidx.core.splashscreen.SplashScreen
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.domain.UnsortedPreferences
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.presentation.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.R
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||
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.controller.ComposeContentController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
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.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryTab
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||
import eu.kanade.tachiyomi.util.Constants
|
||||
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.toast
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||
import exh.EXHMigrations
|
||||
import exh.eh.EHentaiUpdateWorker
|
||||
import exh.source.BlacklistedSources
|
||||
import exh.source.EH_SOURCE_ID
|
||||
import exh.source.EXH_SOURCE_ID
|
||||
import exh.uconfig.WarnConfigureDialogController
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@ -99,24 +94,20 @@ class MainActivity : BaseActivity() {
|
||||
private val unsortedPreferences: UnsortedPreferences by injectLazy()
|
||||
// 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
|
||||
|
||||
/**
|
||||
* App bar lift state for backstack
|
||||
*/
|
||||
private val backstackLiftState = mutableMapOf<String, Boolean>()
|
||||
|
||||
private val chapterCache: ChapterCache by injectLazy()
|
||||
|
||||
// To be checked by splash screen. If true then splash screen will be removed.
|
||||
var ready = false
|
||||
|
||||
/**
|
||||
* Sheet containing filter/sort/display items.
|
||||
*/
|
||||
private var settingsSheet: LibrarySettingsSheet? = null
|
||||
|
||||
private lateinit var navigator: Navigator
|
||||
|
||||
// SY -->
|
||||
// Idle-until-urgent
|
||||
private var firstPaint = false
|
||||
@ -134,6 +125,8 @@ class MainActivity : BaseActivity() {
|
||||
iuuQueue += task
|
||||
}
|
||||
}
|
||||
|
||||
private var runExhConfigureDialog by mutableStateOf(false)
|
||||
// SY <--
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -164,23 +157,64 @@ class MainActivity : BaseActivity() {
|
||||
false
|
||||
}
|
||||
|
||||
binding = MainActivityBinding.inflate(layoutInflater)
|
||||
|
||||
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
||||
if (!isTaskRoot) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
// Draw edge-to-edge
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
binding.bottomNav?.applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
|
||||
settingsSheet = LibrarySettingsSheet(this)
|
||||
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()
|
||||
@ -190,114 +224,26 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
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) {
|
||||
// Set start screen
|
||||
lifecycleScope.launch { handleIntentAction(intent) }
|
||||
|
||||
// Reset Incognito Mode on relaunch
|
||||
preferences.incognitoMode().set(false)
|
||||
|
||||
// Show changelog prompt on update
|
||||
if (didMigration) {
|
||||
WhatsNewDialogController().showDialog(router)
|
||||
}
|
||||
// EXH <--
|
||||
|
||||
// SY -->
|
||||
initWhenIdle {
|
||||
// Upload settings
|
||||
if (unsortedPreferences.enableExhentai().get() &&
|
||||
unsortedPreferences.exhShowSettingsUploadWarning().get()
|
||||
) {
|
||||
WarnConfigureDialogController.uploadSettings(router)
|
||||
runExhConfigureDialog = true
|
||||
}
|
||||
// Scheduler uploader job if required
|
||||
|
||||
EHentaiUpdateWorker.scheduleBackground(this)
|
||||
}
|
||||
// SY <--
|
||||
} else {
|
||||
// Restore selected nav item
|
||||
router.backstack.firstOrNull()?.tag()?.toIntOrNull()?.let {
|
||||
nav.menu.findItem(it).isChecked = true
|
||||
}
|
||||
}
|
||||
// SY -->
|
||||
if (!unsortedPreferences.isHentaiEnabled().get()) {
|
||||
@ -305,40 +251,55 @@ class MainActivity : BaseActivity() {
|
||||
BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID
|
||||
}
|
||||
// SY -->
|
||||
}
|
||||
|
||||
merge(libraryPreferences.showUpdatesNavBadge().changes(), libraryPreferences.unreadUpdatesCount().changes())
|
||||
.onEach { setUnreadUpdatesBadge() }
|
||||
.launchIn(lifecycleScope)
|
||||
private fun showSettingsSheet(category: Category? = null) {
|
||||
if (category != null) {
|
||||
settingsSheet?.show(category)
|
||||
} else {
|
||||
lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() }
|
||||
}
|
||||
}
|
||||
|
||||
sourcePreferences.extensionUpdatesCount()
|
||||
.asHotFlow { setExtensionsBadge() }
|
||||
.launchIn(lifecycleScope)
|
||||
@Composable
|
||||
private fun ConfirmExit() {
|
||||
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()
|
||||
.asHotFlow { binding.downloadedOnly.isVisible = it }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
binding.incognitoMode.isVisible = preferences.incognitoMode().get()
|
||||
preferences.incognitoMode().changes()
|
||||
.drop(1)
|
||||
.onEach {
|
||||
binding.incognitoMode.isVisible = it
|
||||
|
||||
// Close BrowseSourceController and its MangaController child when incognito mode is disabled
|
||||
if (!it) {
|
||||
val fg = router.backstack.lastOrNull()?.controller
|
||||
if (fg is BrowseSourceController || fg is MangaController && fg.fromSource) {
|
||||
router.popToRoot()
|
||||
@Composable
|
||||
private fun CheckForUpdate() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
LaunchedEffect(Unit) {
|
||||
// App updates
|
||||
if (BuildConfig.INCLUDE_UPDATER) {
|
||||
try {
|
||||
val result = AppUpdateChecker().checkForUpdate(context)
|
||||
if (result is AppUpdateResult.NewUpdate) {
|
||||
val updateScreen = NewUpdateScreen(
|
||||
versionName = result.release.version,
|
||||
changelogInfo = result.release.info,
|
||||
releaseLink = result.release.releaseLink,
|
||||
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.
|
||||
*/
|
||||
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
|
||||
val root = findViewById<View>(android.R.id.content)
|
||||
val setNavbarScrim = {
|
||||
// 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) {
|
||||
val elevation = binding.bottomNav?.elevation ?: 0F
|
||||
window.setNavigationBarTransparentCompat(this@MainActivity, elevation)
|
||||
window.setNavigationBarTransparentCompat(this@MainActivity, 3.dpToPx.toFloat())
|
||||
}
|
||||
insets
|
||||
}
|
||||
ViewCompat.requestApplyInsets(binding.root)
|
||||
ViewCompat.requestApplyInsets(root)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
|
||||
@ -375,7 +336,7 @@ class MainActivity : BaseActivity() {
|
||||
duration = SPLASH_EXIT_ANIM_DURATION
|
||||
addUpdateListener { va ->
|
||||
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) {
|
||||
if (!handleIntentAction(intent)) {
|
||||
val handle = runBlocking { handleIntentAction(intent) }
|
||||
if (!handle) {
|
||||
super.onNewIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
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 {
|
||||
private suspend fun handleIntentAction(intent: Intent): Boolean {
|
||||
val notificationId = intent.getIntExtra("notificationId", -1)
|
||||
if (notificationId > -1) {
|
||||
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
||||
@ -474,32 +379,19 @@ class MainActivity : BaseActivity() {
|
||||
isHandlingShortcut = true
|
||||
|
||||
when (intent.action) {
|
||||
SHORTCUT_LIBRARY -> setSelectedNavItem(R.id.nav_library)
|
||||
SHORTCUT_RECENTLY_UPDATED -> setSelectedNavItem(R.id.nav_updates)
|
||||
SHORTCUT_RECENTLY_READ -> setSelectedNavItem(R.id.nav_history)
|
||||
SHORTCUT_CATALOGUES -> setSelectedNavItem(R.id.nav_browse)
|
||||
SHORTCUT_EXTENSIONS -> {
|
||||
if (router.backstackSize > 1) {
|
||||
router.popToRoot()
|
||||
}
|
||||
setSelectedNavItem(R.id.nav_browse)
|
||||
router.pushController(BrowseController(toExtensions = true))
|
||||
}
|
||||
SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library())
|
||||
SHORTCUT_RECENTLY_UPDATED -> HomeScreen.openTab(HomeScreen.Tab.Updates)
|
||||
SHORTCUT_RECENTLY_READ -> HomeScreen.openTab(HomeScreen.Tab.History)
|
||||
SHORTCUT_CATALOGUES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false))
|
||||
SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true))
|
||||
SHORTCUT_MANGA -> {
|
||||
val extras = intent.extras ?: return false
|
||||
val fgController = router.backstack.lastOrNull()?.controller as? MangaController
|
||||
if (fgController?.mangaId != extras.getLong(MangaController.MANGA_EXTRA)) {
|
||||
router.popToRoot()
|
||||
setSelectedNavItem(R.id.nav_library)
|
||||
router.pushController(RouterTransaction.with(MangaController(extras)))
|
||||
}
|
||||
val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
|
||||
navigator.popUntilRoot()
|
||||
HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen))
|
||||
}
|
||||
SHORTCUT_DOWNLOADS -> {
|
||||
if (router.backstackSize > 1) {
|
||||
router.popToRoot()
|
||||
}
|
||||
setSelectedNavItem(R.id.nav_more)
|
||||
router.pushController(DownloadController())
|
||||
navigator.popUntilRoot()
|
||||
HomeScreen.openTab(HomeScreen.Tab.More(toDownloads = true))
|
||||
}
|
||||
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
||||
// 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.
|
||||
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (query != null && query.isNotEmpty()) {
|
||||
if (router.backstackSize > 1) {
|
||||
router.popToRoot()
|
||||
}
|
||||
router.pushController(GlobalSearchController(query))
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(GlobalSearchScreen(query))
|
||||
}
|
||||
}
|
||||
INTENT_SEARCH -> {
|
||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||
if (query != null && query.isNotEmpty()) {
|
||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
||||
if (router.backstackSize > 1) {
|
||||
router.popToRoot()
|
||||
}
|
||||
router.pushController(GlobalSearchController(query, filter ?: ""))
|
||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(GlobalSearchScreen(query, filter))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -535,190 +423,22 @@ class MainActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||
override fun onDestroy() {
|
||||
settingsSheet?.sheetScope?.cancel()
|
||||
settingsSheet = null
|
||||
super.onDestroy()
|
||||
|
||||
// Binding sometimes isn't actually instantiated yet somehow
|
||||
nav?.setOnItemSelectedListener(null)
|
||||
binding?.toolbar?.setNavigationOnClickListener(null)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (router.handleBack()) {
|
||||
// A Router is consuming back press
|
||||
return
|
||||
}
|
||||
val backstackSize = router.backstackSize
|
||||
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()
|
||||
if (navigator.size == 1 &&
|
||||
!onBackPressedDispatcher.hasEnabledCallbacks() &&
|
||||
libraryPreferences.autoClearChapterCache().get()
|
||||
) {
|
||||
chapterCache.clear()
|
||||
}
|
||||
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 {
|
||||
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.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
@ -29,7 +30,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.kanade.domain.UnsortedPreferences
|
||||
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.SelectScanlatorsDialog
|
||||
import eu.kanade.presentation.util.LocalNavigatorContentPadding
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.isTabletUi
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.isLocalOrStub
|
||||
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.source.SourcesScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
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.history.HistoryController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.merged.EditMergedSettingsDialog
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
|
||||
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.util.chapter.getNextUnread
|
||||
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.toShareIntent
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.md.similar.MangaDexSimilarController
|
||||
import exh.pagepreview.PagePreviewController
|
||||
import exh.recs.RecommendsController
|
||||
import exh.md.similar.MangaDexSimilarScreen
|
||||
import exh.pagepreview.PagePreviewScreen
|
||||
import exh.recs.RecommendsScreen
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.source.getMainSource
|
||||
import exh.source.isMdBasedSource
|
||||
@ -89,12 +82,13 @@ import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MangaScreen(
|
||||
private val mangaId: Long,
|
||||
private val fromSource: Boolean = false,
|
||||
val fromSource: Boolean = false,
|
||||
private val smartSearchConfig: SourcesScreen.SmartSearchConfig? = null,
|
||||
) : Screen {
|
||||
|
||||
@ -103,9 +97,9 @@ class MangaScreen(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val screenModel = rememberScreenModel { MangaInfoScreenModel(context, mangaId, fromSource, smartSearchConfig != null) }
|
||||
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -123,8 +117,8 @@ class MangaScreen(
|
||||
screenModel.redirectFlow
|
||||
.take(1)
|
||||
.onEach {
|
||||
router.replaceTopController(
|
||||
MangaController(it).withFadeTransaction(),
|
||||
navigator.replace(
|
||||
MangaScreen(it.mangaId),
|
||||
)
|
||||
}
|
||||
.launchIn(this)
|
||||
@ -135,12 +129,7 @@ class MangaScreen(
|
||||
state = successState,
|
||||
snackbarHostState = screenModel.snackbarHostState,
|
||||
isTabletUi = isTabletUi(),
|
||||
onBackClicked = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
else -> router.popCurrentController()
|
||||
}
|
||||
},
|
||||
onBackClicked = navigator::pop,
|
||||
onChapterClicked = { openChapter(context, it) },
|
||||
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
||||
onAddToLibraryClicked = {
|
||||
@ -158,11 +147,11 @@ class MangaScreen(
|
||||
// SY <--
|
||||
onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable },
|
||||
onTagClicked = { performGenreSearch(router, navigator, it, screenModel.source!!) },
|
||||
onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
|
||||
onFilterButtonClicked = screenModel::showSettingsDialog,
|
||||
onRefresh = screenModel::fetchAllFromSource,
|
||||
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,
|
||||
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
||||
@ -171,12 +160,12 @@ class MangaScreen(
|
||||
onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite },
|
||||
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
|
||||
onEditInfoClicked = screenModel::showEditMangaInfoDialog,
|
||||
onRecommendClicked = { openRecommends(context, router, screenModel.source?.getMainSource(), successState.manga) },
|
||||
onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) },
|
||||
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
|
||||
onMergeClicked = { openSmartSearch(navigator, successState.manga) },
|
||||
onMergeWithAnotherClicked = { mergeWithAnother(navigator, context, successState.manga, screenModel::smartSearchMerge) },
|
||||
onOpenPagePreview = { openPagePreview(context, successState.chapters.getNextUnread(successState.manga), it) },
|
||||
onMorePreviewsClicked = { openMorePagePreviews(router, successState.manga) },
|
||||
onMorePreviewsClicked = { openMorePagePreviews(navigator, successState.manga) },
|
||||
// SY <--
|
||||
onMultiBookmarkClicked = screenModel::bookmarkChapters,
|
||||
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
|
||||
@ -366,52 +355,29 @@ class MangaScreen(
|
||||
*
|
||||
* @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) {
|
||||
router.pushController(GlobalSearchController(query))
|
||||
navigator.push(GlobalSearchScreen(query))
|
||||
return
|
||||
}
|
||||
|
||||
// SY -->
|
||||
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) {
|
||||
if (navigator.size < 2) {
|
||||
return
|
||||
}
|
||||
|
||||
when (val previousController = router.backstack[router.backstackSize - 2].controller) {
|
||||
is LibraryController -> {
|
||||
router.handleBack()
|
||||
when (val previousController = navigator.items[navigator.size - 2]) {
|
||||
is HomeScreen -> {
|
||||
navigator.pop()
|
||||
previousController.search(query)
|
||||
}
|
||||
is UpdatesController,
|
||||
is HistoryController,
|
||||
-> {
|
||||
// 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)
|
||||
is BrowseSourceScreen -> {
|
||||
navigator.pop()
|
||||
previousController.search(query)
|
||||
}
|
||||
// SY -->
|
||||
is SourceFeedController -> {
|
||||
router.handleBack()
|
||||
router.handleBack()
|
||||
router.pushController(BrowseSourceController(previousController.sourceId, query))
|
||||
is SourceFeedScreen -> {
|
||||
navigator.pop()
|
||||
navigator.replace(BrowseSourceScreen(previousController.sourceId, query))
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
@ -422,20 +388,17 @@ class MangaScreen(
|
||||
*
|
||||
* @param genreName the search genre to the parent controller
|
||||
*/
|
||||
private fun performGenreSearch(router: Router, navigator: Navigator, genreName: String, source: Source) {
|
||||
if (router.backstackSize < 2) {
|
||||
private suspend fun performGenreSearch(navigator: Navigator, genreName: String, source: Source) {
|
||||
if (navigator.size < 2) {
|
||||
return
|
||||
}
|
||||
|
||||
val previousController = router.backstack[router.backstackSize - 2].controller
|
||||
|
||||
if (previousController is BrowseSourceController &&
|
||||
source is HttpSource
|
||||
) {
|
||||
router.handleBack()
|
||||
previousController.searchWithGenre(genreName)
|
||||
val previousController = navigator.items[navigator.size - 2]
|
||||
if (previousController is BrowseSourceScreen && source is HttpSource) {
|
||||
navigator.pop()
|
||||
previousController.searchGenre(genreName)
|
||||
} else {
|
||||
performSearch(router, navigator, genreName, global = false)
|
||||
performSearch(navigator, genreName, global = false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,8 +447,8 @@ class MangaScreen(
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun openMorePagePreviews(router: Router, manga: Manga) {
|
||||
router.pushController(PagePreviewController(manga.id))
|
||||
private fun openMorePagePreviews(navigator: Navigator, manga: Manga) {
|
||||
navigator.push(PagePreviewScreen(manga.id))
|
||||
}
|
||||
|
||||
private fun openPagePreview(context: Context, chapter: Chapter?, page: Int) {
|
||||
@ -527,7 +490,7 @@ class MangaScreen(
|
||||
// EXH <--
|
||||
|
||||
// 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
|
||||
if (source.isMdBasedSource()) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
@ -541,13 +504,13 @@ class MangaScreen(
|
||||
) { dialog, index ->
|
||||
dialog.dismiss()
|
||||
when (index) {
|
||||
0 -> router.pushController(MangaDexSimilarController(manga, source as CatalogueSource))
|
||||
1 -> router.pushController(RecommendsController(manga, source as CatalogueSource))
|
||||
0 -> navigator.push(MangaDexSimilarScreen(manga.id, source.id))
|
||||
1 -> navigator.push(RecommendsScreen(manga.id, source.id))
|
||||
}
|
||||
}
|
||||
.show()
|
||||
} else if (source is CatalogueSource) {
|
||||
router.pushController(RecommendsController(manga, source))
|
||||
navigator.push(RecommendsScreen(manga.id, source.id))
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
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.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.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.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import eu.kanade.core.prefs.asState
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
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.DownloadService
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
||||
import eu.kanade.tachiyomi.ui.stats.StatsController
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||
import eu.kanade.tachiyomi.ui.history.HistoryTab
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
||||
import eu.kanade.tachiyomi.ui.stats.StatsScreen
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesTab
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
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.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@ -35,11 +42,28 @@ import kotlinx.coroutines.flow.combine
|
||||
import uy.kohesive.injekt.Injekt
|
||||
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
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { MoreScreenModel() }
|
||||
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
|
||||
MoreScreen(
|
||||
@ -53,16 +77,16 @@ object MoreScreen : Screen {
|
||||
showNavUpdates = screenModel.showNavUpdates,
|
||||
showNavHistory = screenModel.showNavHistory,
|
||||
// SY <--
|
||||
onClickDownloadQueue = { router.pushController(DownloadController()) },
|
||||
onClickCategories = { router.pushController(CategoryController()) },
|
||||
onClickStats = { router.pushController(StatsController()) },
|
||||
onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) },
|
||||
onClickSettings = { router.pushController(SettingsMainController()) },
|
||||
onClickAbout = { router.pushController(SettingsMainController.toAboutScreen()) },
|
||||
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
|
||||
onClickCategories = { navigator.push(CategoryScreen()) },
|
||||
onClickStats = { navigator.push(StatsScreen()) },
|
||||
onClickBackupAndRestore = { navigator.push(SettingsScreen.toBackupScreen()) },
|
||||
onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) },
|
||||
onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) },
|
||||
// SY -->
|
||||
onClickBatchAdd = { router.pushController(BatchAddController()) },
|
||||
onClickUpdates = { router.pushController(UpdatesController()) },
|
||||
onClickHistory = { router.pushController(HistoryController()) },
|
||||
onClickBatchAdd = { navigator.push(BatchAddScreen()) },
|
||||
onClickUpdates = { navigator.push(UpdatesTab) },
|
||||
onClickHistory = { navigator.push(HistoryTab) },
|
||||
// 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.ThemingDelegateImpl
|
||||
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.Error
|
||||
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.webtoon.WebtoonViewer
|
||||
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.system.applySystemAnimatorScale
|
||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||
@ -458,7 +458,7 @@ class ReaderActivity :
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java).apply {
|
||||
action = MainActivity.SHORTCUT_MANGA
|
||||
putExtra(MangaController.MANGA_EXTRA, id)
|
||||
putExtra(Constants.MANGA_EXTRA, id)
|
||||
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.SettingsMainScreen
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.Transition
|
||||
import eu.kanade.presentation.util.isTabletUi
|
||||
|
||||
@ -24,15 +23,8 @@ class SettingsScreen private constructor(
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
if (!isTabletUi()) {
|
||||
val back: () -> Unit = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
router.backstackSize > 1 -> router.handleBack()
|
||||
}
|
||||
}
|
||||
Navigator(
|
||||
screen = if (toBackup) {
|
||||
SettingsBackupScreen
|
||||
@ -42,7 +34,7 @@ class SettingsScreen private constructor(
|
||||
SettingsMainScreen
|
||||
},
|
||||
content = {
|
||||
CompositionLocalProvider(LocalBackPress provides back) {
|
||||
CompositionLocalProvider(LocalBackPress provides navigator::pop) {
|
||||
ScreenTransition(
|
||||
navigator = it,
|
||||
transition = { Transition.OneWayFade },
|
||||
@ -62,7 +54,7 @@ class SettingsScreen private constructor(
|
||||
) {
|
||||
TwoPanelBox(
|
||||
startContent = {
|
||||
CompositionLocalProvider(LocalBackPress provides router::popCurrentController) {
|
||||
CompositionLocalProvider(LocalBackPress provides navigator::pop) {
|
||||
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.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.more.stats.StatsScreenContent
|
||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
class StatsScreen : Screen {
|
||||
@ -23,8 +22,7 @@ class StatsScreen : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val screenModel = rememberScreenModel { StatsScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
@ -38,7 +36,7 @@ class StatsScreen : Screen {
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(R.string.label_stats),
|
||||
navigateUp = router::popCurrentController,
|
||||
navigateUp = navigator::pop,
|
||||
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
|
||||
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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.res.stringResource
|
||||
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.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.UpdatesDeleteConfirmationDialog
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.Tab
|
||||
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.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
||||
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
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
@ -34,7 +75,7 @@ object UpdatesScreen : Screen {
|
||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||
lastUpdated = screenModel.lastUpdated,
|
||||
relativeTime = screenModel.relativeTime,
|
||||
onClickCover = { item -> router.pushController(MangaController(item.update.mangaId)) },
|
||||
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },
|
||||
onSelectAll = screenModel::toggleAllSelection,
|
||||
onInvertSelection = screenModel::invertSelection,
|
||||
onUpdateLibrary = screenModel::updateLibrary,
|
||||
@ -77,8 +118,9 @@ object UpdatesScreen : Screen {
|
||||
}
|
||||
|
||||
LaunchedEffect(state.selectionMode) {
|
||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
||||
HomeScreen.showBottomNav(!state.selectionMode)
|
||||
}
|
||||
|
||||
LaunchedEffect(state.isLoading) {
|
||||
if (!state.isLoading) {
|
||||
(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
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
@ -30,6 +29,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.SpanStyle
|
||||
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.presentation.components.AppBar
|
||||
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.util.plus
|
||||
import eu.kanade.presentation.util.topSmallPaddingValues
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||
import exh.util.capitalize
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -63,12 +66,17 @@ import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
|
||||
class SettingsDebugController : BasicFullComposeController() {
|
||||
class SettingsDebugScreen : Screen {
|
||||
|
||||
data class DebugToggle(val name: String, val pref: PreferenceMutableState<Boolean>, val default: Boolean)
|
||||
|
||||
@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) {
|
||||
value = withContext(Dispatchers.Default) {
|
||||
DebugFunctions::class.declaredFunctions.filter {
|
||||
@ -82,7 +90,7 @@ class SettingsDebugController : BasicFullComposeController() {
|
||||
}
|
||||
val toggles by produceState(initialValue = emptyList()) {
|
||||
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(
|
||||
@ -96,7 +104,7 @@ class SettingsDebugController : BasicFullComposeController() {
|
||||
Crossfade(functions == null) {
|
||||
when (it) {
|
||||
true -> LoadingScreen()
|
||||
false -> FunctionList(paddingValues, functions.orEmpty(), toggles)
|
||||
false -> FunctionList(paddingValues, functions.orEmpty(), toggles, scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,8 +115,8 @@ class SettingsDebugController : BasicFullComposeController() {
|
||||
paddingValues: PaddingValues,
|
||||
functions: List<Pair<KFunction<*>, String>>,
|
||||
toggles: List<DebugToggle>,
|
||||
scope: CoroutineScope,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
var running by remember { mutableStateOf(false) }
|
||||
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.ViewGroup
|
||||
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.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.online.RandomMangaSource
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
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>() {
|
||||
|
||||
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) {
|
||||
fun bind() {
|
||||
binding.mangadexFollows.setOnClickListener {
|
||||
router.replaceTopController(MangaDexFollowsController(source).withFadeTransaction())
|
||||
navigator.replace(MangaDexFollowsScreen(source.id))
|
||||
onClick()
|
||||
}
|
||||
binding.mangadexRandom.setOnClickListener {
|
||||
@ -41,11 +40,11 @@ class MangaDexFabHeaderAdapter(val router: Router, val source: CatalogueSource,
|
||||
val randomMangaUrl = withIOContext {
|
||||
(source as? RandomMangaSource)?.fetchRandomMangaUrl()
|
||||
}
|
||||
router.replaceTopController(
|
||||
BrowseSourceController(
|
||||
source,
|
||||
navigator.replace(
|
||||
BrowseSourceScreen(
|
||||
source.id,
|
||||
"id:$randomMangaUrl",
|
||||
).withFadeTransaction(),
|
||||
),
|
||||
)
|
||||
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
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
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.DuplicateMangaDialog
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
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.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
|
||||
class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
@ -44,20 +40,13 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val navigateUp: () -> Unit = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
router.backstackSize > 1 -> router.popCurrentController()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
BrowseSourceSimpleToolbar(
|
||||
title = stringResource(R.string.mangadex_follows),
|
||||
displayMode = screenModel.displayMode,
|
||||
onDisplayModeChange = { screenModel.displayMode = it },
|
||||
navigateUp = navigateUp,
|
||||
navigateUp = navigator::pop,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
@ -82,7 +71,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||
onWebViewClick = null,
|
||||
onHelpClick = null,
|
||||
onLocalSourceHelpClick = null,
|
||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
||||
onMangaClick = { navigator.push(MangaScreen(it.id, true)) },
|
||||
onMangaLongClick = { manga ->
|
||||
scope.launchIO {
|
||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||
@ -109,7 +98,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||
DuplicateMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
||||
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
||||
)
|
||||
}
|
||||
@ -127,7 +116,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = {
|
||||
router.pushController(CategoryController())
|
||||
navigator.push(CategoryScreen())
|
||||
},
|
||||
onConfirm = { include, _ ->
|
||||
screenModel.changeMangaFavorite(dialog.manga)
|
||||
@ -137,7 +126,5 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen {
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
BackHandler(onBack = navigateUp)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package exh.md.follows
|
||||
|
||||
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.source.model.SourcePagingSourceType
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
@ -22,7 +22,7 @@ class MangaDexFollowsScreenModel(sourceId: Long) : BrowseSourceScreenModel(sourc
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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.components.BrowseSourceSimpleToolbar
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
|
||||
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() {
|
||||
val screenModel = rememberScreenModel { MangaDexSimilarScreenModel(mangaId, sourceId) }
|
||||
val state by screenModel.state.collectAsState()
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
|
||||
val onMangaClick: (Manga) -> Unit = {
|
||||
router.pushController(MangaController(it.id, true))
|
||||
navigator.push(MangaScreen(it.id, true))
|
||||
}
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val navigateUp: () -> Unit = {
|
||||
when {
|
||||
navigator.canPop -> navigator.pop()
|
||||
router.backstackSize > 1 -> router.popCurrentController()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
BrowseSourceSimpleToolbar(
|
||||
navigateUp = navigateUp,
|
||||
navigateUp = navigator::pop,
|
||||
title = stringResource(R.string.similar, screenModel.manga.title),
|
||||
displayMode = screenModel.displayMode,
|
||||
onDisplayModeChange = { screenModel.displayMode = it },
|
||||
|
@ -1,7 +1,7 @@
|
||||
package exh.md.similar
|
||||
|
||||
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.model.Manga
|
||||
import eu.kanade.domain.source.model.SourcePagingSourceType
|
||||
@ -16,9 +16,6 @@ import kotlinx.coroutines.runBlocking
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [MangaDexSimilarController]. Inherit BrowseCataloguePresenter.
|
||||
*/
|
||||
class MangaDexSimilarScreenModel(
|
||||
val mangaId: Long,
|
||||
sourceId: Long,
|
||||
@ -35,7 +32,7 @@ class MangaDexSimilarScreenModel(
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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 cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import exh.pagepreview.components.PagePreviewScreen
|
||||
|
||||
@ -19,7 +19,7 @@ class PagePreviewScreen(private val mangaId: Long) : Screen {
|
||||
val screenModel = rememberScreenModel { PagePreviewScreenModel(mangaId) }
|
||||
val context = LocalContext.current
|
||||
val state by screenModel.state.collectAsState()
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
PagePreviewScreen(
|
||||
state = state,
|
||||
pageDialogOpen = screenModel.pageDialogOpen,
|
||||
@ -27,7 +27,7 @@ class PagePreviewScreen(private val mangaId: Long) : Screen {
|
||||
onOpenPage = { openPage(context, state, it) },
|
||||
onOpenPageDialog = { screenModel.pageDialogOpen = true },
|
||||
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