From 9efcfff4844c775f98682bd2ceba77880b01e3d6 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Sun, 13 Sep 2026 16:29:49 +0200 Subject: [PATCH] Fix profile avatar emoji fallback and bump TV/mobile app versions. --- apps/android-tv/app-mobile/build.gradle.kts | 4 +- .../vonas/mediacluster/tv/data/ApiClient.kt | 29 ++++++++----- .../mediacluster/tv/data/SessionStore.kt | 7 ++- .../java/nl/vonas/mediacluster/tv/ui/McNav.kt | 40 ++++++++++------- apps/android-tv/app/build.gradle.kts | 4 +- .../vonas/mediacluster/tv/data/ApiClient.kt | 29 ++++++++----- .../mediacluster/tv/data/SessionStore.kt | 7 ++- .../java/nl/vonas/mediacluster/tv/ui/McNav.kt | 23 +++++++--- .../mediacluster/tv/ui/components/TopNav.kt | 43 +++++++++++-------- 9 files changed, 113 insertions(+), 73 deletions(-) diff --git a/apps/android-tv/app-mobile/build.gradle.kts b/apps/android-tv/app-mobile/build.gradle.kts index e7f5adb..90b5019 100644 --- a/apps/android-tv/app-mobile/build.gradle.kts +++ b/apps/android-tv/app-mobile/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "nl.vonas.mediacluster" minSdk = 24 targetSdk = 36 - versionCode = 20 - versionName = "1.0.19" + versionCode = 21 + versionName = "1.0.20" buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"") ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") diff --git a/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt b/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt index 1f9d4e1..004f7e1 100644 --- a/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt +++ b/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt @@ -885,14 +885,21 @@ private fun JSONArray?.toProfiles(): List { } } -private fun JSONObject.toProfile(): ViewerProfile = ViewerProfile( - id = getString("id"), - name = optString("name"), - avatarId = optString("avatarId").ifBlank { "fox" }, - isOwner = optBoolean("isOwner"), - isKids = optBoolean("isKids"), - hasPin = optBoolean("hasPin"), - showAvatar = if (has("showAvatar")) optBoolean("showAvatar") else true, - hasCustomAvatar = optBoolean("hasCustomAvatar"), - avatarUrl = optString("avatarUrl").ifBlank { null }, -) +private fun JSONObject.toProfile(): ViewerProfile { + val hasCustom = optBoolean("hasCustomAvatar") + val rawUrl = optString("avatarUrl").takeIf { it.isNotBlank() } + return ViewerProfile( + id = getString("id"), + name = optString("name"), + avatarId = optString("avatarId").ifBlank { "fox" }, + isOwner = optBoolean("isOwner"), + isKids = optBoolean("isKids"), + hasPin = optBoolean("hasPin"), + showAvatar = when { + !has("showAvatar") || isNull("showAvatar") -> true + else -> optBoolean("showAvatar", true) + }, + hasCustomAvatar = hasCustom, + avatarUrl = if (hasCustom) rawUrl else null, + ) +} diff --git a/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt b/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt index 09bf354..631b40d 100644 --- a/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt +++ b/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt @@ -86,10 +86,9 @@ class SessionStore(private val context: Context) { if (showAvatar != null) { prefs[profileShowAvatarKey] = if (showAvatar) "1" else "0" } - if (avatarUrl != null) { - if (avatarUrl.isBlank()) prefs.remove(profileAvatarUrlKey) - else prefs[profileAvatarUrlKey] = avatarUrl - } + // Altijd syncen: null/blank = geen custom foto (emoji tonen) + if (avatarUrl.isNullOrBlank()) prefs.remove(profileAvatarUrlKey) + else prefs[profileAvatarUrlKey] = avatarUrl } } } diff --git a/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt b/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt index 8ae43a1..a29b890 100644 --- a/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt +++ b/apps/android-tv/app-mobile/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt @@ -1350,22 +1350,32 @@ private fun SettingsScreen( .background(Color(0xFF2E2E3A)), contentAlignment = Alignment.Center, ) { - if (profileShowAvatar && !profileAvatarUrl.isNullOrBlank()) { - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(profileAvatarUrl) - .apply { - if (!profileAuthHeader.isNullOrBlank()) { - addHeader("Authorization", profileAuthHeader) - } - } - .build(), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) + if (profileShowAvatar) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text(avatarEmoji(profileAvatarId ?: "fox"), fontSize = 22.sp) + if (!profileAvatarUrl.isNullOrBlank()) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(profileAvatarUrl) + .apply { + if (!profileAuthHeader.isNullOrBlank()) { + addHeader("Authorization", profileAuthHeader) + } + } + .build(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + } + } } else { - Text(avatarEmoji(profileAvatarId ?: "fox"), fontSize = 22.sp) + Text( + (profileName?.firstOrNull()?.uppercaseChar() ?: '?').toString(), + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + ) } } Column(Modifier.weight(1f)) { diff --git a/apps/android-tv/app/build.gradle.kts b/apps/android-tv/app/build.gradle.kts index 714f4b7..bae0fa4 100644 --- a/apps/android-tv/app/build.gradle.kts +++ b/apps/android-tv/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "nl.vonas.mediacluster.tv" minSdk = 24 targetSdk = 36 - versionCode = 106 - versionName = "0.14.43" + versionCode = 107 + versionName = "0.14.44" buildConfigField("String", "DEFAULT_API_BASE", "\"https://master.vonas.nl\"") ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt index 00733c4..cb3cb62 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/ApiClient.kt @@ -875,14 +875,21 @@ private fun JSONArray?.toProfiles(): List { } } -private fun JSONObject.toProfile(): ViewerProfile = ViewerProfile( - id = getString("id"), - name = optString("name"), - avatarId = optString("avatarId").ifBlank { "fox" }, - isOwner = optBoolean("isOwner"), - isKids = optBoolean("isKids"), - hasPin = optBoolean("hasPin"), - showAvatar = if (has("showAvatar")) optBoolean("showAvatar") else true, - hasCustomAvatar = optBoolean("hasCustomAvatar"), - avatarUrl = optString("avatarUrl").ifBlank { null }, -) +private fun JSONObject.toProfile(): ViewerProfile { + val hasCustom = optBoolean("hasCustomAvatar") + val rawUrl = optString("avatarUrl").takeIf { it.isNotBlank() } + return ViewerProfile( + id = getString("id"), + name = optString("name"), + avatarId = optString("avatarId").ifBlank { "fox" }, + isOwner = optBoolean("isOwner"), + isKids = optBoolean("isKids"), + hasPin = optBoolean("hasPin"), + showAvatar = when { + !has("showAvatar") || isNull("showAvatar") -> true + else -> optBoolean("showAvatar", true) + }, + hasCustomAvatar = hasCustom, + avatarUrl = if (hasCustom) rawUrl else null, + ) +} diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt index 4807ce0..7786ec1 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/data/SessionStore.kt @@ -105,10 +105,9 @@ class SessionStore(private val context: Context) { if (showAvatar != null) { prefs[profileShowAvatarKey] = if (showAvatar) "1" else "0" } - if (avatarUrl != null) { - if (avatarUrl.isBlank()) prefs.remove(profileAvatarUrlKey) - else prefs[profileAvatarUrlKey] = avatarUrl - } + // Altijd syncen: null/blank = geen custom foto (emoji tonen) + if (avatarUrl.isNullOrBlank()) prefs.remove(profileAvatarUrlKey) + else prefs[profileAvatarUrlKey] = avatarUrl } } } diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt index add6ba7..416f787 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/McNav.kt @@ -784,6 +784,7 @@ private fun MainHub( MainTab.Settings -> SettingsScreen( api = api, profileAvatarId = profileAvatarId, + profileShowAvatar = profileShowAvatar, profileName = profileName, isOwner = isOwner, onBack = { @@ -1644,6 +1645,7 @@ private fun ShelfBlock( private fun SettingsScreen( api: ApiClient, profileAvatarId: String? = null, + profileShowAvatar: Boolean = true, profileName: String? = null, isOwner: Boolean = false, onBack: () -> Unit, @@ -1737,12 +1739,21 @@ private fun SettingsScreen( .background(Color(0xFF2E2E3A)), contentAlignment = Alignment.Center, ) { - Text( - avatarEmoji(profileAvatarId ?: "fox"), - fontSize = 30.sp, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), - ) + if (profileShowAvatar) { + Text( + avatarEmoji(profileAvatarId ?: "fox"), + fontSize = 30.sp, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } else { + Text( + (profileName?.firstOrNull()?.uppercaseChar() ?: '?').toString(), + color = Color.White, + fontSize = 26.sp, + fontWeight = FontWeight.Bold, + ) + } } Column(Modifier.weight(1f)) { Text( diff --git a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/components/TopNav.kt b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/components/TopNav.kt index b0bb0c5..0805543 100644 --- a/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/components/TopNav.kt +++ b/apps/android-tv/app/src/main/java/nl/vonas/mediacluster/tv/ui/components/TopNav.kt @@ -321,25 +321,32 @@ fun TopNavBar( horizontalArrangement = Arrangement.spacedBy(6.dp), ) { if (profileShowAvatar) { - if (!profileAvatarUrl.isNullOrBlank()) { - val ctx = LocalContext.current - AsyncImage( - model = ImageRequest.Builder(ctx) - .data(profileAvatarUrl) - .apply { - if (!profileAuthHeader.isNullOrBlank()) { - addHeader("Authorization", profileAuthHeader) + Box( + Modifier.size(22.dp), + contentAlignment = Alignment.Center, + ) { + // Emoji altijd als basis; custom foto eroverheen indien beschikbaar + if (profileAvatarEmoji != null) { + Text(profileAvatarEmoji, fontSize = 15.sp) + } + if (!profileAvatarUrl.isNullOrBlank()) { + val ctx = LocalContext.current + AsyncImage( + model = ImageRequest.Builder(ctx) + .data(profileAvatarUrl) + .apply { + if (!profileAuthHeader.isNullOrBlank()) { + addHeader("Authorization", profileAuthHeader) + } } - } - .build(), - contentDescription = null, - modifier = Modifier - .size(22.dp) - .clip(RoundedCornerShape(50)), - contentScale = ContentScale.Crop, - ) - } else if (profileAvatarEmoji != null) { - Text(profileAvatarEmoji, fontSize = 15.sp) + .build(), + contentDescription = null, + modifier = Modifier + .size(22.dp) + .clip(RoundedCornerShape(50)), + contentScale = ContentScale.Crop, + ) + } } } if (!profileLabel.isNullOrBlank()) {