fix: login flash, album name casing, lrclib duration mismatch
- Android: show nothing while DataStore loads credentials (null initial value) to prevent login screen flash on startup when already logged in - Web: dispAlbumName now only uses sortName as romaji for Japanese titles; English albums with lowercase ALBUMSORT tags now display correct casing - lrclib: add no-duration fallback exact match to find songs where local file duration differs from lrclib entry (e.g. Sanctuary Opening 5:58 vs 4:24) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
0272b9e99f
commit
f7012dab49
5 files changed
+34
-8
No files matched your search
@@ -68,7 +68,7 @@ object LrcLibClient {
|
||||
val cacheKey = "$artist|$title"
|
||||
if (cache.containsKey(cacheKey)) return cache[cacheKey]
|
||||
|
||||
// 1. lrclib exact match — single request, only trust HTTP 200
|
||||
// 1a. lrclib exact match with duration
|
||||
val exact = runCatching {
|
||||
val resp = client.get("https://lrclib.net/api/get") {
|
||||
parameter("artist_name", artist)
|
||||
@@ -78,9 +78,19 @@ object LrcLibClient {
|
||||
}
|
||||
if (resp.status == HttpStatusCode.OK) resp.body<LrcLibResponse>() else null
|
||||
}.getOrNull()
|
||||
|
||||
if (exact.hasContent()) return exact.also { cache[cacheKey] = it }
|
||||
|
||||
// 1b. lrclib exact match without duration — handles version mismatches (e.g. 5:58 file vs 4:24 in lrclib)
|
||||
val exactNoDur = if (durationSecs != null && !album.isNullOrBlank()) runCatching {
|
||||
val resp = client.get("https://lrclib.net/api/get") {
|
||||
parameter("artist_name", artist)
|
||||
parameter("track_name", title)
|
||||
parameter("album_name", album)
|
||||
}
|
||||
if (resp.status == HttpStatusCode.OK) resp.body<LrcLibResponse>() else null
|
||||
}.getOrNull() else null
|
||||
if (exactNoDur.hasContent()) return exactNoDur.also { cache[cacheKey] = it }
|
||||
|
||||
// 2+3. lrclib search — artist+title and title-only in parallel (2 requests, not 3)
|
||||
val lrclibResult = coroutineScope {
|
||||
val artistDef = async {
|
||||
|
||||
@@ -35,12 +35,15 @@ private fun NovaSonicRoot() {
|
||||
val context = LocalContext.current
|
||||
val prefs = remember { PrefsStore(context) }
|
||||
val creds by prefs.credentials.collectAsStateWithLifecycle(
|
||||
initialValue = PrefsStore.Credentials("", "", ""),
|
||||
initialValue = null,
|
||||
)
|
||||
|
||||
var forceLogin by remember { mutableStateOf(false) }
|
||||
|
||||
if (!creds.isValid || forceLogin) {
|
||||
// null = still loading from DataStore; don't show anything yet to avoid login screen flash
|
||||
if (creds == null) return
|
||||
|
||||
if (!creds!!.isValid || forceLogin) {
|
||||
LoginScreen(onLoggedIn = { forceLogin = false })
|
||||
} else {
|
||||
AppNavigation()
|
||||
|
||||
@@ -118,7 +118,7 @@ fun NowPlayingScreen(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
.padding(bottom = 16.dp),
|
||||
.padding(bottom = 28.dp),
|
||||
) {
|
||||
NpCircleBtn(onClick = onDismiss) {
|
||||
Icon(Icons.Default.KeyboardArrowDown, "Schließen",
|
||||
@@ -386,7 +386,7 @@ fun NowPlayingScreen(
|
||||
// Center of active line sits at 30% from top (clearly upper area)
|
||||
val targetY = (lyricsPaddingPx + activeIdx * lineHeightPx + lineHeightPx / 2f
|
||||
- lyricsViewH * 0.30f).coerceAtLeast(0f)
|
||||
lyricsScrollState.animateScrollTo(targetY.toInt(), tween<Int>(280, easing = FastOutSlowInEasing))
|
||||
lyricsScrollState.animateScrollTo(targetY.toInt(), tween<Float>(280, easing = FastOutSlowInEasing))
|
||||
}
|
||||
|
||||
when {
|
||||
|
||||
+2
-1
@@ -201,7 +201,8 @@ export function dispAlbumName(album) {
|
||||
const ov = getRomajiOverrides();
|
||||
if (name && ov[name]) return ov[name];
|
||||
const sort = album?.sortName;
|
||||
if (sort && !_isJp(sort)) return sort;
|
||||
// Only use sortName as romaji when the album title itself is Japanese — otherwise sortName is just a sort key (e.g. lowercase "ultra blue")
|
||||
if (sort && _isJp(name) && !_isJp(sort)) return sort;
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ async function _loadNpLyrics(songId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. lrclib exact match — single request, only trust HTTP 200
|
||||
// 1a. lrclib exact match with duration
|
||||
const exactP = new URLSearchParams({ artist_name: artist, track_name: title });
|
||||
if(album) exactP.set('album_name', album);
|
||||
if(dur) exactP.set('duration', dur);
|
||||
@@ -187,6 +187,18 @@ async function _loadNpLyrics(songId) {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// 1b. exact match without duration — handles version mismatches (e.g. file at 5:58, lrclib has 4:24)
|
||||
if(!result && dur && album) {
|
||||
try {
|
||||
const exactNoDurP = new URLSearchParams({ artist_name: artist, track_name: title, album_name: album });
|
||||
const r = await fetch(`https://lrclib.net/api/get?${exactNoDurP}`, { signal: SIG() });
|
||||
if(r.status === 200) {
|
||||
const d = await r.json();
|
||||
if(d?.syncedLyrics || d?.plainLyrics || d?.instrumental) result = d;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if(token !== _lyricsLoadToken) return;
|
||||
|
||||
// 2+3. lrclib search — artist+title and title-only in parallel (2 requests, not 3)
|
||||
|
||||
Reference in new issue
Block a user