From 1946319d22d8a4944ceeea80f96549bd636334ec Mon Sep 17 00:00:00 2001 From: beyond Date: Sat, 5 Sep 2026 19:43:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/myapplication/MainApp.kt | 21 ++++- .../myapplication/data/EmbyRepository.kt | 57 ++++++++++++ .../myapplication/screens/LibraryScreen.kt | 93 ++++++++----------- .../example/myapplication/ui/LiquidGlass.kt | 12 ++- 4 files changed, 124 insertions(+), 59 deletions(-) diff --git a/app/src/main/kotlin/com/example/myapplication/MainApp.kt b/app/src/main/kotlin/com/example/myapplication/MainApp.kt index 75151aa..74a079d 100644 --- a/app/src/main/kotlin/com/example/myapplication/MainApp.kt +++ b/app/src/main/kotlin/com/example/myapplication/MainApp.kt @@ -151,7 +151,16 @@ fun MainApp() { try { val authResult = EmbyRepository.authenticate(server) if (authResult.success) { - ServerRepository.addServer(server.copy(apiKey = authResult.accessToken, userId = authResult.userId)) + var finalServer = server.copy(apiKey = authResult.accessToken, userId = authResult.userId) + if (finalServer.name.isBlank()) { + val fetchedName = EmbyRepository.fetchServerName(finalServer, authResult.accessToken) + finalServer = if (!fetchedName.isNullOrBlank()) { + finalServer.copy(name = fetchedName) + } else { + finalServer.copy(name = finalServer.displayHost) + } + } + ServerRepository.addServer(finalServer) if (selectedServerId.isBlank()) selectedServerId = server.id onResult(null) } else { @@ -167,7 +176,15 @@ fun MainApp() { try { val authResult = EmbyRepository.authenticate(server) if (authResult.success) { - val updatedServer = server.copy(apiKey = authResult.accessToken, userId = authResult.userId) + var updatedServer = server.copy(apiKey = authResult.accessToken, userId = authResult.userId) + if (updatedServer.name.isBlank()) { + val fetchedName = EmbyRepository.fetchServerName(updatedServer, authResult.accessToken) + updatedServer = if (!fetchedName.isNullOrBlank()) { + updatedServer.copy(name = fetchedName) + } else { + updatedServer.copy(name = updatedServer.displayHost) + } + } ServerRepository.updateServer(updatedServer) // Re-check online status after edit ServerRepository.checkServerOnline(updatedServer.id) diff --git a/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt b/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt index 27ff46f..09e35c9 100644 --- a/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt +++ b/app/src/main/kotlin/com/example/myapplication/data/EmbyRepository.kt @@ -894,6 +894,63 @@ object EmbyRepository { return "$serverUrl/emby/Items/${person.id}/Images/Primary?tag=${person.primaryImageTag}&maxWidth=180&quality=70" } + /** + * Fetch the server name from emby/System/Info/Public (no auth required). + * Falls back to emby/System/Info with token if public endpoint fails. + * Returns the server name or null if both fail. + */ + suspend fun fetchServerName(server: EmbyServer, token: String): String? = withContext(Dispatchers.IO) { + // Try public endpoint first (no auth needed, more reliable) + val publicName = fetchServerNameFromUrl("${server.fullUrl}/emby/System/Info/Public") + if (!publicName.isNullOrBlank()) return@withContext publicName + // Fall back to authenticated endpoint + val authUrl = "${server.fullUrl}/emby/System/Info" + try { + val request = Request.Builder() + .url(authUrl) + .header("X-Emby-Token", token) + .header("Accept", "application/json") + .get() + .build() + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) return@use null + val body = response.body?.string() ?: return@use null + parseServerName(body) + } + } catch (_: Exception) { + null + } + } + + private fun fetchServerNameFromUrl(url: String): String? { + try { + val request = Request.Builder() + .url(url) + .header("Accept", "application/json") + .get() + .build() + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) return null + val body = response.body?.string() ?: return null + return parseServerName(body) + } + } catch (_: Exception) { + return null + } + } + + private fun parseServerName(jsonBody: String): String? { + return try { + val json = gson.fromJson(jsonBody, Map::class.java) as? Map<*, *> + // Try common field names across Emby/Jellyfin + val name = json?.get("ServerName")?.toString() + ?: json?.get("Name")?.toString() + name?.takeIf { it.isNotBlank() } + } catch (_: Exception) { + null + } + } + suspend fun checkOnline(server: EmbyServer): Boolean = withContext(Dispatchers.IO) { val url = "${server.fullUrl}/emby/System/Info/Public" try { diff --git a/app/src/main/kotlin/com/example/myapplication/screens/LibraryScreen.kt b/app/src/main/kotlin/com/example/myapplication/screens/LibraryScreen.kt index 62cb6a2..9879ffb 100644 --- a/app/src/main/kotlin/com/example/myapplication/screens/LibraryScreen.kt +++ b/app/src/main/kotlin/com/example/myapplication/screens/LibraryScreen.kt @@ -188,40 +188,32 @@ private fun ServerCard(server: EmbyServer, tint: Color, nowMillis: Long, onClick padding = PaddingValues(0.dp), highlight = false, ) { - Column(modifier = Modifier.fillMaxWidth().background(pressBg).padding(14.dp)) { + Column(modifier = Modifier.fillMaxWidth().background(pressBg).padding(horizontal = 12.dp, vertical = 8.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { if (server.iconUrl.isNotBlank()) { - LumaAsyncImage(model = ImageRequest.Builder(LocalContext.current).data(server.iconUrl).crossfade(true).build(), contentDescription = null, modifier = Modifier.size(46.dp).clip(RoundedCornerShape(14.dp)), contentScale = ContentScale.Crop) + LumaAsyncImage(model = ImageRequest.Builder(LocalContext.current).data(server.iconUrl).crossfade(true).build(), contentDescription = null, modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)), contentScale = ContentScale.Crop) } else { - Box(Modifier.size(46.dp).clip(RoundedCornerShape(14.dp)).background(Brush.linearGradient(listOf(tint, tint.copy(alpha = 0.7f)))), contentAlignment = Alignment.Center) { - LumaIconView(LumaIcon.Server, contentDescription = null, tint = Color.White, modifier = Modifier.size(26.dp)) + Box(Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Brush.linearGradient(listOf(tint, tint.copy(alpha = 0.7f)))), contentAlignment = Alignment.Center) { + LumaIconView(LumaIcon.Server, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp)) } } - Spacer(Modifier.width(12.dp)) + Spacer(Modifier.width(10.dp)) Column(modifier = Modifier.weight(1f)) { - Text(text = server.name.ifBlank { server.displayHost }, color = GlassTokens.text, fontSize = 16.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis) - Text( - text = activeLineName, - color = GlassTokens.accent, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - if (server.note.isNotBlank()) { - Text( - text = server.note, - color = GlassTokens.subtext, - fontSize = 11.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(top = 2.dp), - ) + Row(verticalAlignment = Alignment.CenterVertically) { + Text(text = server.name.ifBlank { server.displayHost }, color = GlassTokens.text, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(0.85f)) + ServerStatusLine(server = server, nowMillis = nowMillis, compact = true) + } + Row(Modifier.padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically) { + ServerMediaCounts(server = server) + if (server.note.isNotBlank()) { + Spacer(Modifier.width(6.dp)) + Text(text = server.note, color = GlassTokens.subtext, fontSize = 10.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) + } } } Box( Modifier - .size(48.dp) + .size(32.dp) .onGloballyPositioned(onMorePositioned) .clickable( indication = null, @@ -230,17 +222,9 @@ private fun ServerCard(server: EmbyServer, tint: Color, nowMillis: Long, onClick ) { onMoreClick() }, contentAlignment = Alignment.Center, ) { - LumaIconView(LumaIcon.More, contentDescription = stringResource(Res.string.server_actions), tint = GlassTokens.subtext, modifier = Modifier.size(24.dp)) + LumaIconView(LumaIcon.More, contentDescription = stringResource(Res.string.server_actions), tint = GlassTokens.subtext, modifier = Modifier.size(18.dp)) } } - ServerStatusLine(server = server, nowMillis = nowMillis, compact = false, modifier = Modifier.padding(top = 8.dp)) - // Fixed-height error line to keep card heights consistent - Box(modifier = Modifier.height(14.dp)) { - if (!server.isOnline && server.authError.isNotBlank()) { - Text(text = server.authError, color = offlineRed, fontSize = 10.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) - } - } - ServerMediaCounts(server = server, modifier = Modifier.padding(top = 4.dp)) } } } @@ -269,39 +253,39 @@ private fun ServerGridCard(server: EmbyServer, tint: Color, nowMillis: Long, onC padding = PaddingValues(0.dp), highlight = false, ) { - Column(modifier = Modifier.fillMaxWidth().background(pressBg).padding(14.dp), horizontalAlignment = Alignment.Start) { + Column(modifier = Modifier.fillMaxWidth().background(pressBg).padding(10.dp), horizontalAlignment = Alignment.Start) { Row(verticalAlignment = Alignment.CenterVertically) { if (server.iconUrl.isNotBlank()) { LumaAsyncImage( model = ImageRequest.Builder(LocalContext.current).data(server.iconUrl).crossfade(true).build(), contentDescription = null, - modifier = Modifier.size(48.dp).clip(RoundedCornerShape(14.dp)), + modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)), contentScale = ContentScale.Crop, ) } else { Box( - Modifier.size(48.dp).clip(RoundedCornerShape(14.dp)).background(Brush.linearGradient(listOf(tint, tint.copy(alpha = 0.7f)))), + Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Brush.linearGradient(listOf(tint, tint.copy(alpha = 0.7f)))), contentAlignment = Alignment.Center, ) { - LumaIconView(LumaIcon.Server, contentDescription = null, tint = Color.White, modifier = Modifier.size(26.dp)) + LumaIconView(LumaIcon.Server, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp)) } } - Spacer(Modifier.width(10.dp)) + Spacer(Modifier.width(8.dp)) Column(Modifier.weight(1f)) { Text( text = server.name.ifBlank { server.displayHost }, color = GlassTokens.text, - fontSize = 14.sp, + fontSize = 13.sp, fontWeight = FontWeight.SemiBold, maxLines = 1, overflow = TextOverflow.Ellipsis, ) - Spacer(Modifier.height(3.dp)) - Text(text = activeLineName, color = GlassTokens.accent, fontSize = 11.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis) + Spacer(Modifier.height(2.dp)) + Text(text = activeLineName, color = GlassTokens.accent, fontSize = 10.sp, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis) } Box( Modifier - .size(36.dp) + .size(30.dp) .onGloballyPositioned(onMorePositioned) .clickable( indication = null, @@ -310,17 +294,16 @@ private fun ServerGridCard(server: EmbyServer, tint: Color, nowMillis: Long, onC ) { onMoreClick() }, contentAlignment = Alignment.Center, ) { - LumaIconView(LumaIcon.More, contentDescription = stringResource(Res.string.server_actions), tint = GlassTokens.subtext, modifier = Modifier.size(20.dp)) + LumaIconView(LumaIcon.More, contentDescription = stringResource(Res.string.server_actions), tint = GlassTokens.subtext, modifier = Modifier.size(18.dp)) } } - ServerStatusLine(server = server, nowMillis = nowMillis, compact = true, modifier = Modifier.padding(top = 8.dp)) - // Fixed-height area for error + note to keep card heights consistent - Box(modifier = Modifier.height(14.dp)) { - if (!server.isOnline && server.authError.isNotBlank()) { - Text(text = server.authError, color = offlineRed, fontSize = 10.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) - } else if (server.note.isNotBlank()) { - Text(text = server.note, color = GlassTokens.subtext, fontSize = 10.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) - } + ServerStatusLine(server = server, nowMillis = nowMillis, compact = true, modifier = Modifier.padding(top = 6.dp)) + if (!server.isOnline && server.authError.isNotBlank()) { + Text(text = server.authError, color = offlineRed, fontSize = 9.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(top = 4.dp)) + } else if (server.note.isNotBlank()) { + Text(text = server.note, color = GlassTokens.subtext, fontSize = 9.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(top = 4.dp)) + } else { + Spacer(Modifier.height(16.dp)) } ServerMediaCounts(server = server, modifier = Modifier.padding(top = 4.dp)) } @@ -607,6 +590,7 @@ private fun ServerStatusLine(server: EmbyServer, nowMillis: Long, compact: Boole fontSize = if (compact) 10.sp else 11.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, + modifier = if (compact) Modifier.widthIn(min = 96.dp) else Modifier, ) } } @@ -1297,11 +1281,10 @@ private fun IconPickerDialog( ) { Row(verticalAlignment = Alignment.CenterVertically) { Box( - Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)) - .background(Brush.linearGradient(listOf(Color(0xFFFF6B9D), Color(0xFFFF8A65)))), + Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)), contentAlignment = Alignment.Center, ) { - LumaIconView(LumaIcon.Image, contentDescription = null, tint = Color.White, modifier = Modifier.size(20.dp)) + LumaIconView(LumaIcon.Image, contentDescription = null, tint = GlassTokens.accent, modifier = Modifier.size(20.dp)) } Spacer(Modifier.width(12.dp)) Text(text = stringResource(Res.string.icon_picker_title), fontWeight = FontWeight.Bold, fontSize = 19.sp, color = GlassTokens.text) @@ -1457,7 +1440,7 @@ private fun IconPickerDialog( Box( modifier = Modifier.fillMaxWidth() - .lumaGlass(radius = 16.dp, level = LumaGlassLevel.Interactive) + .lumaGlass(radius = 16.dp, level = LumaGlassLevel.Clear) .clickable { onDismiss() } .padding(vertical = 14.dp), contentAlignment = Alignment.Center, diff --git a/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt b/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt index dee5eef..72e7045 100644 --- a/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt +++ b/app/src/main/kotlin/com/example/myapplication/ui/LiquidGlass.kt @@ -831,12 +831,20 @@ fun GlassSheet( ) .then( surfaceOverlay?.let { - Modifier.background(it, RoundedCornerShape(28.dp)) + Modifier.background(it, RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp, bottomStart = 0.dp, bottomEnd = 0.dp)) } ?: Modifier, ) - .padding(20.dp), + .padding(horizontal = 20.dp, vertical = 20.dp), content = content, ) + if (surfaceOverlay != null) { + Box( + Modifier + .fillMaxWidth() + .navigationBarsPadding() + .background(surfaceOverlay), + ) + } } } }