Layout changes:

pin "Add more…" button to bottom of populated favourites screen
group empty-state icon/headline/steps and add top breathing room
spread empty-state evenly and add numbered step prefixes
replace empty-state subtitle with 2-step instruction including inline star icon
hide explainer when favorite feeds exist and add "Add more…" button
This commit is contained in:
davotoula
2026-05-14 22:42:07 +02:00
parent 5f89648c54
commit e73715f64a
2 changed files with 116 additions and 24 deletions
@@ -25,6 +25,7 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
@@ -37,6 +38,8 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material3.Button
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -48,10 +51,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.BottomStart
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
@@ -82,6 +89,8 @@ fun FavoriteAlgoFeedsListScreen(
) {
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
val favorites by accountViewModel.account.favoriteAlgoFeedsList.flowNotes
.collectAsStateWithLifecycle()
DisappearingScaffold(
isInvertedLayout = false,
@@ -113,33 +122,34 @@ fun FavoriteAlgoFeedsListScreen(
bottom = padding.calculateBottomPadding(),
).consumeWindowInsets(padding),
) {
Text(
text = stringRes(R.string.favorite_dvms_explainer),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.grayText,
)
if (favorites.isEmpty()) {
Text(
text = stringRes(R.string.favorite_dvms_explainer),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth().padding(top = 24.dp, bottom = 12.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.grayText,
)
}
FavoriteAlgoFeedList(listState, accountViewModel, nav)
FavoriteAlgoFeedList(favorites, listState, accountViewModel, nav)
}
}
}
@Composable
private fun FavoriteAlgoFeedList(
private fun ColumnScope.FavoriteAlgoFeedList(
favorites: List<AddressableNote>,
listState: LazyListState,
accountViewModel: AccountViewModel,
nav: INav,
) {
val favorites by accountViewModel.account.favoriteAlgoFeedsList.flowNotes
.collectAsStateWithLifecycle()
if (favorites.isEmpty()) {
Box(
modifier = Modifier.fillMaxSize().padding(24.dp),
contentAlignment = Alignment.Center,
Column(
modifier = Modifier.fillMaxSize().padding(horizontal = 24.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.weight(1f))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
@@ -156,22 +166,21 @@ private fun FavoriteAlgoFeedList(
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.SemiBold,
)
Text(
text = stringRes(R.string.favorite_dvms_empty_subtitle),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.grayText,
modifier = Modifier.widthIn(max = 280.dp),
FavoriteAlgoFeedsEmptySteps(
ctaLabel = stringRes(R.string.favorite_dvms_empty_cta),
modifier = Modifier.widthIn(max = 320.dp),
)
Button(onClick = { nav.nav(Route.Discover(initialTab = DiscoverTab.ALGOS)) }) {
Text(text = stringRes(R.string.favorite_dvms_empty_cta))
}
}
Spacer(modifier = Modifier.weight(1f))
Button(onClick = { nav.nav(Route.Discover(initialTab = DiscoverTab.ALGOS)) }) {
Text(text = stringRes(R.string.favorite_dvms_empty_cta))
}
}
return
}
LazyColumn(
modifier = Modifier.weight(1f),
state = listState,
verticalArrangement = Arrangement.spacedBy(4.dp),
contentPadding = FeedPadding,
@@ -188,6 +197,14 @@ private fun FavoriteAlgoFeedList(
)
}
}
Box(
modifier = Modifier.fillMaxWidth().padding(vertical = 32.dp),
contentAlignment = Alignment.Center,
) {
Button(onClick = { nav.nav(Route.Discover(initialTab = DiscoverTab.ALGOS)) }) {
Text(text = stringRes(R.string.favorite_dvms_add_more))
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@@ -267,3 +284,75 @@ private fun FavoriteAlgoFeedRow(
}
}
}
@Composable
private fun FavoriteAlgoFeedsEmptySteps(
ctaLabel: String,
modifier: Modifier = Modifier,
) {
val starInlineId = "star"
val step2Template = stringRes(R.string.favorite_dvms_empty_step2)
val step2Parts = step2Template.split("%1\$s", limit = 2)
val step2Text =
buildAnnotatedString {
append(step2Parts.first())
appendInlineContent(starInlineId, "[star]")
if (step2Parts.size > 1) append(step2Parts[1])
}
val starInlineContent =
mapOf(
starInlineId to
InlineTextContent(
Placeholder(
width = 18.sp,
height = 18.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
),
) {
Icon(
symbol = MaterialSymbols.StarBorder,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
},
)
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
NumberedStep(number = "1.") {
Text(
text = stringRes(R.string.favorite_dvms_empty_step1, ctaLabel),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.grayText,
)
}
NumberedStep(number = "2.") {
Text(
text = step2Text,
inlineContent = starInlineContent,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.grayText,
)
}
}
}
@Composable
private fun NumberedStep(
number: String,
content: @Composable () -> Unit,
) {
Row(verticalAlignment = Alignment.Top) {
Text(
text = number,
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(end = 8.dp),
)
content()
}
}
+3
View File
@@ -2181,7 +2181,10 @@
<string name="favorite_dvms_explainer">Feed algorithms you star appear here and as filter chips on the Home feed.</string>
<string name="favorite_dvms_empty_headline">Pin your favorite algorithms</string>
<string name="favorite_dvms_empty_subtitle">Tap the button below to browse algorithms and star the ones you want to keep here.</string>
<string name="favorite_dvms_empty_step1">Tap \"%1$s\" below to browse algorithms.</string>
<string name="favorite_dvms_empty_step2">Tap the %1$s next to a feed to keep it here.</string>
<string name="favorite_dvms_empty_cta">Add some feeds</string>
<string name="favorite_dvms_add_more">Add more…</string>
<string name="dvm_home_status_requesting">Asking %1$s for a feed…</string>
<string name="dvm_home_status_requesting_all">Asking your favorite feed algorithms for feeds…</string>
<string name="dvm_home_status_processing">Processing your feed…</string>