compose: add shared composable foundations
EmptyState, SectionHeader, Shimmer placeholders; TopAppBar and ScrollHint refinements to support the migrated screens.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composable
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||
|
||||
@Composable
|
||||
fun EmptyState(
|
||||
modifier: Modifier = Modifier,
|
||||
@DrawableRes icon: Int,
|
||||
@StringRes message: Int,
|
||||
@StringRes actionLabel: Int? = null,
|
||||
onAction: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
dimensionResource(R.dimen.margin_small),
|
||||
Alignment.CenterVertically
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(dimensionResource(R.dimen.icon_size_medium)),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(message),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
|
||||
if (actionLabel != null) {
|
||||
FilledTonalButton(onClick = onAction) {
|
||||
Text(stringResource(actionLabel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun EmptyStatePreview() {
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_updates,
|
||||
message = R.string.details_no_updates,
|
||||
actionLabel = R.string.check_updates
|
||||
)
|
||||
}
|
||||
@@ -36,8 +36,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment.Companion.Center
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aurora.store.R
|
||||
@@ -48,7 +48,6 @@ import kotlinx.coroutines.launch
|
||||
fun ScrollHint(
|
||||
listState: LazyListState,
|
||||
modifier: Modifier = Modifier,
|
||||
bottomPadding: Dp = 72.dp, // keep above bottom bar
|
||||
autoHideOnScroll: Boolean = true,
|
||||
enableBounce: Boolean = true,
|
||||
onClickScrollOffset: Float = 300f
|
||||
@@ -88,7 +87,7 @@ fun ScrollHint(
|
||||
visible = visible,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = bottomPadding),
|
||||
.padding(bottom = dimensionResource(R.dimen.scroll_hint_padding)),
|
||||
enter = fadeIn() + slideInVertically { it / 2 },
|
||||
exit = fadeOut()
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composable
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||
|
||||
/**
|
||||
* Section header row with optional right-chevron for navigation (replaces HeaderView).
|
||||
* When [browseUrl] is non-null and non-blank the row is clickable and shows a chevron.
|
||||
*/
|
||||
@Composable
|
||||
fun SectionHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
browseUrl: String? = null,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
val hasLink = !browseUrl.isNullOrBlank()
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.then(if (hasLink) Modifier.clickable(onClick = onClick) else Modifier)
|
||||
.padding(all = dimensionResource(R.dimen.padding_medium)),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
if (hasLink) {
|
||||
Spacer(Modifier.width(dimensionResource(R.dimen.margin_small)))
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(dimensionResource(R.dimen.icon_size_default)),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section header row with a right-aligned action button (replaces UpdateHeaderView).
|
||||
*/
|
||||
@Composable
|
||||
fun SectionHeaderWithAction(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
action: String,
|
||||
onAction: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = dimensionResource(R.dimen.padding_small),
|
||||
top = dimensionResource(R.dimen.padding_xxsmall),
|
||||
bottom = dimensionResource(R.dimen.padding_xxsmall)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
TextButton(onClick = onAction) {
|
||||
Text(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SectionHeaderPreview() {
|
||||
SectionHeader(title = "Top Charts", browseUrl = "https://example.com")
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SectionHeaderWithActionPreview() {
|
||||
SectionHeaderWithAction(title = "3 updates available", action = "Update all")
|
||||
}
|
||||
274
app/src/main/java/com/aurora/store/compose/composable/Shimmer.kt
Normal file
274
app/src/main/java/com/aurora/store/compose/composable/Shimmer.kt
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Aurora OSS
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.compose.composable
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||
|
||||
@Composable
|
||||
fun shimmerBrush(): Brush {
|
||||
val base = MaterialTheme.colorScheme.onSurface
|
||||
val colors = listOf(
|
||||
base.copy(alpha = 0.08f),
|
||||
base.copy(alpha = 0.20f),
|
||||
base.copy(alpha = 0.08f)
|
||||
)
|
||||
val transition = rememberInfiniteTransition(label = "shimmer")
|
||||
val offset by transition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(1000, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Restart
|
||||
),
|
||||
label = "shimmer_offset"
|
||||
)
|
||||
val x = offset * 1200f
|
||||
return Brush.linearGradient(
|
||||
colors = colors,
|
||||
start = Offset(x - 400f, 0f),
|
||||
end = Offset(x, 400f)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ShimmerCategoryRow() {
|
||||
val brush = shimmerBrush()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_medium),
|
||||
vertical = dimensionResource(R.dimen.padding_small)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_large))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_category))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(24.dp)
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ShimmerAppRow() {
|
||||
val brush = shimmerBrush()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_small),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.margin_small))
|
||||
) {
|
||||
repeat(3) { i ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(if (i == 0) 0.75f else 0.5f)
|
||||
.height(14.dp)
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
if (i < 2) Spacer(Modifier.height(dimensionResource(R.dimen.padding_xxsmall)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ShimmerUpdateItem() {
|
||||
val brush = shimmerBrush()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = dimensionResource(R.dimen.padding_small),
|
||||
vertical = dimensionResource(R.dimen.padding_xsmall)
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.requiredSize(dimensionResource(R.dimen.icon_size_medium))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
repeat(3) { i ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(if (i == 0) 0.7f else 0.5f)
|
||||
.height(14.dp)
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
if (i < 2) Spacer(Modifier.height(dimensionResource(R.dimen.padding_xxsmall)))
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(80.dp)
|
||||
.height(36.dp)
|
||||
.clip(RoundedCornerShape(50))
|
||||
.background(brush)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ShimmerCarouselSection() {
|
||||
val brush = shimmerBrush()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = dimensionResource(R.dimen.padding_small)),
|
||||
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.42f)
|
||||
.height(16.dp)
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_small))
|
||||
) {
|
||||
repeat(5) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
dimensionResource(R.dimen.padding_xsmall)
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(dimensionResource(R.dimen.icon_size_cluster))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
.background(brush)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(dimensionResource(R.dimen.icon_size_cluster) * 0.75f)
|
||||
.height(11.dp)
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(dimensionResource(R.dimen.icon_size_cluster) * 0.5f)
|
||||
.height(11.dp)
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small)))
|
||||
.background(brush)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ShimmerAppListItem() {
|
||||
val brush = shimmerBrush()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(dimensionResource(R.dimen.padding_xsmall))
|
||||
.size(dimensionResource(R.dimen.icon_size_cluster))
|
||||
.clip(RoundedCornerShape(dimensionResource(R.dimen.radius_medium)))
|
||||
.background(brush)
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ShimmerCategoryRowPreview() {
|
||||
ShimmerCategoryRow()
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ShimmerAppRowPreview() {
|
||||
ShimmerAppRow()
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ShimmerUpdateItemPreview() {
|
||||
ShimmerUpdateItem()
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ShimmerCarouselSectionPreview() {
|
||||
ShimmerCarouselSection()
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ShimmerAppListItemPreview() {
|
||||
ShimmerAppListItem()
|
||||
}
|
||||
@@ -5,11 +5,15 @@
|
||||
|
||||
package com.aurora.store.compose.composable
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.LocalActivity
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
@@ -25,7 +29,7 @@ import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||
* @param modifier The modifier to be applied to the composable
|
||||
* @param title Title of the screen
|
||||
* @param navigationIcon Icon for the navigation button
|
||||
* @param onNavigateUp Action when user clicks the navigation icon
|
||||
* @param showNavigationIcon Whether to show the navigation (back) icon button
|
||||
* @param actions Actions to display on the top app bar (for e.g. menu)
|
||||
*/
|
||||
@Composable
|
||||
@@ -33,15 +37,17 @@ fun TopAppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
navigationIcon: Painter = painterResource(R.drawable.ic_arrow_back),
|
||||
onNavigateUp: (() -> Unit)? = null,
|
||||
showNavigationIcon: Boolean = true,
|
||||
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
|
||||
actions: @Composable (RowScope.() -> Unit) = {}
|
||||
) {
|
||||
val activity = LocalActivity.current as? ComponentActivity
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
title = { if (title != null) Text(text = title) },
|
||||
navigationIcon = {
|
||||
if (onNavigateUp != null) {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
if (showNavigationIcon) {
|
||||
IconButton(onClick = { activity?.onBackPressedDispatcher?.onBackPressed() }) {
|
||||
Icon(
|
||||
painter = navigationIcon,
|
||||
contentDescription = stringResource(R.string.action_back)
|
||||
@@ -49,6 +55,7 @@ fun TopAppBar(
|
||||
}
|
||||
}
|
||||
},
|
||||
windowInsets = windowInsets,
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
@@ -58,7 +65,6 @@ fun TopAppBar(
|
||||
@Composable
|
||||
private fun TopAppBarPreview() {
|
||||
TopAppBar(
|
||||
title = stringResource(R.string.title_about),
|
||||
onNavigateUp = {}
|
||||
title = stringResource(R.string.title_about)
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user