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:
Claude
2026-05-27 02:51:36 +00:00
parent 2210341291
commit 8424140d60
8 changed files with 514 additions and 0 deletions
@@ -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")
}
}
}
@@ -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(),
)
}
}
@@ -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 = {},
)
}
}
@@ -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(),
)
}
}
@@ -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())
}
}
@@ -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())
}
}
@@ -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(),
)
}
}