Marks following users with a Green verified mark

This commit is contained in:
Vitor Pamplona
2023-01-26 13:16:57 -03:00
parent d8ef083086
commit 93033295be
7 changed files with 154 additions and 72 deletions

View File

@ -170,7 +170,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account
) )
) { ) {
itemsIndexed(userSuggestions, key = { _, item -> item.pubkeyHex }) { index, item -> itemsIndexed(userSuggestions, key = { _, item -> item.pubkeyHex }) { index, item ->
UserLine(item) { UserLine(item, account) {
postViewModel.autocompleteWithUser(item) postViewModel.autocompleteWithUser(item)
} }
} }

View File

@ -42,7 +42,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
val account = accountState?.account ?: return val account = accountState?.account ?: return
val accountUserState by account.userProfile().live.observeAsState() val accountUserState by account.userProfile().live.observeAsState()
val accountUser = accountUserState?.user val accountUser = accountUserState?.user ?: return
if (note?.event == null) { if (note?.event == null) {
BlankNote(Modifier) BlankNote(Modifier)
@ -100,8 +100,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr
userToComposeOn?.let { user -> userToComposeOn?.let { user ->
ChannelName( ChannelName(
channelPicture = user.profilePicture(), channelPicture = { UserPicture(user = user, userAccount = accountUser, size = 55.dp) },
channelPicturePlaceholder = rememberAsyncImagePainter("https://robohash.org/${user.pubkeyHex}.png"),
channelTitle = { UsernameDisplay(user, it) }, channelTitle = { UsernameDisplay(user, it) },
channelLastTime = note.event?.createdAt, channelLastTime = note.event?.createdAt,
channelLastContent = accountViewModel.decrypt(note), channelLastContent = accountViewModel.decrypt(note),
@ -120,11 +119,8 @@ fun ChannelName(
channelLastContent: String?, channelLastContent: String?,
onClick: () -> Unit onClick: () -> Unit
) { ) {
Column(modifier = Modifier.clickable(onClick = onClick) ) { ChannelName(
Row( channelPicture = {
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)
) {
AsyncImage( AsyncImage(
model = channelPicture, model = channelPicture,
placeholder = channelPicturePlaceholder, placeholder = channelPicturePlaceholder,
@ -134,6 +130,27 @@ fun ChannelName(
.height(55.dp) .height(55.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
) )
},
channelTitle,
channelLastTime,
channelLastContent,
onClick
)
}
@Composable
fun ChannelName(
channelPicture: @Composable () -> Unit,
channelTitle: @Composable (Modifier) -> Unit,
channelLastTime: Long?,
channelLastContent: String?,
onClick: () -> Unit
) {
Column(modifier = Modifier.clickable(onClick = onClick) ) {
Row(
modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)
) {
channelPicture()
Column(modifier = Modifier.padding(start = 10.dp), Column(modifier = Modifier.padding(start = 10.dp),
verticalArrangement = Arrangement.SpaceAround) { verticalArrangement = Arrangement.SpaceAround) {

View File

@ -8,13 +8,17 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Divider import androidx.compose.material.Divider
import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
@ -27,21 +31,29 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.toNote import com.vitorpamplona.amethyst.model.toNote
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.ui.components.RichTextViewer import com.vitorpamplona.amethyst.ui.components.RichTextViewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Following
import nostr.postr.events.TextNoteEvent import nostr.postr.events.TextNoteEvent
import nostr.postr.toNpub import nostr.postr.toNpub
@ -49,7 +61,7 @@ import nostr.postr.toNpub
@Composable @Composable
fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) { fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account val account = accountState?.account ?: return
val noteState by baseNote.live.observeAsState() val noteState by baseNote.live.observeAsState()
val note = noteState?.note val note = noteState?.note
@ -67,8 +79,8 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
onLongClick = { popupExpanded = true }, onLongClick = { popupExpanded = true },
), isInnerNote) ), isInnerNote)
} else { } else {
val authorState by note.author!!.live.observeAsState() val authorState by note.author?.live!!.observeAsState()
val author = authorState?.user val author = authorState?.user ?: return // if it has event, it should have an author
Column(modifier = Column(modifier =
modifier.combinedClickable( modifier.combinedClickable(
@ -96,40 +108,24 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
// Draws the boosted picture outside the boosted card. // Draws the boosted picture outside the boosted card.
if (!isInnerNote) { if (!isInnerNote) {
Box(modifier = Modifier.width(55.dp).padding(0.dp)) { Box(modifier = Modifier
AsyncImage( .width(55.dp)
model = author?.profilePicture(), .padding(0.dp)) {
contentDescription = "Profile Image", UserPicture(author, navController, account.userProfile(), 55.dp)
placeholder = rememberAsyncImagePainter("https://robohash.org/${author?.pubkeyHex}.png"),
modifier = Modifier
.width(55.dp).height(55.dp)
.clip(shape = CircleShape)
.clickable(onClick = {
author?.let {
navController.navigate("User/${it.pubkeyHex}")
}
})
)
// boosted picture // boosted picture
val boostedPosts = note.replyTo val boostedPosts = note.replyTo
if (note.event is RepostEvent && boostedPosts != null && boostedPosts.isNotEmpty()) { if (note.event is RepostEvent && boostedPosts != null && boostedPosts.isNotEmpty()) {
AsyncImage( val boostedAuthor = boostedPosts[0].author
model = boostedPosts[0].author?.profilePicture(), Box(
contentDescription = "Profile Image", Modifier
placeholder = rememberAsyncImagePainter("https://robohash.org/${boostedPosts[0].author?.pubkeyHex}.png"), .width(30.dp)
modifier = Modifier .height(30.dp)
.width(35.dp).height(35.dp) .align(Alignment.BottomEnd)) {
.clip(shape = CircleShape) UserPicture(boostedAuthor, navController, account.userProfile(), 35.dp,
.align(Alignment.BottomEnd) pictureModifier = Modifier.border(2.dp, MaterialTheme.colors.background, CircleShape)
.background(MaterialTheme.colors.background) )
.border(2.dp, MaterialTheme.colors.primary, CircleShape) }
.clickable(onClick = {
boostedPosts[0].author?.let {
navController.navigate("User/${it.pubkeyHex}")
}
})
)
} }
} }
} }
@ -204,6 +200,90 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
} }
} }
@Composable
fun UserPicture(
user: User?,
navController: NavController,
userAccount: User,
size: Dp,
pictureModifier: Modifier = Modifier
) {
UserPicture(user, userAccount, size, pictureModifier) {
user?.let {
navController.navigate("User/${it.pubkeyHex}")
}
}
}
@Composable
fun UserPicture(
user: User?,
userAccount: User,
size: Dp,
pictureModifier: Modifier = Modifier,
onClick: (() -> Unit)? = null
) {
Box(
Modifier
.width(size)
.height(size)) {
if (user == null) {
AsyncImage(
model = "https://robohash.org/ohno.png",
contentDescription = "Profile Image",
placeholder = rememberAsyncImagePainter("https://robohash.org/ohno.png"),
modifier = pictureModifier
.fillMaxSize(1f)
.clip(shape = CircleShape)
.background(MaterialTheme.colors.background)
)
} else {
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)
else
this
}
)
if (userAccount.isFollowing(user) || user == userAccount) {
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
)
}
}
}
}
}
@Composable @Composable
fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) { fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current

View File

@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@Composable @Composable
fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account val account = accountState?.account ?: return
val userState by baseUser.live.observeAsState() val userState by baseUser.live.observeAsState()
val user = userState?.user ?: return val user = userState?.user ?: return
@ -52,14 +52,7 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
top = 10.dp) top = 10.dp)
) { ) {
AsyncImage( UserPicture(user, navController, account.userProfile(), 55.dp)
model = user.profilePicture(),
placeholder = rememberAsyncImagePainter("https://robohash.org/${user.pubkeyHex}.png"),
contentDescription = "Profile Image",
modifier = Modifier
.width(55.dp).height(55.dp)
.clip(shape = CircleShape)
)
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) {

View File

@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
import com.vitorpamplona.amethyst.ui.actions.NewChannelView import com.vitorpamplona.amethyst.ui.actions.NewChannelView
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import nostr.postr.toNpub import nostr.postr.toNpub
@ -138,17 +139,9 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
Row(horizontalArrangement = Arrangement.SpaceBetween, Row(horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom) { verticalAlignment = Alignment.Bottom) {
AsyncImage(
model = user.profilePicture(), UserPicture(user, navController, account.userProfile(), 100.dp,
placeholder = rememberAsyncImagePainter("https://robohash.org/${user?.pubkeyHex}.png"), pictureModifier = Modifier.border(3.dp, MaterialTheme.colors.background, CircleShape))
contentDescription = "Profile Image",
modifier = Modifier
.width(100.dp)
.height(100.dp)
.clip(shape = CircleShape)
.border(3.dp, MaterialTheme.colors.background, CircleShape)
.background(MaterialTheme.colors.background)
)
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))

View File

@ -47,6 +47,7 @@ import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@ -56,6 +57,7 @@ import com.vitorpamplona.amethyst.service.NostrThreadDataSource
import com.vitorpamplona.amethyst.ui.note.ChannelName import com.vitorpamplona.amethyst.ui.note.ChannelName
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@ -197,6 +199,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
@Composable @Composable
fun UserLine( fun UserLine(
baseUser: User, baseUser: User,
account: Account,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val userState by baseUser.live.observeAsState() val userState by baseUser.live.observeAsState()
@ -215,15 +218,7 @@ fun UserLine(
) )
) { ) {
AsyncImage( UserPicture(user, account.userProfile(), 55.dp, Modifier, null)
model = user.profilePicture(),
placeholder = rememberAsyncImagePainter("https://robohash.org/${user.pubkeyHex}.png"),
contentDescription = "Profile Image",
modifier = Modifier
.width(55.dp)
.height(55.dp)
.clip(shape = CircleShape)
)
Column( Column(
modifier = Modifier modifier = Modifier

View File

@ -6,3 +6,7 @@ val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE) val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3) val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5) val Teal200 = Color(0xFF03DAC5)
val Following = Color(0xFF2CC03D)
val FollowsFollow = Color.Yellow
val NIP05Verified = Color.Blue