feat: fetch Ghost profile avatar for account icon, fallback to colored initial

This commit is contained in:
Paweł Orzech 2026-03-19 15:25:47 +01:00
parent edca4dd0c5
commit dcb9c50c02
No known key found for this signature in database
5 changed files with 56 additions and 18 deletions

View file

@ -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)

View file

@ -37,6 +37,9 @@ interface GhostApiService {
@Path("id") id: String
): Response<Unit>
@GET("ghost/api/admin/users/me/")
suspend fun getCurrentUser(): Response<UsersResponse>
@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<GhostUser>
)
data class GhostUser(
val id: String,
val name: String?,
val profile_image: String?,
val email: String?
)

View file

@ -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(

View file

@ -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
)
}
}
}

View file

@ -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 ->