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 {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
|
|
|
@ -56,6 +56,7 @@ import androidx.compose.ui.unit.dp
|
|||
* @sample androidx.compose.material3.samples.ScaffoldWithSimpleSnackbar
|
||||
*
|
||||
* Tachiyomi changes:
|
||||
* * Pass scroll behavior to top bar by default
|
||||
* * Remove height constraint for expanded app bar
|
||||
* * Also take account of fab height when providing inner padding
|
||||
*
|
||||
|
@ -80,6 +81,7 @@ import androidx.compose.ui.unit.dp
|
|||
@Composable
|
||||
fun Scaffold(
|
||||
modifier: Modifier = Modifier,
|
||||
topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||
topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {},
|
||||
bottomBar: @Composable () -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
|
@ -89,21 +91,16 @@ fun Scaffold(
|
|||
contentColor: Color = contentColorFor(containerColor),
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
/**
|
||||
* Tachiyomi: Pass scroll behavior to topBar
|
||||
*/
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
androidx.compose.material3.Surface(
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.nestedScroll(topBarScrollBehavior.nestedScrollConnection)
|
||||
.then(modifier),
|
||||
color = containerColor,
|
||||
contentColor = contentColor,
|
||||
) {
|
||||
ScaffoldLayout(
|
||||
fabPosition = floatingActionButtonPosition,
|
||||
topBar = { topBar(scrollBehavior) },
|
||||
topBar = { topBar(topBarScrollBehavior) },
|
||||
bottomBar = bottomBar,
|
||||
content = content,
|
||||
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.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
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.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||
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.Scaffold
|
||||
import eu.kanade.presentation.components.SwipeRefresh
|
||||
import eu.kanade.presentation.components.TwoPanelBox
|
||||
import eu.kanade.presentation.components.VerticalFastScroller
|
||||
import eu.kanade.presentation.manga.components.ChapterHeader
|
||||
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
|
||||
|
@ -501,14 +498,10 @@ fun MangaScreenLargeImpl(
|
|||
}
|
||||
},
|
||||
) { contentPadding ->
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
||||
val firstWidth = (maxWidth / 2).coerceAtMost(450.dp)
|
||||
val secondWidth = maxWidth - firstWidth
|
||||
|
||||
TwoPanelBox(
|
||||
startContent = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.width(firstWidth)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
MangaInfoBox(
|
||||
|
@ -539,12 +532,10 @@ fun MangaScreenLargeImpl(
|
|||
onTagClicked = onTagClicked,
|
||||
)
|
||||
}
|
||||
|
||||
},
|
||||
endContent = {
|
||||
VerticalFastScroller(
|
||||
listState = chapterListState,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.width(secondWidth),
|
||||
topContentPadding = contentPadding.calculateTopPadding(),
|
||||
) {
|
||||
LazyColumn(
|
||||
|
@ -573,7 +564,8 @@ fun MangaScreenLargeImpl(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,25 +2,48 @@ package eu.kanade.presentation.more.settings
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
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.ui.Modifier
|
||||
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.tachiyomi.R
|
||||
|
||||
@Composable
|
||||
fun PreferenceScaffold(
|
||||
@StringRes titleRes: Int,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
onBackPressed: () -> Unit = {},
|
||||
onBackPressed: (() -> Unit)? = null,
|
||||
itemsProvider: @Composable () -> List<Preference>,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
AppBar(
|
||||
title = stringResource(titleRes),
|
||||
navigateUp = onBackPressed,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
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,
|
||||
scrollBehavior = scrollBehavior,
|
||||
scrollBehavior = it,
|
||||
)
|
||||
},
|
||||
content = { contentPadding ->
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -12,7 +11,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import eu.kanade.presentation.components.Divider
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.more.settings.screen.SearchableSettings
|
||||
import eu.kanade.presentation.more.settings.widget.PreferenceGroupHeader
|
||||
|
@ -55,9 +53,6 @@ fun PreferenceScreen(
|
|||
|
||||
item {
|
||||
Column {
|
||||
if (i != 0) {
|
||||
Divider(modifier = Modifier.padding(bottom = 8.dp))
|
||||
}
|
||||
PreferenceGroupHeader(title = preference.title)
|
||||
}
|
||||
}
|
||||
|
@ -68,9 +63,11 @@ fun PreferenceScreen(
|
|||
)
|
||||
}
|
||||
item {
|
||||
if (i < items.lastIndex) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create Preference Item
|
||||
is Preference.PreferenceItem<*> -> item {
|
||||
|
|
|
@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.RowScope
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
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.PreferenceScaffold
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
|
@ -26,10 +25,10 @@ interface SearchableSettings : Screen {
|
|||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val handleBack = LocalBackPress.currentOrThrow
|
||||
val handleBack = LocalBackPress.current
|
||||
PreferenceScaffold(
|
||||
titleRes = getTitleRes(),
|
||||
onBackPressed = handleBack::invoke,
|
||||
onBackPressed = if (handleBack != null) handleBack::invoke else null,
|
||||
actions = { AppBarAction() },
|
||||
itemsProvider = { getPreferences() },
|
||||
)
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
package eu.kanade.presentation.more.settings.screen
|
||||
|
||||
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.filled.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.ChromeReaderMode
|
||||
import androidx.compose.material.icons.outlined.Code
|
||||
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.Sync
|
||||
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.NonRestartableComposable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
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.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.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.PreferenceScaffold
|
||||
import eu.kanade.presentation.components.LazyColumn
|
||||
import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
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()) },
|
||||
),
|
||||
)
|
||||
override fun Content() {
|
||||
Content(twoPane = false)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
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 backPress = LocalBackPress.currentOrThrow
|
||||
PreferenceScaffold(
|
||||
titleRes = getTitleRes(),
|
||||
val containerColor = if (twoPane) getPalerSurface() else MaterialTheme.colorScheme.surface
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
// https://issuetracker.google.com/issues/249688556
|
||||
MaterialTheme(
|
||||
colorScheme = MaterialTheme.colorScheme.copy(surface = containerColor),
|
||||
) {
|
||||
TopAppBar(
|
||||
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.push(SettingsSearchScreen()) },
|
||||
onClick = { navigator.navigate(SettingsSearchScreen(), twoPane) },
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
onBackPressed = backPress::invoke,
|
||||
itemsProvider = { getPreferences() },
|
||||
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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
) { result ->
|
||||
SearchableSettings.highlightKey = result.highlightKey
|
||||
navigator.popUntil { it is SettingsMainScreen }
|
||||
navigator.push(result.route)
|
||||
navigator.replace(result.route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.RowScope
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
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.Visibility
|
||||
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.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -30,6 +30,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
|
@ -189,7 +190,20 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||
|
||||
AlertDialog(
|
||||
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 = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
OutlinedTextField(
|
||||
|
@ -232,7 +246,6 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Column {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !processing,
|
||||
|
@ -258,13 +271,6 @@ class SettingsTrackingScreen : SearchableSettings {
|
|||
val id = if (processing) R.string.loading else R.string.login
|
||||
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,
|
||||
textAlign = TextAlign.Center,
|
||||
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.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -54,12 +55,12 @@ internal fun BasePreferenceWidget(
|
|||
modifier = Modifier
|
||||
.padding(
|
||||
start = HorizontalPadding,
|
||||
top = 4.dp,
|
||||
top = 0.dp,
|
||||
end = HorizontalPadding,
|
||||
)
|
||||
.secondaryItemAlpha(),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 10,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -106,15 +107,13 @@ private fun BasePreferenceWidgetImpl(
|
|||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(start = HorizontalPadding, end = 12.dp)
|
||||
.secondaryItemAlpha(),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
.padding(start = HorizontalPadding, end = 0.dp),
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(vertical = 14.dp),
|
||||
.padding(vertical = 16.dp),
|
||||
) {
|
||||
if (title.isNotBlank()) {
|
||||
Row(
|
||||
|
@ -125,7 +124,8 @@ private fun BasePreferenceWidgetImpl(
|
|||
text = title,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
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 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.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
|
@ -16,6 +17,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.util.isScrolledToEnd
|
||||
import eu.kanade.presentation.util.isScrolledToStart
|
||||
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||
|
||||
@Composable
|
||||
fun <T> ListPreferenceWidget(
|
||||
|
@ -86,20 +89,22 @@ private fun DialogRow(
|
|||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { if (!isSelected) onSelected() },
|
||||
),
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.minimumTouchTargetSize(),
|
||||
) {
|
||||
RadioButton(
|
||||
selected = isSelected,
|
||||
onClick = { if (!isSelected) onSelected() },
|
||||
onClick = null,
|
||||
)
|
||||
Text(
|
||||
text = label,
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
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.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -16,10 +17,12 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.util.minimumTouchTargetSize
|
||||
|
||||
@Composable
|
||||
fun MultiSelectListPreferenceWidget(
|
||||
|
@ -59,17 +62,22 @@ fun MultiSelectListPreferenceWidget(
|
|||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onSelectionChanged() },
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { onSelectionChanged() },
|
||||
)
|
||||
.minimumTouchTargetSize()
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Checkbox(
|
||||
checked = isSelected,
|
||||
onCheckedChange = { onSelectionChanged() },
|
||||
onCheckedChange = null,
|
||||
)
|
||||
Text(
|
||||
text = current.value,
|
||||
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 = title,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,18 +8,20 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun TextPreferenceWidget(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
subtitle: String? = null,
|
||||
icon: ImageVector? = null,
|
||||
onPreferenceClick: (() -> Unit)? = null,
|
||||
) {
|
||||
// TODO: Handle auth requirement here?
|
||||
BasePreferenceWidget(
|
||||
modifier = modifier,
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
icon = icon,
|
||||
|
|
|
@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted
|
||||
|
||||
@Composable
|
||||
|
@ -39,7 +40,7 @@ fun TrackingPreferenceWidget(
|
|||
modifier = modifier
|
||||
.clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
.padding(horizontal = HorizontalPadding, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
|
@ -60,7 +61,8 @@ fun TrackingPreferenceWidget(
|
|||
.weight(1f)
|
||||
.padding(horizontal = 16.dp),
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
if (checked) {
|
||||
Icon(
|
||||
|
|
|
@ -79,7 +79,7 @@ fun <T> TriStateListDialog(
|
|||
val state = selected[index]
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(25))
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable {
|
||||
selected[index] = when (state) {
|
||||
State.UNCHECKED -> State.CHECKED
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.core.os.bundleOf
|
||||
import cafe.adriel.voyager.core.stack.StackEvent
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
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.SettingsGeneralScreen
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.presentation.util.calculateWindowWidthSizeClass
|
||||
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 {
|
||||
|
||||
|
@ -25,20 +30,52 @@ class SettingsMainController : BasicFullComposeController {
|
|||
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
CompositionLocalProvider(LocalRouter provides router) {
|
||||
val widthSizeClass = calculateWindowWidthSizeClass()
|
||||
if (widthSizeClass == WindowWidthSizeClass.Compact) {
|
||||
Navigator(
|
||||
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsMainScreen,
|
||||
content = {
|
||||
CompositionLocalProvider(
|
||||
LocalRouter provides router,
|
||||
LocalBackPress provides this::back,
|
||||
) {
|
||||
CompositionLocalProvider(LocalBackPress provides this::back) {
|
||||
val slideDistance = rememberSlideDistance()
|
||||
ScreenTransition(
|
||||
navigator = it,
|
||||
transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) },
|
||||
transition = {
|
||||
materialSharedAxisX(
|
||||
forward = it.lastEvent != StackEvent.Pop,
|
||||
slideDistance = slideDistance,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Navigator(
|
||||
screen = if (toBackupScreen) SettingsBackupScreen() else SettingsGeneralScreen(),
|
||||
) {
|
||||
TwoPanelBox(
|
||||
startContent = {
|
||||
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() {
|
||||
|
|
|
@ -8,7 +8,7 @@ flowbinding_version = "1.2.0"
|
|||
shizuku_version = "12.2.0"
|
||||
sqldelight = "1.5.4"
|
||||
leakcanary = "2.9.1"
|
||||
voyager = "1.0.0-rc02"
|
||||
voyager = "1.0.0-beta16"
|
||||
|
||||
[libraries]
|
||||
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_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 -->
|
||||
<string name="pref_category_theme">Theme</string>
|
||||
<string name="pref_theme_mode">Dark mode</string>
|
||||
|
|
Reference in a new issue