Moving Picture and Display Name Observables to leaf UI nodes.

This commit is contained in:
Vitor Pamplona
2023-01-31 22:12:24 -03:00
parent 94a4bfc2f3
commit 814fb1845f
9 changed files with 175 additions and 133 deletions

View File

@@ -76,19 +76,12 @@ fun BoostSetCompose(likeSetCard: BoostSetCard, isInnerNote: Boolean = false, rou
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) { Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
FlowRow() { FlowRow() {
likeSetCard.boostEvents.forEach { likeSetCard.boostEvents.forEach {
val cardNoteState by it.live.observeAsState() NoteAuthorPicture(
val cardNote = cardNoteState?.note note = it,
navController = navController,
if (cardNote?.author != null) { userAccount = account.userProfile(),
val userState by cardNote.author!!.live.observeAsState() size = 35.dp
)
UserPicture(
user = userState?.user,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
)
}
} }
} }

View File

@@ -134,7 +134,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
false false
ChannelName( ChannelName(
channelPicture = { UserPicture(user = user, userAccount = accountUser, size = 55.dp) }, channelPicture = { UserPicture(user, accountUser, size = 55.dp) },
channelTitle = { UsernameDisplay(user, it) }, channelTitle = { UsernameDisplay(user, it) },
channelLastTime = noteEvent?.createdAt, channelLastTime = noteEvent?.createdAt,
channelLastContent = accountViewModel.decrypt(note), channelLastContent = accountViewModel.decrypt(note),

View File

@@ -76,19 +76,12 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isIn
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) { Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
FlowRow() { FlowRow() {
likeSetCard.likeEvents.forEach { likeSetCard.likeEvents.forEach {
val cardNoteState by it.live.observeAsState() NoteAuthorPicture(
val cardNote = cardNoteState?.note note = it,
navController = navController,
if (cardNote?.author != null) { userAccount = account.userProfile(),
val userState by cardNote.author!!.live.observeAsState() size = 35.dp
)
UserPicture(
user = userState?.user,
navController = navController,
userAccount = account.userProfile(),
size = 35.dp
)
}
} }
} }

View File

@@ -95,9 +95,6 @@ fun NoteCompose(
onLongClick = { popupExpanded = true }, onLongClick = { popupExpanded = true },
), isInnerNote) ), isInnerNote)
} else { } else {
val authorState by note.author?.live!!.observeAsState()
val author = authorState?.user ?: return // if it has event, it should have an author
val isNew = routeForLastRead?.run { val isNew = routeForLastRead?.run {
val lastTime = NotificationCache.load(this, context) val lastTime = NotificationCache.load(this, context)
@@ -145,20 +142,20 @@ fun NoteCompose(
Box(modifier = Modifier Box(modifier = Modifier
.width(55.dp) .width(55.dp)
.padding(0.dp)) { .padding(0.dp)) {
UserPicture(author, navController, account.userProfile(), 55.dp)
// boosted picture NoteAuthorPicture(note, navController, account.userProfile(), 55.dp)
val boostedPosts = note.replyTo
if (note.event is RepostEvent && boostedPosts != null && boostedPosts.isNotEmpty()) { if (note.event is RepostEvent) {
val boostedAuthor = boostedPosts[0].author note.replyTo?.lastOrNull()?.let {
Box( Box(
Modifier Modifier
.width(30.dp) .width(30.dp)
.height(30.dp) .height(30.dp)
.align(Alignment.BottomEnd)) { .align(Alignment.BottomEnd)) {
UserPicture(boostedAuthor, navController, account.userProfile(), 35.dp, NoteAuthorPicture(it, navController, account.userProfile(), 35.dp,
pictureModifier = Modifier.border(2.dp, MaterialTheme.colors.background, CircleShape) pictureModifier = Modifier.border(2.dp, MaterialTheme.colors.background, CircleShape)
) )
}
} }
} }
@@ -197,8 +194,7 @@ fun NoteCompose(
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) { Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (author != null) NoteUsernameDisplay(note, Modifier.weight(1f))
UsernameDisplay(author, Modifier.weight(1f))
if (note.event !is RepostEvent) { if (note.event !is RepostEvent) {
Text( Text(
@@ -274,40 +270,42 @@ fun NoteCompose(
} }
} }
@Composable @Composable
fun UserPicture( fun NoteAuthorPicture(
user: User?, note: Note,
navController: NavController, navController: NavController,
userAccount: User, userAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier pictureModifier: Modifier = Modifier
) { ) {
UserPicture(user, userAccount, size, pictureModifier) { NoteAuthorPicture(note, userAccount, size, pictureModifier) {
user?.let { navController.navigate("User/${it.pubkeyHex}")
navController.navigate("User/${it.pubkeyHex}")
}
} }
} }
@Composable @Composable
fun UserPicture( fun NoteAuthorPicture(
user: User?, baseNote: Note,
userAccount: User, baseUserAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier, pictureModifier: Modifier = Modifier,
onClick: (() -> Unit)? = null onClick: ((User) -> Unit)? = null
) { ) {
val accountState by userAccount.live.observeAsState() val noteState by baseNote.live.observeAsState()
val accountUser = accountState?.user ?: return val note = noteState?.note ?: return
val author = note.author
Box( Box(
Modifier Modifier
.width(size) .width(size)
.height(size)) { .height(size)) {
if (user == null) { if (author == null) {
AsyncImage( AsyncImage(
model = "https://robohash.org/ohno.png", model = "https://robohash.org/ohno.png",
contentDescription = "Profile Image", contentDescription = "Unknown Author",
placeholder = rememberAsyncImagePainter("https://robohash.org/ohno.png"), placeholder = rememberAsyncImagePainter("https://robohash.org/ohno.png"),
modifier = pictureModifier modifier = pictureModifier
.fillMaxSize(1f) .fillMaxSize(1f)
@@ -315,54 +313,88 @@ fun UserPicture(
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
) )
} else { } else {
val userState by user.live.observeAsState() UserPicture(author, baseUserAccount, size, pictureModifier, onClick)
val userLive = userState?.user ?: return }
}
}
AsyncImage( @Composable
model = userLive.profilePicture(), fun UserPicture(
contentDescription = "Profile Image", user: User,
placeholder = rememberAsyncImagePainter("https://robohash.org/${userLive.pubkeyHex}.png"), navController: NavController,
modifier = pictureModifier userAccount: User,
.fillMaxSize(1f) size: Dp,
.clip(shape = CircleShape) pictureModifier: Modifier = Modifier
.background(MaterialTheme.colors.background) ) {
.run { UserPicture(user, userAccount, size, pictureModifier) {
if (onClick != null) navController.navigate("User/${it.pubkeyHex}")
this.clickable(onClick = onClick) }
else }
this
}
) @Composable
fun UserPicture(
baseUser: User,
baseUserAccount: User,
size: Dp,
pictureModifier: Modifier = Modifier,
onClick: ((User) -> Unit)? = null
) {
val accountState by baseUserAccount.live.observeAsState()
val accountUser = accountState?.user ?: return
if (accountUser.isFollowing(userLive) || userLive == accountUser) { val userState by baseUser.live.observeAsState()
Box( val user = userState?.user ?: return
Modifier
.width(size.div(3.5f))
.height(size.div(3.5f))
.align(Alignment.TopEnd),
contentAlignment = Alignment.Center
) {
// Background for the transparent checkmark
Text(
"x",
Modifier
.padding(4.dp)
.fillMaxSize()
.align(Alignment.Center)
.background(MaterialTheme.colors.background)
.clip(shape = CircleShape)
)
Icon( Box(
painter = painterResource(R.drawable.ic_verified), Modifier
"Following", .width(size)
modifier = Modifier.fillMaxSize(), .height(size)) {
tint = Following
) AsyncImage(
model = user.profilePicture(),
contentDescription = "Profile Image",
placeholder = rememberAsyncImagePainter("https://robohash.org/${user.pubkeyHex}.png"),
modifier = pictureModifier
.fillMaxSize(1f)
.clip(shape = CircleShape)
.background(MaterialTheme.colors.background)
.run {
if (onClick != null)
this.clickable(onClick = { onClick(user) } )
else
this
} }
)
if (accountUser.isFollowing(user) || user == accountUser) {
Box(
Modifier
.width(size.div(3.5f))
.height(size.div(3.5f))
.align(Alignment.TopEnd),
contentAlignment = Alignment.Center
) {
// Background for the transparent checkmark
Text(
"x",
Modifier
.padding(4.dp)
.fillMaxSize()
.align(Alignment.Center)
.background(MaterialTheme.colors.background)
.clip(shape = CircleShape)
)
Icon(
painter = painterResource(R.drawable.ic_verified),
"Following",
modifier = Modifier.fillMaxSize(),
tint = Following
)
} }
} }
} }
} }

View File

@@ -52,11 +52,11 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
top = 10.dp) top = 10.dp)
) { ) {
UserPicture(user, navController, account.userProfile(), 55.dp) UserPicture(baseUser, navController, account.userProfile(), 55.dp)
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
UsernameDisplay(user) UsernameDisplay(baseUser)
} }
Text( Text(

View File

@@ -3,42 +3,61 @@ package com.vitorpamplona.amethyst.ui.note
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
@Composable @Composable
fun UsernameDisplay(user: User, weight: Modifier = Modifier) { fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
if (user.bestUsername() != null || user.bestDisplayName() != null) { val noteState by baseNote.live.observeAsState()
if (user.bestDisplayName().isNullOrBlank()) { val note = noteState?.note ?: return
Text(
"@${(user.bestUsername() ?: "")}", val author = note.author
fontWeight = FontWeight.Bold,
maxLines = 1, if (author != null) {
overflow = TextOverflow.Ellipsis, UsernameDisplay(author, weight)
modifier = weight }
) }
} else {
Text( @Composable
user.bestDisplayName() ?: "", fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
fontWeight = FontWeight.Bold, val userState by baseUser.live.observeAsState()
) val user = userState?.user ?: return
Text(
"@${(user.bestUsername() ?: "")}", if (user.bestUsername() != null || user.bestDisplayName() != null) {
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), if (user.bestDisplayName().isNullOrBlank()) {
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = weight
)
}
} else {
Text( Text(
user.pubkeyDisplayHex, "@${(user.bestUsername() ?: "")}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = weight modifier = weight
) )
} else {
Text(
user.bestDisplayName() ?: "",
fontWeight = FontWeight.Bold,
)
Text(
"@${(user.bestUsername() ?: "")}",
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = weight
)
} }
} else {
Text(
user.pubkeyDisplayHex,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = weight
)
}
} }

View File

@@ -41,7 +41,9 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.RichTextViewer import com.vitorpamplona.amethyst.ui.components.RichTextViewer
import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.BlankNote
import com.vitorpamplona.amethyst.ui.note.HiddenNote import com.vitorpamplona.amethyst.ui.note.HiddenNote
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.note.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
@@ -185,12 +187,16 @@ fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController
} }
}) })
) { ) {
UserPicture(user = author, navController, userAccount = account.userProfile(), size = 55.dp) NoteAuthorPicture(
note = baseNote,
navController = navController,
userAccount = account.userProfile(),
size = 55.dp
)
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (author != null) NoteUsernameDisplay(baseNote)
UsernameDisplay(author)
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {

View File

@@ -139,8 +139,7 @@ fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navContro
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (author != null) UsernameDisplay(baseUser)
UsernameDisplay(author)
} }
} }
} }

View File

@@ -234,7 +234,7 @@ fun UserLine(
.weight(1f) .weight(1f)
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
UsernameDisplay(user) UsernameDisplay(baseUser)
} }
Text( Text(