小调整
This commit is contained in:
1 parent
082c42e177
commit
1946319d22
4 files changed
+124
-59
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user