diff --git a/CHANGELOG.md b/CHANGELOG.md index f991dcf..709948c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,14 @@ like Android), concert events become **reliable across web/Android**, several An - **Android artist albums incomplete** — `lookupArtistAlbums` filtered to `record_type == "album"`, dropping EPs and singles that Deezer (and the web app) show. Now returns all, sorted album → EP → single. +- **Japanese/CJK albums vanished from the artist discography (Web + Android)** — the album-name + normaliser stripped everything except `a-z0-9`, so any Japanese/CJK title collapsed to an empty + string. All of an artist's JP singles then normalised identically (`""`), so they de-duplicated + into one entry and even "matched" each other — e.g. inabakumori (稲葉曇) showed only 2 of 9 releases, + the rest hidden behind one album. The normaliser now keeps Unicode letters/numbers (`\p{L}\p{N}`), + so JP titles stay distinct and the full discography (In Library / Unvollständig / Noch nicht + heruntergeladen) shows correctly. Same fix applied to the partial-album track match and the + Deezer-artist name match. - **Android "Unvollständig" (partial albums) never showed** — Deezer's `/artist/{id}/albums` omits `nb_tracks`, so an owned album's real total was unknown (0) and it never got flagged incomplete; every partly-downloaded album looked complete. The artist VM now resolves the real track count for diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt index 7bbc29e..c95dbff 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/ItunesArtistScreen.kt @@ -40,7 +40,9 @@ import com.novasonic.app.ui.theme.* import com.novasonic.app.viewmodel.ItunesArtistState private fun normAlbum(s: String?): String = - (s ?: "").lowercase().replace(Regex("[^a-z0-9\\s]"), " ").replace(Regex("\\s+"), " ").trim() + // Keep Unicode letters/numbers (\p{L}\p{N}) — stripping to a-z0-9 emptied every Japanese/CJK + // title, so all of an artist's JP singles collapsed into one and "matched" each other. + (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}\\s]"), " ").replace(Regex("\\s+"), " ").trim() data class ArtistAlbumCategories( val downloaded: List>, diff --git a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/PartialAlbumScreen.kt b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/PartialAlbumScreen.kt index 97f7bc2..a17c319 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/ui/screen/PartialAlbumScreen.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/ui/screen/PartialAlbumScreen.kt @@ -27,7 +27,9 @@ import com.novasonic.app.ui.theme.* import com.novasonic.app.viewmodel.PartialAlbumState private fun normTrack(s: String?) = - (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "").trim() + // Keep Unicode letters/numbers so Japanese track titles aren't all emptied to "" (which made + // every downloaded JP track "match" every missing one in the partial-album split). + (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "").trim() @Composable fun PartialAlbumScreen( diff --git a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ArtistViewModel.kt b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ArtistViewModel.kt index 8455e36..5cbcbe7 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ArtistViewModel.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ArtistViewModel.kt @@ -158,7 +158,7 @@ class ArtistViewModel(app: Application, savedState: SavedStateHandle) : AndroidV // artists are fan-sorted (ItunesClient), so firstOrNull gives the most popular match. // Prefer an EXACT normalized-name match: "Flay!" must not resolve to the far more popular // "K.FLAY" (its name merely contains "flay"). Only fall back to a loose contains-match. - fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "") + fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "") val q = norm(artistName) val iArtist = ( artists.firstOrNull { norm(it.artistName) == q } @@ -198,7 +198,7 @@ class ArtistViewModel(app: Application, savedState: SavedStateHandle) : AndroidV val ndAlbums = _state.value.artist?.album ?: return val iAlbums = _state.value.iAlbums if (ndAlbums.isEmpty() || iAlbums.isEmpty()) return - fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "") + fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "") val enriched = coroutineScope { iAlbums.map { ia -> val cid = ia.collectionId diff --git a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt index 2c26ef0..9d607f2 100644 --- a/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt +++ b/android/app/src/main/kotlin/com/novasonic/app/viewmodel/ExploreViewModel.kt @@ -220,7 +220,7 @@ class ExploreViewModel(app: Application) : AndroidViewModel(app) { ndAlbums: List, iAlbums: List, ): List { if (ndAlbums.isEmpty() || iAlbums.isEmpty()) return iAlbums - fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^a-z0-9]"), "") + fun norm(s: String?) = (s ?: "").lowercase().replace(Regex("[^\\p{L}\\p{N}]"), "") var changed = false val out = coroutineScope { iAlbums.map { ia -> diff --git a/src/views/detail.js b/src/views/detail.js index 6fc1be2..132274c 100644 --- a/src/views/detail.js +++ b/src/views/detail.js @@ -337,7 +337,10 @@ export async function loadArtistDetail(artist) { } function _normAlbum(s) { - return (s || '').toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim(); + // Keep Unicode letters/numbers (not just a-z0-9) — otherwise every Japanese/CJK title strips to an + // empty string, so all of an artist's JP singles collapse into one entry and even "match" each + // other (that hid inabakumori's whole singles discography behind one album). + return (s || '').toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, ' ').replace(/\s+/g, ' ').trim(); } async function _renderArtistAlbums(el, ndAlbums, iAlbums, artist) {