feat: fetch tracks on-demand, logout button
This commit is contained in:
34
BUGS.md
34
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
|
- 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
|
## Nice-to
|
||||||
|
|
||||||
@@ -22,3 +25,34 @@
|
|||||||
|
|
||||||
|
|
||||||
- toast: "no tracks loaded for this playlist yet. [...]"
|
- 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class LockstepViewModel(
|
|||||||
app.setSpotifyAccessTokenForApi(token)
|
app.setSpotifyAccessTokenForApi(token)
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
app.playlistRepository.syncInitial()
|
app.playlistRepository.syncDelta(false)
|
||||||
null
|
null
|
||||||
} catch (e: LockstepApiException) {
|
} catch (e: LockstepApiException) {
|
||||||
e.message ?: "Sync failed"
|
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() {
|
fun completeOnboarding() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
prefs.setOnboardingComplete(true)
|
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(
|
private suspend fun upsertPairing(
|
||||||
playlistId: String,
|
playlistId: String,
|
||||||
trackId: String,
|
trackId: String,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ fun LockstepAppNavHost(
|
|||||||
}
|
}
|
||||||
composable(Routes.Settings) {
|
composable(Routes.Settings) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
|
viewModel = viewModel,
|
||||||
onBack = { navController.popBackStack() },
|
onBack = { navController.popBackStack() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ fun LibraryScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable {
|
.clickable {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
val err = viewModel.syncPlaylistDetailForLibraryOpen(playlist.id)
|
||||||
|
if (err != null) {
|
||||||
|
Toast.makeText(context, err, Toast.LENGTH_LONG).show()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
val hasPaired =
|
val hasPaired =
|
||||||
viewModel.hasPairedTracks(playlist.id)
|
viewModel.hasPairedTracks(playlist.id)
|
||||||
onPlaylistSelected(playlist, hasPaired)
|
onPlaylistSelected(playlist, hasPaired)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -17,11 +19,13 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import at.lockstep.player.LockstepViewModel
|
||||||
import at.lockstep.player.R
|
import at.lockstep.player.R
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
|
viewModel: LockstepViewModel,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -51,6 +55,21 @@ fun SettingsScreen(
|
|||||||
text = context.getString(R.string.settings_stub_body),
|
text = context.getString(R.string.settings_stub_body),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
|
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
<string name="settings_stub_body">More controls will land here in a later milestone.</string>
|
<string name="settings_stub_body">More controls will land here in a later milestone.</string>
|
||||||
|
<string name="settings_logout_spotify">Sign out of Spotify</string>
|
||||||
|
<string name="settings_logout_spotify_help">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.</string>
|
||||||
|
|
||||||
<string name="pairing_title">Pair local MP3s</string>
|
<string name="pairing_title">Pair local MP3s</string>
|
||||||
<string name="pairing_choose_folder">Choose folder of MP3s</string>
|
<string name="pairing_choose_folder">Choose folder of MP3s</string>
|
||||||
|
|||||||
2
jukebox
2
jukebox
Submodule jukebox updated: e139e810a3...8a7265194c
Reference in New Issue
Block a user