From 3be5cdd3651511327dd1345b79ab64311ba37543 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 19:58:37 +0200 Subject: [PATCH] Show Android preview loading states and retry failed neighbors --- .../com/utun/chat/ui/screens/AvatarViewerScreen.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/AvatarViewerScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/AvatarViewerScreen.kt index bf7ca9bb..9727f602 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/AvatarViewerScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/AvatarViewerScreen.kt @@ -39,6 +39,7 @@ fun AvatarViewerScreen(group: String?, owner: Long, name: String, onClose: () -> var original by remember { mutableStateOf(null) } var failed by remember { mutableStateOf(false) } var catalogFailed by remember { mutableStateOf(false) } + var failedPreviews by remember { mutableStateOf(emptySet()) } var retry by remember { mutableStateOf(0) } LaunchedEffect(account,revision) { if (account==0L) onClose() } LaunchedEffect(source?.first,source?.second?.avatarServer,source?.second?.avatarFile,retry) { @@ -74,10 +75,11 @@ fun AvatarViewerScreen(group: String?, owner: Long, name: String, onClose: () -> LaunchedEffect(source?.first,source?.second?.avatarServer,visible,retry) { val (channel,member)=source ?: return@LaunchedEffect previews=previews.filterKeys { it in visible } + failedPreviews=emptySet() coroutineScope { visible.filter { it!=selected && it !in previews }.forEach { file -> launch { try { previews=previews+(file to repository.loadAvatar(channel,member.copy(avatarFile=file),false)) } catch (e: CancellationException) { throw e } - catch (e: Exception) { LogManager.addLog("WARN","Avatar","Neighbor failed file=$file: ${e.message}") } + catch (e: Exception) { failedPreviews=failedPreviews+file; LogManager.addLog("WARN","Avatar","Neighbor failed file=$file: ${e.message}") } } } } } fun move(delta: Int) { val next=index+delta; if (next in files.indices) selected=files[next] } @@ -93,7 +95,7 @@ fun AvatarViewerScreen(group: String?, owner: Long, name: String, onClose: () -> else if (source!=null && !failed) CircularProgressIndicator() else Text(stringResource(if (source==null) R.string.avatar_none else R.string.avatar_load_failed),color=Color.White) } - if (failed || catalogFailed) TextButton(onClick={ retry++ },modifier=Modifier.align(Alignment.CenterHorizontally)) { + if (failed || catalogFailed || failedPreviews.isNotEmpty()) TextButton(onClick={ retry++ },modifier=Modifier.align(Alignment.CenterHorizontally)) { Text(stringResource(R.string.avatar_retry)) } else if (original==null && source!=null) LinearProgressIndicator(Modifier.fillMaxWidth()) if (index>=0) Text("${index+1} / ${files.size}",color=Color.White,modifier=Modifier.align(Alignment.CenterHorizontally)) @@ -107,6 +109,10 @@ fun AvatarViewerScreen(group: String?, owner: Long, name: String, onClose: () -> .clickable(enabled=file!=null) { if (file!=null) selected=file },contentAlignment=Alignment.Center) { previews[file]?.let { Image(it.asImageBitmap(),contentDescription=null,contentScale=ContentScale.Crop, modifier=Modifier.fillMaxSize()) } + if (file!=null && file !in previews) { + if (file in failedPreviews) Text("!",color=Color.White) + else CircularProgressIndicator(Modifier.size(18.dp),strokeWidth=2.dp) + } } } TextButton(enabled=index>=0 && index+1