mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 00:16:59 +00:00
feat(nip82): render bundled assets inside the release card
Replace the placeholder "N assets bundled" line with a real list of compact rows for each `e`-tagged Software Asset in the release. Each row loads the referenced asset event id through `LoadAssetNote` (uses LocalCache first, falls back to `checkGetOrCreateNote` for ids never seen) and then `observeNoteEvent<SoftwareAssetEvent>` — which both observes the LocalCache flow and registers the note with `EventFinder` so the relay round-trips the missing asset event. When the asset arrives the row recomposes with MIME, version, optional variant, size, platform chips, and a Download link to the asset url. The standalone `RenderSoftwareAsset` card (kind 3063 in a feed or thread) is unchanged; this only fills out the release detail view.
This commit is contained in:
@@ -38,6 +38,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -53,6 +56,7 @@ import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LinkIcon
|
||||
@@ -307,6 +311,122 @@ fun RenderSoftwareRelease(
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
BundledAssetsList(
|
||||
assetIds = assets.map { it.eventId },
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BundledAssetsList(
|
||||
assetIds: List<String>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
assetIds.forEach { id ->
|
||||
key(id) {
|
||||
LoadAssetNote(id, accountViewModel) { assetNote ->
|
||||
if (assetNote != null) {
|
||||
SoftwareAssetRow(assetNote, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadAssetNote(
|
||||
eventId: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable (Note?) -> Unit,
|
||||
) {
|
||||
val note by produceState<Note?>(initialValue = accountViewModel.getNoteIfExists(eventId), eventId) {
|
||||
if (value == null) {
|
||||
value = accountViewModel.checkGetOrCreateNote(eventId)
|
||||
}
|
||||
}
|
||||
content(note)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SoftwareAssetRow(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
// Subscribe so a missing asset event is fetched from the relay and we recompose when it arrives.
|
||||
val eventState = observeNoteEvent<SoftwareAssetEvent>(note, accountViewModel)
|
||||
val event = eventState.value ?: return
|
||||
|
||||
val uri = LocalUriHandler.current
|
||||
val version = remember(event) { event.version() }
|
||||
val mimeType = remember(event) { event.mimeType() }
|
||||
val sizeBytes = remember(event) { event.sizeInBytes() }
|
||||
val platforms = remember(event) { event.platforms() }
|
||||
val downloadUrl = remember(event) { event.url() }
|
||||
val variant = remember(event) { event.variant() }
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.border(1.dp, MaterialTheme.colorScheme.subtleBorder, RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
mimeType?.let {
|
||||
Text(
|
||||
text = prettyMime(it),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
}
|
||||
version?.let {
|
||||
Text(
|
||||
text = stringRes(R.string.nip82_version_label, it),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
variant?.let {
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "· $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
sizeBytes?.let {
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "· ${formatBytes(it.toLong())}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (platforms.isNotEmpty()) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
items(platforms) { Chip(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
downloadUrl?.let {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
ClickableTextPrimary(
|
||||
text = stringRes(R.string.nip82_download),
|
||||
onClick = { runCatching { uri.openUri(it) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user