parent
5313a5d5d2
commit
3d66eaea83
@ -267,15 +267,12 @@ dependencies {
|
|||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
}
|
}
|
||||||
implementation(libs.insetter)
|
implementation(libs.insetter)
|
||||||
implementation(libs.markwon)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.cascade)
|
implementation(libs.cascade)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.wheelpicker)
|
implementation(libs.wheelpicker)
|
||||||
|
|
||||||
// Conductor
|
|
||||||
implementation(libs.conductor)
|
|
||||||
|
|
||||||
// FlowBinding
|
// FlowBinding
|
||||||
implementation(libs.flowbinding.android)
|
implementation(libs.flowbinding.android)
|
||||||
|
|
||||||
@ -328,6 +325,7 @@ tasks {
|
|||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||||
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.components
|
|||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@ -16,10 +17,10 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
|
||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.BookmarkAdd
|
import androidx.compose.material.icons.outlined.BookmarkAdd
|
||||||
@ -95,7 +96,11 @@ fun MangaBottomActionMenu(
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues())
|
.padding(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.only(WindowInsetsSides.Bottom)
|
||||||
|
.asPaddingValues(),
|
||||||
|
)
|
||||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
) {
|
) {
|
||||||
if (onBookmarkClicked != null) {
|
if (onBookmarkClicked != null) {
|
||||||
@ -213,16 +218,16 @@ private fun RowScope.Button(
|
|||||||
fun LibraryBottomActionMenu(
|
fun LibraryBottomActionMenu(
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onChangeCategoryClicked: (() -> Unit)?,
|
onChangeCategoryClicked: () -> Unit,
|
||||||
onMarkAsReadClicked: (() -> Unit)?,
|
onMarkAsReadClicked: () -> Unit,
|
||||||
onMarkAsUnreadClicked: (() -> Unit)?,
|
onMarkAsUnreadClicked: () -> Unit,
|
||||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||||
onDeleteClicked: (() -> Unit)?,
|
onDeleteClicked: () -> Unit,
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = visible,
|
visible = visible,
|
||||||
enter = expandVertically(expandFrom = Alignment.Bottom),
|
enter = expandVertically(animationSpec = tween(delayMillis = 300)),
|
||||||
exit = shrinkVertically(shrinkTowards = Alignment.Bottom),
|
exit = shrinkVertically(animationSpec = tween()),
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
Surface(
|
Surface(
|
||||||
@ -244,10 +249,12 @@ fun LibraryBottomActionMenu(
|
|||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.navigationBarsPadding()
|
.windowInsetsPadding(
|
||||||
|
WindowInsets.navigationBars
|
||||||
|
.only(WindowInsetsSides.Bottom),
|
||||||
|
)
|
||||||
.padding(horizontal = 8.dp, vertical = 12.dp),
|
.padding(horizontal = 8.dp, vertical = 12.dp),
|
||||||
) {
|
) {
|
||||||
if (onChangeCategoryClicked != null) {
|
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_move_category),
|
title = stringResource(R.string.action_move_category),
|
||||||
icon = Icons.Outlined.Label,
|
icon = Icons.Outlined.Label,
|
||||||
@ -255,8 +262,6 @@ fun LibraryBottomActionMenu(
|
|||||||
onLongClick = { onLongClickItem(0) },
|
onLongClick = { onLongClickItem(0) },
|
||||||
onClick = onChangeCategoryClicked,
|
onClick = onChangeCategoryClicked,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
if (onMarkAsReadClicked != null) {
|
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_read),
|
title = stringResource(R.string.action_mark_as_read),
|
||||||
icon = Icons.Outlined.DoneAll,
|
icon = Icons.Outlined.DoneAll,
|
||||||
@ -264,8 +269,6 @@ fun LibraryBottomActionMenu(
|
|||||||
onLongClick = { onLongClickItem(1) },
|
onLongClick = { onLongClickItem(1) },
|
||||||
onClick = onMarkAsReadClicked,
|
onClick = onMarkAsReadClicked,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
if (onMarkAsUnreadClicked != null) {
|
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_mark_as_unread),
|
title = stringResource(R.string.action_mark_as_unread),
|
||||||
icon = Icons.Outlined.RemoveDone,
|
icon = Icons.Outlined.RemoveDone,
|
||||||
@ -273,7 +276,6 @@ fun LibraryBottomActionMenu(
|
|||||||
onLongClick = { onLongClickItem(2) },
|
onLongClick = { onLongClickItem(2) },
|
||||||
onClick = onMarkAsUnreadClicked,
|
onClick = onMarkAsUnreadClicked,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
if (onDownloadClicked != null) {
|
if (onDownloadClicked != null) {
|
||||||
var downloadExpanded by remember { mutableStateOf(false) }
|
var downloadExpanded by remember { mutableStateOf(false) }
|
||||||
Button(
|
Button(
|
||||||
@ -292,7 +294,6 @@ fun LibraryBottomActionMenu(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onDeleteClicked != null) {
|
|
||||||
Button(
|
Button(
|
||||||
title = stringResource(R.string.action_delete),
|
title = stringResource(R.string.action_delete),
|
||||||
icon = Icons.Outlined.Delete,
|
icon = Icons.Outlined.Delete,
|
||||||
@ -304,4 +305,3 @@ fun LibraryBottomActionMenu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
|
import androidx.compose.material3.contentColorFor
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M3 Navbar with no horizontal spacer
|
||||||
|
*
|
||||||
|
* @see [androidx.compose.material3.NavigationBar]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NavigationBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
containerColor: Color = NavigationBarDefaults.containerColor,
|
||||||
|
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
|
||||||
|
tonalElevation: Dp = NavigationBarDefaults.Elevation,
|
||||||
|
windowInsets: WindowInsets = NavigationBarDefaults.windowInsets,
|
||||||
|
content: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Surface(
|
||||||
|
color = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
tonalElevation = tonalElevation,
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.windowInsetsPadding(windowInsets)
|
||||||
|
.height(80.dp)
|
||||||
|
.selectableGroup(),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material3.NavigationRailDefaults
|
||||||
|
import androidx.compose.material3.contentColorFor
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center-aligned M3 Navigation rail
|
||||||
|
*
|
||||||
|
* @see [androidx.compose.material3.NavigationRail]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun NavigationRail(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
containerColor: Color = NavigationRailDefaults.ContainerColor,
|
||||||
|
contentColor: Color = contentColorFor(containerColor),
|
||||||
|
header: @Composable (ColumnScope.() -> Unit)? = null,
|
||||||
|
windowInsets: WindowInsets = NavigationRailDefaults.windowInsets,
|
||||||
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Surface(
|
||||||
|
color = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
modifier = modifier,
|
||||||
|
tonalElevation = 3.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.windowInsetsPadding(windowInsets)
|
||||||
|
.widthIn(min = 80.dp)
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.selectableGroup(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 4.dp, alignment = Alignment.CenterVertically),
|
||||||
|
) {
|
||||||
|
if (header != null) {
|
||||||
|
header()
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
package eu.kanade.presentation.components
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.MutableWindowInsets
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.exclude
|
||||||
|
import androidx.compose.foundation.layout.withConsumedWindowInsets
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
import androidx.compose.material3.ScaffoldDefaults
|
||||||
@ -31,6 +34,7 @@ import androidx.compose.material3.rememberTopAppBarState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@ -67,6 +71,7 @@ import kotlin.math.max
|
|||||||
* * Remove height constraint for expanded app bar
|
* * Remove height constraint for expanded app bar
|
||||||
* * Also take account of fab height when providing inner padding
|
* * Also take account of fab height when providing inner padding
|
||||||
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
|
* * Fixes for fab and snackbar horizontal placements when [contentWindowInsets] is used
|
||||||
|
* * Handle consumed window insets
|
||||||
*
|
*
|
||||||
* @param modifier the [Modifier] to be applied to this scaffold
|
* @param modifier the [Modifier] to be applied to this scaffold
|
||||||
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
* @param topBar top app bar of the screen, typically a [SmallTopAppBar]
|
||||||
@ -103,9 +108,12 @@ fun Scaffold(
|
|||||||
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
// Tachiyomi: Handle consumed window insets
|
||||||
|
val remainingWindowInsets = remember { MutableWindowInsets() }
|
||||||
androidx.compose.material3.Surface(
|
androidx.compose.material3.Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
||||||
|
.withConsumedWindowInsets { remainingWindowInsets.insets = contentWindowInsets.exclude(it) }
|
||||||
.then(modifier),
|
.then(modifier),
|
||||||
color = containerColor,
|
color = containerColor,
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
@ -116,7 +124,7 @@ fun Scaffold(
|
|||||||
bottomBar = bottomBar,
|
bottomBar = bottomBar,
|
||||||
content = content,
|
content = content,
|
||||||
snackbar = snackbarHost,
|
snackbar = snackbarHost,
|
||||||
contentWindowInsets = contentWindowInsets,
|
contentWindowInsets = remainingWindowInsets,
|
||||||
fab = floatingActionButton,
|
fab = floatingActionButton,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -88,9 +87,7 @@ fun TabbedScreen(
|
|||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
) { page ->
|
) { page ->
|
||||||
tabs[page].content(
|
tabs[page].content(
|
||||||
TachiyomiBottomNavigationView.withBottomNavPadding(
|
|
||||||
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
PaddingValues(bottom = contentPadding.calculateBottomPadding()),
|
||||||
),
|
|
||||||
snackbarHostState,
|
snackbarHostState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -21,7 +20,6 @@ import eu.kanade.presentation.history.components.HistoryContent
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryState
|
import eu.kanade.tachiyomi.ui.history.HistoryState
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -55,7 +53,6 @@ fun HistoryScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
state.list.let {
|
state.list.let {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package eu.kanade.presentation.more
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.CloudOff
|
import androidx.compose.material.icons.outlined.CloudOff
|
||||||
import androidx.compose.material.icons.outlined.GetApp
|
import androidx.compose.material.icons.outlined.GetApp
|
||||||
@ -29,8 +26,7 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
import eu.kanade.tachiyomi.ui.more.DownloadQueueState
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MoreScreen(
|
fun MoreScreen(
|
||||||
@ -50,10 +46,7 @@ fun MoreScreen(
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier.systemBarsPadding(),
|
||||||
contentPadding = TachiyomiBottomNavigationView.withBottomNavPadding(
|
|
||||||
WindowInsets.navigationBars.asPaddingValues(),
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
if (isFDroid) {
|
if (isFDroid) {
|
||||||
item {
|
item {
|
||||||
@ -169,7 +162,7 @@ fun MoreScreen(
|
|||||||
TextPreferenceWidget(
|
TextPreferenceWidget(
|
||||||
title = stringResource(R.string.label_help),
|
title = stringResource(R.string.label_help),
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.Outlined.HelpOutline,
|
||||||
onPreferenceClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
144
app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt
Normal file
144
app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package eu.kanade.presentation.more
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.OpenInNew
|
||||||
|
import androidx.compose.material.icons.outlined.NewReleases
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import com.halilibo.richtext.markdown.Markdown
|
||||||
|
import com.halilibo.richtext.ui.RichTextStyle
|
||||||
|
import com.halilibo.richtext.ui.material3.Material3RichText
|
||||||
|
import com.halilibo.richtext.ui.string.RichTextStringStyle
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.util.padding
|
||||||
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewUpdateScreen(
|
||||||
|
versionName: String,
|
||||||
|
changelogInfo: String,
|
||||||
|
onOpenInBrowser: () -> Unit,
|
||||||
|
onRejectUpdate: () -> Unit,
|
||||||
|
onAcceptUpdate: () -> Unit,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
val strokeWidth = Dp.Hairline
|
||||||
|
val borderColor = MaterialTheme.colorScheme.outline
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.drawBehind {
|
||||||
|
drawLine(
|
||||||
|
borderColor,
|
||||||
|
Offset(0f, 0f),
|
||||||
|
Offset(size.width, 0f),
|
||||||
|
strokeWidth.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.windowInsetsPadding(NavigationBarDefaults.windowInsets)
|
||||||
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onAcceptUpdate,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.update_check_confirm))
|
||||||
|
}
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = onRejectUpdate,
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_not_now))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
// Status bar scrim
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.zIndex(2f)
|
||||||
|
.secondaryItemAlpha()
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(paddingValues.calculateTopPadding()),
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(top = 48.dp)
|
||||||
|
.padding(horizontal = MaterialTheme.padding.medium),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.NewReleases,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = MaterialTheme.padding.small)
|
||||||
|
.size(48.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.update_check_notification_update_available),
|
||||||
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = versionName,
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
|
||||||
|
Material3RichText(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = MaterialTheme.padding.large),
|
||||||
|
style = RichTextStyle(
|
||||||
|
stringStyle = RichTextStringStyle(
|
||||||
|
linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Markdown(content = changelogInfo)
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = onOpenInBrowser,
|
||||||
|
modifier = Modifier.padding(top = MaterialTheme.padding.small),
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.update_check_open))
|
||||||
|
Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny))
|
||||||
|
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.LinkIcon
|
import eu.kanade.presentation.components.LinkIcon
|
||||||
@ -29,13 +28,12 @@ import eu.kanade.presentation.more.LogoHeader
|
|||||||
import eu.kanade.presentation.more.about.LicensesScreen
|
import eu.kanade.presentation.more.about.LicensesScreen
|
||||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||||
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||||
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.CrashLogUtil
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
@ -61,7 +59,6 @@ object AboutScreen : Screen {
|
|||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val handleBack = LocalBackPress.current
|
val handleBack = LocalBackPress.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
@ -96,7 +93,15 @@ object AboutScreen : Screen {
|
|||||||
title = stringResource(R.string.check_for_updates),
|
title = stringResource(R.string.check_for_updates),
|
||||||
onPreferenceClick = {
|
onPreferenceClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
checkVersion(context, router)
|
checkVersion(context) { result ->
|
||||||
|
val updateScreen = NewUpdateScreen(
|
||||||
|
versionName = result.release.version,
|
||||||
|
changelogInfo = result.release.info,
|
||||||
|
releaseLink = result.release.releaseLink,
|
||||||
|
downloadLink = result.release.getDownloadLink(),
|
||||||
|
)
|
||||||
|
navigator.push(updateScreen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -178,14 +183,14 @@ object AboutScreen : Screen {
|
|||||||
/**
|
/**
|
||||||
* Checks version and shows a user prompt if an update is available.
|
* Checks version and shows a user prompt if an update is available.
|
||||||
*/
|
*/
|
||||||
private suspend fun checkVersion(context: Context, router: Router) {
|
private suspend fun checkVersion(context: Context, onAvailableUpdate: (AppUpdateResult.NewUpdate) -> Unit) {
|
||||||
val updateChecker = AppUpdateChecker()
|
val updateChecker = AppUpdateChecker()
|
||||||
withUIContext {
|
withUIContext {
|
||||||
context.toast(R.string.update_check_look_for_updates)
|
context.toast(R.string.update_check_look_for_updates)
|
||||||
try {
|
try {
|
||||||
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
|
when (val result = withIOContext { updateChecker.checkForUpdate(context, isUserPrompt = true) }) {
|
||||||
is AppUpdateResult.NewUpdate -> {
|
is AppUpdateResult.NewUpdate -> {
|
||||||
NewUpdateDialogController(result).showDialog(router)
|
onAvailableUpdate(result)
|
||||||
}
|
}
|
||||||
is AppUpdateResult.NoNewUpdate -> {
|
is AppUpdateResult.NoNewUpdate -> {
|
||||||
context.toast(R.string.update_check_no_new_updates)
|
context.toast(R.string.update_check_no_new_updates)
|
||||||
|
@ -9,7 +9,6 @@ import androidx.compose.material.icons.outlined.Refresh
|
|||||||
import androidx.compose.material.icons.outlined.SelectAll
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
@ -36,7 +35,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesState
|
import eu.kanade.tachiyomi.ui.updates.UpdatesState
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@ -87,7 +85,6 @@ fun UpdateScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
|
@ -4,12 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
|
||||||
/**
|
|
||||||
* For interop with Conductor
|
|
||||||
*/
|
|
||||||
val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf { null }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For invoking back press to the parent activity
|
* For invoking back press to the parent activity
|
||||||
@ -17,3 +12,7 @@ val LocalRouter: ProvidableCompositionLocal<Router?> = staticCompositionLocalOf
|
|||||||
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
|
||||||
|
|
||||||
val LocalNavigatorContentPadding: ProvidableCompositionLocal<PaddingValues> = compositionLocalOf { PaddingValues() }
|
val LocalNavigatorContentPadding: ProvidableCompositionLocal<PaddingValues> = compositionLocalOf { PaddingValues() }
|
||||||
|
|
||||||
|
interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
|
||||||
|
suspend fun onReselect(navigator: Navigator) {}
|
||||||
|
}
|
||||||
|
@ -23,8 +23,8 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
import eu.kanade.tachiyomi.data.updater.AppUpdateService
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
@ -457,7 +457,7 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
val newIntent =
|
val newIntent =
|
||||||
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
.putExtra(MangaController.MANGA_EXTRA, manga.id)
|
.putExtra(Constants.MANGA_EXTRA, manga.id)
|
||||||
.putExtra("notificationId", manga.id.hashCode())
|
.putExtra("notificationId", manga.id.hashCode())
|
||||||
.putExtra("groupId", groupId)
|
.putExtra("groupId", groupId)
|
||||||
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
@ -48,7 +48,7 @@ import eu.kanade.domain.manga.model.MangaCover
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
@ -136,7 +136,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||||||
) {
|
) {
|
||||||
val intent = Intent(LocalContext.current, MainActivity::class.java).apply {
|
val intent = Intent(LocalContext.current, MainActivity::class.java).apply {
|
||||||
action = MainActivity.SHORTCUT_MANGA
|
action = MainActivity.SHORTCUT_MANGA
|
||||||
putExtra(MangaController.MANGA_EXTRA, mangaId)
|
putExtra(Constants.MANGA_EXTRA, mangaId)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
|
||||||
|
@ -1,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,25 +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.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,46 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.controller
|
|
||||||
|
|
||||||
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.FadeChangeHandler
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A variation of [FadeChangeHandler] that only fades in.
|
|
||||||
*/
|
|
||||||
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,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,17 +1,23 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.core.prefs.asState
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.presentation.components.TabbedScreen
|
import eu.kanade.presentation.components.TabbedScreen
|
||||||
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
||||||
@ -22,9 +28,21 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
data class BrowseScreen(
|
data class BrowseTab(
|
||||||
private val toExtensions: Boolean,
|
private val toExtensions: Boolean = false,
|
||||||
) : Screen {
|
) : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 3u,
|
||||||
|
title = stringResource(R.string.browse),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
@ -11,7 +11,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.MigrateMangaScreen
|
import eu.kanade.presentation.browse.MigrateMangaScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
@ -26,7 +25,6 @@ data class MigrationMangaScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val screenModel = rememberScreenModel { MigrationMangaScreenModel(sourceId) }
|
val screenModel = rememberScreenModel { MigrationMangaScreenModel(sourceId) }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
@ -35,7 +35,6 @@ import eu.kanade.domain.manga.model.hasCustomCover
|
|||||||
import eu.kanade.domain.track.interactor.GetTracks
|
import eu.kanade.domain.track.interactor.GetTracks
|
||||||
import eu.kanade.domain.track.interactor.InsertTrack
|
import eu.kanade.domain.track.interactor.InsertTrack
|
||||||
import eu.kanade.presentation.browse.MigrateSearchScreen
|
import eu.kanade.presentation.browse.MigrateSearchScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.core.preference.Preference
|
import eu.kanade.tachiyomi.core.preference.Preference
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||||
@ -45,9 +44,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
|
|||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
|
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
@ -60,7 +57,6 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val screenModel = rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) }
|
val screenModel = rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -76,7 +72,7 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen {
|
|||||||
if (!screenModel.incognitoMode.get()) {
|
if (!screenModel.incognitoMode.get()) {
|
||||||
screenModel.lastUsedSourceId.set(it.id)
|
screenModel.lastUsedSourceId.set(it.id)
|
||||||
}
|
}
|
||||||
router.pushController(SourceSearchController(state.manga, it, state.searchQuery))
|
navigator.push(SourceSearchScreen(state.manga!!, it.id, state.searchQuery))
|
||||||
},
|
},
|
||||||
onClickItem = { screenModel.setDialog(MigrateSearchDialog.Migrate(it)) },
|
onClickItem = { screenModel.setDialog(MigrateSearchDialog.Migrate(it)) },
|
||||||
onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||||
@ -99,8 +95,7 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen {
|
|||||||
navigator.popUntil { navigator.items.contains(lastItem) }
|
navigator.popUntil { navigator.items.contains(lastItem) }
|
||||||
navigator.push(MangaScreen(dialog.manga.id))
|
navigator.push(MangaScreen(dialog.manga.id))
|
||||||
} else {
|
} else {
|
||||||
navigator.pop()
|
navigator.replace(MangaScreen(dialog.manga.id))
|
||||||
router.pushController(MangaController(dialog.manga.id))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -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? = null, 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"
|
|
@ -12,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
@ -26,17 +27,15 @@ import eu.kanade.presentation.browse.BrowseSourceContent
|
|||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
data class SourceSearchScreen(
|
data class SourceSearchScreen(
|
||||||
private val oldManga: Manga,
|
private val oldManga: Manga,
|
||||||
@ -48,27 +47,20 @@ data class SourceSearchScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId = sourceId, searchQuery = query) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
searchQuery = state.toolbarQuery ?: "",
|
searchQuery = state.toolbarQuery ?: "",
|
||||||
onChangeSearchQuery = screenModel::setToolbarQuery,
|
onChangeSearchQuery = screenModel::setToolbarQuery,
|
||||||
onClickCloseSearch = navigateUp,
|
onClickCloseSearch = navigator::pop,
|
||||||
onSearch = { screenModel.search(it) },
|
onSearch = { screenModel.search(it) },
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@ -102,7 +94,7 @@ data class SourceSearchScreen(
|
|||||||
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
val intent = WebViewActivity.newIntent(context, source.baseUrl, source.id, source.name)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
|
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
|
||||||
onMangaClick = openMigrateDialog,
|
onMangaClick = openMigrateDialog,
|
||||||
onMangaLongClick = openMigrateDialog,
|
onMangaLongClick = openMigrateDialog,
|
||||||
@ -116,11 +108,13 @@ data class SourceSearchScreen(
|
|||||||
newManga = dialog.newManga,
|
newManga = dialog.newManga,
|
||||||
screenModel = rememberScreenModel { MigrateDialogScreenModel() },
|
screenModel = rememberScreenModel { MigrateDialogScreenModel() },
|
||||||
onDismissRequest = { screenModel.setDialog(null) },
|
onDismissRequest = { screenModel.setDialog(null) },
|
||||||
onClickTitle = { router.pushController(MangaController(dialog.newManga.id)) },
|
onClickTitle = { navigator.push(MangaScreen(dialog.newManga.id)) },
|
||||||
onPopScreen = {
|
onPopScreen = {
|
||||||
// TODO: Push to manga screen and remove this and the previous screen when it moves to Voyager
|
scope.launch {
|
||||||
router.setRoot(BrowseController(toExtensions = false), R.id.nav_browse)
|
navigator.popUntilRoot()
|
||||||
router.pushController(MangaController(dialog.newManga.id))
|
HomeScreen.openTab(HomeScreen.Tab.Browse())
|
||||||
|
navigator.push(MangaScreen(dialog.newManga.id))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class SourceFilterController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = SourcesFilterScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,10 +7,10 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.SourcesFilterScreen
|
import eu.kanade.presentation.browse.SourcesFilterScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class SourcesFilterScreen : Screen {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourcesFilterScreenModel() }
|
val screenModel = rememberScreenModel { SourcesFilterScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ class SourcesFilterScreen : Screen {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
context.toast(R.string.internal_error)
|
context.toast(R.string.internal_error)
|
||||||
router.popCurrentController()
|
navigator.pop()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ class SourcesFilterScreen : Screen {
|
|||||||
val successState = state as SourcesFilterState.Success
|
val successState = state as SourcesFilterState.Success
|
||||||
|
|
||||||
SourcesFilterScreen(
|
SourcesFilterScreen(
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickLanguage = screenModel::toggleLanguage,
|
onClickLanguage = screenModel::toggleLanguage,
|
||||||
onClickSource = screenModel::toggleSource,
|
onClickSource = screenModel::toggleSource,
|
||||||
|
@ -10,22 +10,21 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.SourceOptionsDialog
|
import eu.kanade.presentation.browse.SourceOptionsDialog
|
||||||
import eu.kanade.presentation.browse.SourcesScreen
|
import eu.kanade.presentation.browse.SourcesScreen
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.TabContent
|
import eu.kanade.presentation.components.TabContent
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Screen.sourcesTab(): TabContent {
|
fun Screen.sourcesTab(): TabContent {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourcesScreenModel() }
|
val screenModel = rememberScreenModel { SourcesScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -35,12 +34,12 @@ fun Screen.sourcesTab(): TabContent {
|
|||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_global_search),
|
title = stringResource(R.string.action_global_search),
|
||||||
icon = Icons.Outlined.TravelExplore,
|
icon = Icons.Outlined.TravelExplore,
|
||||||
onClick = { router.pushController(GlobalSearchController()) },
|
onClick = { navigator.push(GlobalSearchScreen()) },
|
||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(R.string.action_filter),
|
title = stringResource(R.string.action_filter),
|
||||||
icon = Icons.Outlined.FilterList,
|
icon = Icons.Outlined.FilterList,
|
||||||
onClick = { router.pushController(SourceFilterController()) },
|
onClick = { navigator.push(SourcesFilterScreen()) },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
content = { contentPadding, snackbarHostState ->
|
content = { contentPadding, snackbarHostState ->
|
||||||
@ -49,7 +48,7 @@ fun Screen.sourcesTab(): TabContent {
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickItem = { source, query ->
|
onClickItem = { source, query ->
|
||||||
screenModel.onOpenSource(source)
|
screenModel.onOpenSource(source)
|
||||||
router.pushController(BrowseSourceController(source.id, query))
|
navigator.push(BrowseSourceScreen(source.id, query))
|
||||||
},
|
},
|
||||||
onClickPin = screenModel::togglePin,
|
onClickPin = screenModel::togglePin,
|
||||||
onLongClickItem = screenModel::showSourceDialog,
|
onLongClickItem = screenModel::showSourceDialog,
|
||||||
|
@ -1,69 +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 androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
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) : this(
|
|
||||||
bundleOf(
|
|
||||||
SOURCE_ID_KEY to sourceId,
|
|
||||||
SEARCH_QUERY_KEY to query,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val sourceId = args.getLong(SOURCE_ID_KEY)
|
|
||||||
private val initialQuery = args.getString(SEARCH_QUERY_KEY)
|
|
||||||
|
|
||||||
private val queryEvent = Channel<BrowseSourceScreen.SearchType>()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = BrowseSourceScreen(sourceId = sourceId, query = initialQuery)) { 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"
|
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -49,16 +48,13 @@ import eu.kanade.presentation.components.ChangeCategoryDialog
|
|||||||
import eu.kanade.presentation.components.Divider
|
import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.DuplicateMangaDialog
|
import eu.kanade.presentation.components.DuplicateMangaDialog
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
@ -73,7 +69,6 @@ data class BrowseSourceScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -93,13 +88,6 @@ data class BrowseSourceScreen(
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
val navigateUp: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.popCurrentController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||||
@ -109,7 +97,7 @@ data class BrowseSourceScreen(
|
|||||||
source = screenModel.source,
|
source = screenModel.source,
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = screenModel.displayMode,
|
||||||
onDisplayModeChange = { screenModel.displayMode = it },
|
onDisplayModeChange = { screenModel.displayMode = it },
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigator::pop,
|
||||||
onWebViewClick = onWebViewClick,
|
onWebViewClick = onWebViewClick,
|
||||||
onHelpClick = onHelpClick,
|
onHelpClick = onHelpClick,
|
||||||
onSearch = { screenModel.search(it) },
|
onSearch = { screenModel.search(it) },
|
||||||
@ -197,9 +185,9 @@ data class BrowseSourceScreen(
|
|||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
onWebViewClick = onWebViewClick,
|
onWebViewClick = onWebViewClick,
|
||||||
onHelpClick = { uriHandler.openUri(MoreController.URL_HELP) },
|
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
|
||||||
onLocalSourceHelpClick = onHelpClick,
|
onLocalSourceHelpClick = onHelpClick,
|
||||||
onMangaClick = { router.pushController(MangaController(it.id, true)) },
|
onMangaClick = { navigator.push((MangaScreen(it.id, true))) },
|
||||||
onMangaLongClick = { manga ->
|
onMangaLongClick = { manga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||||
@ -226,7 +214,7 @@ data class BrowseSourceScreen(
|
|||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||||
onOpenManga = { router.pushController(MangaController(dialog.duplicate.id)) },
|
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||||
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
duplicateFrom = screenModel.getSourceOrStub(dialog.duplicate),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -243,9 +231,7 @@ data class BrowseSourceScreen(
|
|||||||
ChangeCategoryDialog(
|
ChangeCategoryDialog(
|
||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = {
|
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||||
router.pushController(CategoryController())
|
|
||||||
},
|
|
||||||
onConfirm = { include, _ ->
|
onConfirm = { include, _ ->
|
||||||
screenModel.changeMangaFavorite(dialog.manga)
|
screenModel.changeMangaFavorite(dialog.manga)
|
||||||
screenModel.moveMangaToCategories(dialog.manga, include)
|
screenModel.moveMangaToCategories(dialog.manga, include)
|
||||||
@ -255,8 +241,6 @@ data class BrowseSourceScreen(
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(onBack = navigateUp)
|
|
||||||
|
|
||||||
LaunchedEffect(state.filters) {
|
LaunchedEffect(state.filters) {
|
||||||
screenModel.initFilterSheet(context)
|
screenModel.initFilterSheet(context)
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class GlobalSearchController(
|
|
||||||
val searchQuery: String = "",
|
|
||||||
val extensionFilter: String = "",
|
|
||||||
) : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(
|
|
||||||
screen = GlobalSearchScreen(
|
|
||||||
searchQuery = searchQuery,
|
|
||||||
extensionFilter = extensionFilter,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,12 +5,11 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.GlobalSearchScreen
|
import eu.kanade.presentation.browse.GlobalSearchScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
|
|
||||||
class GlobalSearchScreen(
|
class GlobalSearchScreen(
|
||||||
val searchQuery: String = "",
|
val searchQuery: String = "",
|
||||||
@ -19,7 +18,7 @@ class GlobalSearchScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel {
|
val screenModel = rememberScreenModel {
|
||||||
GlobalSearchScreenModel(
|
GlobalSearchScreenModel(
|
||||||
@ -31,7 +30,7 @@ class GlobalSearchScreen(
|
|||||||
|
|
||||||
GlobalSearchScreen(
|
GlobalSearchScreen(
|
||||||
state = state,
|
state = state,
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
onChangeSearchQuery = screenModel::updateSearchQuery,
|
||||||
onSearch = screenModel::search,
|
onSearch = screenModel::search,
|
||||||
getManga = { source, manga ->
|
getManga = { source, manga ->
|
||||||
@ -44,10 +43,10 @@ class GlobalSearchScreen(
|
|||||||
if (!screenModel.incognitoMode.get()) {
|
if (!screenModel.incognitoMode.get()) {
|
||||||
screenModel.lastUsedSourceId.set(it.id)
|
screenModel.lastUsedSourceId.set(it.id)
|
||||||
}
|
}
|
||||||
router.pushController(BrowseSourceController(it.id, state.searchQuery))
|
navigator.push(BrowseSourceScreen(it.id, state.searchQuery))
|
||||||
},
|
},
|
||||||
onClickItem = { router.pushController(MangaController(it.id, true)) },
|
onClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||||
onLongClickItem = { router.pushController(MangaController(it.id, true)) },
|
onLongClickItem = { navigator.push(MangaScreen(it.id, true)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class CategoryController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
CompositionLocalProvider(LocalRouter provides router) {
|
|
||||||
Navigator(screen = CategoryScreen())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,6 @@ import eu.kanade.presentation.category.components.CategoryCreateDialog
|
|||||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||||
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
import eu.kanade.presentation.category.components.CategoryRenameDialog
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ class CategoryScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { CategoryScreenModel() }
|
val screenModel = rememberScreenModel { CategoryScreenModel() }
|
||||||
|
|
||||||
@ -47,12 +45,7 @@ class CategoryScreen : Screen {
|
|||||||
onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) },
|
||||||
onClickMoveUp = screenModel::moveUp,
|
onClickMoveUp = screenModel::moveUp,
|
||||||
onClickMoveDown = screenModel::moveDown,
|
onClickMoveDown = screenModel::moveDown,
|
||||||
navigateUp = {
|
navigateUp = navigator::pop,
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.handleBack()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.download
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controller that shows the currently active downloads.
|
|
||||||
*/
|
|
||||||
class DownloadController : BasicFullComposeController() {
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = DownloadQueueScreen)
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,6 +47,7 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
@ -54,7 +55,6 @@ import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
|||||||
import eu.kanade.presentation.components.OverflowMenu
|
import eu.kanade.presentation.components.OverflowMenu
|
||||||
import eu.kanade.presentation.components.Pill
|
import eu.kanade.presentation.components.Pill
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.databinding.DownloadListBinding
|
import eu.kanade.tachiyomi.databinding.DownloadListBinding
|
||||||
@ -66,7 +66,7 @@ object DownloadQueueScreen : Screen {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val screenModel = rememberScreenModel { DownloadQueueScreenModel() }
|
val screenModel = rememberScreenModel { DownloadQueueScreenModel() }
|
||||||
val downloadList by screenModel.state.collectAsState()
|
val downloadList by screenModel.state.collectAsState()
|
||||||
@ -121,7 +121,7 @@ object DownloadQueueScreen : Screen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
actions = {
|
actions = {
|
||||||
if (downloadList.isNotEmpty()) {
|
if (downloadList.isNotEmpty()) {
|
||||||
OverflowMenu { closeMenu ->
|
OverflowMenu { closeMenu ->
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.history
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.history.interactor.GetNextChapters
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class HistoryController : BasicFullComposeController(), RootController {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = HistoryScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resumeLastChapterRead() {
|
|
||||||
val context = activity ?: return
|
|
||||||
viewScope.launchIO {
|
|
||||||
val chapter = Injekt.get<GetNextChapters>().await(onlyUnread = false).firstOrNull()
|
|
||||||
HistoryScreen.openChapter(context, chapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,6 +15,7 @@ import eu.kanade.domain.history.model.HistoryWithRelations
|
|||||||
import eu.kanade.presentation.history.HistoryUiModel
|
import eu.kanade.presentation.history.HistoryUiModel
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||||
|
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@ -76,6 +77,10 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getNextChapter(): Chapter? {
|
||||||
|
return withIOContext { getNextChapters.await(onlyUnread = false).firstOrNull() }
|
||||||
|
}
|
||||||
|
|
||||||
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
||||||
|
@ -1,34 +1,60 @@
|
|||||||
package eu.kanade.tachiyomi.ui.history
|
package eu.kanade.tachiyomi.ui.history
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.presentation.history.HistoryScreen
|
import eu.kanade.presentation.history.HistoryScreen
|
||||||
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
import eu.kanade.presentation.history.components.HistoryDeleteAllDialog
|
||||||
import eu.kanade.presentation.history.components.HistoryDeleteDialog
|
import eu.kanade.presentation.history.components.HistoryDeleteDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
|
|
||||||
object HistoryScreen : Screen {
|
object HistoryTab : Tab {
|
||||||
|
|
||||||
private val snackbarHostState = SnackbarHostState()
|
private val snackbarHostState = SnackbarHostState()
|
||||||
|
|
||||||
|
private val resumeLastChapterReadEvent = Channel<Unit>()
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 2u,
|
||||||
|
title = stringResource(R.string.label_recent_manga),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
resumeLastChapterReadEvent.send(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -39,7 +65,7 @@ object HistoryScreen : Screen {
|
|||||||
incognitoMode = screenModel.isIncognitoMode,
|
incognitoMode = screenModel.isIncognitoMode,
|
||||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||||
onSearchQueryChange = screenModel::updateSearchQuery,
|
onSearchQueryChange = screenModel::updateSearchQuery,
|
||||||
onClickCover = { router.pushController(MangaController(it)) },
|
onClickCover = { navigator.push(MangaScreen(it)) },
|
||||||
onClickResume = screenModel::getNextChapterForManga,
|
onClickResume = screenModel::getNextChapterForManga,
|
||||||
onDialogChange = screenModel::setDialog,
|
onDialogChange = screenModel::setDialog,
|
||||||
)
|
)
|
||||||
@ -84,6 +110,12 @@ object HistoryScreen : Screen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
resumeLastChapterReadEvent.consumeAsFlow().collectLatest {
|
||||||
|
openChapter(context, screenModel.getNextChapter())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun openChapter(context: Context, chapter: Chapter?) {
|
suspend fun openChapter(context: Context, chapter: Chapter?) {
|
288
app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt
Normal file
288
app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
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.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.domain.library.service.LibraryPreferences
|
||||||
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
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
|
||||||
|
TabNavigator(
|
||||||
|
tab = LibraryTab,
|
||||||
|
) { tabNavigator ->
|
||||||
|
// Provide usable navigator to content screen
|
||||||
|
CompositionLocalProvider(LocalNavigator provides navigator) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
if (isTabletUi()) {
|
||||||
|
NavigationRail {
|
||||||
|
tabs.fastForEach {
|
||||||
|
NavigationRailItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
if (!isTabletUi()) {
|
||||||
|
val bottomNavVisible by produceState(initialValue = true) {
|
||||||
|
showBottomNavEvent.receiveAsFlow().collectLatest { value = it }
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = bottomNavVisible,
|
||||||
|
enter = expandVertically(),
|
||||||
|
exit = shrinkVertically(),
|
||||||
|
) {
|
||||||
|
NavigationBar {
|
||||||
|
tabs.fastForEach {
|
||||||
|
NavigationBarItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
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 = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NavigationRailItem(tab: eu.kanade.presentation.util.Tab) {
|
||||||
|
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 = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NavigationIconItem(tab: eu.kanade.presentation.util.Tab) {
|
||||||
|
BadgedBox(
|
||||||
|
badge = {
|
||||||
|
when {
|
||||||
|
tab is UpdatesTab -> {
|
||||||
|
val count by produceState(initialValue = 0) {
|
||||||
|
val pref = Injekt.get<LibraryPreferences>()
|
||||||
|
combine(
|
||||||
|
pref.showUpdatesNavBadge().changes(),
|
||||||
|
pref.unreadUpdatesCount().changes(),
|
||||||
|
) { show, count -> if (show) count else 0 }
|
||||||
|
.collectLatest { value = it }
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
Badge {
|
||||||
|
val desc = pluralStringResource(
|
||||||
|
id = R.plurals.notification_chapters_generic,
|
||||||
|
count = count,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = count.toString(),
|
||||||
|
modifier = Modifier.semantics { contentDescription = desc },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BrowseTab::class.isInstance(tab) -> {
|
||||||
|
val count by produceState(initialValue = 0) {
|
||||||
|
Injekt.get<SourcePreferences>().extensionUpdatesCount().changes()
|
||||||
|
.collectLatest { value = it }
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
Badge {
|
||||||
|
val desc = pluralStringResource(
|
||||||
|
id = R.plurals.update_check_notification_ext_updates,
|
||||||
|
count = count,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = count.toString(),
|
||||||
|
modifier = Modifier.semantics { contentDescription = desc },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Icon(painter = tab.options.icon!!, contentDescription = tab.options.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun search(query: String) {
|
||||||
|
librarySearchEvent.send(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun openTab(tab: Tab) {
|
||||||
|
openTabEvent.send(tab)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun showBottomNav(show: Boolean) {
|
||||||
|
showBottomNavEvent.send(show)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Tab {
|
||||||
|
data class Library(val mangaIdToOpen: Long? = null) : Tab()
|
||||||
|
object Updates : Tab()
|
||||||
|
object History : Tab()
|
||||||
|
data class Browse(val toExtensions: Boolean = false) : Tab()
|
||||||
|
data class More(val toDownloads: Boolean) : Tab()
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class LibraryController(
|
|
||||||
bundle: Bundle? = null,
|
|
||||||
) : BasicFullComposeController(bundle), RootController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sheet containing filter/sort/display items.
|
|
||||||
*/
|
|
||||||
private var settingsSheet: LibrarySettingsSheet? = null
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = LibraryScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
settingsSheet = LibrarySettingsSheet(router)
|
|
||||||
viewScope.launch {
|
|
||||||
LibraryScreen.openSettingsSheetEvent
|
|
||||||
.collectLatest(::showSettingsSheet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
settingsSheet?.sheetScope?.cancel()
|
|
||||||
settingsSheet = null
|
|
||||||
super.onDestroyView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showSettingsSheet(category: Category? = null) {
|
|
||||||
if (category != null) {
|
|
||||||
settingsSheet?.show(category)
|
|
||||||
} else {
|
|
||||||
viewScope.launch { LibraryScreen.requestOpenSettingsSheet() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun search(query: String) = LibraryScreen.search(query)
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
import eu.kanade.domain.category.interactor.SetDisplayModeForCategory
|
||||||
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
import eu.kanade.domain.category.interactor.SetSortModeForCategory
|
||||||
@ -28,11 +28,11 @@ import uy.kohesive.injekt.api.get
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class LibrarySettingsSheet(
|
class LibrarySettingsSheet(
|
||||||
router: Router,
|
activity: Activity,
|
||||||
private val trackManager: TrackManager = Injekt.get(),
|
private val trackManager: TrackManager = Injekt.get(),
|
||||||
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
private val setDisplayModeForCategory: SetDisplayModeForCategory = Injekt.get(),
|
||||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||||
) : TabbedBottomSheetDialog(router.activity!!) {
|
) : TabbedBottomSheetDialog(activity) {
|
||||||
|
|
||||||
val filters: Filter
|
val filters: Filter
|
||||||
private val sort: Sort
|
private val sort: Sort
|
||||||
@ -41,9 +41,9 @@ class LibrarySettingsSheet(
|
|||||||
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
val sheetScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
filters = Filter(router.activity!!)
|
filters = Filter(activity)
|
||||||
sort = Sort(router.activity!!)
|
sort = Sort(activity)
|
||||||
display = Display(router.activity!!)
|
display = Display(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.HelpOutline
|
import androidx.compose.material.icons.outlined.HelpOutline
|
||||||
import androidx.compose.material3.ScaffoldDefaults
|
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -21,9 +23,11 @@ import androidx.compose.ui.platform.LocalUriHandler
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.domain.category.model.Category
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.library.model.LibraryManga
|
import eu.kanade.domain.library.model.LibraryManga
|
||||||
import eu.kanade.domain.library.model.display
|
import eu.kanade.domain.library.model.display
|
||||||
@ -39,27 +43,42 @@ import eu.kanade.presentation.components.Scaffold
|
|||||||
import eu.kanade.presentation.library.components.LibraryContent
|
import eu.kanade.presentation.library.components.LibraryContent
|
||||||
import eu.kanade.presentation.library.components.LibraryToolbar
|
import eu.kanade.presentation.library.components.LibraryToolbar
|
||||||
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
object LibraryScreen : Screen {
|
object LibraryTab : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 0u,
|
||||||
|
title = stringResource(R.string.label_library),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
requestOpenSettingsSheet()
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
@ -104,7 +123,7 @@ object LibraryScreen : Screen {
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
||||||
if (randomItem != null) {
|
if (randomItem != null) {
|
||||||
router.openManga(randomItem.libraryManga.manga.id)
|
navigator.push(MangaScreen(randomItem.libraryManga.manga.id))
|
||||||
} else {
|
} else {
|
||||||
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
|
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
|
||||||
}
|
}
|
||||||
@ -127,14 +146,10 @@ object LibraryScreen : Screen {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
contentWindowInsets = TachiyomiBottomNavigationView.withBottomNavInset(ScaffoldDefaults.contentWindowInsets),
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
if (state.isLoading) {
|
when {
|
||||||
LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||||
return@Scaffold
|
state.searchQuery.isNullOrEmpty() && state.libraryCount == 0 -> {
|
||||||
}
|
|
||||||
|
|
||||||
if (state.searchQuery.isNullOrEmpty() && state.libraryCount == 0) {
|
|
||||||
val handler = LocalUriHandler.current
|
val handler = LocalUriHandler.current
|
||||||
EmptyScreen(
|
EmptyScreen(
|
||||||
textResource = R.string.information_empty_library,
|
textResource = R.string.information_empty_library,
|
||||||
@ -147,9 +162,8 @@ object LibraryScreen : Screen {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return@Scaffold
|
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
LibraryContent(
|
LibraryContent(
|
||||||
categories = state.categories,
|
categories = state.categories,
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
@ -159,7 +173,7 @@ object LibraryScreen : Screen {
|
|||||||
isLibraryEmpty = state.libraryCount == 0,
|
isLibraryEmpty = state.libraryCount == 0,
|
||||||
showPageTabs = state.showCategoryTabs,
|
showPageTabs = state.showCategoryTabs,
|
||||||
onChangeCurrentPage = { screenModel.activeCategory = it },
|
onChangeCurrentPage = { screenModel.activeCategory = it },
|
||||||
onMangaClicked = { router.openManga(it) },
|
onMangaClicked = { navigator.push(MangaScreen(it)) },
|
||||||
onContinueReadingClicked = { it: LibraryManga ->
|
onContinueReadingClicked = { it: LibraryManga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val chapter = screenModel.getNextUnreadChapter(it.manga)
|
val chapter = screenModel.getNextUnreadChapter(it.manga)
|
||||||
@ -178,7 +192,7 @@ object LibraryScreen : Screen {
|
|||||||
},
|
},
|
||||||
onRefresh = onClickRefresh,
|
onRefresh = onClickRefresh,
|
||||||
onGlobalSearchClicked = {
|
onGlobalSearchClicked = {
|
||||||
router.pushController(GlobalSearchController(screenModel.state.value.searchQuery ?: ""))
|
navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: ""))
|
||||||
},
|
},
|
||||||
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
|
getNumberOfMangaForCategory = { state.getMangaCountForCategory(it) },
|
||||||
getDisplayModeForPage = { state.categories[it].display },
|
getDisplayModeForPage = { state.categories[it].display },
|
||||||
@ -188,6 +202,8 @@ object LibraryScreen : Screen {
|
|||||||
isIncognitoMode = screenModel.isIncognitoMode,
|
isIncognitoMode = screenModel.isIncognitoMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val onDismissRequest = screenModel::closeDialog
|
val onDismissRequest = screenModel::closeDialog
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
@ -197,7 +213,7 @@ object LibraryScreen : Screen {
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = {
|
onEditCategories = {
|
||||||
screenModel.clearSelection()
|
screenModel.clearSelection()
|
||||||
router.pushController(CategoryController())
|
navigator.push(CategoryScreen())
|
||||||
},
|
},
|
||||||
onConfirm = { include, exclude ->
|
onConfirm = { include, exclude ->
|
||||||
screenModel.clearSelection()
|
screenModel.clearSelection()
|
||||||
@ -236,11 +252,9 @@ object LibraryScreen : Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.selectionMode) {
|
LaunchedEffect(state.selectionMode) {
|
||||||
// Could perhaps be removed when navigation is in a Compose world
|
HomeScreen.showBottomNav(!state.selectionMode)
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.isLoading) {
|
LaunchedEffect(state.isLoading) {
|
||||||
if (!state.isLoading) {
|
if (!state.isLoading) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
||||||
@ -248,23 +262,19 @@ object LibraryScreen : Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch { queryEvent.collectLatest(screenModel::search) }
|
launch { queryEvent.receiveAsFlow().collect(screenModel::search) }
|
||||||
launch { requestSettingsSheetEvent.collectLatest { onClickFilter() } }
|
launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { onClickFilter() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Router.openManga(mangaId: Long) {
|
|
||||||
pushController(MangaController(mangaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
// For invoking search from other screen
|
// For invoking search from other screen
|
||||||
private val queryEvent = MutableSharedFlow<String>(replay = 1)
|
private val queryEvent = Channel<String>()
|
||||||
fun search(query: String) = queryEvent.tryEmit(query)
|
suspend fun search(query: String) = queryEvent.send(query)
|
||||||
|
|
||||||
// For opening settings sheet in LibraryController
|
// For opening settings sheet in LibraryController
|
||||||
private val requestSettingsSheetEvent = MutableSharedFlow<Unit>()
|
private val requestSettingsSheetEvent = Channel<Unit>()
|
||||||
private val openSettingsSheetEvent_ = MutableSharedFlow<Category>()
|
private val openSettingsSheetEvent_ = Channel<Category>()
|
||||||
val openSettingsSheetEvent = openSettingsSheetEvent_.asSharedFlow()
|
val openSettingsSheetEvent = openSettingsSheetEvent_.receiveAsFlow()
|
||||||
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.emit(category)
|
private suspend fun sendSettingsSheetIntent(category: Category) = openSettingsSheetEvent_.send(category)
|
||||||
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.emit(Unit)
|
suspend fun requestOpenSettingsSheet() = requestSettingsSheetEvent.send(Unit)
|
||||||
}
|
}
|
@ -6,33 +6,44 @@ import android.content.Intent
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.ViewGroup
|
import android.view.View
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.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.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.graphics.ColorUtils
|
|
||||||
import androidx.core.splashscreen.SplashScreen
|
import androidx.core.splashscreen.SplashScreen
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bluelinelabs.conductor.Conductor
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import com.bluelinelabs.conductor.Controller
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
|
||||||
import com.bluelinelabs.conductor.Router
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
|
||||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
|
import eu.kanade.domain.category.model.Category
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
import eu.kanade.domain.library.service.LibraryPreferences
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
|
import eu.kanade.presentation.util.Transition
|
||||||
|
import eu.kanade.presentation.util.collectAsState
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.Migrations
|
import eu.kanade.tachiyomi.Migrations
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -40,39 +51,29 @@ import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||||
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
||||||
import eu.kanade.tachiyomi.databinding.MainActivityBinding
|
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.ComposeContentController
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
import eu.kanade.tachiyomi.ui.library.LibraryTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.BrowseController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.more.MoreController
|
|
||||||
import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
|
||||||
import eu.kanade.tachiyomi.util.preference.asHotFlow
|
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.system.getThemeColor
|
|
||||||
import eu.kanade.tachiyomi.util.system.isTabletUi
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.merge
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -86,24 +87,20 @@ class MainActivity : BaseActivity() {
|
|||||||
private val uiPreferences: UiPreferences by injectLazy()
|
private val uiPreferences: UiPreferences by injectLazy()
|
||||||
private val preferences: BasePreferences by injectLazy()
|
private val preferences: BasePreferences by injectLazy()
|
||||||
|
|
||||||
lateinit var binding: MainActivityBinding
|
|
||||||
|
|
||||||
private lateinit var router: Router
|
|
||||||
|
|
||||||
private val startScreenId = R.id.nav_library
|
|
||||||
private var isConfirmingExit: Boolean = false
|
|
||||||
private var isHandlingShortcut: Boolean = false
|
private var isHandlingShortcut: Boolean = false
|
||||||
|
|
||||||
/**
|
|
||||||
* App bar lift state for backstack
|
|
||||||
*/
|
|
||||||
private val backstackLiftState = mutableMapOf<String, Boolean>()
|
|
||||||
|
|
||||||
private val chapterCache: ChapterCache by injectLazy()
|
private val chapterCache: ChapterCache by injectLazy()
|
||||||
|
|
||||||
// To be checked by splash screen. If true then splash screen will be removed.
|
// To be checked by splash screen. If true then splash screen will be removed.
|
||||||
var ready = false
|
var ready = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sheet containing filter/sort/display items.
|
||||||
|
*/
|
||||||
|
private var settingsSheet: LibrarySettingsSheet? = null
|
||||||
|
|
||||||
|
private lateinit var navigator: Navigator
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// Prevent splash screen showing up on configuration changes
|
// Prevent splash screen showing up on configuration changes
|
||||||
val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
|
val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
|
||||||
@ -132,22 +129,72 @@ class MainActivity : BaseActivity() {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
binding = MainActivityBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
||||||
if (!isTaskRoot) {
|
if (!isTaskRoot) {
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(binding.root)
|
|
||||||
setSupportActionBar(binding.toolbar)
|
|
||||||
|
|
||||||
// Draw edge-to-edge
|
// Draw edge-to-edge
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
binding.bottomNav?.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
settingsSheet = LibrarySettingsSheet(this)
|
||||||
padding()
|
LibraryTab.openSettingsSheetEvent
|
||||||
|
.onEach(::showSettingsSheet)
|
||||||
|
.launchIn(lifecycleScope)
|
||||||
|
|
||||||
|
setComposeContent {
|
||||||
|
Navigator(
|
||||||
|
screen = HomeScreen,
|
||||||
|
disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = true),
|
||||||
|
) { navigator ->
|
||||||
|
if (navigator.size == 1) {
|
||||||
|
ConfirmExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shows current screen
|
||||||
|
ScreenTransition(navigator = navigator, transition = { Transition.OneWayFade })
|
||||||
|
|
||||||
|
// Pop source-related screens when incognito mode is turned off
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
preferences.incognitoMode().changes()
|
||||||
|
.drop(1)
|
||||||
|
.onEach {
|
||||||
|
if (!it) {
|
||||||
|
val currentScreen = navigator.lastItem
|
||||||
|
if (currentScreen is BrowseSourceScreen ||
|
||||||
|
(currentScreen is MangaScreen && currentScreen.fromSource)
|
||||||
|
) {
|
||||||
|
navigator.popUntilRoot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(navigator) {
|
||||||
|
this@MainActivity.navigator = navigator
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckForUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
||||||
|
if (showChangelog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showChangelog = false },
|
||||||
|
title = { Text(text = stringResource(R.string.updated_version, BuildConfig.VERSION_NAME)) },
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { openInBrowser(RELEASE_URL) }) {
|
||||||
|
Text(text = stringResource(R.string.whats_new))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { showChangelog = false }) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,128 +205,62 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
setSplashScreenExitAnimation(splashScreen)
|
setSplashScreenExitAnimation(splashScreen)
|
||||||
|
|
||||||
nav.setOnItemSelectedListener { item ->
|
|
||||||
val id = item.itemId
|
|
||||||
|
|
||||||
val currentRoot = router.backstack.firstOrNull()
|
|
||||||
if (currentRoot?.tag()?.toIntOrNull() != id) {
|
|
||||||
when (id) {
|
|
||||||
R.id.nav_library -> router.setRoot(LibraryController(), id)
|
|
||||||
R.id.nav_updates -> router.setRoot(UpdatesController(), id)
|
|
||||||
R.id.nav_history -> router.setRoot(HistoryController(), id)
|
|
||||||
R.id.nav_browse -> router.setRoot(BrowseController(toExtensions = false), id)
|
|
||||||
R.id.nav_more -> router.setRoot(MoreController(), id)
|
|
||||||
}
|
|
||||||
} else if (!isHandlingShortcut) {
|
|
||||||
when (id) {
|
|
||||||
R.id.nav_library -> {
|
|
||||||
val controller = router.getControllerWithTag(id.toString()) as? LibraryController
|
|
||||||
controller?.showSettingsSheet()
|
|
||||||
}
|
|
||||||
R.id.nav_updates -> {
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
router.pushController(DownloadController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.nav_history -> {
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
try {
|
|
||||||
val historyController = router.backstack[0].controller as HistoryController
|
|
||||||
historyController.resumeLastChapterRead()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
toast(R.string.cant_open_last_read_chapter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.nav_more -> {
|
|
||||||
if (router.backstackSize == 1) {
|
|
||||||
router.pushController(SettingsMainController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val container: ViewGroup = binding.controllerContainer
|
|
||||||
router = Conductor.attachRouter(this, container, savedInstanceState)
|
|
||||||
.setPopRootControllerMode(Router.PopRootControllerMode.NEVER)
|
|
||||||
router.addChangeListener(
|
|
||||||
object : ControllerChangeHandler.ControllerChangeListener {
|
|
||||||
override fun onChangeStarted(
|
|
||||||
to: Controller?,
|
|
||||||
from: Controller?,
|
|
||||||
isPush: Boolean,
|
|
||||||
container: ViewGroup,
|
|
||||||
handler: ControllerChangeHandler,
|
|
||||||
) {
|
|
||||||
syncActivityViewWithController(to, from, isPush)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeCompleted(
|
|
||||||
to: Controller?,
|
|
||||||
from: Controller?,
|
|
||||||
isPush: Boolean,
|
|
||||||
container: ViewGroup,
|
|
||||||
handler: ControllerChangeHandler,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (!router.hasRootController()) {
|
|
||||||
// Set start screen
|
|
||||||
if (!handleIntentAction(intent)) {
|
|
||||||
moveToStartScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
syncActivityViewWithController()
|
|
||||||
|
|
||||||
binding.toolbar.setNavigationOnClickListener {
|
|
||||||
onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
|
// Set start screen
|
||||||
|
lifecycleScope.launch { handleIntentAction(intent) }
|
||||||
|
|
||||||
// Reset Incognito Mode on relaunch
|
// Reset Incognito Mode on relaunch
|
||||||
preferences.incognitoMode().set(false)
|
preferences.incognitoMode().set(false)
|
||||||
|
|
||||||
// Show changelog prompt on update
|
|
||||||
if (didMigration && !BuildConfig.DEBUG) {
|
|
||||||
WhatsNewDialogController().showDialog(router)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSettingsSheet(category: Category? = null) {
|
||||||
|
if (category != null) {
|
||||||
|
settingsSheet?.show(category)
|
||||||
} else {
|
} else {
|
||||||
// Restore selected nav item
|
lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() }
|
||||||
router.backstack.firstOrNull()?.tag()?.toIntOrNull()?.let {
|
|
||||||
nav.menu.findItem(it).isChecked = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
merge(libraryPreferences.showUpdatesNavBadge().changes(), libraryPreferences.unreadUpdatesCount().changes())
|
@Composable
|
||||||
.onEach { setUnreadUpdatesBadge() }
|
private fun ConfirmExit() {
|
||||||
.launchIn(lifecycleScope)
|
val scope = rememberCoroutineScope()
|
||||||
|
val confirmExit by preferences.confirmExit().collectAsState()
|
||||||
sourcePreferences.extensionUpdatesCount()
|
var waitingConfirmation by remember { mutableStateOf(false) }
|
||||||
.asHotFlow { setExtensionsBadge() }
|
BackHandler(enabled = !waitingConfirmation && confirmExit) {
|
||||||
.launchIn(lifecycleScope)
|
scope.launch {
|
||||||
|
waitingConfirmation = true
|
||||||
preferences.downloadedOnly()
|
val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
|
||||||
.asHotFlow { binding.downloadedOnly.isVisible = it }
|
delay(2.seconds)
|
||||||
.launchIn(lifecycleScope)
|
toast.cancel()
|
||||||
|
waitingConfirmation = false
|
||||||
binding.incognitoMode.isVisible = preferences.incognitoMode().get()
|
}
|
||||||
preferences.incognitoMode().changes()
|
}
|
||||||
.drop(1)
|
}
|
||||||
.onEach {
|
|
||||||
binding.incognitoMode.isVisible = it
|
@Composable
|
||||||
|
private fun CheckForUpdate() {
|
||||||
// Close BrowseSourceController and its MangaController child when incognito mode is disabled
|
val context = LocalContext.current
|
||||||
if (!it) {
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val fg = router.backstack.lastOrNull()?.controller
|
LaunchedEffect(Unit) {
|
||||||
if (fg is BrowseSourceController || fg is MangaController && fg.fromSource) {
|
// App updates
|
||||||
router.popToRoot()
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -289,16 +270,16 @@ class MainActivity : BaseActivity() {
|
|||||||
* after the animation is finished.
|
* after the animation is finished.
|
||||||
*/
|
*/
|
||||||
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
|
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
|
||||||
|
val root = findViewById<View>(android.R.id.content)
|
||||||
val setNavbarScrim = {
|
val setNavbarScrim = {
|
||||||
// Make sure navigation bar is on bottom before we modify it
|
// Make sure navigation bar is on bottom before we modify it
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(root) { _, insets ->
|
||||||
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
||||||
val elevation = binding.bottomNav?.elevation ?: 0F
|
window.setNavigationBarTransparentCompat(this@MainActivity, 3.dpToPx.toFloat())
|
||||||
window.setNavigationBarTransparentCompat(this@MainActivity, elevation)
|
|
||||||
}
|
}
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
ViewCompat.requestApplyInsets(binding.root)
|
ViewCompat.requestApplyInsets(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
|
||||||
@ -316,7 +297,7 @@ class MainActivity : BaseActivity() {
|
|||||||
duration = SPLASH_EXIT_ANIM_DURATION
|
duration = SPLASH_EXIT_ANIM_DURATION
|
||||||
addUpdateListener { va ->
|
addUpdateListener { va ->
|
||||||
val value = va.animatedValue as Float
|
val value = va.animatedValue as Float
|
||||||
binding.root.translationY = value * 16.dpToPx
|
root.translationY = value * 16.dpToPx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,69 +325,13 @@ class MainActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
if (!handleIntentAction(intent)) {
|
val handle = runBlocking { handleIntentAction(intent) }
|
||||||
|
if (!handle) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
private suspend fun handleIntentAction(intent: Intent): Boolean {
|
||||||
super.onResume()
|
|
||||||
checkForUpdates()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkForUpdates() {
|
|
||||||
lifecycleScope.launchIO {
|
|
||||||
// App updates
|
|
||||||
if (BuildConfig.INCLUDE_UPDATER) {
|
|
||||||
try {
|
|
||||||
val result = AppUpdateChecker().checkForUpdate(this@MainActivity)
|
|
||||||
if (result is AppUpdateResult.NewUpdate) {
|
|
||||||
NewUpdateDialogController(result).showDialog(router)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extension updates
|
|
||||||
try {
|
|
||||||
ExtensionGithubApi().checkForUpdates(
|
|
||||||
this@MainActivity,
|
|
||||||
fromAvailableExtensionList = true,
|
|
||||||
)?.let { pendingUpdates ->
|
|
||||||
sourcePreferences.extensionUpdatesCount().set(pendingUpdates.size)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUnreadUpdatesBadge() {
|
|
||||||
val updates = if (libraryPreferences.showUpdatesNavBadge().get()) libraryPreferences.unreadUpdatesCount().get() else 0
|
|
||||||
if (updates > 0) {
|
|
||||||
nav.getOrCreateBadge(R.id.nav_updates).apply {
|
|
||||||
number = updates
|
|
||||||
setContentDescriptionQuantityStringsResource(R.plurals.notification_chapters_generic)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nav.removeBadge(R.id.nav_updates)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setExtensionsBadge() {
|
|
||||||
val updates = sourcePreferences.extensionUpdatesCount().get()
|
|
||||||
if (updates > 0) {
|
|
||||||
nav.getOrCreateBadge(R.id.nav_browse).apply {
|
|
||||||
number = updates
|
|
||||||
setContentDescriptionQuantityStringsResource(R.plurals.update_check_notification_ext_updates)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nav.removeBadge(R.id.nav_browse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIntentAction(intent: Intent): Boolean {
|
|
||||||
val notificationId = intent.getIntExtra("notificationId", -1)
|
val notificationId = intent.getIntExtra("notificationId", -1)
|
||||||
if (notificationId > -1) {
|
if (notificationId > -1) {
|
||||||
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
||||||
@ -415,32 +340,19 @@ class MainActivity : BaseActivity() {
|
|||||||
isHandlingShortcut = true
|
isHandlingShortcut = true
|
||||||
|
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
SHORTCUT_LIBRARY -> setSelectedNavItem(R.id.nav_library)
|
SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library())
|
||||||
SHORTCUT_RECENTLY_UPDATED -> setSelectedNavItem(R.id.nav_updates)
|
SHORTCUT_RECENTLY_UPDATED -> HomeScreen.openTab(HomeScreen.Tab.Updates)
|
||||||
SHORTCUT_RECENTLY_READ -> setSelectedNavItem(R.id.nav_history)
|
SHORTCUT_RECENTLY_READ -> HomeScreen.openTab(HomeScreen.Tab.History)
|
||||||
SHORTCUT_CATALOGUES -> setSelectedNavItem(R.id.nav_browse)
|
SHORTCUT_CATALOGUES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false))
|
||||||
SHORTCUT_EXTENSIONS -> {
|
SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true))
|
||||||
if (router.backstackSize > 1) {
|
|
||||||
router.popToRoot()
|
|
||||||
}
|
|
||||||
setSelectedNavItem(R.id.nav_browse)
|
|
||||||
router.pushController(BrowseController(toExtensions = true))
|
|
||||||
}
|
|
||||||
SHORTCUT_MANGA -> {
|
SHORTCUT_MANGA -> {
|
||||||
val extras = intent.extras ?: return false
|
val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
|
||||||
val fgController = router.backstack.lastOrNull()?.controller as? MangaController
|
navigator.popUntilRoot()
|
||||||
if (fgController?.mangaId != extras.getLong(MangaController.MANGA_EXTRA)) {
|
HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen))
|
||||||
router.popToRoot()
|
|
||||||
setSelectedNavItem(R.id.nav_library)
|
|
||||||
router.pushController(RouterTransaction.with(MangaController(extras)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SHORTCUT_DOWNLOADS -> {
|
SHORTCUT_DOWNLOADS -> {
|
||||||
if (router.backstackSize > 1) {
|
navigator.popUntilRoot()
|
||||||
router.popToRoot()
|
HomeScreen.openTab(HomeScreen.Tab.More(toDownloads = true))
|
||||||
}
|
|
||||||
setSelectedNavItem(R.id.nav_more)
|
|
||||||
router.pushController(DownloadController())
|
|
||||||
}
|
}
|
||||||
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
||||||
// If the intent match the "standard" Android search intent
|
// If the intent match the "standard" Android search intent
|
||||||
@ -449,20 +361,16 @@ class MainActivity : BaseActivity() {
|
|||||||
// Get the search query provided in extras, and if not null, perform a global search with it.
|
// Get the search query provided in extras, and if not null, perform a global search with it.
|
||||||
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
if (query != null && query.isNotEmpty()) {
|
if (query != null && query.isNotEmpty()) {
|
||||||
if (router.backstackSize > 1) {
|
navigator.popUntilRoot()
|
||||||
router.popToRoot()
|
navigator.push(GlobalSearchScreen(query))
|
||||||
}
|
|
||||||
router.pushController(GlobalSearchController(query))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
INTENT_SEARCH -> {
|
INTENT_SEARCH -> {
|
||||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||||
if (query != null && query.isNotEmpty()) {
|
if (query != null && query.isNotEmpty()) {
|
||||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
||||||
if (router.backstackSize > 1) {
|
navigator.popUntilRoot()
|
||||||
router.popToRoot()
|
navigator.push(GlobalSearchScreen(query, filter))
|
||||||
}
|
|
||||||
router.pushController(GlobalSearchController(query, filter ?: ""))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@ -476,166 +384,21 @@ class MainActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
settingsSheet?.sheetScope?.cancel()
|
||||||
|
settingsSheet = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
// Binding sometimes isn't actually instantiated yet somehow
|
|
||||||
nav?.setOnItemSelectedListener(null)
|
|
||||||
binding?.toolbar?.setNavigationOnClickListener(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (router.handleBack()) {
|
if (navigator.size == 1 &&
|
||||||
// A Router is consuming back press
|
!onBackPressedDispatcher.hasEnabledCallbacks() &&
|
||||||
return
|
libraryPreferences.autoClearChapterCache().get()
|
||||||
}
|
) {
|
||||||
val backstackSize = router.backstackSize
|
|
||||||
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()
|
chapterCache.clear()
|
||||||
}
|
}
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun moveToStartScreen() {
|
|
||||||
setSelectedNavItem(startScreenId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportActionModeStarted(mode: ActionMode) {
|
|
||||||
binding.appbar.apply {
|
|
||||||
tag = isTransparentWhenNotLifted
|
|
||||||
isTransparentWhenNotLifted = false
|
|
||||||
}
|
|
||||||
// Color taken from m3_appbar_background
|
|
||||||
window.statusBarColor = ColorUtils.compositeColors(
|
|
||||||
getColor(R.color.m3_appbar_overlay_color),
|
|
||||||
getThemeColor(R.attr.colorSurface),
|
|
||||||
)
|
|
||||||
super.onSupportActionModeStarted(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSupportActionModeFinished(mode: ActionMode) {
|
|
||||||
binding.appbar.apply {
|
|
||||||
isTransparentWhenNotLifted = (tag as? Boolean) ?: false
|
|
||||||
tag = null
|
|
||||||
}
|
|
||||||
window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
|
|
||||||
super.onSupportActionModeFinished(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun resetExitConfirmation() {
|
|
||||||
isConfirmingExit = true
|
|
||||||
val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
|
|
||||||
delay(2.seconds)
|
|
||||||
toast.cancel()
|
|
||||||
isConfirmingExit = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldHandleExitConfirmation(): Boolean {
|
|
||||||
return router.backstackSize == 1 &&
|
|
||||||
router.getControllerWithTag("$startScreenId") != null &&
|
|
||||||
preferences.confirmExit().get() &&
|
|
||||||
!isConfirmingExit
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSelectedNavItem(itemId: Int) {
|
|
||||||
if (!isFinishing) {
|
|
||||||
nav.selectedItemId = itemId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun syncActivityViewWithController(
|
|
||||||
to: Controller? = null,
|
|
||||||
from: Controller? = null,
|
|
||||||
isPush: Boolean = true,
|
|
||||||
) {
|
|
||||||
var internalTo = to
|
|
||||||
|
|
||||||
if (internalTo == null) {
|
|
||||||
// Should go here when the activity is recreated and dialog controller is on top of the backstack
|
|
||||||
// Then we'll assume the top controller is the parent controller of this dialog
|
|
||||||
val backstack = router.backstack
|
|
||||||
internalTo = backstack.lastOrNull()?.controller
|
|
||||||
if (internalTo is DialogController) {
|
|
||||||
internalTo = backstack.getOrNull(backstack.size - 2)?.controller ?: return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Ignore changes for normal transactions
|
|
||||||
if (from is DialogController || internalTo is DialogController) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(router.backstackSize != 1)
|
|
||||||
|
|
||||||
// Always show appbar again when changing controllers
|
|
||||||
binding.appbar.setExpanded(true)
|
|
||||||
|
|
||||||
if ((from == null || from is RootController) && internalTo !is RootController) {
|
|
||||||
showNav(false)
|
|
||||||
}
|
|
||||||
if (internalTo is RootController) {
|
|
||||||
// Always show bottom nav again when returning to a RootController
|
|
||||||
showNav(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val isComposeController = internalTo is ComposeContentController
|
|
||||||
binding.appbar.isVisible = !isComposeController
|
|
||||||
binding.controllerContainer.enableScrollingBehavior(!isComposeController)
|
|
||||||
|
|
||||||
if (!isTabletUi()) {
|
|
||||||
// Save lift state
|
|
||||||
if (isPush) {
|
|
||||||
if (router.backstackSize > 1) {
|
|
||||||
// Save lift state
|
|
||||||
from?.let {
|
|
||||||
backstackLiftState[it.instanceId] = binding.appbar.isLifted
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
backstackLiftState.clear()
|
|
||||||
}
|
|
||||||
binding.appbar.isLifted = false
|
|
||||||
} else {
|
|
||||||
internalTo?.let {
|
|
||||||
binding.appbar.isLifted = backstackLiftState.getOrElse(it.instanceId) { false }
|
|
||||||
}
|
|
||||||
from?.let {
|
|
||||||
backstackLiftState.remove(it.instanceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showNav(visible: Boolean) {
|
|
||||||
showBottomNav(visible)
|
|
||||||
showSideNav(visible)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also used from some controllers to swap bottom nav with action toolbar
|
|
||||||
fun showBottomNav(visible: Boolean) {
|
|
||||||
if (visible) {
|
|
||||||
binding.bottomNav?.slideUp()
|
|
||||||
} else {
|
|
||||||
binding.bottomNav?.slideDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showSideNav(visible: Boolean) {
|
|
||||||
binding.sideNav?.isVisible = visible
|
|
||||||
}
|
|
||||||
|
|
||||||
private val nav: NavigationBarView
|
|
||||||
get() = binding.bottomNav ?: binding.sideNav!!
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
registerSecureActivity(this)
|
registerSecureActivity(this)
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.main
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
|
||||||
|
|
||||||
class WhatsNewDialogController(bundle: Bundle? = null) : DialogController(bundle) {
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(activity!!.getString(R.string.updated_version, BuildConfig.VERSION_NAME))
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.setNeutralButton(R.string.whats_new) { _, _ ->
|
|
||||||
openInBrowser(RELEASE_URL)
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +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
|
|
||||||
|
|
||||||
class MangaController : BasicFullComposeController {
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
constructor(bundle: Bundle) : this(bundle.getLong(MANGA_EXTRA))
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
mangaId: Long,
|
|
||||||
fromSource: Boolean = false,
|
|
||||||
) : super(bundleOf(MANGA_EXTRA to mangaId, FROM_SOURCE_EXTRA to fromSource))
|
|
||||||
|
|
||||||
val mangaId: Long
|
|
||||||
get() = args.getLong(MANGA_EXTRA)
|
|
||||||
|
|
||||||
val fromSource: Boolean
|
|
||||||
get() = args.getBoolean(FROM_SOURCE_EXTRA)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = MangaScreen(mangaId, fromSource))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val FROM_SOURCE_EXTRA = "from_source"
|
|
||||||
const val MANGA_EXTRA = "manga"
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,6 +16,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
@ -28,7 +29,6 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||||
import com.bluelinelabs.conductor.Router
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.domain.manga.model.Manga
|
import eu.kanade.domain.manga.model.Manga
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
@ -43,31 +43,27 @@ import eu.kanade.presentation.manga.components.DeleteChaptersDialog
|
|||||||
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog
|
||||||
import eu.kanade.presentation.manga.components.MangaCoverDialog
|
import eu.kanade.presentation.manga.components.MangaCoverDialog
|
||||||
import eu.kanade.presentation.util.LocalNavigatorContentPadding
|
import eu.kanade.presentation.util.LocalNavigatorContentPadding
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.isLocalOrStub
|
import eu.kanade.tachiyomi.source.isLocalOrStub
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||||
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.globalsearch.GlobalSearchController
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryController
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.library.LibraryController
|
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
|
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MangaScreen(
|
class MangaScreen(
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val fromSource: Boolean = false,
|
val fromSource: Boolean = false,
|
||||||
) : Screen {
|
) : Screen {
|
||||||
|
|
||||||
override val key = uniqueScreenKey
|
override val key = uniqueScreenKey
|
||||||
@ -75,9 +71,9 @@ class MangaScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
val screenModel = rememberScreenModel { MangaInfoScreenModel(context, mangaId, fromSource) }
|
val screenModel = rememberScreenModel { MangaInfoScreenModel(context, mangaId, fromSource) }
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -94,7 +90,7 @@ class MangaScreen(
|
|||||||
state = successState,
|
state = successState,
|
||||||
snackbarHostState = screenModel.snackbarHostState,
|
snackbarHostState = screenModel.snackbarHostState,
|
||||||
isTabletUi = isTabletUi(),
|
isTabletUi = isTabletUi(),
|
||||||
onBackClicked = router::popCurrentController,
|
onBackClicked = navigator::pop,
|
||||||
onChapterClicked = { openChapter(context, it) },
|
onChapterClicked = { openChapter(context, it) },
|
||||||
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
||||||
onAddToLibraryClicked = {
|
onAddToLibraryClicked = {
|
||||||
@ -104,11 +100,11 @@ class MangaScreen(
|
|||||||
onWebViewClicked = { openMangaInWebView(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
onWebViewClicked = { openMangaInWebView(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||||
onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||||
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable },
|
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable },
|
||||||
onTagClicked = { performGenreSearch(router, it, screenModel.source!!) },
|
onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
|
||||||
onFilterButtonClicked = screenModel::showSettingsDialog,
|
onFilterButtonClicked = screenModel::showSettingsDialog,
|
||||||
onRefresh = screenModel::fetchAllFromSource,
|
onRefresh = screenModel::fetchAllFromSource,
|
||||||
onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) },
|
onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) },
|
||||||
onSearch = { query, global -> performSearch(router, query, global) },
|
onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } },
|
||||||
onCoverClicked = screenModel::showCoverDialog,
|
onCoverClicked = screenModel::showCoverDialog,
|
||||||
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
||||||
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
||||||
@ -268,33 +264,24 @@ class MangaScreen(
|
|||||||
*
|
*
|
||||||
* @param query the search query to the parent controller
|
* @param query the search query to the parent controller
|
||||||
*/
|
*/
|
||||||
private fun performSearch(router: Router, query: String, global: Boolean) {
|
private suspend fun performSearch(navigator: Navigator, query: String, global: Boolean) {
|
||||||
if (global) {
|
if (global) {
|
||||||
router.pushController(GlobalSearchController(query))
|
navigator.push(GlobalSearchScreen(query))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (router.backstackSize < 2) {
|
if (navigator.size < 2) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
when (val previousController = router.backstack[router.backstackSize - 2].controller) {
|
when (val previousController = navigator.items[navigator.size - 2]) {
|
||||||
is LibraryController -> {
|
is HomeScreen -> {
|
||||||
router.handleBack()
|
navigator.pop()
|
||||||
previousController.search(query)
|
previousController.search(query)
|
||||||
}
|
}
|
||||||
is UpdatesController,
|
is BrowseSourceScreen -> {
|
||||||
is HistoryController,
|
navigator.pop()
|
||||||
-> {
|
previousController.search(query)
|
||||||
// Manually navigate to LibraryController
|
|
||||||
router.handleBack()
|
|
||||||
(router.activity as MainActivity).setSelectedNavItem(R.id.nav_library)
|
|
||||||
val controller = router.getControllerWithTag(R.id.nav_library.toString()) as LibraryController
|
|
||||||
controller.search(query)
|
|
||||||
}
|
|
||||||
is BrowseSourceController -> {
|
|
||||||
router.handleBack()
|
|
||||||
previousController.searchWithQuery(query)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,20 +291,17 @@ class MangaScreen(
|
|||||||
*
|
*
|
||||||
* @param genreName the search genre to the parent controller
|
* @param genreName the search genre to the parent controller
|
||||||
*/
|
*/
|
||||||
private fun performGenreSearch(router: Router, genreName: String, source: Source) {
|
private suspend fun performGenreSearch(navigator: Navigator, genreName: String, source: Source) {
|
||||||
if (router.backstackSize < 2) {
|
if (navigator.size < 2) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val previousController = router.backstack[router.backstackSize - 2].controller
|
val previousController = navigator.items[navigator.size - 2]
|
||||||
|
if (previousController is BrowseSourceScreen && source is HttpSource) {
|
||||||
if (previousController is BrowseSourceController &&
|
navigator.pop()
|
||||||
source is HttpSource
|
previousController.searchGenre(genreName)
|
||||||
) {
|
|
||||||
router.handleBack()
|
|
||||||
previousController.searchWithGenre(genreName)
|
|
||||||
} else {
|
} else {
|
||||||
performSearch(router, genreName, global = false)
|
performSearch(navigator, genreName, global = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,26 +1,33 @@
|
|||||||
package eu.kanade.tachiyomi.ui.more
|
package eu.kanade.tachiyomi.ui.more
|
||||||
|
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import cafe.adriel.voyager.core.model.coroutineScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.core.prefs.asState
|
import eu.kanade.core.prefs.asState
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.presentation.more.MoreScreen
|
import eu.kanade.presentation.more.MoreScreen
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadController
|
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
import eu.kanade.tachiyomi.ui.stats.StatsScreen
|
||||||
import eu.kanade.tachiyomi.ui.stats.StatsController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
|
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ -31,11 +38,28 @@ import kotlinx.coroutines.flow.combine
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
object MoreScreen : Screen {
|
data class MoreTab(private val toDownloads: Boolean = false) : Tab {
|
||||||
|
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val isSelected = LocalTabNavigator.current.current.key == key
|
||||||
|
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_more_enter)
|
||||||
|
return TabOptions(
|
||||||
|
index = 4u,
|
||||||
|
title = stringResource(R.string.label_more),
|
||||||
|
icon = rememberAnimatedVectorPainter(image, isSelected),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onReselect(navigator: Navigator) {
|
||||||
|
navigator.push(SettingsScreen.toMainScreen())
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { MoreScreenModel() }
|
val screenModel = rememberScreenModel { MoreScreenModel() }
|
||||||
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
|
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
|
||||||
MoreScreen(
|
MoreScreen(
|
||||||
@ -45,12 +69,12 @@ object MoreScreen : Screen {
|
|||||||
incognitoMode = screenModel.incognitoMode,
|
incognitoMode = screenModel.incognitoMode,
|
||||||
onIncognitoModeChange = { screenModel.incognitoMode = it },
|
onIncognitoModeChange = { screenModel.incognitoMode = it },
|
||||||
isFDroid = context.isInstalledFromFDroid(),
|
isFDroid = context.isInstalledFromFDroid(),
|
||||||
onClickDownloadQueue = { router.pushController(DownloadController()) },
|
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
|
||||||
onClickCategories = { router.pushController(CategoryController()) },
|
onClickCategories = { navigator.push(CategoryScreen()) },
|
||||||
onClickStats = { router.pushController(StatsController()) },
|
onClickStats = { navigator.push(StatsScreen()) },
|
||||||
onClickBackupAndRestore = { router.pushController(SettingsMainController.toBackupScreen()) },
|
onClickBackupAndRestore = { navigator.push(SettingsScreen.toBackupScreen()) },
|
||||||
onClickSettings = { router.pushController(SettingsMainController()) },
|
onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) },
|
||||||
onClickAbout = { router.pushController(SettingsMainController.toAboutScreen()) },
|
onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,6 @@ import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegateImpl
|
|||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl
|
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
|
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
|
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
|
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
|
||||||
@ -71,6 +70,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
|||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
|
import eu.kanade.tachiyomi.util.Constants
|
||||||
import eu.kanade.tachiyomi.util.preference.toggle
|
import eu.kanade.tachiyomi.util.preference.toggle
|
||||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||||
@ -375,7 +375,7 @@ class ReaderActivity :
|
|||||||
startActivity(
|
startActivity(
|
||||||
Intent(this, MainActivity::class.java).apply {
|
Intent(this, MainActivity::class.java).apply {
|
||||||
action = MainActivity.SHORTCUT_MANGA
|
action = MainActivity.SHORTCUT_MANGA
|
||||||
putExtra(MangaController.MANGA_EXTRA, id)
|
putExtra(Constants.MANGA_EXTRA, id)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class SettingsMainController(bundle: Bundle = bundleOf()) : BasicFullComposeController(bundle) {
|
|
||||||
|
|
||||||
private val toBackupScreen = args.getBoolean(TO_BACKUP_SCREEN)
|
|
||||||
private val toAboutScreen = args.getBoolean(TO_ABOUT_SCREEN)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(
|
|
||||||
screen = when {
|
|
||||||
toBackupScreen -> SettingsScreen.toBackupScreen()
|
|
||||||
toAboutScreen -> SettingsScreen.toAboutScreen()
|
|
||||||
else -> SettingsScreen.toMainScreen()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun toBackupScreen(): SettingsMainController {
|
|
||||||
return SettingsMainController(bundleOf(TO_BACKUP_SCREEN to true))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toAboutScreen(): SettingsMainController {
|
|
||||||
return SettingsMainController(bundleOf(TO_ABOUT_SCREEN to true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val TO_BACKUP_SCREEN = "to_backup_screen"
|
|
||||||
private const val TO_ABOUT_SCREEN = "to_about_screen"
|
|
@ -13,7 +13,6 @@ import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
|
|||||||
import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
|
import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.presentation.util.Transition
|
import eu.kanade.presentation.util.Transition
|
||||||
import eu.kanade.presentation.util.isTabletUi
|
import eu.kanade.presentation.util.isTabletUi
|
||||||
|
|
||||||
@ -24,15 +23,8 @@ class SettingsScreen private constructor(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
if (!isTabletUi()) {
|
if (!isTabletUi()) {
|
||||||
val back: () -> Unit = {
|
|
||||||
when {
|
|
||||||
navigator.canPop -> navigator.pop()
|
|
||||||
router.backstackSize > 1 -> router.handleBack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Navigator(
|
Navigator(
|
||||||
screen = if (toBackup) {
|
screen = if (toBackup) {
|
||||||
SettingsBackupScreen
|
SettingsBackupScreen
|
||||||
@ -42,7 +34,7 @@ class SettingsScreen private constructor(
|
|||||||
SettingsMainScreen
|
SettingsMainScreen
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
CompositionLocalProvider(LocalBackPress provides back) {
|
CompositionLocalProvider(LocalBackPress provides navigator::pop) {
|
||||||
ScreenTransition(
|
ScreenTransition(
|
||||||
navigator = it,
|
navigator = it,
|
||||||
transition = { Transition.OneWayFade },
|
transition = { Transition.OneWayFade },
|
||||||
@ -62,7 +54,7 @@ class SettingsScreen private constructor(
|
|||||||
) {
|
) {
|
||||||
TwoPanelBox(
|
TwoPanelBox(
|
||||||
startContent = {
|
startContent = {
|
||||||
CompositionLocalProvider(LocalBackPress provides router::popCurrentController) {
|
CompositionLocalProvider(LocalBackPress provides navigator::pop) {
|
||||||
SettingsMainScreen.Content(twoPane = true)
|
SettingsMainScreen.Content(twoPane = true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.stats
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
|
|
||||||
class StatsController : BasicFullComposeController() {
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = StatsScreen())
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,18 +3,17 @@ package eu.kanade.tachiyomi.ui.stats
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenContent
|
import eu.kanade.presentation.more.stats.StatsScreenContent
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
class StatsScreen : Screen {
|
class StatsScreen : Screen {
|
||||||
@ -23,8 +22,7 @@ class StatsScreen : Screen {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { StatsScreenModel() }
|
val screenModel = rememberScreenModel { StatsScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
@ -38,7 +36,7 @@ class StatsScreen : Screen {
|
|||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
title = stringResource(R.string.label_stats),
|
title = stringResource(R.string.label_stats),
|
||||||
navigateUp = router::popCurrentController,
|
navigateUp = navigator::pop,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.updates
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
||||||
|
|
||||||
class UpdatesController : BasicFullComposeController(), RootController {
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
Navigator(screen = UpdatesScreen)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +1,54 @@
|
|||||||
package eu.kanade.tachiyomi.ui.updates
|
package eu.kanade.tachiyomi.ui.updates
|
||||||
|
|
||||||
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.presentation.updates.UpdateScreen
|
import eu.kanade.presentation.updates.UpdateScreen
|
||||||
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
||||||
import eu.kanade.presentation.util.LocalRouter
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
object UpdatesScreen : Screen {
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val router = LocalRouter.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
@ -34,7 +59,7 @@ object UpdatesScreen : Screen {
|
|||||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||||
lastUpdated = screenModel.lastUpdated,
|
lastUpdated = screenModel.lastUpdated,
|
||||||
relativeTime = screenModel.relativeTime,
|
relativeTime = screenModel.relativeTime,
|
||||||
onClickCover = { item -> router.pushController(MangaController(item.update.mangaId)) },
|
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },
|
||||||
onSelectAll = screenModel::toggleAllSelection,
|
onSelectAll = screenModel::toggleAllSelection,
|
||||||
onInvertSelection = screenModel::invertSelection,
|
onInvertSelection = screenModel::invertSelection,
|
||||||
onUpdateLibrary = screenModel::updateLibrary,
|
onUpdateLibrary = screenModel::updateLibrary,
|
||||||
@ -77,8 +102,9 @@ object UpdatesScreen : Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.selectionMode) {
|
LaunchedEffect(state.selectionMode) {
|
||||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
HomeScreen.showBottomNav(!state.selectionMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(state.isLoading) {
|
LaunchedEffect(state.isLoading) {
|
||||||
if (!state.isLoading) {
|
if (!state.isLoading) {
|
||||||
(context as? MainActivity)?.ready = true
|
(context as? MainActivity)?.ready = true
|
7
app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt
Normal file
7
app/src/main/java/eu/kanade/tachiyomi/util/Constants.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val URL_HELP = "https://tachiyomi.org/help/"
|
||||||
|
|
||||||
|
const val MANGA_EXTRA = "manga"
|
||||||
|
}
|
@ -1,196 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.animation.TimeInterpolator
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.ViewPropertyAnimator
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.max
|
|
||||||
import androidx.customview.view.AbsSavedState
|
|
||||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
|
||||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
|
||||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class TachiyomiBottomNavigationView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = R.attr.bottomNavigationStyle,
|
|
||||||
defStyleRes: Int = R.style.Widget_Design_BottomNavigationView,
|
|
||||||
) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) {
|
|
||||||
|
|
||||||
private var currentAnimator: ViewPropertyAnimator? = null
|
|
||||||
|
|
||||||
private var currentState = STATE_UP
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(): Parcelable {
|
|
||||||
val superState = super.onSaveInstanceState()
|
|
||||||
return SavedState(superState).also {
|
|
||||||
it.currentState = currentState
|
|
||||||
it.translationY = translationY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
|
||||||
if (state is SavedState) {
|
|
||||||
super.onRestoreInstanceState(state.superState)
|
|
||||||
super.setTranslationY(state.translationY)
|
|
||||||
currentState = state.currentState
|
|
||||||
} else {
|
|
||||||
super.onRestoreInstanceState(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTranslationY(translationY: Float) {
|
|
||||||
// Disallow translation change when state down
|
|
||||||
if (currentState == STATE_DOWN) return
|
|
||||||
super.setTranslationY(translationY)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
||||||
super.onSizeChanged(w, h, oldw, oldh)
|
|
||||||
bottomNavPadding = h.pxToDp.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows this view up.
|
|
||||||
*/
|
|
||||||
fun slideUp() = post {
|
|
||||||
currentAnimator?.cancel()
|
|
||||||
clearAnimation()
|
|
||||||
|
|
||||||
currentState = STATE_UP
|
|
||||||
animateTranslation(
|
|
||||||
0F,
|
|
||||||
SLIDE_UP_ANIMATION_DURATION,
|
|
||||||
LinearOutSlowInInterpolator(),
|
|
||||||
)
|
|
||||||
bottomNavPadding = height.pxToDp.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hides this view down. [setTranslationY] won't work until [slideUp] is called.
|
|
||||||
*/
|
|
||||||
fun slideDown() = post {
|
|
||||||
currentAnimator?.cancel()
|
|
||||||
clearAnimation()
|
|
||||||
|
|
||||||
currentState = STATE_DOWN
|
|
||||||
animateTranslation(
|
|
||||||
height.toFloat(),
|
|
||||||
SLIDE_DOWN_ANIMATION_DURATION,
|
|
||||||
FastOutLinearInInterpolator(),
|
|
||||||
)
|
|
||||||
bottomNavPadding = 0.dp
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) {
|
|
||||||
currentAnimator = animate()
|
|
||||||
.translationY(targetY)
|
|
||||||
.setInterpolator(interpolator)
|
|
||||||
.setDuration(duration)
|
|
||||||
.applySystemAnimatorScale(context)
|
|
||||||
.setListener(
|
|
||||||
object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
currentAnimator = null
|
|
||||||
postInvalidate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class SavedState : AbsSavedState {
|
|
||||||
var currentState = STATE_UP
|
|
||||||
var translationY = 0F
|
|
||||||
|
|
||||||
constructor(superState: Parcelable) : super(superState)
|
|
||||||
|
|
||||||
constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) {
|
|
||||||
currentState = source.readInt()
|
|
||||||
translationY = source.readFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
|
||||||
super.writeToParcel(out, flags)
|
|
||||||
out.writeInt(currentState)
|
|
||||||
out.writeFloat(translationY)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmField
|
|
||||||
val CREATOR: Parcelable.ClassLoaderCreator<SavedState> = object : Parcelable.ClassLoaderCreator<SavedState> {
|
|
||||||
override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState {
|
|
||||||
return SavedState(source, loader)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createFromParcel(source: Parcel): SavedState {
|
|
||||||
return SavedState(source, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<SavedState> {
|
|
||||||
return newArray(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val STATE_DOWN = 1
|
|
||||||
private const val STATE_UP = 2
|
|
||||||
|
|
||||||
private const val SLIDE_UP_ANIMATION_DURATION = 225L
|
|
||||||
private const val SLIDE_DOWN_ANIMATION_DURATION = 175L
|
|
||||||
|
|
||||||
private var bottomNavPadding by mutableStateOf(0.dp)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges [bottomNavPadding] to the origin's [PaddingValues] bottom side.
|
|
||||||
*/
|
|
||||||
@ReadOnlyComposable
|
|
||||||
@Composable
|
|
||||||
fun withBottomNavPadding(origin: PaddingValues = PaddingValues()): PaddingValues {
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
|
||||||
return PaddingValues(
|
|
||||||
start = origin.calculateStartPadding(layoutDirection),
|
|
||||||
top = origin.calculateTopPadding(),
|
|
||||||
end = origin.calculateEndPadding(layoutDirection),
|
|
||||||
bottom = max(origin.calculateBottomPadding(), bottomNavPadding),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see withBottomNavPadding
|
|
||||||
*/
|
|
||||||
@ReadOnlyComposable
|
|
||||||
@Composable
|
|
||||||
fun withBottomNavInset(origin: WindowInsets): WindowInsets {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
|
||||||
return WindowInsets(
|
|
||||||
left = origin.getLeft(density, layoutDirection),
|
|
||||||
top = origin.getTop(density),
|
|
||||||
right = origin.getRight(density, layoutDirection),
|
|
||||||
bottom = max(origin.getBottom(density), with(density) { bottomNavPadding.roundToPx() }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import com.bluelinelabs.conductor.ChangeHandlerFrameLayout
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [ChangeHandlerFrameLayout] with the ability to draw behind the header sibling in [CoordinatorLayout].
|
|
||||||
* The layout behavior of this view is set to [TachiyomiScrollingViewBehavior] and should not be changed.
|
|
||||||
*/
|
|
||||||
class TachiyomiChangeHandlerFrameLayout(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet,
|
|
||||||
) : ChangeHandlerFrameLayout(context, attrs), CoordinatorLayout.AttachedBehavior {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, this view will draw behind the header sibling.
|
|
||||||
*
|
|
||||||
* @see TachiyomiScrollingViewBehavior.shouldHeaderOverlap
|
|
||||||
*/
|
|
||||||
var overlapHeader = false
|
|
||||||
set(value) {
|
|
||||||
if (field != value) {
|
|
||||||
field = value
|
|
||||||
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = behavior.apply {
|
|
||||||
shouldHeaderOverlap = value
|
|
||||||
}
|
|
||||||
if (!value) {
|
|
||||||
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
|
|
||||||
translationY = 0F
|
|
||||||
}
|
|
||||||
forceLayout()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enableScrollingBehavior(enable: Boolean) {
|
|
||||||
(layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = if (enable) {
|
|
||||||
behavior.apply {
|
|
||||||
shouldHeaderOverlap = overlapHeader
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (!enable) {
|
|
||||||
// The behavior doesn't reset translationY when shouldHeaderOverlap is false
|
|
||||||
translationY = 0F
|
|
||||||
}
|
|
||||||
forceLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getBehavior() = TachiyomiScrollingViewBehavior()
|
|
||||||
}
|
|
@ -2,8 +2,8 @@
|
|||||||
xmlns:aapt="http://schemas.android.com/aapt">
|
xmlns:aapt="http://schemas.android.com/aapt">
|
||||||
<aapt:attr name="android:drawable">
|
<aapt:attr name="android:drawable">
|
||||||
<vector
|
<vector
|
||||||
android:width="1080dp"
|
android:width="24dp"
|
||||||
android:height="1080dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="1080"
|
android:viewportWidth="1080"
|
||||||
android:viewportHeight="1080">
|
android:viewportHeight="1080">
|
||||||
<group android:name="_R_G">
|
<group android:name="_R_G">
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/root_coordinator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.TachiyomiAppBarLayout
|
|
||||||
android:id="@+id/appbar"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/side_nav"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:theme="?attr/actionBarTheme" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/downloaded_only"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorTertiary"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="4dp"
|
|
||||||
android:text="@string/label_downloaded_only"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textAppearance="?attr/textAppearanceLabelMedium"
|
|
||||||
android:textColor="?attr/colorOnTertiary"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/incognito_mode"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorPrimary"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="4dp"
|
|
||||||
android:text="@string/pref_incognito_mode"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textAppearance="?attr/textAppearanceLabelMedium"
|
|
||||||
android:textColor="?attr/colorOnPrimary"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.TachiyomiAppBarLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.navigationrail.NavigationRailView
|
|
||||||
android:id="@+id/side_nav"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingTop="?attr/actionBarSize"
|
|
||||||
app:elevation="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:menu="@menu/main_nav"
|
|
||||||
app:menuGravity="center" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiChangeHandlerFrameLayout
|
|
||||||
android:id="@+id/controller_container"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/side_nav"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/appbar" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout>
|
|
@ -1,68 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/root_coordinator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiChangeHandlerFrameLayout
|
|
||||||
android:id="@+id/controller_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.TachiyomiAppBarLayout
|
|
||||||
android:id="@+id/appbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:elevation="0dp">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:theme="?attr/actionBarTheme" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/downloaded_only"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorTertiary"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="4dp"
|
|
||||||
android:text="@string/label_downloaded_only"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textAppearance="?attr/textAppearanceLabelMedium"
|
|
||||||
android:textColor="?attr/colorOnTertiary"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/incognito_mode"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/colorPrimary"
|
|
||||||
android:gravity="center"
|
|
||||||
android:padding="4dp"
|
|
||||||
android:text="@string/pref_incognito_mode"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textAppearance="?attr/textAppearanceLabelMedium"
|
|
||||||
android:textColor="?attr/colorOnPrimary"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.TachiyomiAppBarLayout>
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
|
|
||||||
android:id="@+id/bottom_nav"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:clickable="true"
|
|
||||||
app:layout_insetEdge="bottom"
|
|
||||||
app:menu="@menu/main_nav"
|
|
||||||
tools:ignore="KeyboardInaccessibleWidget" />
|
|
||||||
|
|
||||||
</eu.kanade.tachiyomi.widget.TachiyomiCoordinatorLayout>
|
|
@ -6,7 +6,8 @@ coil_version = "2.2.2"
|
|||||||
shizuku_version = "12.2.0"
|
shizuku_version = "12.2.0"
|
||||||
sqldelight = "1.5.4"
|
sqldelight = "1.5.4"
|
||||||
leakcanary = "2.10"
|
leakcanary = "2.10"
|
||||||
voyager = "1.0.0-rc06"
|
voyager = "1.0.0-rc07"
|
||||||
|
richtext = "0.15.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
desugar = "com.android.tools:desugar_jdk_libs:1.2.2"
|
desugar = "com.android.tools:desugar_jdk_libs:1.2.2"
|
||||||
@ -52,7 +53,8 @@ image-decoder = "com.github.tachiyomiorg:image-decoder:7879b45"
|
|||||||
|
|
||||||
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
||||||
|
|
||||||
markwon = "io.noties.markwon:core:4.6.2"
|
richtext-commonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" }
|
||||||
|
richtext-m3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" }
|
||||||
|
|
||||||
material = "com.google.android.material:material:1.7.0"
|
material = "com.google.android.material:material:1.7.0"
|
||||||
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
||||||
@ -63,8 +65,6 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
|
|||||||
cascade = "me.saket.cascade:cascade-compose:2.0.0-beta1"
|
cascade = "me.saket.cascade:cascade-compose:2.0.0-beta1"
|
||||||
wheelpicker = "com.github.commandiron:WheelPickerCompose:1.0.11"
|
wheelpicker = "com.github.commandiron:WheelPickerCompose:1.0.11"
|
||||||
|
|
||||||
conductor = "com.bluelinelabs:conductor:3.1.8"
|
|
||||||
|
|
||||||
flowbinding-android = "io.github.reactivecircus.flowbinding:flowbinding-android:1.2.0"
|
flowbinding-android = "io.github.reactivecircus.flowbinding:flowbinding-android:1.2.0"
|
||||||
|
|
||||||
logcat = "com.squareup.logcat:logcat:0.1"
|
logcat = "com.squareup.logcat:logcat:0.1"
|
||||||
@ -89,6 +89,7 @@ sqldelight-gradle = { module = "com.squareup.sqldelight:gradle-plugin", version.
|
|||||||
junit = "org.junit.jupiter:junit-jupiter:5.9.1"
|
junit = "org.junit.jupiter:junit-jupiter:5.9.1"
|
||||||
|
|
||||||
voyager-navigator = { module = "ca.gosyer:voyager-navigator", version.ref = "voyager" }
|
voyager-navigator = { module = "ca.gosyer:voyager-navigator", version.ref = "voyager" }
|
||||||
|
voyager-tab-navigator = { module = "ca.gosyer:voyager-tab-navigator", version.ref = "voyager" }
|
||||||
voyager-transitions = { module = "ca.gosyer:voyager-transitions", version.ref = "voyager" }
|
voyager-transitions = { module = "ca.gosyer:voyager-transitions", version.ref = "voyager" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
@ -99,7 +100,8 @@ sqlite = ["sqlitektx", "sqlite-android"]
|
|||||||
nucleus = ["nucleus-core", "nucleus-supportv7"]
|
nucleus = ["nucleus-core", "nucleus-supportv7"]
|
||||||
coil = ["coil-core", "coil-gif", "coil-compose"]
|
coil = ["coil-core", "coil-gif", "coil-compose"]
|
||||||
shizuku = ["shizuku-api", "shizuku-provider"]
|
shizuku = ["shizuku-api", "shizuku-provider"]
|
||||||
voyager = ["voyager-navigator", "voyager-transitions"]
|
voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"]
|
||||||
|
richtext = ["richtext-commonmark", "richtext-m3"]
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0" }
|
kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0" }
|
@ -145,6 +145,7 @@
|
|||||||
<string name="action_webview_refresh">Refresh</string>
|
<string name="action_webview_refresh">Refresh</string>
|
||||||
<string name="action_start_downloading_now">Start downloading now</string>
|
<string name="action_start_downloading_now">Start downloading now</string>
|
||||||
<string name="action_faq_and_guides">FAQ and Guides</string>
|
<string name="action_faq_and_guides">FAQ and Guides</string>
|
||||||
|
<string name="action_not_now">Not now</string>
|
||||||
|
|
||||||
<!-- Operations -->
|
<!-- Operations -->
|
||||||
<string name="loading">Loading…</string>
|
<string name="loading">Loading…</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user