mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 08:47:33 +00:00
Code review:
- Use the app's cached stringRes helper instead of raw stringResource,
matching the dominant convention in ui/note/types
- Drive the icon animation from the Compose frame clock (withFrameMillis)
instead of a delay loop, so the ticker suspends whenever the composition
stops drawing rather than waking the main dispatcher 4x/sec from the
back stack
- Drop the unconsumed memoryCardId/blockState/blockHash accessors; the
tag schema stays documented in the class KDoc
- Document the frames arrays as frozen: mutating them in place would
silently break the @Immutable skip contract; build a new instance
to change pixels
- Replace the API-29-deprecated Bitmap.createBitmap(IntArray, ...)
overload with createBitmap(w, h, config) + setPixels
This commit is contained in:
@@ -37,22 +37,22 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.withFrameMillis
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.experimental.ps1saves.Ps1SaveEvent
|
||||
import com.vitorpamplona.quartz.experimental.ps1saves.Ps1SaveIcon
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/** How many hex characters of the block to preview (the full block is ~16K chars). */
|
||||
private const val HEX_PREVIEW_CHARS = 192
|
||||
@@ -102,7 +102,7 @@ fun RenderPs1Save(baseNote: Note) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
Text(
|
||||
text = (if (save.icon == null) FLOPPY_PREFIX else "") + (save.title ?: stringResource(R.string.ps1_save_title)),
|
||||
text = (if (save.icon == null) FLOPPY_PREFIX else "") + (save.title ?: stringRes(R.string.ps1_save_title)),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@@ -113,7 +113,7 @@ fun RenderPs1Save(baseNote: Note) {
|
||||
listOfNotNull(
|
||||
save.filename,
|
||||
save.region,
|
||||
save.blockNumber?.let { stringResource(R.string.ps1_save_block, it) },
|
||||
save.blockNumber?.let { stringRes(R.string.ps1_save_block, it) },
|
||||
).joinToString(" · ")
|
||||
|
||||
if (details.isNotEmpty()) {
|
||||
@@ -151,7 +151,8 @@ private fun Ps1SaveIconImage(icon: Ps1SaveIcon) {
|
||||
remember(icon) {
|
||||
icon.frames.map { pixels ->
|
||||
Bitmap
|
||||
.createBitmap(pixels, Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE, Bitmap.Config.ARGB_8888)
|
||||
.createBitmap(Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE, Bitmap.Config.ARGB_8888)
|
||||
.apply { setPixels(pixels, 0, Ps1SaveIcon.SIZE, 0, 0, Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE) }
|
||||
.asImageBitmap()
|
||||
}
|
||||
}
|
||||
@@ -159,10 +160,14 @@ private fun Ps1SaveIconImage(icon: Ps1SaveIcon) {
|
||||
var frameIndex by remember(icon) { mutableIntStateOf(0) }
|
||||
|
||||
if (frames.size > 1) {
|
||||
// Driven by the frame clock (not delay) so the ticker suspends whenever
|
||||
// the composition stops drawing (backgrounded, behind another screen).
|
||||
LaunchedEffect(icon) {
|
||||
val start = withFrameMillis { it }
|
||||
while (true) {
|
||||
delay(ICON_FRAME_MILLIS)
|
||||
frameIndex = (frameIndex + 1) % frames.size
|
||||
withFrameMillis { now ->
|
||||
frameIndex = (((now - start) / ICON_FRAME_MILLIS) % frames.size).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-9
@@ -71,18 +71,9 @@ class Ps1SaveEvent(
|
||||
/** Game region, from the `region` tag (may be null). */
|
||||
fun region() = tags.firstTagValue("region")
|
||||
|
||||
/** Identifier of the memory card this block belongs to, from the `m` tag (may be null). */
|
||||
fun memoryCardId() = tags.firstTagValue("m")
|
||||
|
||||
/** Block number within the card, from the `block` tag (null when missing or not a number). */
|
||||
fun blockNumber() = tags.firstTagValue("block")?.toIntOrNull()
|
||||
|
||||
/** Position of this block in a multi-block save chain, from the `state` tag (may be null). */
|
||||
fun blockState() = tags.firstTagValue("state")
|
||||
|
||||
/** SHA-256 of the block data, from the `x` tag (may be null). */
|
||||
fun blockHash() = tags.firstTagValue("x")
|
||||
|
||||
/** Publisher-provided human-readable summary, from the NIP-31 `alt` tag (may be null). */
|
||||
fun summary() = tags.alt()
|
||||
|
||||
|
||||
+6
-3
@@ -35,12 +35,15 @@ import com.vitorpamplona.quartz.nip01Core.core.hexToByteArrayOrNull
|
||||
* (bits 0-4 red, 5-9 green, 10-14 blue); raw `0x0000` is transparent.
|
||||
* - `0x80` — the frames, each 16×16 pixels at 4bpp (128 bytes), low nibble
|
||||
* first; pixel values index the CLUT.
|
||||
*
|
||||
* [frames] holds one 256-entry row-major ARGB pixel array per animation frame,
|
||||
* ready to blit into a bitmap.
|
||||
*/
|
||||
@Immutable
|
||||
class Ps1SaveIcon(
|
||||
/**
|
||||
* One 256-entry row-major ARGB pixel array per animation frame. Treat as
|
||||
* frozen: the arrays back the class's `@Immutable` contract, so mutating
|
||||
* them in place would not recompose — build a new [Ps1SaveIcon] to change
|
||||
* pixels.
|
||||
*/
|
||||
val frames: List<IntArray>,
|
||||
) {
|
||||
companion object {
|
||||
|
||||
-3
@@ -72,10 +72,7 @@ class Ps1SaveEventTest {
|
||||
assertEquals("SYNCED OVER NOST Card", event.saveTitle())
|
||||
assertEquals("BASCUS-00001SOFTCARD", event.filename())
|
||||
assertEquals("America", event.region())
|
||||
assertEquals("synctest-f1fc95d1", event.memoryCardId())
|
||||
assertEquals(1, event.blockNumber())
|
||||
assertEquals("first", event.blockState())
|
||||
assertEquals("4235e8c1d7164927b78dd50e0a12acbea04b2d80f0864fc3298e7d84225d8f41", event.blockHash())
|
||||
assertEquals("PS1 save 'SYNCED OVER NOST Card' (BASCUS-00001SOFTCARD)", event.summary())
|
||||
assertEquals("synctest-f1fc95d1-1", event.dTag())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user