From 6f8fa456851465598ea900a4213301b01f33c58e Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Mon, 20 Apr 2026 22:57:15 +0200 Subject: [PATCH] Render profile pictures --- composeApp/build.gradle.kts | 3 + .../composable/widgets/feed/EventListView.kt | 10 ++- .../widgets/profile/ProfileAvatar.kt | 83 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/profile/ProfileAvatar.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 19efd697..81d40c6a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -57,6 +57,9 @@ kotlin { implementation(libs.androidx.room.runtime) implementation(libs.androidx.sqlite.bundled) + implementation("io.coil-kt.coil3:coil-compose:3.4.0") + implementation("io.coil-kt.coil3:coil-network-ktor3:3.4.0") + implementation(libs.compose.runtime) implementation(libs.compose.foundation) implementation(libs.compose.material3) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt index 99c958bb..15810d0d 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/feed/EventListView.kt @@ -5,6 +5,7 @@ import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.Repost import ac.aux.compose.database.model.intermdiate.LocalNostrEvent import ac.aux.compose.extensions.toFormattedTimeAndDateString +import ac.aux.compose.ui.composable.widgets.profile.ProfileAvatar import ac.aux.compose.ui.theme.AuxTheme import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -162,16 +163,17 @@ private fun TextNoteFeedItem( Row( modifier = Modifier.fillMaxWidth().clickable( onClick = { - onNavigateToEvent.invoke(profile?.nostrEventId ?: nostrEvent.id) + onNavigateToEvent.invoke(profile.nostrEventId) } ), horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.Start), verticalAlignment = Alignment.CenterVertically ) { - Icon( - Icons.Default.Person, - contentDescription = "Profile picture", + ProfileAvatar( + publicKey = nostrEvent.pubKey, + profile = profile ) + Text( modifier = Modifier.weight(1f), text = profile.displayName ?: profile.userName ?: nostrEvent.pubKey, diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/profile/ProfileAvatar.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/profile/ProfileAvatar.kt new file mode 100644 index 00000000..8a1ffc32 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/profile/ProfileAvatar.kt @@ -0,0 +1,83 @@ +package ac.aux.compose.ui.composable.widgets.profile + +import ac.aux.compose.database.model.Profile +import ac.aux.compose.ui.theme.AuxTheme +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Face5 +import androidx.compose.material.icons.filled.Face6 +import androidx.compose.material.icons.filled.FaceRetouchingOff +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import co.touchlab.kermit.Logger +import coil3.compose.AsyncImage +import kotlin.math.abs + +@Composable +fun ProfileAvatar(publicKey: String, profile: Profile?) { + val tintColor = Color.hsl( + hue = abs( + publicKey.hashCode() + ).mod(360).toFloat(), + saturation = 0.55f, + lightness = 0.45f + ) + val imageModifier = Modifier.size(55.dp).clip(shape = CircleShape) + + if (profile?.picture == null) { + Icon( + modifier = imageModifier, + imageVector = Icons.Default.AccountCircle, + tint = tintColor, + contentDescription = "Profile picture", + ) + } else { + // Load image... + val placeHolderGraphic = rememberVectorPainter(Icons.Default.Face5) + val fallbackGraphic = rememberVectorPainter(Icons.Default.Face5) + val errorGraphic = rememberVectorPainter(Icons.Default.FaceRetouchingOff) + + AsyncImage( + model = profile.picture, + modifier = imageModifier, + contentDescription = "Profile picture for ${profile.displayName ?: profile.publicKey}", + placeholder = placeHolderGraphic, + fallback = fallbackGraphic, + error = errorGraphic, + contentScale = ContentScale.Crop + ) + } + // TODO: Show if we follow user... + // TODO: Show cards... +} + +@Preview +@Composable +fun ProfileAvatarPreview() { + AuxTheme { + Surface( + modifier = Modifier.padding(20.dp) + ) { + ProfileAvatar( + publicKey = "hwgwefwase", + profile = Profile( + publicKey = "", + nostrEventId = "referenced", + displayName = "Frank Talk", + picture = "pictureUrl" + ) + ) + } + } +} \ No newline at end of file