feat(ui): separate toolbars from the content below with dividers (#747)

This commit is contained in:
junkfood 2024-11-10 20:27:55 +09:00 committed by GitHub
parent ed7c272193
commit 99e1d894e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 214 additions and 157 deletions

View File

@ -1,6 +1,13 @@
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
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
@ -18,6 +25,7 @@ import androidx.compose.material.icons.outlined.Headphones
import androidx.compose.material.icons.rounded.ExpandMore
import androidx.compose.material.icons.rounded.Star
import androidx.compose.material.icons.rounded.StarOutline
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
@ -25,6 +33,7 @@ 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
@ -59,113 +68,120 @@ fun BottomBar(
.zIndex(1f),
contentAlignment = Alignment.BottomCenter
) {
RYExtensibleVisibility(visible = isShow) {
AnimatedVisibility(
visible = isShow,
enter = expandVertically(),
exit = shrinkVertically()
) {
val view = LocalView.current
Surface(
tonalElevation = tonalElevation.value.dp,
) {
// TODO: Component styles await refactoring
Row(
modifier = Modifier
.navigationBarsPadding()
.fillMaxWidth()
.height(60.dp),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
) {
CanBeDisabledIconButton(
modifier = Modifier.size(40.dp),
disabled = false,
imageVector = if (isUnread) {
Icons.Filled.FiberManualRecord
} else {
Icons.Outlined.FiberManualRecord
},
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
tint = if (isUnread) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
},
Column {
HorizontalDivider(
color = MaterialTheme.colorScheme.surfaceContainerHighest,
thickness = 0.5f.dp
)
Surface() {
// TODO: Component styles await refactoring
Row(
modifier = Modifier
.navigationBarsPadding()
.fillMaxWidth()
.height(60.dp),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onUnread(!isUnread)
}
CanBeDisabledIconButton(
modifier = Modifier.size(40.dp),
disabled = false,
imageVector = if (isStarred) {
Icons.Rounded.Star
} else {
Icons.Rounded.StarOutline
},
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
tint = if (isStarred) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onStarred(!isStarred)
}
CanBeDisabledIconButton(
disabled = !isNextArticleAvailable,
modifier = Modifier.size(40.dp),
imageVector = Icons.Rounded.ExpandMore,
contentDescription = "Next Article",
tint = MaterialTheme.colorScheme.outline,
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onNextArticle()
}
CanBeDisabledIconButton(
modifier = Modifier.size(36.dp),
disabled = false,
imageVector = if (renderer == ReadingRendererPreference.WebView) null else Icons.Outlined.Headphones,
contentDescription = if (renderer == ReadingRendererPreference.WebView) {
stringResource(R.string.bionic_reading)
} else {
stringResource(R.string.read_aloud)
},
tint = MaterialTheme.colorScheme.outline,
icon = {
BionicReadingIcon(
filled = isBionicReading,
size = 24.dp,
tint = if (renderer == ReadingRendererPreference.WebView) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
}
)
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (renderer == ReadingRendererPreference.WebView) {
onBionicReading()
} else {
onReadAloud()
CanBeDisabledIconButton(
modifier = Modifier.size(40.dp),
disabled = false,
imageVector = if (isUnread) {
Icons.Filled.FiberManualRecord
} else {
Icons.Outlined.FiberManualRecord
},
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
tint = if (isUnread) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onUnread(!isUnread)
}
CanBeDisabledIconButton(
modifier = Modifier.size(40.dp),
disabled = false,
imageVector = if (isStarred) {
Icons.Rounded.Star
} else {
Icons.Rounded.StarOutline
},
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
tint = if (isStarred) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onStarred(!isStarred)
}
CanBeDisabledIconButton(
disabled = !isNextArticleAvailable,
modifier = Modifier.size(40.dp),
imageVector = Icons.Rounded.ExpandMore,
contentDescription = "Next Article",
tint = MaterialTheme.colorScheme.outline,
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onNextArticle()
}
CanBeDisabledIconButton(
modifier = Modifier.size(36.dp),
disabled = false,
imageVector = if (renderer == ReadingRendererPreference.WebView) null else Icons.Outlined.Headphones,
contentDescription = if (renderer == ReadingRendererPreference.WebView) {
stringResource(R.string.bionic_reading)
} else {
stringResource(R.string.read_aloud)
},
tint = MaterialTheme.colorScheme.outline,
icon = {
BionicReadingIcon(
filled = isBionicReading,
size = 24.dp,
tint = if (renderer == ReadingRendererPreference.WebView) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
}
)
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (renderer == ReadingRendererPreference.WebView) {
onBionicReading()
} else {
onReadAloud()
}
}
CanBeDisabledIconButton(
disabled = false,
modifier = Modifier.size(40.dp),
imageVector = if (isFullContent) {
Icons.AutoMirrored.Rounded.Article
} else {
Icons.AutoMirrored.Outlined.Article
},
contentDescription = stringResource(R.string.parse_full_content),
tint = if (isFullContent) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onFullContent(!isFullContent)
}
}
CanBeDisabledIconButton(
disabled = false,
modifier = Modifier.size(40.dp),
imageVector = if (isFullContent) {
Icons.AutoMirrored.Rounded.Article
} else {
Icons.AutoMirrored.Outlined.Article
},
contentDescription = stringResource(R.string.parse_full_content),
tint = if (isFullContent) {
MaterialTheme.colorScheme.onSecondaryContainer
} else {
MaterialTheme.colorScheme.outline
},
) {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onFullContent(!isFullContent)
}
}
}

View File

@ -68,6 +68,7 @@ fun Content(
}
} else {
when (renderer) {
ReadingRendererPreference.WebView -> {
Column(
@ -81,8 +82,7 @@ fun Content(
Spacer(modifier = Modifier.height(64.dp))
// padding
Column(
modifier = Modifier
.padding(horizontal = 12.dp)
modifier = Modifier.padding(horizontal = 12.dp)
) {
DisableSelection {
Metadata(

View File

@ -21,6 +21,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -73,6 +74,10 @@ fun ReadingPage(
true
}
var showTopDivider by remember {
mutableStateOf(false)
}
val pagingItems = homeUiState.pagingData.collectAsLazyPagingItems().itemSnapshotList
LaunchedEffect(Unit) {
@ -97,23 +102,22 @@ fun ReadingPage(
Scaffold(
containerColor = MaterialTheme.colorScheme.surface,
// topBarTonalElevation = tonalElevation.value.dp,
// containerTonalElevation = tonalElevation.value.dp,
content = { paddings ->
Log.i("RLog", "TopBar: recomposition")
Box(modifier = Modifier.fillMaxSize()) {
// Top Bar
TopBar(
navController = navController,
isShow = isShowToolBar,
windowInsets = WindowInsets(top = paddings.calculateTopPadding()),
title = readerState.title,
link = readerState.link,
onClose = {
navController.popBackStack()
},
)
if (readerState.articleId != null) {
TopBar(
navController = navController,
isShow = isShowToolBar,
showDivider = showTopDivider,
title = readerState.title,
link = readerState.link,
onClose = {
navController.popBackStack()
},
)
}
val isNextArticleAvailable = !readerState.nextArticleId.isNullOrEmpty()
val isPreviousArticleAvailable = !readerState.previousArticleId.isNullOrEmpty()
@ -163,6 +167,9 @@ fun ReadingPage(
saver = LazyListState.Saver
) { LazyListState() }
showTopDivider = snapshotFlow {
listState.firstVisibleItemIndex != 0
}.collectAsStateValue(initial = false)
CompositionLocalProvider(
LocalOverscrollConfiguration provides

View File

@ -1,15 +1,27 @@
package me.ash.reader.ui.page.home.reading
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
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
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
@ -17,6 +29,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
@ -24,7 +37,6 @@ import me.ash.reader.R
import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation
import me.ash.reader.infrastructure.preference.LocalSharedContent
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYExtensibleVisibility
import me.ash.reader.ui.ext.surfaceColorAtElevation
import me.ash.reader.ui.page.common.RouteName
@ -33,7 +45,7 @@ import me.ash.reader.ui.page.common.RouteName
fun TopBar(
navController: NavHostController,
isShow: Boolean,
windowInsets: WindowInsets = WindowInsets(0.dp),
showDivider: Boolean = false,
title: String? = "",
link: String? = "",
onClose: () -> Unit = {},
@ -48,43 +60,65 @@ fun TopBar(
.zIndex(1f),
contentAlignment = Alignment.TopCenter
) {
RYExtensibleVisibility(visible = isShow) {
TopAppBar(
title = {},
modifier = Modifier,
windowInsets = windowInsets,
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.Close,
contentDescription = stringResource(R.string.close),
tint = MaterialTheme.colorScheme.onSurface
) {
onClose()
}
},
actions = {
FeedbackIconButton(
modifier = Modifier.size(22.dp),
imageVector = Icons.Outlined.Palette,
contentDescription = stringResource(R.string.style),
tint = MaterialTheme.colorScheme.onSurface
) {
navController.navigate(RouteName.READING_PAGE_STYLE) {
launchSingleTop = true
Column {
Surface(
modifier = Modifier
.fillMaxWidth()
.height(
WindowInsets.statusBars
.asPaddingValues()
.calculateTopPadding()
)
) {}
AnimatedVisibility(
visible = isShow,
enter = expandVertically(expandFrom = Alignment.Top),
exit = shrinkVertically(shrinkTowards = Alignment.Top)
) {
TopAppBar(
title = {},
modifier = Modifier,
windowInsets = WindowInsets(0.dp),
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.Close,
contentDescription = stringResource(R.string.close),
tint = MaterialTheme.colorScheme.onSurface
) {
onClose()
}
}
FeedbackIconButton(
modifier = Modifier.size(20.dp),
imageVector = Icons.Outlined.Share,
contentDescription = stringResource(R.string.share),
tint = MaterialTheme.colorScheme.onSurface,
) {
sharedContent.share(context, title, link)
}
}, colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(tonalElevation.value.dp),
}, actions = {
FeedbackIconButton(
modifier = Modifier.size(22.dp),
imageVector = Icons.Outlined.Palette,
contentDescription = stringResource(R.string.style),
tint = MaterialTheme.colorScheme.onSurface
) {
navController.navigate(RouteName.READING_PAGE_STYLE) {
launchSingleTop = true
}
}
FeedbackIconButton(
modifier = Modifier.size(20.dp),
imageVector = Icons.Outlined.Share,
contentDescription = stringResource(R.string.share),
tint = MaterialTheme.colorScheme.onSurface,
) {
sharedContent.share(context, title, link)
}
}, colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
tonalElevation.value.dp
),
)
)
)
}
if (showDivider) {
HorizontalDivider(
color = MaterialTheme.colorScheme.surfaceContainerHighest,
thickness = 0.5f.dp
)
}
}
}
}
}