Settings: M3 and two pane ui (#8211)
* Settings: M3 and two pane ui
* TrackingLoginDialog: Move close button
* Use small top bar
* Revert "Update voyager to v1.0.0-rc02"
This reverts commit 570fec6ea6
.
https://github.com/adrielcafe/voyager/issues/62
This commit is contained in:
parent
6635dd2990
commit
5c5468f9af
21 changed files with 482 additions and 256 deletions
|
@ -141,12 +141,12 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
sqldelight {
|
sqldelight {
|
||||||
|
|
|
@ -56,6 +56,7 @@ import androidx.compose.ui.unit.dp
|
||||||
* @sample androidx.compose.material3.samples.ScaffoldWithSimpleSnackbar
|
* @sample androidx.compose.material3.samples.ScaffoldWithSimpleSnackbar
|
||||||
*
|
*
|
||||||
* Tachiyomi changes:
|
* Tachiyomi changes:
|
||||||
|
* * Pass scroll behavior to top bar by default
|
||||||
* * 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
|
||||||
*
|
*
|
||||||
|
@ -80,6 +81,7 @@ import androidx.compose.ui.unit.dp
|
||||||
@Composable
|
@Composable
|
||||||
fun Scaffold(
|
fun Scaffold(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||||
topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {},
|
topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {},
|
||||||
bottomBar: @Composable () -> Unit = {},
|
bottomBar: @Composable () -> Unit = {},
|
||||||
snackbarHost: @Composable () -> Unit = {},
|
snackbarHost: @Composable () -> Unit = {},
|
||||||
|
@ -89,21 +91,16 @@ fun Scaffold(
|
||||||
contentColor: Color = contentColorFor(containerColor),
|
contentColor: Color = contentColorFor(containerColor),
|
||||||
content: @Composable (PaddingValues) -> Unit,
|
content: @Composable (PaddingValues) -> Unit,
|
||||||
) {
|
) {
|
||||||
/**
|
|
||||||
* Tachiyomi: Pass scroll behavior to topBar
|
|
||||||
*/
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
|
||||||
|
|
||||||
androidx.compose.material3.Surface(
|
androidx.compose.material3.Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
||||||
.then(modifier),
|
.then(modifier),
|
||||||
color = containerColor,
|
color = containerColor,
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
) {
|
) {
|
||||||
ScaffoldLayout(
|
ScaffoldLayout(
|
||||||
fabPosition = floatingActionButtonPosition,
|
fabPosition = floatingActionButtonPosition,
|
||||||
topBar = { topBar(scrollBehavior) },
|
topBar = { topBar(topBarScrollBehavior) },
|
||||||
bottomBar = bottomBar,
|
bottomBar = bottomBar,
|
||||||
content = content,
|
content = content,
|
||||||
snackbar = snackbarHost,
|
snackbar = snackbarHost,
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package eu.kanade.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TwoPanelBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
startContent: @Composable BoxScope.() -> Unit,
|
||||||
|
endContent: @Composable BoxScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
|
||||||
|
val firstWidth = (maxWidth / 2).coerceAtMost(450.dp)
|
||||||
|
val secondWidth = maxWidth - firstWidth
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopStart)
|
||||||
|
.width(firstWidth),
|
||||||
|
content = startContent,
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.width(secondWidth),
|
||||||
|
content = endContent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
@ -15,13 +14,11 @@ 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.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
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.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
@ -47,7 +44,6 @@ import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
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 androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.domain.chapter.model.Chapter
|
import eu.kanade.domain.chapter.model.Chapter
|
||||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
|
@ -55,6 +51,7 @@ import eu.kanade.presentation.components.LazyColumn
|
||||||
import eu.kanade.presentation.components.MangaBottomActionMenu
|
import eu.kanade.presentation.components.MangaBottomActionMenu
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
import eu.kanade.presentation.components.SwipeRefresh
|
import eu.kanade.presentation.components.SwipeRefresh
|
||||||
|
import eu.kanade.presentation.components.TwoPanelBox
|
||||||
import eu.kanade.presentation.components.VerticalFastScroller
|
import eu.kanade.presentation.components.VerticalFastScroller
|
||||||
import eu.kanade.presentation.manga.components.ChapterHeader
|
import eu.kanade.presentation.manga.components.ChapterHeader
|
||||||
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
||||||
|
@ -501,79 +498,74 @@ fun MangaScreenLargeImpl(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
TwoPanelBox(
|
||||||
val firstWidth = (maxWidth / 2).coerceAtMost(450.dp)
|
startContent = {
|
||||||
val secondWidth = maxWidth - firstWidth
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
Column(
|
.verticalScroll(rememberScrollState()),
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopStart)
|
|
||||||
.width(firstWidth)
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
|
||||||
MangaInfoBox(
|
|
||||||
windowWidthSizeClass = windowWidthSizeClass,
|
|
||||||
appBarPadding = contentPadding.calculateTopPadding(),
|
|
||||||
title = state.manga.title,
|
|
||||||
author = state.manga.author,
|
|
||||||
artist = state.manga.artist,
|
|
||||||
sourceName = remember { state.source.getNameForMangaInfo() },
|
|
||||||
isStubSource = remember { state.source is SourceManager.StubSource },
|
|
||||||
coverDataProvider = { state.manga },
|
|
||||||
status = state.manga.status,
|
|
||||||
onCoverClick = onCoverClicked,
|
|
||||||
doSearch = onSearch,
|
|
||||||
)
|
|
||||||
MangaActionRow(
|
|
||||||
favorite = state.manga.favorite,
|
|
||||||
trackingCount = state.trackingCount,
|
|
||||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
|
||||||
onWebViewClicked = onWebViewClicked,
|
|
||||||
onTrackingClicked = onTrackingClicked,
|
|
||||||
onEditCategory = onEditCategoryClicked,
|
|
||||||
)
|
|
||||||
ExpandableMangaDescription(
|
|
||||||
defaultExpandState = true,
|
|
||||||
description = state.manga.description,
|
|
||||||
tagsProvider = { state.manga.genre },
|
|
||||||
onTagClicked = onTagClicked,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticalFastScroller(
|
|
||||||
listState = chapterListState,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
.width(secondWidth),
|
|
||||||
topContentPadding = contentPadding.calculateTopPadding(),
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
state = chapterListState,
|
|
||||||
contentPadding = PaddingValues(
|
|
||||||
top = contentPadding.calculateTopPadding(),
|
|
||||||
bottom = contentPadding.calculateBottomPadding(),
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
item(
|
MangaInfoBox(
|
||||||
key = MangaScreenItem.CHAPTER_HEADER,
|
windowWidthSizeClass = windowWidthSizeClass,
|
||||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
appBarPadding = contentPadding.calculateTopPadding(),
|
||||||
) {
|
title = state.manga.title,
|
||||||
ChapterHeader(
|
author = state.manga.author,
|
||||||
chapterCount = chapters.size,
|
artist = state.manga.artist,
|
||||||
onClick = onFilterButtonClicked,
|
sourceName = remember { state.source.getNameForMangaInfo() },
|
||||||
)
|
isStubSource = remember { state.source is SourceManager.StubSource },
|
||||||
}
|
coverDataProvider = { state.manga },
|
||||||
|
status = state.manga.status,
|
||||||
sharedChapterItems(
|
onCoverClick = onCoverClicked,
|
||||||
chapters = chapters,
|
doSearch = onSearch,
|
||||||
onChapterClicked = onChapterClicked,
|
)
|
||||||
onDownloadChapter = onDownloadChapter,
|
MangaActionRow(
|
||||||
onChapterSelected = onChapterSelected,
|
favorite = state.manga.favorite,
|
||||||
|
trackingCount = state.trackingCount,
|
||||||
|
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||||
|
onWebViewClicked = onWebViewClicked,
|
||||||
|
onTrackingClicked = onTrackingClicked,
|
||||||
|
onEditCategory = onEditCategoryClicked,
|
||||||
|
)
|
||||||
|
ExpandableMangaDescription(
|
||||||
|
defaultExpandState = true,
|
||||||
|
description = state.manga.description,
|
||||||
|
tagsProvider = { state.manga.genre },
|
||||||
|
onTagClicked = onTagClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
endContent = {
|
||||||
|
VerticalFastScroller(
|
||||||
|
listState = chapterListState,
|
||||||
|
topContentPadding = contentPadding.calculateTopPadding(),
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
state = chapterListState,
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = contentPadding.calculateTopPadding(),
|
||||||
|
bottom = contentPadding.calculateBottomPadding(),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
item(
|
||||||
|
key = MangaScreenItem.CHAPTER_HEADER,
|
||||||
|
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||||
|
) {
|
||||||
|
ChapterHeader(
|
||||||
|
chapterCount = chapters.size,
|
||||||
|
onClick = onFilterButtonClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedChapterItems(
|
||||||
|
chapters = chapters,
|
||||||
|
onChapterClicked = onChapterClicked,
|
||||||
|
onDownloadChapter = onDownloadChapter,
|
||||||
|
onChapterSelected = onChapterSelected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,48 @@ package eu.kanade.presentation.more.settings
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import eu.kanade.presentation.components.AppBar
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.Scaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PreferenceScaffold(
|
fun PreferenceScaffold(
|
||||||
@StringRes titleRes: Int,
|
@StringRes titleRes: Int,
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
onBackPressed: () -> Unit = {},
|
onBackPressed: (() -> Unit)? = null,
|
||||||
itemsProvider: @Composable () -> List<Preference>,
|
itemsProvider: @Composable () -> List<Preference>,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = {
|
||||||
AppBar(
|
TopAppBar(
|
||||||
title = stringResource(titleRes),
|
title = {
|
||||||
navigateUp = onBackPressed,
|
Text(
|
||||||
|
text = stringResource(id = titleRes),
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
if (onBackPressed != null) {
|
||||||
|
IconButton(onClick = onBackPressed) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
actions = actions,
|
actions = actions,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = it,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = { contentPadding ->
|
content = { contentPadding ->
|
||||||
|
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -12,7 +11,6 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
import eu.kanade.presentation.components.Divider
|
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||||
import eu.kanade.presentation.more.settings.widget.PreferenceGroupHeader
|
import eu.kanade.presentation.more.settings.widget.PreferenceGroupHeader
|
||||||
|
@ -55,9 +53,6 @@ fun PreferenceScreen(
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Column {
|
Column {
|
||||||
if (i != 0) {
|
|
||||||
Divider(modifier = Modifier.padding(bottom = 8.dp))
|
|
||||||
}
|
|
||||||
PreferenceGroupHeader(title = preference.title)
|
PreferenceGroupHeader(title = preference.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +63,9 @@ fun PreferenceScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
if (i < items.lastIndex) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
|
@ -26,10 +25,10 @@ interface SearchableSettings : Screen {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val handleBack = LocalBackPress.currentOrThrow
|
val handleBack = LocalBackPress.current
|
||||||
PreferenceScaffold(
|
PreferenceScaffold(
|
||||||
titleRes = getTitleRes(),
|
titleRes = getTitleRes(),
|
||||||
onBackPressed = handleBack::invoke,
|
onBackPressed = if (handleBack != null) handleBack::invoke else null,
|
||||||
actions = { AppBarAction() },
|
actions = { AppBarAction() },
|
||||||
itemsProvider = { getPreferences() },
|
itemsProvider = { getPreferences() },
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
package eu.kanade.presentation.more.settings.screen
|
package eu.kanade.presentation.more.settings.screen
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.ChromeReaderMode
|
import androidx.compose.material.icons.outlined.ChromeReaderMode
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
import androidx.compose.material.icons.outlined.CollectionsBookmark
|
||||||
|
@ -13,103 +19,210 @@ import androidx.compose.material.icons.outlined.Security
|
||||||
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
import androidx.compose.material.icons.outlined.SettingsBackupRestore
|
||||||
import androidx.compose.material.icons.outlined.Sync
|
import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material.icons.outlined.Tune
|
import androidx.compose.material.icons.outlined.Tune
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.NonRestartableComposable
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastFirstOrNull
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.components.LazyColumn
|
||||||
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import eu.kanade.presentation.util.LocalBackPress
|
import eu.kanade.presentation.util.LocalBackPress
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
object SettingsMainScreen : SearchableSettings {
|
object SettingsMainScreen : Screen {
|
||||||
|
|
||||||
@Composable
|
|
||||||
@ReadOnlyComposable
|
|
||||||
@StringRes
|
|
||||||
override fun getTitleRes() = R.string.label_settings
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@NonRestartableComposable
|
|
||||||
override fun getPreferences(): List<Preference> {
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
|
||||||
return listOf(
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_general),
|
|
||||||
icon = Icons.Outlined.Tune,
|
|
||||||
onClick = { navigator.push(SettingsGeneralScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_appearance),
|
|
||||||
icon = Icons.Outlined.Palette,
|
|
||||||
onClick = { navigator.push(SettingsAppearanceScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_library),
|
|
||||||
icon = Icons.Outlined.CollectionsBookmark,
|
|
||||||
onClick = { navigator.push(SettingsLibraryScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_reader),
|
|
||||||
icon = Icons.Outlined.ChromeReaderMode,
|
|
||||||
onClick = { navigator.push(SettingsReaderScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_downloads),
|
|
||||||
icon = Icons.Outlined.GetApp,
|
|
||||||
onClick = { navigator.push(SettingsDownloadScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_tracking),
|
|
||||||
icon = Icons.Outlined.Sync,
|
|
||||||
onClick = { navigator.push(SettingsTrackingScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.browse),
|
|
||||||
icon = Icons.Outlined.Explore,
|
|
||||||
onClick = { navigator.push(SettingsBrowseScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.label_backup),
|
|
||||||
icon = Icons.Outlined.SettingsBackupRestore,
|
|
||||||
onClick = { navigator.push(SettingsBackupScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_security),
|
|
||||||
icon = Icons.Outlined.Security,
|
|
||||||
onClick = { navigator.push(SettingsSecurityScreen()) },
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.TextPreference(
|
|
||||||
title = stringResource(R.string.pref_category_advanced),
|
|
||||||
icon = Icons.Outlined.Code,
|
|
||||||
onClick = { navigator.push(SettingsAdvancedScreen()) },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
|
Content(twoPane = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getPalerSurface(): Color {
|
||||||
|
val surface = MaterialTheme.colorScheme.surface
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
return remember(surface, dark) {
|
||||||
|
val arr = FloatArray(3)
|
||||||
|
ColorUtils.colorToHSL(surface.toArgb(), arr)
|
||||||
|
arr[2] = if (dark) {
|
||||||
|
arr[2] - 0.05f
|
||||||
|
} else {
|
||||||
|
arr[2] + 0.02f
|
||||||
|
}.coerceIn(0f, 1f)
|
||||||
|
Color.hsl(arr[0], arr[1], arr[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Content(twoPane: Boolean) {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val backPress = LocalBackPress.currentOrThrow
|
val backPress = LocalBackPress.currentOrThrow
|
||||||
PreferenceScaffold(
|
val containerColor = if (twoPane) getPalerSurface() else MaterialTheme.colorScheme.surface
|
||||||
titleRes = getTitleRes(),
|
Scaffold(
|
||||||
actions = {
|
topBar = { scrollBehavior ->
|
||||||
AppBarActions(
|
// https://issuetracker.google.com/issues/249688556
|
||||||
listOf(
|
MaterialTheme(
|
||||||
AppBar.Action(
|
colorScheme = MaterialTheme.colorScheme.copy(surface = containerColor),
|
||||||
title = stringResource(R.string.action_search),
|
) {
|
||||||
icon = Icons.Outlined.Search,
|
TopAppBar(
|
||||||
onClick = { navigator.push(SettingsSearchScreen()) },
|
title = {
|
||||||
),
|
Text(
|
||||||
),
|
text = stringResource(R.string.label_settings),
|
||||||
)
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = backPress::invoke) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.abc_action_bar_up_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
AppBarActions(
|
||||||
|
listOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(R.string.action_search),
|
||||||
|
icon = Icons.Outlined.Search,
|
||||||
|
onClick = { navigator.navigate(SettingsSearchScreen(), twoPane) },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = containerColor,
|
||||||
|
content = { contentPadding ->
|
||||||
|
LazyColumn(contentPadding = contentPadding) {
|
||||||
|
items(
|
||||||
|
items = items,
|
||||||
|
key = { it.hashCode() },
|
||||||
|
) { item ->
|
||||||
|
var modifier: Modifier = Modifier
|
||||||
|
var contentColor = LocalContentColor.current
|
||||||
|
if (twoPane) {
|
||||||
|
val selected = navigator.items.fastFirstOrNull { it::class == item.screen::class } != null
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.clip(RoundedCornerShape(24.dp))
|
||||||
|
.then(
|
||||||
|
if (selected) {
|
||||||
|
Modifier.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (selected) {
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
||||||
|
TextPreferenceWidget(
|
||||||
|
modifier = modifier,
|
||||||
|
title = stringResource(item.titleRes),
|
||||||
|
subtitle = stringResource(item.subtitleRes),
|
||||||
|
icon = item.icon,
|
||||||
|
onPreferenceClick = { navigator.navigate(item.screen, twoPane) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onBackPressed = backPress::invoke,
|
|
||||||
itemsProvider = { getPreferences() },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Navigator.navigate(screen: Screen, twoPane: Boolean) {
|
||||||
|
if (twoPane) replaceAll(screen) else push(screen)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class Item(
|
||||||
|
@StringRes val titleRes: Int,
|
||||||
|
@StringRes val subtitleRes: Int,
|
||||||
|
val icon: ImageVector,
|
||||||
|
val screen: Screen,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val items = listOf(
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_general,
|
||||||
|
subtitleRes = R.string.pref_general_summary,
|
||||||
|
icon = Icons.Outlined.Tune,
|
||||||
|
screen = SettingsGeneralScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_appearance,
|
||||||
|
subtitleRes = R.string.pref_appearance_summary,
|
||||||
|
icon = Icons.Outlined.Palette,
|
||||||
|
screen = SettingsAppearanceScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_library,
|
||||||
|
subtitleRes = R.string.pref_library_summary,
|
||||||
|
icon = Icons.Outlined.CollectionsBookmark,
|
||||||
|
screen = SettingsLibraryScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_reader,
|
||||||
|
subtitleRes = R.string.pref_reader_summary,
|
||||||
|
icon = Icons.Outlined.ChromeReaderMode,
|
||||||
|
screen = SettingsReaderScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_downloads,
|
||||||
|
subtitleRes = R.string.pref_downloads_summary,
|
||||||
|
icon = Icons.Outlined.GetApp,
|
||||||
|
screen = SettingsDownloadScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_tracking,
|
||||||
|
subtitleRes = R.string.pref_tracking_summary,
|
||||||
|
icon = Icons.Outlined.Sync,
|
||||||
|
screen = SettingsTrackingScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.browse,
|
||||||
|
subtitleRes = R.string.pref_browse_summary,
|
||||||
|
icon = Icons.Outlined.Explore,
|
||||||
|
screen = SettingsBrowseScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.label_backup,
|
||||||
|
subtitleRes = R.string.pref_backup_summary,
|
||||||
|
icon = Icons.Outlined.SettingsBackupRestore,
|
||||||
|
screen = SettingsBackupScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_security,
|
||||||
|
subtitleRes = R.string.pref_security_summary,
|
||||||
|
icon = Icons.Outlined.Security,
|
||||||
|
screen = SettingsSecurityScreen(),
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
titleRes = R.string.pref_category_advanced,
|
||||||
|
subtitleRes = R.string.pref_advanced_summary,
|
||||||
|
icon = Icons.Outlined.Code,
|
||||||
|
screen = SettingsAdvancedScreen(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -146,8 +146,7 @@ class SettingsSearchScreen : Screen {
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) { result ->
|
) { result ->
|
||||||
SearchableSettings.highlightKey = result.highlightKey
|
SearchableSettings.highlightKey = result.highlightKey
|
||||||
navigator.popUntil { it is SettingsMainScreen }
|
navigator.replace(result.route)
|
||||||
navigator.push(result.route)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.HelpOutline
|
import androidx.compose.material.icons.filled.HelpOutline
|
||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
import androidx.compose.material.icons.filled.VisibilityOff
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
@ -22,7 +23,6 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -30,6 +30,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
@ -189,7 +190,20 @@ class SettingsTrackingScreen : SearchableSettings {
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = { Text(text = stringResource(R.string.login_title, stringResource(service.nameRes()))) },
|
title = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.login_title, stringResource(service.nameRes())),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
IconButton(onClick = onDismissRequest) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = stringResource(R.string.action_close),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
text = {
|
text = {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
|
@ -232,38 +246,30 @@ class SettingsTrackingScreen : SearchableSettings {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Column {
|
Button(
|
||||||
Button(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
enabled = !processing,
|
||||||
enabled = !processing,
|
onClick = {
|
||||||
onClick = {
|
if (username.text.isEmpty() || password.text.isEmpty()) {
|
||||||
if (username.text.isEmpty() || password.text.isEmpty()) {
|
inputError = true
|
||||||
inputError = true
|
return@Button
|
||||||
return@Button
|
}
|
||||||
}
|
scope.launchIO {
|
||||||
scope.launchIO {
|
inputError = false
|
||||||
inputError = false
|
processing = true
|
||||||
processing = true
|
val result = checkLogin(
|
||||||
val result = checkLogin(
|
context = context,
|
||||||
context = context,
|
service = service,
|
||||||
service = service,
|
username = username.text,
|
||||||
username = username.text,
|
password = password.text,
|
||||||
password = password.text,
|
)
|
||||||
)
|
if (result) onDismissRequest()
|
||||||
if (result) onDismissRequest()
|
processing = false
|
||||||
processing = false
|
}
|
||||||
}
|
},
|
||||||
},
|
) {
|
||||||
) {
|
val id = if (processing) R.string.loading else R.string.login
|
||||||
val id = if (processing) R.string.loading else R.string.login
|
Text(text = stringResource(id))
|
||||||
Text(text = stringResource(id))
|
|
||||||
}
|
|
||||||
TextButton(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
onClick = onDismissRequest,
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(android.R.string.cancel))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -109,7 +109,7 @@ private fun AppThemesList(
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
||||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -54,12 +55,12 @@ internal fun BasePreferenceWidget(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
start = HorizontalPadding,
|
start = HorizontalPadding,
|
||||||
top = 4.dp,
|
top = 0.dp,
|
||||||
end = HorizontalPadding,
|
end = HorizontalPadding,
|
||||||
)
|
)
|
||||||
.secondaryItemAlpha(),
|
.secondaryItemAlpha(),
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
maxLines = 10,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -106,15 +107,13 @@ private fun BasePreferenceWidgetImpl(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = HorizontalPadding, end = 12.dp)
|
.padding(start = HorizontalPadding, end = 0.dp),
|
||||||
.secondaryItemAlpha(),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(vertical = 14.dp),
|
.padding(vertical = 16.dp),
|
||||||
) {
|
) {
|
||||||
if (title.isNotBlank()) {
|
if (title.isNotBlank()) {
|
||||||
Row(
|
Row(
|
||||||
|
@ -125,7 +124,8 @@ private fun BasePreferenceWidgetImpl(
|
||||||
text = title,
|
text = title,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontSize = 20.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,4 +173,4 @@ internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier = comp
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val TrailingWidgetBuffer = 16.dp
|
internal val TrailingWidgetBuffer = 16.dp
|
||||||
internal val HorizontalPadding = 16.dp
|
internal val HorizontalPadding = 24.dp
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
|
@ -16,6 +17,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
@ -23,6 +25,7 @@ import eu.kanade.presentation.components.Divider
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
import eu.kanade.presentation.util.isScrolledToEnd
|
import eu.kanade.presentation.util.isScrolledToEnd
|
||||||
import eu.kanade.presentation.util.isScrolledToStart
|
import eu.kanade.presentation.util.isScrolledToStart
|
||||||
|
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> ListPreferenceWidget(
|
fun <T> ListPreferenceWidget(
|
||||||
|
@ -86,20 +89,22 @@ private fun DialogRow(
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.selectable(
|
.selectable(
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = { if (!isSelected) onSelected() },
|
onClick = { if (!isSelected) onSelected() },
|
||||||
),
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.minimumTouchTargetSize(),
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = { if (!isSelected) onSelected() },
|
onClick = null,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
style = MaterialTheme.typography.bodyLarge.merge(),
|
style = MaterialTheme.typography.bodyLarge.merge(),
|
||||||
modifier = Modifier.padding(start = 12.dp),
|
modifier = Modifier.padding(start = 24.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package eu.kanade.presentation.more.settings.widget
|
package eu.kanade.presentation.more.settings.widget
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -16,10 +17,12 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MultiSelectListPreferenceWidget(
|
fun MultiSelectListPreferenceWidget(
|
||||||
|
@ -59,17 +62,22 @@ fun MultiSelectListPreferenceWidget(
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.clickable { onSelectionChanged() },
|
.selectable(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = { onSelectionChanged() },
|
||||||
|
)
|
||||||
|
.minimumTouchTargetSize()
|
||||||
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked = isSelected,
|
checked = isSelected,
|
||||||
onCheckedChange = { onSelectionChanged() },
|
onCheckedChange = null,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = current.value,
|
text = current.value,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
modifier = Modifier.padding(start = 12.dp),
|
modifier = Modifier.padding(start = 24.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ fun PreferenceGroupHeader(title: String) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 24.dp),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,20 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextPreferenceWidget(
|
fun TextPreferenceWidget(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = null,
|
subtitle: String? = null,
|
||||||
icon: ImageVector? = null,
|
icon: ImageVector? = null,
|
||||||
onPreferenceClick: (() -> Unit)? = null,
|
onPreferenceClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
// TODO: Handle auth requirement here?
|
|
||||||
BasePreferenceWidget(
|
BasePreferenceWidget(
|
||||||
|
modifier = modifier,
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -39,7 +40,7 @@ fun TrackingPreferenceWidget(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = HorizontalPadding, vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
@ -60,7 +61,8 @@ fun TrackingPreferenceWidget(
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontSize = 20.sp,
|
||||||
)
|
)
|
||||||
if (checked) {
|
if (checked) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
@ -79,7 +79,7 @@ fun <T> TriStateListDialog(
|
||||||
val state = selected[index]
|
val state = selected[index]
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(25))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.clickable {
|
.clickable {
|
||||||
selected[index] = when (state) {
|
selected[index] = when (state) {
|
||||||
State.UNCHECKED -> State.CHECKED
|
State.UNCHECKED -> State.CHECKED
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import cafe.adriel.voyager.core.stack.StackEvent
|
import cafe.adriel.voyager.core.stack.StackEvent
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.transitions.ScreenTransition
|
import cafe.adriel.voyager.transitions.ScreenTransition
|
||||||
|
import eu.kanade.presentation.components.TwoPanelBox
|
||||||
import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
|
import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
|
||||||
|
import eu.kanade.presentation.more.settings.screen.SettingsGeneralScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
import eu.kanade.presentation.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.LocalRouter
|
||||||
|
import eu.kanade.presentation.util.calculateWindowWidthSizeClass
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||||
import soup.compose.material.motion.animation.materialSharedAxisZ
|
import soup.compose.material.motion.animation.materialSharedAxisX
|
||||||
|
import soup.compose.material.motion.animation.rememberSlideDistance
|
||||||
|
|
||||||
class SettingsMainController : BasicFullComposeController {
|
class SettingsMainController : BasicFullComposeController {
|
||||||
|
|
||||||
|
@ -25,20 +30,52 @@ class SettingsMainController : BasicFullComposeController {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
Navigator(
|
CompositionLocalProvider(LocalRouter provides router) {
|
||||||
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsMainScreen,
|
val widthSizeClass = calculateWindowWidthSizeClass()
|
||||||
content = {
|
if (widthSizeClass == WindowWidthSizeClass.Compact) {
|
||||||
CompositionLocalProvider(
|
Navigator(
|
||||||
LocalRouter provides router,
|
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsMainScreen,
|
||||||
LocalBackPress provides this::back,
|
content = {
|
||||||
|
CompositionLocalProvider(LocalBackPress provides this::back) {
|
||||||
|
val slideDistance = rememberSlideDistance()
|
||||||
|
ScreenTransition(
|
||||||
|
navigator = it,
|
||||||
|
transition = {
|
||||||
|
materialSharedAxisX(
|
||||||
|
forward = it.lastEvent != StackEvent.Pop,
|
||||||
|
slideDistance = slideDistance,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Navigator(
|
||||||
|
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsGeneralScreen(),
|
||||||
) {
|
) {
|
||||||
ScreenTransition(
|
TwoPanelBox(
|
||||||
navigator = it,
|
startContent = {
|
||||||
transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) },
|
CompositionLocalProvider(LocalBackPress provides this@SettingsMainController::back) {
|
||||||
|
SettingsMainScreen.Content(twoPane = true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endContent = {
|
||||||
|
val slideDistance = rememberSlideDistance()
|
||||||
|
ScreenTransition(
|
||||||
|
navigator = it,
|
||||||
|
transition = {
|
||||||
|
materialSharedAxisX(
|
||||||
|
forward = it.lastEvent != StackEvent.Pop,
|
||||||
|
slideDistance = slideDistance,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun back() {
|
private fun back() {
|
||||||
|
|
|
@ -8,7 +8,7 @@ flowbinding_version = "1.2.0"
|
||||||
shizuku_version = "12.2.0"
|
shizuku_version = "12.2.0"
|
||||||
sqldelight = "1.5.4"
|
sqldelight = "1.5.4"
|
||||||
leakcanary = "2.9.1"
|
leakcanary = "2.9.1"
|
||||||
voyager = "1.0.0-rc02"
|
voyager = "1.0.0-beta16"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"
|
android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2"
|
||||||
|
|
|
@ -159,6 +159,17 @@
|
||||||
<string name="pref_category_advanced">Advanced</string>
|
<string name="pref_category_advanced">Advanced</string>
|
||||||
<string name="pref_category_about">About</string>
|
<string name="pref_category_about">About</string>
|
||||||
|
|
||||||
|
<string name="pref_general_summary">App language, notifications</string>
|
||||||
|
<string name="pref_appearance_summary">Theme, date & time format</string>
|
||||||
|
<string name="pref_library_summary">Categories, global update</string>
|
||||||
|
<string name="pref_reader_summary">Reading mode, display, navigation</string>
|
||||||
|
<string name="pref_downloads_summary">Automatic download, download ahead</string>
|
||||||
|
<string name="pref_tracking_summary">One-way progress sync, enhanced sync</string>
|
||||||
|
<string name="pref_browse_summary">Sources, extensions, global search</string>
|
||||||
|
<string name="pref_backup_summary">Manual & automatic backups</string>
|
||||||
|
<string name="pref_security_summary">App lock, secure screen</string>
|
||||||
|
<string name="pref_advanced_summary">Dump crash logs, battery optimizations</string>
|
||||||
|
|
||||||
<!-- General section -->
|
<!-- General section -->
|
||||||
<string name="pref_category_theme">Theme</string>
|
<string name="pref_category_theme">Theme</string>
|
||||||
<string name="pref_theme_mode">Dark mode</string>
|
<string name="pref_theme_mode">Dark mode</string>
|
||||||
|
|
Reference in a new issue