feat(ui): elevated style reader toolbars (#894)

* feat(ui): toolbar animation

* feat(ui): elevated variant for reader toolbars
This commit is contained in:
junkfood 2024-11-22 23:34:36 +09:00 committed by GitHub
parent 629f489ee3
commit 6e55c12ca8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 66 additions and 64 deletions

View File

@ -15,42 +15,30 @@ val LocalReadingPageTonalElevation =
compositionLocalOf<ReadingPageTonalElevationPreference> { ReadingPageTonalElevationPreference.default }
sealed class ReadingPageTonalElevationPreference(val value: Int) : Preference() {
object Level0 : ReadingPageTonalElevationPreference(ElevationTokens.Level0)
object Level1 : ReadingPageTonalElevationPreference(ElevationTokens.Level1)
object Level2 : ReadingPageTonalElevationPreference(ElevationTokens.Level2)
object Level3 : ReadingPageTonalElevationPreference(ElevationTokens.Level3)
object Level4 : ReadingPageTonalElevationPreference(ElevationTokens.Level4)
object Level5 : ReadingPageTonalElevationPreference(ElevationTokens.Level5)
data object Outlined : ReadingPageTonalElevationPreference(ElevationTokens.Level0)
data object Elevated : ReadingPageTonalElevationPreference(ElevationTokens.Level2)
override fun put(context: Context, scope: CoroutineScope) {
scope.launch {
context.dataStore.put(DataStoreKey.readingPageTonalElevation, value)
context.dataStore.put(readingPageTonalElevation, value)
}
}
fun toDesc(context: Context): String =
when (this) {
Level0 -> "Level 0 (${ElevationTokens.Level0}dp)"
Level1 -> "Level 1 (${ElevationTokens.Level1}dp)"
Level2 -> "Level 2 (${ElevationTokens.Level2}dp)"
Level3 -> "Level 3 (${ElevationTokens.Level3}dp)"
Level4 -> "Level 4 (${ElevationTokens.Level4}dp)"
Level5 -> "Level 5 (${ElevationTokens.Level5}dp)"
Outlined -> "${ElevationTokens.Level0}dp"
Elevated -> "${ElevationTokens.Level2}dp"
}
companion object {
val default = Level0
val values = listOf(Level0, Level1, Level2, Level3, Level4, Level5)
val default = Outlined
val values = listOf(Outlined, Elevated)
fun fromPreferences(preferences: Preferences) =
when (preferences[DataStoreKey.keys[readingPageTonalElevation]?.key as Preferences.Key<Int>]) {
ElevationTokens.Level0 -> Level0
ElevationTokens.Level1 -> Level1
ElevationTokens.Level2 -> Level2
ElevationTokens.Level3 -> Level3
ElevationTokens.Level4 -> Level4
ElevationTokens.Level5 -> Level5
ElevationTokens.Level0 -> Outlined
ElevationTokens.Level2 -> Elevated
else -> default
}
}

View File

@ -2,10 +2,7 @@ package me.ash.reader.ui.page.home.reading
import android.view.HapticFeedbackConstants
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
@ -33,15 +30,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import me.ash.reader.R
import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation
import me.ash.reader.infrastructure.preference.LocalReadingRenderer
import me.ash.reader.infrastructure.preference.ReadingPageTonalElevationPreference
import me.ash.reader.infrastructure.preference.ReadingRendererPreference
import me.ash.reader.ui.component.base.CanBeDisabledIconButton
import me.ash.reader.ui.component.base.RYExtensibleVisibility
import me.ash.reader.ui.component.webview.BionicReadingIcon
@Composable
@ -60,6 +56,7 @@ fun BottomBar(
onReadAloud: () -> Unit = {},
) {
val tonalElevation = LocalReadingPageTonalElevation.current
val isOutlined = tonalElevation == ReadingPageTonalElevationPreference.Outlined
val renderer = LocalReadingRenderer.current
Box(
@ -70,16 +67,20 @@ fun BottomBar(
) {
AnimatedVisibility(
visible = isShow,
enter = expandVertically(),
exit = shrinkVertically()
enter = expandVertically(expandFrom = Alignment.Top),
exit = shrinkVertically(shrinkTowards = Alignment.Top)
) {
val view = LocalView.current
Column {
HorizontalDivider(
color = MaterialTheme.colorScheme.surfaceContainerHighest,
thickness = 0.5f.dp
)
Surface() {
if (isOutlined) {
HorizontalDivider(
color = MaterialTheme.colorScheme.surfaceContainerHighest,
thickness = 0.5f.dp
)
}
Surface(
color = MaterialTheme.colorScheme.run { if (isOutlined) surface else surfaceContainer }
) {
// TODO: Component styles await refactoring
Row(
modifier = Modifier

View File

@ -5,9 +5,7 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.ExperimentalMaterialApi
@ -16,9 +14,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -28,11 +24,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.isSpecified
import androidx.compose.ui.unit.sp
@ -40,7 +34,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import me.ash.reader.R
import me.ash.reader.infrastructure.preference.LocalPullToSwitchArticle
@ -123,7 +116,7 @@ fun ReadingPage(
TopBar(
navController = navController,
isShow = isShowToolBar,
showDivider = showTopDivider,
isScrolled = showTopDivider,
title = readerState.title,
link = readerState.link,
onClick = { bringToTop = true },
@ -194,7 +187,7 @@ fun ReadingPage(
showTopDivider = snapshotFlow {
scrollState.value != 0 || listState.firstVisibleItemIndex != 0
scrollState.value >= 120 || listState.firstVisibleItemIndex != 0
}.collectAsStateValue(initial = false)
CompositionLocalProvider(

View File

@ -1,6 +1,9 @@
package me.ash.reader.ui.page.home.reading
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
@ -15,7 +18,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Share
@ -27,20 +29,22 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation
import me.ash.reader.infrastructure.preference.LocalSharedContent
import me.ash.reader.infrastructure.preference.ReadingPageTonalElevationPreference
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.common.RouteName
@OptIn(ExperimentalMaterial3Api::class)
@ -48,7 +52,7 @@ import me.ash.reader.ui.page.common.RouteName
fun TopBar(
navController: NavHostController,
isShow: Boolean,
showDivider: Boolean = false,
isScrolled: Boolean = false,
title: String? = "",
link: String? = "",
onClick: (() -> Unit)? = null,
@ -56,6 +60,13 @@ fun TopBar(
) {
val context = LocalContext.current
val sharedContent = LocalSharedContent.current
val isOutlined =
LocalReadingPageTonalElevation.current == ReadingPageTonalElevationPreference.Outlined
val containerColor by animateColorAsState(with(MaterialTheme.colorScheme) {
if (isOutlined || !isScrolled) surface else surfaceContainer
}, label = "", animationSpec = spring(stiffness = Spring.StiffnessMediumLow))
Box(
modifier = Modifier
@ -63,29 +74,30 @@ fun TopBar(
.zIndex(1f),
contentAlignment = Alignment.TopCenter
) {
Column(modifier = if (onClick == null) Modifier else Modifier.clickable(
onClick = onClick,
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
Column(
modifier = Modifier.drawBehind { drawRect(containerColor) }
) {
Surface(
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(
WindowInsets.statusBars
.asPaddingValues()
.calculateTopPadding()
)
) {}
),
)
AnimatedVisibility(
visible = isShow,
enter = expandVertically(expandFrom = Alignment.Top),
exit = shrinkVertically(shrinkTowards = Alignment.Top)
enter = expandVertically(expandFrom = Alignment.Bottom),
exit = shrinkVertically(shrinkTowards = Alignment.Bottom)
) {
TopAppBar(
title = {},
modifier = Modifier,
modifier = if (onClick == null) Modifier else Modifier.clickable(
onClick = onClick,
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
windowInsets = WindowInsets(0.dp),
navigationIcon = {
FeedbackIconButton(
@ -95,7 +107,8 @@ fun TopBar(
) {
onClose()
}
}, actions = {
},
actions = {
FeedbackIconButton(
modifier = Modifier.size(22.dp),
imageVector = Icons.Outlined.Palette,
@ -114,10 +127,11 @@ fun TopBar(
) {
sharedContent.share(context, title, link)
}
}, colors = TopAppBarDefaults.topAppBarColors()
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
)
}
if (showDivider) {
if (isOutlined && isScrolled) {
HorizontalDivider(
color = MaterialTheme.colorScheme.surfaceContainerHighest,
thickness = 0.5f.dp

View File

@ -204,14 +204,19 @@ fun ReadingStylePage(
onClick = { pullToSwitchArticle.toggle(context, scope) }) {
RYSwitch(activated = pullToSwitchArticle.value)
}
/* SettingItem(
Subtitle(
modifier = Modifier.padding(horizontal = 24.dp),
text = stringResource(R.string.toolbars)
)
SettingItem(
title = stringResource(R.string.tonal_elevation),
desc = "${tonalElevation.value}dp",
onClick = {
tonalElevationDialogVisible = true
},
) {}
Spacer(modifier = Modifier.height(24.dp))*/
Spacer(modifier = Modifier.height(24.dp))
}
// Advanced
@ -271,7 +276,7 @@ fun ReadingStylePage(
}
)
/* RadioDialog(
RadioDialog(
visible = tonalElevationDialogVisible,
title = stringResource(R.string.tonal_elevation),
options = ReadingPageTonalElevationPreference.values.map {
@ -284,7 +289,7 @@ fun ReadingStylePage(
}
) {
tonalElevationDialogVisible = false
}*/
}
RadioDialog(
visible = rendererDialogVisible,

View File

@ -460,4 +460,5 @@
<string name="mark_as_read_on_scroll">Mark as read on scroll</string>
<string name="become_a_sponsor">Become a sponsor</string>
<string name="sponsor_desc">We build and upkeep this free, open-source app in our off-hours. If you enjoy it, please consider supporting us with a small donation! ☕️</string>
<string name="toolbars">Toolbars</string>
</resources>