diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 8fe0f8a30..e4ae2a48c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -170,7 +170,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account ) ) { itemsIndexed(userSuggestions, key = { _, item -> item.pubkeyHex }) { index, item -> - UserLine(item) { + UserLine(item, account) { postViewModel.autocompleteWithUser(item) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt index d00c4acfe..d8904e37d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt @@ -42,7 +42,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr val account = accountState?.account ?: return val accountUserState by account.userProfile().live.observeAsState() - val accountUser = accountUserState?.user + val accountUser = accountUserState?.user ?: return if (note?.event == null) { BlankNote(Modifier) @@ -100,8 +100,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr userToComposeOn?.let { user -> ChannelName( - channelPicture = user.profilePicture(), - channelPicturePlaceholder = rememberAsyncImagePainter("https://robohash.org/${user.pubkeyHex}.png"), + channelPicture = { UserPicture(user = user, userAccount = accountUser, size = 55.dp) }, channelTitle = { UsernameDisplay(user, it) }, channelLastTime = note.event?.createdAt, channelLastContent = accountViewModel.decrypt(note), @@ -120,11 +119,8 @@ fun ChannelName( channelLastContent: String?, onClick: () -> Unit ) { - Column(modifier = Modifier.clickable(onClick = onClick) ) { - Row( - modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp) - ) { - + ChannelName( + channelPicture = { AsyncImage( model = channelPicture, placeholder = channelPicturePlaceholder, @@ -134,6 +130,27 @@ fun ChannelName( .height(55.dp) .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), verticalArrangement = Arrangement.SpaceAround) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index c43091aa2..fad2ef432 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -8,13 +8,17 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column 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.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Divider import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.Icon import androidx.compose.material.LocalContentColor import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -27,21 +31,29 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation.NavController import coil.compose.AsyncImage 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.User import com.vitorpamplona.amethyst.model.toNote import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.ui.components.RichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.Following import nostr.postr.events.TextNoteEvent import nostr.postr.toNpub @@ -49,7 +61,7 @@ import nostr.postr.toNpub @Composable fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account + val account = accountState?.account ?: return val noteState by baseNote.live.observeAsState() val note = noteState?.note @@ -67,8 +79,8 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool onLongClick = { popupExpanded = true }, ), isInnerNote) } else { - val authorState by note.author!!.live.observeAsState() - val author = authorState?.user + val authorState by note.author?.live!!.observeAsState() + val author = authorState?.user ?: return // if it has event, it should have an author Column(modifier = modifier.combinedClickable( @@ -96,40 +108,24 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool // Draws the boosted picture outside the boosted card. if (!isInnerNote) { - Box(modifier = Modifier.width(55.dp).padding(0.dp)) { - AsyncImage( - model = author?.profilePicture(), - contentDescription = "Profile Image", - 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}") - } - }) - ) + Box(modifier = Modifier + .width(55.dp) + .padding(0.dp)) { + UserPicture(author, navController, account.userProfile(), 55.dp) // boosted picture val boostedPosts = note.replyTo if (note.event is RepostEvent && boostedPosts != null && boostedPosts.isNotEmpty()) { - AsyncImage( - model = boostedPosts[0].author?.profilePicture(), - contentDescription = "Profile Image", - placeholder = rememberAsyncImagePainter("https://robohash.org/${boostedPosts[0].author?.pubkeyHex}.png"), - modifier = Modifier - .width(35.dp).height(35.dp) - .clip(shape = CircleShape) - .align(Alignment.BottomEnd) - .background(MaterialTheme.colors.background) - .border(2.dp, MaterialTheme.colors.primary, CircleShape) - .clickable(onClick = { - boostedPosts[0].author?.let { - navController.navigate("User/${it.pubkeyHex}") - } - }) - ) + val boostedAuthor = boostedPosts[0].author + Box( + Modifier + .width(30.dp) + .height(30.dp) + .align(Alignment.BottomEnd)) { + UserPicture(boostedAuthor, navController, account.userProfile(), 35.dp, + pictureModifier = Modifier.border(2.dp, MaterialTheme.colors.background, CircleShape) + ) + } } } } @@ -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 fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) { val clipboardManager = LocalClipboardManager.current diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 6bbbceebf..571da8b4b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account + val account = accountState?.account ?: return val userState by baseUser.live.observeAsState() val user = userState?.user ?: return @@ -52,14 +52,7 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle top = 10.dp) ) { - AsyncImage( - 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) - ) + UserPicture(user, navController, account.userProfile(), 55.dp) Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 11d6d6d6a..86566cb38 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource import com.vitorpamplona.amethyst.ui.actions.NewChannelView import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView +import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.launch import nostr.postr.toNpub @@ -138,17 +139,9 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Bottom) { - AsyncImage( - model = user.profilePicture(), - placeholder = rememberAsyncImagePainter("https://robohash.org/${user?.pubkeyHex}.png"), - 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) - ) + + UserPicture(user, navController, account.userProfile(), 100.dp, + pictureModifier = Modifier.border(3.dp, MaterialTheme.colors.background, CircleShape)) Spacer(Modifier.weight(1f)) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 0ace05c03..2d4503f0b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -47,6 +47,7 @@ import androidx.navigation.NavController import coil.compose.AsyncImage import coil.compose.rememberAsyncImagePainter import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.LocalCache 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.NoteCompose 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.screen.loggedIn.AccountViewModel @@ -197,6 +199,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont @Composable fun UserLine( baseUser: User, + account: Account, onClick: () -> Unit ) { val userState by baseUser.live.observeAsState() @@ -215,15 +218,7 @@ fun UserLine( ) ) { - AsyncImage( - 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) - ) + UserPicture(user, account.userProfile(), 55.dp, Modifier, null) Column( modifier = Modifier diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt index 74b3e6c49..0e5e6ca79 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt @@ -5,4 +5,8 @@ import androidx.compose.ui.graphics.Color val Purple200 = Color(0xFFBB86FC) val Purple500 = Color(0xFF6200EE) val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) \ No newline at end of file +val Teal200 = Color(0xFF03DAC5) + +val Following = Color(0xFF2CC03D) +val FollowsFollow = Color.Yellow +val NIP05Verified = Color.Blue