feat: M4a outbound ListenBrainz scrobble worker #26

Merged
bvandeusen merged 262 commits from dev into main 2026-04-28 18:36:59 -04:00
Showing only changes of commit ec585997ae - Show all commits
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@@ -50,6 +51,8 @@ import androidx.navigation.NavHostController
import com.composables.icons.lucide.ArrowLeft import com.composables.icons.lucide.ArrowLeft
import com.composables.icons.lucide.Lucide import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.X import com.composables.icons.lucide.X
import com.fabledsword.minstrel.library.widgets.AlbumCard
import com.fabledsword.minstrel.library.widgets.ArtistCard
import com.fabledsword.minstrel.models.AlbumRef import com.fabledsword.minstrel.models.AlbumRef
import com.fabledsword.minstrel.models.ArtistRef import com.fabledsword.minstrel.models.ArtistRef
import com.fabledsword.minstrel.models.SearchResponseRef import com.fabledsword.minstrel.models.SearchResponseRef
@@ -208,14 +211,16 @@ private fun LazyListScope.artistsSection(
onArtistClick: (String) -> Unit, onArtistClick: (String) -> Unit,
) { ) {
if (artists.isEmpty()) return if (artists.isEmpty()) return
item { SectionHeader("Artists") } item { SectionHeader("Artists", artists.size) }
item {
LazyRow(
contentPadding = PaddingValues(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(items = artists, key = { "art-${it.id}" }) { artist -> items(items = artists, key = { "art-${it.id}" }) { artist ->
TextRow( ArtistCard(artist = artist, onClick = { onArtistClick(artist.id) })
primary = artist.name, }
secondary = null, }
onClick = { onArtistClick(artist.id) },
)
HorizontalDivider()
} }
} }
@@ -224,14 +229,16 @@ private fun LazyListScope.albumsSection(
onAlbumClick: (String) -> Unit, onAlbumClick: (String) -> Unit,
) { ) {
if (albums.isEmpty()) return if (albums.isEmpty()) return
item { SectionHeader("Albums") } item { SectionHeader("Albums", albums.size) }
item {
LazyRow(
contentPadding = PaddingValues(horizontal = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(items = albums, key = { "alb-${it.id}" }) { album -> items(items = albums, key = { "alb-${it.id}" }) { album ->
TextRow( AlbumCard(album = album, onClick = { onAlbumClick(album.id) })
primary = album.title, }
secondary = album.artistName.takeIf { it.isNotEmpty() }, }
onClick = { onAlbumClick(album.id) },
)
HorizontalDivider()
} }
} }
@@ -243,7 +250,7 @@ private fun LazyListScope.tracksSection(
onNavigateToArtist: (String) -> Unit, onNavigateToArtist: (String) -> Unit,
) { ) {
if (tracks.isEmpty()) return if (tracks.isEmpty()) return
item { SectionHeader("Tracks") } item { SectionHeader("Tracks", tracks.size) }
items(items = tracks, key = { "trk-${it.id}" }) { track -> items(items = tracks, key = { "trk-${it.id}" }) { track ->
TrackResultRow( TrackResultRow(
track = track, track = track,
@@ -307,47 +314,26 @@ private fun TrackResultRow(
} }
@Composable @Composable
private fun SectionHeader(text: String) { private fun SectionHeader(label: String, count: Int) {
Text(
text = text,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
@Composable
private fun TextRow(primary: String, secondary: String?, onClick: () -> Unit) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 8.dp),
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Column(modifier = Modifier.fillMaxWidth()) {
Text( Text(
text = primary, text = label,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
if (!secondary.isNullOrEmpty()) {
Text( Text(
text = secondary, text = "$count",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
) )
} }
} }
}
}
@Composable @Composable
private fun CenteredHint(text: String) { private fun CenteredHint(text: String) {