From dcb9c50c02751180ac8c48a7e28892b30d9f3b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Thu, 19 Mar 2026 15:25:47 +0100 Subject: [PATCH] feat: fetch Ghost profile avatar for account icon, fallback to colored initial --- .../swoosh/microblog/data/AccountManager.kt | 5 ++- .../microblog/data/api/GhostApiService.kt | 14 +++++++ .../microblog/data/model/GhostAccount.kt | 3 +- .../swoosh/microblog/ui/feed/FeedScreen.kt | 40 ++++++++++++------- .../microblog/ui/setup/SetupViewModel.kt | 12 ++++++ 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/swoosh/microblog/data/AccountManager.kt b/app/src/main/java/com/swoosh/microblog/data/AccountManager.kt index 1facf0a..31ed250 100644 --- a/app/src/main/java/com/swoosh/microblog/data/AccountManager.kt +++ b/app/src/main/java/com/swoosh/microblog/data/AccountManager.kt @@ -149,7 +149,7 @@ class AccountManager private constructor( return true } - fun updateAccount(id: String, name: String? = null, blogUrl: String? = null, apiKey: String? = null): Boolean { + fun updateAccount(id: String, name: String? = null, blogUrl: String? = null, apiKey: String? = null, avatarUrl: String? = null): Boolean { val accounts = getAccounts().toMutableList() val index = accounts.indexOfFirst { it.id == id } if (index == -1) return false @@ -158,7 +158,8 @@ class AccountManager private constructor( accounts[index] = current.copy( name = name ?: current.name, blogUrl = blogUrl ?: current.blogUrl, - apiKey = apiKey ?: current.apiKey + apiKey = apiKey ?: current.apiKey, + avatarUrl = avatarUrl ?: current.avatarUrl ) saveAccountsList(accounts) diff --git a/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt b/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt index 6469db0..cd41155 100644 --- a/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt +++ b/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt @@ -37,6 +37,9 @@ interface GhostApiService { @Path("id") id: String ): Response + @GET("ghost/api/admin/users/me/") + suspend fun getCurrentUser(): Response + @Multipart @POST("ghost/api/admin/images/upload/") suspend fun uploadImage( @@ -53,3 +56,14 @@ data class UploadedImage( val url: String, val ref: String? ) + +data class UsersResponse( + val users: List +) + +data class GhostUser( + val id: String, + val name: String?, + val profile_image: String?, + val email: String? +) diff --git a/app/src/main/java/com/swoosh/microblog/data/model/GhostAccount.kt b/app/src/main/java/com/swoosh/microblog/data/model/GhostAccount.kt index 11c8e12..2d73801 100644 --- a/app/src/main/java/com/swoosh/microblog/data/model/GhostAccount.kt +++ b/app/src/main/java/com/swoosh/microblog/data/model/GhostAccount.kt @@ -9,7 +9,8 @@ data class GhostAccount( val blogUrl: String, val apiKey: String, val isActive: Boolean = false, - val colorIndex: Int = 0 + val colorIndex: Int = 0, + val avatarUrl: String? = null ) { companion object { val ACCOUNT_COLORS = listOf( diff --git a/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt b/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt index 2071001..d46edb6 100644 --- a/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt +++ b/app/src/main/java/com/swoosh/microblog/ui/feed/FeedScreen.kt @@ -1159,22 +1159,32 @@ fun AccountAvatar( account: GhostAccount, size: Int = 36 ) { - val color = Color(GhostAccount.colorForIndex(account.colorIndex)) - val initial = account.name.firstOrNull()?.uppercase() ?: "?" - - Box( - modifier = Modifier - .size(size.dp) - .clip(CircleShape) - .background(color), - contentAlignment = Alignment.Center - ) { - Text( - text = initial, - color = Color.White, - style = if (size <= 28) MaterialTheme.typography.labelSmall - else MaterialTheme.typography.labelLarge + if (!account.avatarUrl.isNullOrBlank()) { + AsyncImage( + model = account.avatarUrl, + contentDescription = account.name, + modifier = Modifier + .size(size.dp) + .clip(CircleShape), + contentScale = ContentScale.Crop ) + } else { + val color = Color(GhostAccount.colorForIndex(account.colorIndex)) + val initial = account.name.firstOrNull()?.uppercase() ?: "?" + Box( + modifier = Modifier + .size(size.dp) + .clip(CircleShape) + .background(color), + contentAlignment = Alignment.Center + ) { + Text( + text = initial, + color = Color.White, + style = if (size <= 28) MaterialTheme.typography.labelSmall + else MaterialTheme.typography.labelLarge + ) + } } } diff --git a/app/src/main/java/com/swoosh/microblog/ui/setup/SetupViewModel.kt b/app/src/main/java/com/swoosh/microblog/ui/setup/SetupViewModel.kt index 485afab..f3d42d7 100644 --- a/app/src/main/java/com/swoosh/microblog/ui/setup/SetupViewModel.kt +++ b/app/src/main/java/com/swoosh/microblog/ui/setup/SetupViewModel.kt @@ -71,6 +71,18 @@ class SetupViewModel(application: Application) : AndroidViewModel(application) { val repo = PostRepository(getApplication()) repo.fetchPosts(page = 1, limit = 1).fold( onSuccess = { + // Fetch user profile for avatar + try { + val service = ApiClient.getService( + state.url, + apiKeyProvider = { state.apiKey } + ) + val userResponse = service.getCurrentUser() + val avatarUrl = userResponse.body()?.users?.firstOrNull()?.profile_image + if (avatarUrl != null) { + accountManager.updateAccount(id = account.id, avatarUrl = avatarUrl) + } + } catch (_: Exception) { /* avatar is best-effort */ } _uiState.update { it.copy(isTesting = false, isSuccess = true) } }, onFailure = { e ->