mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
chore(music): @Preview composables for every new UI surface
Adds previews wherever there's something visual worth previewing: - MusicTrack.kt: RenderMusicTrack (full event with cover + meta), RenderMusicTrackExplicit (explicit-badge variant), MusicTrackCover (no cover fallback), ExplicitBadge, TopicChip. Uses the same Event-constructor + LocalCache.justConsume + LoadNote pattern Attestation and Wiki previews already use. - MusicPlaylist.kt: RenderMusicPlaylist (with three resolved track refs), RenderMusicPlaylistCollaborativePrivate (chips visible), MusicPlaylistCover, MissingPlaylistTrackRow, TrackCoverPlaceholder, PlaylistTag. - MusicTracksScreen.kt: full feed scaffold via mockAccountViewModel(). - MusicTracksTopBar.kt: the static-title top bar. - NewMusicTrackButton.kt: the FAB on its own. - NewMusicTrackScreen.kt: the composer form. - AddToMusicPlaylistSheet.kt: NewMusicPlaylistDialog leaf, and the empty state of the full sheet. - MusicPlaylistManagementItem.kt: four variants — not-in / in / empty (zero tracks) / untitled. Skipped MusicTracksFeedLoaded — FeedState.Loaded can't be easily mocked and no existing feed-loaded previews exist in the codebase. The screen preview already covers the scaffold around it. URLs in the constructed events point at example.invalid so MyAsyncImage falls back to the deterministic DefaultImageHeader robohash and VideoView shows its tap-to-load thumbnail state — both of which are the real first-paint state users see before tapping anything.
This commit is contained in:
@@ -40,6 +40,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -49,27 +50,36 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
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.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.experimental.music.playlist.MusicPlaylistEvent
|
||||
import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private val PLAYLIST_COVER_ASPECT_RATIO = 1f
|
||||
private const val MAX_PREVIEW_TRACKS = 25
|
||||
@@ -451,3 +461,202 @@ private fun formatTrackDuration(seconds: Int): String {
|
||||
val secs = seconds % 60
|
||||
return "%d:%02d".format(minutes, secs)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// @Preview composables. Constructs a MusicPlaylistEvent plus a handful of
|
||||
// MusicTrackEvents it references, pushes them through LocalCache, then
|
||||
// renders the same code path the production feed uses. The cover image URL
|
||||
// is unreachable on purpose so MyAsyncImage falls back to the deterministic
|
||||
// DefaultImageHeader robohash — that's what users see while the real cover
|
||||
// is still loading.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private const val PREVIEW_PLAYLIST_PUBKEY = "989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"
|
||||
private val PREVIEW_PLAYLIST_SIG = "0".repeat(128)
|
||||
|
||||
private fun previewPlaylistTrack(
|
||||
dTag: String,
|
||||
title: String,
|
||||
artist: String,
|
||||
duration: Int,
|
||||
): MusicTrackEvent =
|
||||
MusicTrackEvent(
|
||||
id = "ptrack_${dTag}_${title.hashCode()}".padEnd(64, '0').take(64),
|
||||
pubKey = PREVIEW_PLAYLIST_PUBKEY,
|
||||
createdAt = 1_730_000_000L,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("d", dTag),
|
||||
arrayOf("title", title),
|
||||
arrayOf("artist", artist),
|
||||
arrayOf("url", "https://example.invalid/$dTag.mp3"),
|
||||
arrayOf("duration", duration.toString()),
|
||||
arrayOf("t", "music"),
|
||||
),
|
||||
content = "",
|
||||
sig = PREVIEW_PLAYLIST_SIG,
|
||||
)
|
||||
|
||||
private fun previewPlaylistEvent(
|
||||
dTag: String,
|
||||
title: String,
|
||||
description: String,
|
||||
tracks: List<MusicTrackEvent>,
|
||||
image: String? = "https://example.invalid/playlist.jpg",
|
||||
isCollaborative: Boolean = false,
|
||||
isPrivate: Boolean = false,
|
||||
content: String = "",
|
||||
): MusicPlaylistEvent {
|
||||
val tags =
|
||||
buildList {
|
||||
add(arrayOf("d", dTag))
|
||||
add(arrayOf("title", title))
|
||||
add(arrayOf("description", description))
|
||||
image?.let { add(arrayOf("image", it)) }
|
||||
add(arrayOf("t", "playlist"))
|
||||
tracks.forEach { track ->
|
||||
add(arrayOf("a", "${MusicTrackEvent.KIND}:${track.pubKey}:${track.dTag()}"))
|
||||
}
|
||||
if (isPrivate) add(arrayOf("private", "true")) else add(arrayOf("public", "true"))
|
||||
if (isCollaborative) add(arrayOf("collaborative", "true"))
|
||||
}.toTypedArray()
|
||||
|
||||
return MusicPlaylistEvent(
|
||||
id = "pl_${dTag}_${title.hashCode()}".padEnd(64, '0').take(64),
|
||||
pubKey = PREVIEW_PLAYLIST_PUBKEY,
|
||||
createdAt = 1_730_000_000L,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = PREVIEW_PLAYLIST_SIG,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RenderMusicPlaylistPreview() {
|
||||
val tracks =
|
||||
listOf(
|
||||
previewPlaylistTrack("summer-nights-2024", "Summer Nights", "The Midnight Collective", 245),
|
||||
previewPlaylistTrack("sunset-dreams", "Sunset Dreams", "Pacific Wave", 198),
|
||||
previewPlaylistTrack("ocean-breeze", "Ocean Breeze", "The Midnight Collective", 312),
|
||||
)
|
||||
val event =
|
||||
previewPlaylistEvent(
|
||||
dTag = "summer-vibes-2024",
|
||||
title = "Summer Vibes 2024",
|
||||
description = "Chill electronic tracks for summer",
|
||||
tracks = tracks,
|
||||
content = "My favorite summer vibes from 2024",
|
||||
)
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
tracks.forEach { LocalCache.justConsume(it, null, true) }
|
||||
LocalCache.justConsume(event, null, true)
|
||||
}
|
||||
}
|
||||
|
||||
ThemeComparisonColumn {
|
||||
LoadNote(baseNoteHex = event.address().toValue(), accountViewModel = mockAccountViewModel()) { note ->
|
||||
note?.let {
|
||||
RenderMusicPlaylist(
|
||||
note = it,
|
||||
makeItShort = false,
|
||||
canPreview = true,
|
||||
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RenderMusicPlaylistCollaborativePrivatePreview() {
|
||||
val event =
|
||||
previewPlaylistEvent(
|
||||
dTag = "friends-collab",
|
||||
title = "Friends Collab",
|
||||
description = "Everyone adds their picks",
|
||||
tracks = emptyList(),
|
||||
isPrivate = true,
|
||||
isCollaborative = true,
|
||||
content = "",
|
||||
)
|
||||
|
||||
runBlocking { withContext(Dispatchers.IO) { LocalCache.justConsume(event, null, true) } }
|
||||
|
||||
ThemeComparisonColumn {
|
||||
LoadNote(baseNoteHex = event.address().toValue(), accountViewModel = mockAccountViewModel()) { note ->
|
||||
note?.let {
|
||||
RenderMusicPlaylist(
|
||||
note = it,
|
||||
makeItShort = false,
|
||||
canPreview = true,
|
||||
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicPlaylistCoverPreview() {
|
||||
val event =
|
||||
previewPlaylistEvent(
|
||||
dTag = "cover-only",
|
||||
title = "Cover Only",
|
||||
description = "",
|
||||
tracks = emptyList(),
|
||||
image = null,
|
||||
)
|
||||
|
||||
runBlocking { withContext(Dispatchers.IO) { LocalCache.justConsume(event, null, true) } }
|
||||
|
||||
ThemeComparisonColumn {
|
||||
LoadNote(baseNoteHex = event.address().toValue(), accountViewModel = mockAccountViewModel()) { note ->
|
||||
note?.let {
|
||||
MusicPlaylistCover(
|
||||
image = null,
|
||||
note = it,
|
||||
trackCount = 7,
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MissingPlaylistTrackRowPreview() {
|
||||
ThemeComparisonColumn {
|
||||
MissingPlaylistTrackRow(position = 4)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TrackCoverPlaceholderPreview() {
|
||||
ThemeComparisonColumn {
|
||||
Row(modifier = Modifier.padding(8.dp)) {
|
||||
TrackCoverPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PlaylistTagPreview() {
|
||||
ThemeComparisonColumn {
|
||||
Row(modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
PlaylistTag(text = "Collaborative")
|
||||
PlaylistTag(text = "Private")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -47,29 +48,38 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
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.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.LoadThumbAndThenVideoView
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font10SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private val COVER_ASPECT_RATIO = 1f
|
||||
|
||||
@@ -350,3 +360,157 @@ private fun formatDuration(seconds: Int): String {
|
||||
val secs = seconds % 60
|
||||
return "%d:%02d".format(minutes, secs)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// @Preview composables. These wire a constructed MusicTrackEvent through
|
||||
// LocalCache so the renderer pulls the real Note path it would in production.
|
||||
// The audio/image URLs are intentionally unreachable — VideoView falls back to
|
||||
// its tap-to-load thumbnail state, and MyAsyncImage falls back to the
|
||||
// DefaultImageHeader robohash. That's the same first-paint state real users
|
||||
// see for a track they haven't tapped to download yet, which is what the
|
||||
// preview is meant to show.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private const val PREVIEW_TRACK_PUBKEY = "989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"
|
||||
private val PREVIEW_TRACK_SIG = "0".repeat(128)
|
||||
|
||||
private fun previewMusicTrackEvent(
|
||||
dTag: String,
|
||||
title: String,
|
||||
artist: String,
|
||||
album: String? = null,
|
||||
duration: Int? = null,
|
||||
released: String? = null,
|
||||
trackNumber: Int? = null,
|
||||
explicit: Boolean = false,
|
||||
image: String? = "https://example.invalid/cover.jpg",
|
||||
url: String = "https://example.invalid/track.mp3",
|
||||
content: String = "",
|
||||
): MusicTrackEvent {
|
||||
val tags =
|
||||
buildList {
|
||||
add(arrayOf("d", dTag))
|
||||
add(arrayOf("title", title))
|
||||
add(arrayOf("artist", artist))
|
||||
add(arrayOf("url", url))
|
||||
add(arrayOf("t", "music"))
|
||||
image?.let { add(arrayOf("image", it)) }
|
||||
album?.let { add(arrayOf("album", it)) }
|
||||
trackNumber?.let { add(arrayOf("track_number", it.toString())) }
|
||||
released?.let { add(arrayOf("released", it)) }
|
||||
duration?.let { add(arrayOf("duration", it.toString())) }
|
||||
if (explicit) add(arrayOf("explicit", "true"))
|
||||
}.toTypedArray()
|
||||
|
||||
return MusicTrackEvent(
|
||||
id = "track_${dTag}_${title.hashCode()}".padEnd(64, '0').take(64),
|
||||
pubKey = PREVIEW_TRACK_PUBKEY,
|
||||
createdAt = 1_730_000_000L,
|
||||
tags = tags,
|
||||
content = content,
|
||||
sig = PREVIEW_TRACK_SIG,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RenderMusicTrackPreview() {
|
||||
val event =
|
||||
previewMusicTrackEvent(
|
||||
dTag = "summer-nights-2024",
|
||||
title = "Summer Nights",
|
||||
artist = "The Midnight Collective",
|
||||
album = "Endless Summer",
|
||||
duration = 245,
|
||||
released = "2024-06-15",
|
||||
trackNumber = 3,
|
||||
explicit = false,
|
||||
content = "Lyrics:\n[Verse 1]\nWalking through the city lights...\n\nProducer: John Doe",
|
||||
)
|
||||
|
||||
runBlocking { withContext(Dispatchers.IO) { LocalCache.justConsume(event, null, true) } }
|
||||
|
||||
ThemeComparisonColumn {
|
||||
LoadNote(baseNoteHex = event.address().toValue(), accountViewModel = mockAccountViewModel()) { note ->
|
||||
note?.let {
|
||||
RenderMusicTrack(
|
||||
note = it,
|
||||
makeItShort = false,
|
||||
canPreview = true,
|
||||
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RenderMusicTrackExplicitPreview() {
|
||||
val event =
|
||||
previewMusicTrackEvent(
|
||||
dTag = "loud-and-clear",
|
||||
title = "Loud and Clear",
|
||||
artist = "Static Riot",
|
||||
album = "Distortion",
|
||||
duration = 187,
|
||||
explicit = true,
|
||||
content = "",
|
||||
)
|
||||
|
||||
runBlocking { withContext(Dispatchers.IO) { LocalCache.justConsume(event, null, true) } }
|
||||
|
||||
ThemeComparisonColumn {
|
||||
LoadNote(baseNoteHex = event.address().toValue(), accountViewModel = mockAccountViewModel()) { note ->
|
||||
note?.let {
|
||||
RenderMusicTrack(
|
||||
note = it,
|
||||
makeItShort = false,
|
||||
canPreview = true,
|
||||
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicTrackCoverPreview() {
|
||||
val event = previewMusicTrackEvent(dTag = "cover-only", title = "Cover Only", artist = "Anon")
|
||||
runBlocking { withContext(Dispatchers.IO) { LocalCache.justConsume(event, null, true) } }
|
||||
|
||||
ThemeComparisonColumn {
|
||||
LoadNote(baseNoteHex = event.address().toValue(), accountViewModel = mockAccountViewModel()) { note ->
|
||||
note?.let { MusicTrackCover(image = null, note = it, accountViewModel = mockAccountViewModel()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ExplicitBadgePreview() {
|
||||
ThemeComparisonColumn {
|
||||
Row(modifier = Modifier.padding(8.dp)) {
|
||||
ExplicitBadge()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TopicChipPreview() {
|
||||
ThemeComparisonColumn {
|
||||
Row(modifier = Modifier.padding(8.dp)) {
|
||||
TopicChip("electronic")
|
||||
Spacer(Modifier.padding(start = 4.dp))
|
||||
TopicChip("synthwave")
|
||||
Spacer(Modifier.padding(start = 4.dp))
|
||||
TopicChip("80s")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -44,16 +44,20 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.FabBottomBarPadded
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.NewListButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
|
||||
@Composable
|
||||
fun AddToMusicPlaylistSheet(
|
||||
@@ -184,3 +188,25 @@ private fun NewMusicPlaylistDialog(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NewMusicPlaylistDialogPreview() {
|
||||
ThemeComparisonColumn {
|
||||
NewMusicPlaylistDialog(onDismiss = {}, onCreate = {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AddToMusicPlaylistSheetEmptyPreview() {
|
||||
ThemeComparisonColumn {
|
||||
AddToMusicPlaylistSheet(
|
||||
// No matching addressable in LocalCache → the VM resolves zero playlists,
|
||||
// which is the empty-state path we want to capture.
|
||||
trackAddress = "36787:0000000000000000000000000000000000000000000000000000000000000000:preview-empty",
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+58
@@ -36,6 +36,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
@@ -45,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.SpacedBy5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
|
||||
/**
|
||||
* One row in the Add-to-Playlist sheet. Mirrors `BookmarkGroupManagementItem` so the visual
|
||||
@@ -176,3 +178,59 @@ private fun PlaylistToggleButton(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicPlaylistManagementItemNotInPreview() {
|
||||
ThemeComparisonColumn {
|
||||
MusicPlaylistManagementItem(
|
||||
playlistTitle = "Summer Vibes 2024",
|
||||
isTrackInPlaylist = false,
|
||||
totalTracks = 12,
|
||||
onClick = {},
|
||||
onToggle = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicPlaylistManagementItemInPreview() {
|
||||
ThemeComparisonColumn {
|
||||
MusicPlaylistManagementItem(
|
||||
playlistTitle = "Late-night Coding Beats",
|
||||
isTrackInPlaylist = true,
|
||||
totalTracks = 47,
|
||||
onClick = {},
|
||||
onToggle = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicPlaylistManagementItemEmptyPreview() {
|
||||
ThemeComparisonColumn {
|
||||
MusicPlaylistManagementItem(
|
||||
playlistTitle = "Fresh Picks",
|
||||
isTrackInPlaylist = false,
|
||||
totalTracks = 0,
|
||||
onClick = {},
|
||||
onToggle = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicPlaylistManagementItemUntitledPreview() {
|
||||
ThemeComparisonColumn {
|
||||
MusicPlaylistManagementItem(
|
||||
playlistTitle = "",
|
||||
isTrackInPlaylist = false,
|
||||
totalTracks = 3,
|
||||
onClick = {},
|
||||
onToggle = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.music
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||
@@ -33,9 +34,12 @@ import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.FabBottomBarPadded
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
|
||||
@Composable
|
||||
fun MusicTracksScreen(
|
||||
@@ -118,3 +122,16 @@ fun WatchAccountForMusicTracksScreen(
|
||||
musicFeedState.checkKeysInvalidateDataAndSendToTop()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicTracksScreenPreview() {
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
ThemeComparisonColumn {
|
||||
MusicTracksScreen(
|
||||
musicTracksFeedContentState = accountViewModel.feedStates.musicTracksFeed,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -24,11 +24,15 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
|
||||
@Composable
|
||||
fun MusicTracksTopBar(
|
||||
@@ -46,3 +50,11 @@ fun MusicTracksTopBar(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun MusicTracksTopBarPreview() {
|
||||
ThemeComparisonColumn {
|
||||
MusicTracksTopBar(accountViewModel = mockAccountViewModel(), nav = EmptyNav())
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -25,15 +25,19 @@ import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size26Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||
|
||||
@Composable
|
||||
fun NewMusicTrackButton(
|
||||
@@ -54,3 +58,11 @@ fun NewMusicTrackButton(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NewMusicTrackButtonPreview() {
|
||||
ThemeComparisonRow {
|
||||
NewMusicTrackButton(accountViewModel = mockAccountViewModel(), nav = EmptyNav())
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -46,13 +46,17 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -214,3 +218,15 @@ private fun DeleteMusicTrackRow(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun NewMusicTrackScreenPreview() {
|
||||
ThemeComparisonColumn {
|
||||
NewMusicTrackScreen(
|
||||
editDTag = null,
|
||||
accountViewModel = mockAccountViewModel(),
|
||||
nav = EmptyNav(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user