feat: sync Playlists, wip: pair songs

This commit is contained in:
2026-05-14 02:43:49 +02:00
parent 26115f773f
commit e2ab026e84
36 changed files with 2324 additions and 34 deletions

View File

@@ -0,0 +1,109 @@
package at.lockstep.player.ui.library
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import at.lockstep.jukebox.PlaylistSummary
import at.lockstep.player.LockstepViewModel
import at.lockstep.player.R
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LibraryScreen(
viewModel: LockstepViewModel,
onPlaylistSelected: (PlaylistSummary, Boolean) -> Unit,
onOpenSettings: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val token by viewModel.spotifyAccessToken.collectAsStateWithLifecycle()
val playlists by viewModel.observePlaylists().observeAsState(initial = emptyList())
LaunchedEffect(token) {
if (token.isNullOrBlank()) return@LaunchedEffect
val err = viewModel.syncJukeboxIfToken()
if (err != null) {
Toast.makeText(context, err, Toast.LENGTH_LONG).show()
}
}
Scaffold(
modifier = modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text(text = context.getString(R.string.library_title)) },
actions = {
IconButton(onClick = onOpenSettings) {
Icon(Icons.Default.Settings, contentDescription = context.getString(R.string.settings_title))
}
},
)
},
) { padding ->
LazyColumn(
modifier =
Modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
items(playlists, key = { it.id }) { playlist ->
Card(
modifier =
Modifier
.fillMaxWidth()
.clickable {
scope.launch {
val hasPaired =
viewModel.hasPairedTracks(playlist.id)
onPlaylistSelected(playlist, hasPaired)
}
},
colors =
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
),
) {
ListItem(
headlineContent = { Text(playlist.name) },
supportingContent = {
Text(
text = context.getString(R.string.library_open_playlist),
style = MaterialTheme.typography.bodySmall,
)
},
)
}
}
}
}
}