diff --git a/BUGS.md b/BUGS.md index da1a5cc..6db8346 100644 --- a/BUGS.md +++ b/BUGS.md @@ -8,6 +8,9 @@ - the server should implement some rate limiting / deadman switch / to avoid spamming Spotify API with broken-app API calls +- pairing fails: + - (tag data from mp3 files is there) + - are we missing tracks? (or title/artist on the tracks?) ## Nice-to @@ -22,3 +25,34 @@ - toast: "no tracks loaded for this playlist yet. [...]" + +## Spotify API check + +after fetching about 90 playlists, I start to see 429 rate limiting. + +ALSO, now, I seem to get this very soon with 429: +(even after logging in again) +""" + +84.115.228.119 - - [14/May/2026 00:46:04] "GET /playlists HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:05] "GET /playlists HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:07] "GET /playlists HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:09] "GET /playlists/4f4P6ULiHn0oEedTHal2AU HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:10] "GET /playlists/4f4P6ULiHn0oEedTHal2AU HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:12] "GET /playlists HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:12] "GET /playlists/4f4P6ULiHn0oEedTHal2AU HTTP/1.1" 429 - + + +84.115.228.119 - - [14/May/2026 00:46:17] "GET /playlists/4f4P6ULiHn0oEedTHal2AU HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:22] "GET /playlists HTTP/1.1" 429 - +84.115.228.119 - - [14/May/2026 00:46:26] "GET /playlists/4f4P6ULiHn0oEedTHal2AU HTTP/1.1" 429 - + +""" + +--- + +""" +can we please *avoid* fetching playlist details upon first-run? i.e. only fetch the playlists but not their contents. +*only* fetch playlist details upon clicking a playlist. +""" + diff --git a/app/src/main/java/at/lockstep/player/LockstepViewModel.kt b/app/src/main/java/at/lockstep/player/LockstepViewModel.kt index bf4dd2a..8eb0ab0 100644 --- a/app/src/main/java/at/lockstep/player/LockstepViewModel.kt +++ b/app/src/main/java/at/lockstep/player/LockstepViewModel.kt @@ -128,7 +128,7 @@ class LockstepViewModel( app.setSpotifyAccessTokenForApi(token) return withContext(Dispatchers.IO) { try { - app.playlistRepository.syncInitial() + app.playlistRepository.syncDelta(false) null } catch (e: LockstepApiException) { e.message ?: "Sync failed" @@ -138,6 +138,27 @@ class LockstepViewModel( } } + suspend fun syncPlaylistDetailForLibraryOpen(playlistId: String): String? { + val token = spotifyAccessToken.value + if (token.isNullOrBlank()) { + return "Not signed in" + } + app.setSpotifyAccessTokenForApi(token) + return withContext(Dispatchers.IO) { + if (app.playlistRepository.getTracks(playlistId).isNotEmpty()) { + return@withContext null + } + try { + app.playlistRepository.syncPlaylistDetail(playlistId) + null + } catch (e: LockstepApiException) { + e.message ?: "Load failed" + } catch (e: IOException) { + e.message ?: "Load failed" + } + } + } + fun completeOnboarding() { viewModelScope.launch { prefs.setOnboardingComplete(true) @@ -150,6 +171,14 @@ class LockstepViewModel( } } + /** Clears the Spotify token and shows the first-run flow again so you can sign in with a fresh token. */ + fun logoutSpotifyAndRestartOnboarding() { + viewModelScope.launch { + prefs.setSpotifyAccessToken(null) + prefs.setOnboardingComplete(false) + } + } + private suspend fun upsertPairing( playlistId: String, trackId: String, diff --git a/app/src/main/java/at/lockstep/player/ui/LockstepAppNavHost.kt b/app/src/main/java/at/lockstep/player/ui/LockstepAppNavHost.kt index ac1ea12..e879dae 100644 --- a/app/src/main/java/at/lockstep/player/ui/LockstepAppNavHost.kt +++ b/app/src/main/java/at/lockstep/player/ui/LockstepAppNavHost.kt @@ -80,6 +80,7 @@ fun LockstepAppNavHost( } composable(Routes.Settings) { SettingsScreen( + viewModel = viewModel, onBack = { navController.popBackStack() }, ) } diff --git a/app/src/main/java/at/lockstep/player/ui/library/LibraryScreen.kt b/app/src/main/java/at/lockstep/player/ui/library/LibraryScreen.kt index a44c847..7aa07b1 100644 --- a/app/src/main/java/at/lockstep/player/ui/library/LibraryScreen.kt +++ b/app/src/main/java/at/lockstep/player/ui/library/LibraryScreen.kt @@ -83,6 +83,11 @@ fun LibraryScreen( .fillMaxWidth() .clickable { scope.launch { + val err = viewModel.syncPlaylistDetailForLibraryOpen(playlist.id) + if (err != null) { + Toast.makeText(context, err, Toast.LENGTH_LONG).show() + return@launch + } val hasPaired = viewModel.hasPairedTracks(playlist.id) onPlaylistSelected(playlist, hasPaired) diff --git a/app/src/main/java/at/lockstep/player/ui/settings/SettingsScreen.kt b/app/src/main/java/at/lockstep/player/ui/settings/SettingsScreen.kt index e03f136..a7be2ae 100644 --- a/app/src/main/java/at/lockstep/player/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/at/lockstep/player/ui/settings/SettingsScreen.kt @@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -17,11 +19,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import at.lockstep.player.LockstepViewModel import at.lockstep.player.R @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( + viewModel: LockstepViewModel, onBack: () -> Unit, modifier: Modifier = Modifier, ) { @@ -51,6 +55,21 @@ fun SettingsScreen( text = context.getString(R.string.settings_stub_body), style = MaterialTheme.typography.bodyLarge, ) + Text( + text = context.getString(R.string.settings_logout_spotify_help), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Button( + onClick = { viewModel.logoutSpotifyAndRestartOnboarding() }, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer, + ), + ) { + Text(text = context.getString(R.string.settings_logout_spotify)) + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e38f5b0..9de33c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,8 @@ Settings More controls will land here in a later milestone. + Sign out of Spotify + Clears your stored access token and returns to the welcome steps so you can log in again. Use this if the app gets HTTP 401 from the server. Pair local MP3s Choose folder of MP3s diff --git a/jukebox b/jukebox index e139e81..8a72651 160000 --- a/jukebox +++ b/jukebox @@ -1 +1 @@ -Subproject commit e139e810a3103e19aeacbea816f3d00d8328eab5 +Subproject commit 8a7265194c30b4cbdfd2c79a71664b39e949adaf