fix(android): scrobble respects 1/3 & 2/3 again; no settings flicker on open
Scrobble timing: at onMediaItemTransition ctrl.duration is still TIME_UNSET (→0), so getScrobbleDelayMs's third/two_thirds modes hit the 15s floor and scrobbled way too early (both Navidrome + our play-log). Use the song's KNOWN duration (song.duration), falling back to the player only if absent. Web was unaffected (it already used the song's duration). Settings flicker: the ViewModel started with default state (theme = Dark Ocean) and rendered a frame before DataStore emitted the real values, so the theme picker visibly jumped. Gate the form on a 'loaded' flag set after the first emit; show a spinner until then. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
0c7132a7f2
commit
711a084686
3 files changed
+16
-1
No files matched your search
@@ -315,7 +315,11 @@ class PlayerController(private val context: Context, private val repo: MusicRepo
|
|||||||
val mediaId = ctrl.currentMediaItem?.mediaId ?: return
|
val mediaId = ctrl.currentMediaItem?.mediaId ?: return
|
||||||
if (mediaId.startsWith("abs:")) return
|
if (mediaId.startsWith("abs:")) return
|
||||||
val song = queue.getOrNull(ctrl.currentMediaItemIndex) ?: return
|
val song = queue.getOrNull(ctrl.currentMediaItemIndex) ?: return
|
||||||
val dur = ctrl.duration.coerceAtLeast(0)
|
// Use the song's KNOWN duration — at a media transition ctrl.duration is still
|
||||||
|
// TIME_UNSET (→0), which collapsed the 1/3 / 2/3 modes to the 15s floor and scrobbled
|
||||||
|
// far too early. Fall back to the player only if the song has no duration.
|
||||||
|
val songDurMs = (song.duration?.toLong() ?: 0L) * 1000L
|
||||||
|
val dur = if (songDurMs > 0) songDurMs else ctrl.duration.coerceAtLeast(0)
|
||||||
scheduleScrobble(song, dur)
|
scheduleScrobble(song, dur)
|
||||||
scope.launch { runCatching { repo.saveLastSong(song) } }
|
scope.launch { runCatching { repo.saveLastSong(song) } }
|
||||||
prefetchLyrics(song)
|
prefetchLyrics(song)
|
||||||
|
|||||||
@@ -67,6 +67,15 @@ fun SettingsScreen(onOpenStats: () -> Unit = {}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the persisted settings before drawing the form — otherwise the default state
|
||||||
|
// (e.g. theme = Dark Ocean) flashes for a frame and then jumps to the real values.
|
||||||
|
if (!state.loaded) {
|
||||||
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator(color = NsAccent)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ data class SettingsUiState(
|
|||||||
val absUrl: String = "",
|
val absUrl: String = "",
|
||||||
val absApiKey: String = "",
|
val absApiKey: String = "",
|
||||||
val absChapterTrackMode: Boolean = false,
|
val absChapterTrackMode: Boolean = false,
|
||||||
|
val loaded: Boolean = false,
|
||||||
val isSaving: Boolean = false,
|
val isSaving: Boolean = false,
|
||||||
val savedOk: Boolean = false,
|
val savedOk: Boolean = false,
|
||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
@@ -66,6 +67,7 @@ class SettingsViewModel(app: Application) : AndroidViewModel(app) {
|
|||||||
absUrl = s.absUrl,
|
absUrl = s.absUrl,
|
||||||
absApiKey = s.absApiKey,
|
absApiKey = s.absApiKey,
|
||||||
absChapterTrackMode = s.absChapterTrackMode,
|
absChapterTrackMode = s.absChapterTrackMode,
|
||||||
|
loaded = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user