mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-06 13:08:24 +00:00
feat: show app name byline and bold version pill
Drop app ID and channel from card/detail headers; attribute apps with a smaller profile byline and keep a bold blue version pill.
This commit is contained in:
@@ -22,10 +22,14 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
|
||||
@Composable
|
||||
@@ -69,6 +73,8 @@ fun AppCard(
|
||||
release: ReleaseInfo? = null,
|
||||
onProfileClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val authorName = rememberProfileDisplayName(app.event.pubKey, repository)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -77,42 +83,27 @@ fun AppCard(
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(verticalAlignment = Alignment.Top) {
|
||||
AppIcon(
|
||||
title = app.name,
|
||||
iconUrl = app.iconUrl,
|
||||
modifier = Modifier.size(52.dp),
|
||||
)
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = app.name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = buildString {
|
||||
release?.let {
|
||||
append(it.version)
|
||||
it.channel?.let { channel -> append(" · ").append(channel) }
|
||||
append(" · ")
|
||||
}
|
||||
append(app.identifier)
|
||||
},
|
||||
color = ZapMuted,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
Spacer(modifier.width(12.dp))
|
||||
Column(modifier.weight(1f)) {
|
||||
AppNameWithByline(
|
||||
name = app.name,
|
||||
authorName = authorName,
|
||||
nameStyle = MaterialTheme.typography.titleMedium,
|
||||
onAuthorClick = onProfileClick,
|
||||
authorTestTag = "profile:${app.event.pubKey}",
|
||||
)
|
||||
release?.let {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
VersionPill(version = it.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
ProfileComponent(
|
||||
pubkey = app.event.pubKey,
|
||||
repository = repository,
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onProfileClick,
|
||||
)
|
||||
if (app.summary.isNotBlank()) {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text(
|
||||
@@ -126,6 +117,56 @@ fun AppCard(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppNameWithByline(
|
||||
name: String,
|
||||
authorName: String,
|
||||
nameStyle: TextStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
onAuthorClick: (() -> Unit)? = null,
|
||||
authorTestTag: String? = null,
|
||||
) {
|
||||
val bylineSize = (nameStyle.fontSize.value * 0.72f).sp
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = name,
|
||||
style = nameStyle,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.app_by_author, authorName),
|
||||
color = ZapMuted,
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontSize = bylineSize,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontFamily = InterFontFamily,
|
||||
),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.then(authorTestTag?.let { Modifier.testTag(it) } ?: Modifier)
|
||||
.then(onAuthorClick?.let { Modifier.clickable(onClick = it) } ?: Modifier),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VersionPill(
|
||||
version: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = version,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold),
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(ZapPrimary)
|
||||
.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StackCard(
|
||||
stack: StackInfo,
|
||||
@@ -154,7 +195,7 @@ fun StackCard(
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Spacer(modifier.height(12.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
stack.appAddresses.take(3).forEach { address ->
|
||||
val app = appsByAddress[address]
|
||||
|
||||
@@ -27,12 +27,7 @@ import kotlinx.coroutines.flow.collect
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@Composable
|
||||
fun ProfileComponent(
|
||||
pubkey: String,
|
||||
repository: CatalogRepository?,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
fun rememberProfile(pubkey: String, repository: CatalogRepository?): ProfileInfo? {
|
||||
val profile by produceState<ProfileInfo?>(initialValue = null, pubkey, repository) {
|
||||
if (repository == null) return@produceState
|
||||
|
||||
@@ -45,12 +40,30 @@ fun ProfileComponent(
|
||||
value = state.items.maxByOrNull(Event::createdAt)?.let(::ProfileInfo)
|
||||
}
|
||||
}
|
||||
return profile
|
||||
}
|
||||
|
||||
val npub = pubkey.toNpub()
|
||||
val displayName = profile?.displayName
|
||||
fun profileDisplayName(profile: ProfileInfo?, pubkey: String): String =
|
||||
profile?.displayName
|
||||
?.takeIf(String::isNotBlank)
|
||||
?: profile?.name?.takeIf(String::isNotBlank)
|
||||
?: npub
|
||||
?: pubkey.toNpub()
|
||||
|
||||
@Composable
|
||||
fun rememberProfileDisplayName(
|
||||
pubkey: String,
|
||||
repository: CatalogRepository?,
|
||||
): String = profileDisplayName(rememberProfile(pubkey, repository), pubkey)
|
||||
|
||||
@Composable
|
||||
fun ProfileComponent(
|
||||
pubkey: String,
|
||||
repository: CatalogRepository?,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val profile = rememberProfile(pubkey, repository)
|
||||
val displayName = profileDisplayName(profile, pubkey)
|
||||
val pictureUrl = profile?.picture?.takeIf(::isHttpUrl)
|
||||
|
||||
Row(
|
||||
|
||||
@@ -296,44 +296,29 @@ fun AppDetailScreen(
|
||||
}
|
||||
|
||||
item {
|
||||
val authorName = rememberProfileDisplayName(app.event.pubKey, repository)
|
||||
Row(verticalAlignment = Alignment.Top) {
|
||||
AppIcon(
|
||||
title = app.name,
|
||||
iconUrl = app.iconUrl,
|
||||
modifier = Modifier.size(84.dp),
|
||||
)
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = app.name,
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
Spacer(modifier.width(16.dp))
|
||||
Column(modifier.weight(1f)) {
|
||||
AppNameWithByline(
|
||||
name = app.name,
|
||||
authorName = authorName,
|
||||
nameStyle = MaterialTheme.typography.displaySmall,
|
||||
onAuthorClick = { onProfileClick(app.event.pubKey) },
|
||||
authorTestTag = "profile:${app.event.pubKey}",
|
||||
)
|
||||
StatusText(app.identifier)
|
||||
state.release?.let { release ->
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text(
|
||||
text = buildString {
|
||||
append(release.version)
|
||||
release.channel?.let { append(" · ").append(it) }
|
||||
},
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(ZapPrimary)
|
||||
.padding(horizontal = 10.dp, vertical = 4.dp),
|
||||
)
|
||||
Spacer(modifier.height(10.dp))
|
||||
VersionPill(version = release.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
ProfileComponent(
|
||||
pubkey = app.event.pubKey,
|
||||
repository = repository,
|
||||
onClick = { onProfileClick(app.event.pubKey) },
|
||||
)
|
||||
}
|
||||
|
||||
if (app.screenshots.isNotEmpty()) {
|
||||
item {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<string name="app_name">Zapstore</string>
|
||||
<string name="search_apps">Search apps</string>
|
||||
<string name="clear_search">Clear search</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="no_notifications">You are all caught up.</string>
|
||||
<string name="curated_stacks">Curated stacks</string>
|
||||
<string name="latest_releases">Latest releases</string>
|
||||
<string name="no_releases">No releases found.</string>
|
||||
@@ -18,7 +20,9 @@
|
||||
<string name="profile_apps">Apps by this profile</string>
|
||||
<string name="no_profile_apps">No apps published by this profile.</string>
|
||||
<string name="published_by">Published by · %1$s…</string>
|
||||
<string name="app_by_author">by %1$s</string>
|
||||
<string name="latest_release">Latest release</string>
|
||||
<string name="read_more">Read more</string>
|
||||
<string name="version">Version:</string>
|
||||
<string name="source">Source</string>
|
||||
<string name="license">License</string>
|
||||
|
||||
Reference in New Issue
Block a user