diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt index c45c27848..e67083cc0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt @@ -31,6 +31,11 @@ class PrivateDmEvent( */ fun replyTo() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1) + fun with(pubkeyHex: String): Boolean { + return pubkeyHex == pubKey || + tags.firstOrNull { it.size > 1 && it[0] == "p" }?.getOrNull(1) == pubkeyHex + } + fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? { return try { val sharedSecret = Utils.getSharedSecret(privKey, pubKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index b2b0e7824..f0e8a89b8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -359,9 +359,9 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountV LocalCache.checkGetOrCreateNote(it.hex)?.let { note -> baseNotePair = Pair(note, it.additionalChars) } - } else { - nip19Route = it } + + nip19Route = it } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt index 5e4fef8b2..85325fc6c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BadgeCompose.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.ui.screen.BadgeCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.Dispatchers @@ -48,7 +47,6 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL val context = LocalContext.current.applicationContext - val noteEvent = note?.event var popupExpanded by remember { mutableStateOf(false) } if (note == null) { @@ -73,15 +71,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL Column( modifier = Modifier.background(backgroundColor).combinedClickable( onClick = { - if (noteEvent !is ChannelMessageEvent) { - navController.navigate("Note/${note.idHex}") { - launchSingleTop = true - } - } else { - note.channel()?.let { - navController.navigate("Channel/${it.idHex}") - } - } + routeFor(note, accountViewModel.userProfile())?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt index e25b5ba26..98aa24cdb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt @@ -74,7 +74,7 @@ fun HiddenNote(reports: Set, loggedIn: User, modifier: Modifier = Modifier FlowRow(modifier = Modifier.padding(top = 10.dp)) { reports.forEach { NoteAuthorPicture( - note = it, + baseNote = it, navController = navController, userAccount = loggedIn, size = 35.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt index da4d768e0..107e02fe1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt @@ -28,7 +28,6 @@ import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.ui.screen.BoostSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.Dispatchers @@ -68,15 +67,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro Column( modifier = Modifier.background(backgroundColor).combinedClickable( onClick = { - if (noteEvent !is ChannelMessageEvent) { - navController.navigate("Note/${note.idHex}") { - launchSingleTop = true - } - } else { - note.channel()?.let { - navController.navigate("Channel/${it.idHex}") - } - } + routeFor(note, account.userProfile())?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ) @@ -109,7 +100,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro FlowRow() { boostSetCard.boostEvents.forEach { NoteAuthorPicture( - note = it, + baseNote = it, navController = navController, userAccount = account.userProfile(), size = 35.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt index 2d347b9f7..9436512a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt @@ -28,7 +28,6 @@ import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.ui.screen.LikeSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.Dispatchers @@ -68,15 +67,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route Column( modifier = Modifier.background(backgroundColor).combinedClickable( onClick = { - if (noteEvent !is ChannelMessageEvent) { - navController.navigate("Note/${note.idHex}") { - launchSingleTop = true - } - } else { - note.channel()?.let { - navController.navigate("Channel/${it.idHex}") - } - } + routeFor(note, account.userProfile())?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ) @@ -109,7 +100,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route FlowRow() { likeSetCard.likeEvents.forEach { NoteAuthorPicture( - note = it, + baseNote = it, navController = navController, userAccount = account.userProfile(), size = 35.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt index 8deb5bcb1..f7f41e3cd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt @@ -26,9 +26,6 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent -import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.ui.screen.MessageSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.Dispatchers @@ -66,30 +63,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal Column( modifier = Modifier.background(backgroundColor).combinedClickable( onClick = { - if (noteEvent is ChannelMessageEvent) { - note.channel()?.let { - navController.navigate("Channel/${it.idHex}") - } - } else if (noteEvent is PrivateDmEvent) { - val replyAuthorBase = - (note.event as? PrivateDmEvent) - ?.recipientPubKey() - ?.let { LocalCache.getOrCreateUser(it) } - - var userToComposeOn = note.author!! - - if (replyAuthorBase != null) { - if (note.author == accountViewModel.userProfile()) { - userToComposeOn = replyAuthorBase - } - } - - navController.navigate("Room/${userToComposeOn.pubkeyHex}") - } else { - navController.navigate("Note/${note.idHex}") { - launchSingleTop = true - } - } + routeFor(note, accountViewModel.userProfile())?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ) @@ -120,7 +94,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) { NoteCompose( - baseNote = note, + baseNote = messageSetCard.note, routeForLastRead = null, isBoostedNote = true, addMarginTop = false, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 95433ce74..a27c7e9df 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -37,9 +37,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent -import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -83,32 +81,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun .background(backgroundColor) .combinedClickable( onClick = { - if (noteEvent is ChannelMessageEvent) { - note - .channel() - ?.let { - navController.navigate("Channel/${it.idHex}") - } - } else if (noteEvent is PrivateDmEvent) { - val replyAuthorBase = - (note.event as? PrivateDmEvent) - ?.recipientPubKey() - ?.let { LocalCache.getOrCreateUser(it) } - - var userToComposeOn = note.author!! - - if (replyAuthorBase != null) { - if (note.author == accountViewModel.userProfile()) { - userToComposeOn = replyAuthorBase - } - } - - navController.navigate("Room/${userToComposeOn.pubkeyHex}") - } else { - navController.navigate("Note/${note.idHex}") { - launchSingleTop = true - } - } + routeFor(note, account.userProfile())?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ) @@ -195,7 +168,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun } NoteCompose( - baseNote = note, + baseNote = multiSetCard.note, routeForLastRead = null, modifier = Modifier.padding(top = 5.dp), isBoostedNote = true, @@ -225,7 +198,7 @@ fun AuthorGalleryZaps( Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { authorNotes.forEach { - AuthorPictureAndComment(it.key, it.value, navController, accountUser, accountViewModel) + AuthorPictureAndComment(it.key, navController, accountUser, accountViewModel) } } } @@ -234,7 +207,6 @@ fun AuthorGalleryZaps( @Composable private fun AuthorPictureAndComment( zapRequest: Note, - zapEvent: Note, navController: NavController, accountUser: User, accountViewModel: AccountViewModel 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 ad71c37ae..545650749 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 @@ -127,10 +127,6 @@ fun NoteComposeInner( var popupExpanded by remember { mutableStateOf(false) } var showHiddenNote by remember { mutableStateOf(false) } - val context = LocalContext.current.applicationContext - - var moreActionsExpanded by remember { mutableStateOf(false) } - var isAcceptable by remember { mutableStateOf(true) } var canPreview by remember { mutableStateOf(true) } @@ -205,28 +201,7 @@ fun NoteComposeInner( Column( modifier = modifier.combinedClickable( onClick = { - if (noteEvent is ChannelMessageEvent) { - baseChannel?.let { - navController.navigate("Channel/${it.idHex}") - } - } else if (noteEvent is PrivateDmEvent) { - val replyAuthorBase = - (note.event as? PrivateDmEvent) - ?.recipientPubKey() - ?.let { LocalCache.getOrCreateUser(it) } - - var userToComposeOn = note.author!! - - if (replyAuthorBase != null) { - if (note.author == accountViewModel.userProfile()) { - userToComposeOn = replyAuthorBase - } - } - - navController.navigate("Room/${userToComposeOn.pubkeyHex}") - } else { - navController.navigate("Note/${note.idHex}") - } + routeFor(note, loggedIn)?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ).background(backgroundColor) @@ -244,281 +219,88 @@ fun NoteComposeInner( } Column( - modifier = Modifier.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) + modifier = Modifier + .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) ) { - Row(verticalAlignment = Alignment.CenterVertically) { - if (isQuotedNote) { - NoteAuthorPicture(note, navController, loggedIn, 25.dp) - Spacer(Modifier.padding(horizontal = 5.dp)) - NoteUsernameDisplay(note, Modifier.weight(1f)) - } else { - NoteUsernameDisplay(note, Modifier.weight(1f)) - } + FirstUserInfoRow( + baseNote = baseNote, + showAuthorPicture = isQuotedNote, + account = account, + accountViewModel = accountViewModel, + navController = navController + ) - if (noteEvent is RepostEvent) { - Text( - " ${stringResource(id = R.string.boosted)}", - fontWeight = FontWeight.Bold, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) - ) - } else { - DisplayFollowingHashtagsInPost(noteEvent, account, navController) - } - - Text( - timeAgo(note.createdAt(), context = context), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - maxLines = 1 + if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { + SecondUserInfoRow( + note, + account, + navController ) - - IconButton( - modifier = Modifier.size(24.dp), - onClick = { moreActionsExpanded = true } - ) { - Icon( - imageVector = Icons.Default.MoreVert, - null, - modifier = Modifier.size(15.dp), - tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) - ) - - NoteDropDownMenu(baseNote, moreActionsExpanded, { moreActionsExpanded = false }, accountViewModel) - } - } - - if (noteEvent !is RepostEvent && note.author != null && !makeItShort && !isQuotedNote) { - Row(verticalAlignment = Alignment.CenterVertically) { - ObserveDisplayNip05Status(note.author!!, Modifier.weight(1f)) - - val baseReward = noteEvent.getReward() - if (baseReward != null) { - DisplayReward(baseReward, baseNote, account, navController) - } - - val pow = noteEvent.getPoWRank() - if (pow > 20) { - DisplayPoW(pow) - } - } } Spacer(modifier = Modifier.height(2.dp)) - if (!makeItShort && noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.mentions().isNotEmpty())) { - val replyingDirectlyTo = note.replyTo?.lastOrNull() - if (replyingDirectlyTo != null && unPackReply) { - NoteCompose( - baseNote = replyingDirectlyTo, - isQuotedNote = true, - modifier = Modifier - .padding(top = 5.dp) - .fillMaxWidth() - .clip(shape = RoundedCornerShape(15.dp)) - .border( - 1.dp, - MaterialTheme.colors.onSurface.copy(alpha = 0.12f), - RoundedCornerShape(15.dp) - ), - unPackReply = false, - makeItShort = true, - parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f).compositeOver(backgroundColor), - accountViewModel = accountViewModel, - navController = navController - ) - } else { - ReplyInformation(note.replyTo, noteEvent.mentions(), account, navController) - } - Spacer(modifier = Modifier.height(5.dp)) - } else if (!makeItShort && noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.mentions().isNotEmpty())) { - val sortedMentions = noteEvent.mentions() - .mapNotNull { LocalCache.checkGetOrCreateUser(it) } - .toSet() - .sortedBy { loggedIn.isFollowingCached(it) } - - note.channel()?.let { - ReplyInformationChannel(note.replyTo, sortedMentions, it, navController) - } - Spacer(modifier = Modifier.height(5.dp)) + if (!makeItShort) { + ReplyRow( + note, + unPackReply, + backgroundColor, + account, + accountViewModel, + navController + ) } - if (noteEvent is ReactionEvent || noteEvent is RepostEvent) { - note.replyTo?.lastOrNull()?.let { - NoteCompose( - it, - modifier = Modifier, - isBoostedNote = true, - unPackReply = false, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - navController = navController + when (noteEvent) { + is ReactionEvent -> { + RenderReaction(note, backgroundColor, accountViewModel, navController) + } + + is RepostEvent -> { + RenderRepost(note, backgroundColor, accountViewModel, navController) + } + + is ReportEvent -> { + RenderReport(note) + } + + is LongTextNoteEvent -> { + RenderLongFormContent(note, loggedIn, accountViewModel, navController) + } + + is BadgeAwardEvent -> { + RenderBadgeAward(note, backgroundColor, accountViewModel, navController) + } + + is PrivateDmEvent -> { + RenderPrivateMessage(note, makeItShort, canPreview, backgroundColor, accountViewModel, navController) + } + + is HighlightEvent -> { + RenderHighlight(note, makeItShort, canPreview, backgroundColor, accountViewModel, navController) + } + + is PollNoteEvent -> { + RenderPoll( + note, + makeItShort, + canPreview, + backgroundColor, + accountViewModel, + navController ) } - // Reposts have trash in their contents. - if (noteEvent is ReactionEvent) { - val refactorReactionText = - if (noteEvent.content == "+") "❤" else noteEvent.content - - Text( - text = refactorReactionText + else -> { + RenderTextEvent( + note, + makeItShort, + canPreview, + backgroundColor, + accountViewModel, + navController ) } - } else if (noteEvent is ReportEvent) { - val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map { - when (it.reportType) { - ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content) - ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity) - ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech) - ReportEvent.ReportType.SPAM -> stringResource(R.string.spam) - ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation) - ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior) - } - }.toSet().joinToString(", ") - - Text( - text = reportType - ) - - Divider( - modifier = Modifier.padding(top = 40.dp), - thickness = 0.25.dp - ) - } else if (noteEvent is LongTextNoteEvent) { - LongFormHeader(noteEvent, note, loggedIn) - - ReactionsRow(note, accountViewModel, navController) - - Divider( - modifier = Modifier.padding(top = 10.dp), - thickness = 0.25.dp - ) - } else if (noteEvent is BadgeAwardEvent && !note.replyTo.isNullOrEmpty()) { - Text(text = stringResource(R.string.award_granted_to)) - - FlowRow(modifier = Modifier.padding(top = 5.dp)) { - noteEvent.awardees() - .map { LocalCache.getOrCreateUser(it) } - .forEach { - UserPicture( - user = it, - navController = navController, - userAccount = loggedIn, - size = 35.dp - ) - } - } - - note.replyTo?.firstOrNull()?.let { - NoteCompose( - it, - modifier = Modifier, - isBoostedNote = false, - isQuotedNote = true, - unPackReply = false, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - navController = navController - ) - } - - ReactionsRow(note, accountViewModel, navController) - - Divider( - modifier = Modifier.padding(top = 10.dp), - thickness = 0.25.dp - ) - } else if (noteEvent is PrivateDmEvent && - noteEvent.recipientPubKey() != loggedIn.pubkeyHex && - note.author !== loggedIn - ) { - val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) } - - TranslatableRichTextViewer( - stringResource( - id = R.string.private_conversation_notification, - "@${note.author?.pubkeyNpub()}", - "@${recipient?.pubkeyNpub()}" - ), - canPreview = !makeItShort, - Modifier.fillMaxWidth(), - noteEvent.tags(), - backgroundColor, - accountViewModel, - navController - ) - - if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) - } - - Divider( - modifier = Modifier.padding(top = 10.dp), - thickness = 0.25.dp - ) - } else if (noteEvent is HighlightEvent) { - DisplayHighlight( - noteEvent.quote(), - noteEvent.author(), - noteEvent.inUrl(), - makeItShort, - canPreview, - backgroundColor, - accountViewModel, - navController - ) - - if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) - } - - Divider( - modifier = Modifier.padding(top = 10.dp), - thickness = 0.25.dp - ) - } else { - val eventContent = accountViewModel.decrypt(note) - - if (eventContent != null) { - if (makeItShort && note.author == loggedIn) { - Text( - text = eventContent, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } else { - TranslatableRichTextViewer( - eventContent, - canPreview = canPreview && !makeItShort, - Modifier.fillMaxWidth(), - noteEvent.tags(), - backgroundColor, - accountViewModel, - navController - ) - - DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) - } - - if (noteEvent is PollNoteEvent) { - PollNote( - note, - canPreview = canPreview && !makeItShort, - backgroundColor, - accountViewModel, - navController - ) - } - } - - if (!makeItShort) { - ReactionsRow(note, accountViewModel, navController) - } - - Divider( - modifier = Modifier.padding(top = 10.dp), - thickness = 0.25.dp - ) } NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) @@ -528,6 +310,508 @@ fun NoteComposeInner( } } +fun routeFor(note: Note, loggedIn: User): String? { + val noteEvent = note.event + + if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) { + note.channel()?.let { + return "Channel/${it.idHex}" + } + } else if (noteEvent is PrivateDmEvent) { + val replyAuthorBase = + (note.event as? PrivateDmEvent) + ?.recipientPubKey() + ?.let { LocalCache.getOrCreateUser(it) } + + var userToComposeOn = note.author!! + + if (replyAuthorBase != null) { + if (note.author == loggedIn) { + userToComposeOn = replyAuthorBase + } + } + + return "Room/${userToComposeOn.pubkeyHex}" + } else { + return "Note/${note.idHex}" + } + + return null +} + +@Composable +private fun RenderTextEvent( + note: Note, + makeItShort: Boolean, + canPreview: Boolean, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + val noteEvent = note.event ?: return + + val eventContent = accountViewModel.decrypt(note) + + if (eventContent != null) { + if (makeItShort && accountViewModel.isLoggedUser(note.author)) { + Text( + text = eventContent, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } else { + TranslatableRichTextViewer( + content = eventContent, + canPreview = canPreview && !makeItShort, + modifier = Modifier.fillMaxWidth(), + tags = noteEvent.tags(), + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + navController = navController + ) + + DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + } + } + + if (!makeItShort) { + ReactionsRow(note, accountViewModel, navController) + } + + Divider( + modifier = Modifier.padding(top = 10.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun RenderPoll( + note: Note, + makeItShort: Boolean, + canPreview: Boolean, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + val noteEvent = note.event as? PollNoteEvent ?: return + val eventContent = noteEvent.content() + + if (makeItShort && accountViewModel.isLoggedUser(note.author)) { + Text( + text = eventContent, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } else { + TranslatableRichTextViewer( + eventContent, + canPreview = canPreview && !makeItShort, + Modifier.fillMaxWidth(), + noteEvent.tags(), + backgroundColor, + accountViewModel, + navController + ) + + DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + + PollNote( + note, + canPreview = canPreview && !makeItShort, + backgroundColor, + accountViewModel, + navController + ) + } + + if (!makeItShort) { + ReactionsRow(note, accountViewModel, navController) + } + + Divider( + modifier = Modifier.padding(top = 10.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun RenderHighlight( + note: Note, + makeItShort: Boolean, + canPreview: Boolean, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + val noteEvent = note.event as? HighlightEvent ?: return + + DisplayHighlight( + noteEvent.quote(), + noteEvent.author(), + noteEvent.inUrl(), + makeItShort, + canPreview, + backgroundColor, + accountViewModel, + navController + ) + + if (!makeItShort) { + ReactionsRow(note, accountViewModel, navController) + } + + Divider( + modifier = Modifier.padding(top = 10.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun RenderPrivateMessage( + note: Note, + makeItShort: Boolean, + canPreview: Boolean, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + val noteEvent = note.event as? PrivateDmEvent ?: return + val withMe = noteEvent.with(accountViewModel.userProfile().pubkeyHex) + + if (withMe) { + val eventContent = accountViewModel.decrypt(note) + + if (eventContent != null) { + if (makeItShort && accountViewModel.isLoggedUser(note.author)) { + Text( + text = eventContent, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } else { + TranslatableRichTextViewer( + content = eventContent, + canPreview = canPreview && !makeItShort, + modifier = Modifier.fillMaxWidth(), + tags = noteEvent.tags(), + backgroundColor = backgroundColor, + accountViewModel = accountViewModel, + navController = navController + ) + + DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + } + } + } else { + val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) } + + TranslatableRichTextViewer( + stringResource( + id = R.string.private_conversation_notification, + "@${note.author?.pubkeyNpub()}", + "@${recipient?.pubkeyNpub()}" + ), + canPreview = !makeItShort, + Modifier.fillMaxWidth(), + noteEvent.tags(), + backgroundColor, + accountViewModel, + navController + ) + } + + if (!makeItShort) { + ReactionsRow(note, accountViewModel, navController) + } + + Divider( + modifier = Modifier.padding(top = 10.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun RenderBadgeAward( + note: Note, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + if (note.replyTo.isNullOrEmpty()) return + + val noteEvent = note.event as? BadgeAwardEvent ?: return + + Text(text = stringResource(R.string.award_granted_to)) + + FlowRow(modifier = Modifier.padding(top = 5.dp)) { + noteEvent.awardees() + .map { LocalCache.getOrCreateUser(it) } + .forEach { + UserPicture( + user = it, + navController = navController, + userAccount = accountViewModel.userProfile(), + size = 35.dp + ) + } + } + + note.replyTo?.firstOrNull()?.let { + NoteCompose( + it, + modifier = Modifier, + isBoostedNote = false, + isQuotedNote = true, + unPackReply = false, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + navController = navController + ) + } + + ReactionsRow(note, accountViewModel, navController) + + Divider( + modifier = Modifier.padding(top = 10.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun RenderReaction( + note: Note, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + note.replyTo?.lastOrNull()?.let { + NoteCompose( + it, + modifier = Modifier, + isBoostedNote = true, + unPackReply = false, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + navController = navController + ) + } + + // Reposts have trash in their contents. + val refactorReactionText = + if (note.event?.content() == "+") "❤" else note.event?.content() ?: "" + + Text( + text = refactorReactionText + ) +} + +@Composable +private fun RenderRepost( + note: Note, + backgroundColor: Color, + accountViewModel: AccountViewModel, + navController: NavController +) { + note.replyTo?.lastOrNull()?.let { + NoteCompose( + it, + modifier = Modifier, + isBoostedNote = true, + unPackReply = false, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + navController = navController + ) + } +} + +@Composable +private fun RenderLongFormContent( + note: Note, + loggedIn: User, + accountViewModel: AccountViewModel, + navController: NavController +) { + val noteEvent = note.event as? LongTextNoteEvent ?: return + + LongFormHeader(noteEvent, note, loggedIn) + + ReactionsRow(note, accountViewModel, navController) + + Divider( + modifier = Modifier.padding(top = 10.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun RenderReport(note: Note) { + val noteEvent = note.event as? ReportEvent ?: return + + val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map { + when (it.reportType) { + ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content) + ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity) + ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech) + ReportEvent.ReportType.SPAM -> stringResource(R.string.spam) + ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation) + ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior) + } + }.toSet().joinToString(", ") + + Text( + text = reportType + ) + + Divider( + modifier = Modifier.padding(top = 40.dp), + thickness = 0.25.dp + ) +} + +@Composable +private fun ReplyRow( + note: Note, + unPackReply: Boolean, + backgroundColor: Color, + account: Account, + accountViewModel: AccountViewModel, + navController: NavController +) { + val noteEvent = note.event + + if (noteEvent is TextNoteEvent && + (note.replyTo != null || noteEvent.mentions().isNotEmpty()) + ) { + val replyingDirectlyTo = note.replyTo?.lastOrNull() + if (replyingDirectlyTo != null && unPackReply) { + NoteCompose( + baseNote = replyingDirectlyTo, + isQuotedNote = true, + modifier = Modifier + .padding(top = 5.dp) + .fillMaxWidth() + .clip(shape = RoundedCornerShape(15.dp)) + .border( + 1.dp, + MaterialTheme.colors.onSurface.copy(alpha = 0.12f), + RoundedCornerShape(15.dp) + ), + unPackReply = false, + makeItShort = true, + parentBackgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.05f) + .compositeOver(backgroundColor), + accountViewModel = accountViewModel, + navController = navController + ) + } else { + ReplyInformation(note.replyTo, noteEvent.mentions(), account, navController) + } + + Spacer(modifier = Modifier.height(5.dp)) + } else if ( + noteEvent is ChannelMessageEvent && + (note.replyTo != null || noteEvent.mentions().isNotEmpty()) + ) { + val sortedMentions = noteEvent.mentions() + .mapNotNull { LocalCache.checkGetOrCreateUser(it) } + .toSet() + .sortedBy { account.isFollowing(it) } + + note.channel()?.let { + ReplyInformationChannel(note.replyTo, sortedMentions, it, navController) + } + + Spacer(modifier = Modifier.height(5.dp)) + } +} + +@Composable +private fun SecondUserInfoRow( + note: Note, + account: Account, + navController: NavController +) { + val noteEvent = note.event ?: return + val noteAuthor = note.author ?: return + + Row(verticalAlignment = Alignment.CenterVertically) { + ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f)) + + val baseReward = noteEvent.getReward() + if (baseReward != null) { + DisplayReward(baseReward, note, account, navController) + } + + val pow = noteEvent.getPoWRank() + if (pow > 20) { + DisplayPoW(pow) + } + } +} + +@Composable +private fun FirstUserInfoRow( + baseNote: Note, + showAuthorPicture: Boolean, + account: Account, + accountViewModel: AccountViewModel, + navController: NavController +) { + var moreActionsExpanded by remember { mutableStateOf(false) } + val context = LocalContext.current.applicationContext + + Row(verticalAlignment = Alignment.CenterVertically) { + if (showAuthorPicture) { + NoteAuthorPicture(baseNote, navController, account.userProfile(), 25.dp) + Spacer(Modifier.padding(horizontal = 5.dp)) + NoteUsernameDisplay(baseNote, Modifier.weight(1f)) + } else { + NoteUsernameDisplay(baseNote, Modifier.weight(1f)) + } + + if (baseNote.event is RepostEvent) { + Text( + " ${stringResource(id = R.string.boosted)}", + fontWeight = FontWeight.Bold, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + } else { + baseNote.event?.let { + DisplayFollowingHashtagsInPost(it, account, navController) + } + } + + Text( + timeAgo(baseNote.createdAt(), context = context), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + maxLines = 1 + ) + + IconButton( + modifier = Modifier.size(24.dp), + onClick = { moreActionsExpanded = true } + ) { + Icon( + imageVector = Icons.Default.MoreVert, + null, + modifier = Modifier.size(15.dp), + tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + + NoteDropDownMenu( + baseNote, + moreActionsExpanded, + { moreActionsExpanded = false }, + accountViewModel + ) + } + } +} + @Composable private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { val baseChannel = baseNote.channel() @@ -626,17 +910,15 @@ fun DisplayHighlight( ) { val quote = highlight.split("\n").map { "> *${it.removeSuffix(" ")}*" }.joinToString("\n") - if (quote != null) { - TranslatableRichTextViewer( - quote, - canPreview = canPreview && !makeItShort, - Modifier.fillMaxWidth(), - emptyList(), - backgroundColor, - accountViewModel, - navController - ) - } + TranslatableRichTextViewer( + quote, + canPreview = canPreview && !makeItShort, + Modifier.fillMaxWidth(), + emptyList(), + backgroundColor, + accountViewModel, + navController + ) FlowRow() { authorHex?.let { authorHex -> @@ -1158,13 +1440,13 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) { @Composable fun NoteAuthorPicture( - note: Note, + baseNote: Note, navController: NavController, userAccount: User, size: Dp, pictureModifier: Modifier = Modifier ) { - NoteAuthorPicture(note, userAccount, size, pictureModifier) { + NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) { navController.navigate("User/${it.pubkeyHex}") } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt index 8e9c2ed0d..d0c8d7b78 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt @@ -29,7 +29,6 @@ import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.ui.screen.ZapSetCard import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange @@ -70,15 +69,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor Column( modifier = Modifier.background(backgroundColor).combinedClickable( onClick = { - if (noteEvent !is ChannelMessageEvent) { - navController.navigate("Note/${note.idHex}") { - launchSingleTop = true - } - } else { - note.channel()?.let { - navController.navigate("Channel/${it.idHex}") - } - } + routeFor(note, account.userProfile())?.let { navController.navigate(it) } }, onLongClick = { popupExpanded = true } ) @@ -113,7 +104,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor FlowRow() { zapSetCard.zapEvents.forEach { NoteAuthorPicture( - note = it.key, + baseNote = it.key, navController = navController, userAccount = account.userProfile(), size = 35.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt index 7d0094295..70aa994f7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapUserSetCompose.kt @@ -94,7 +94,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, FlowRow() { zapSetCard.zapEvents.forEach { NoteAuthorPicture( - note = it.key, + baseNote = it.key, navController = navController, userAccount = account.userProfile(), size = 35.dp diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index fb3797759..f355c6fd9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.screen +import android.util.Log import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box @@ -32,6 +33,8 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.ZapSetCompose import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue @OptIn(ExperimentalMaterialApi::class) @Composable @@ -85,6 +88,7 @@ fun CardFeedView( } } +@OptIn(ExperimentalTime::class) @Composable private fun FeedLoaded( state: CardFeedState.Loaded, @@ -114,61 +118,64 @@ private fun FeedLoaded( state = listState ) { itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item -> - when (item) { - is NoteCard -> NoteCompose( - item.note, - isBoostedNote = false, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is ZapSetCard -> ZapSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is ZapUserSetCard -> ZapUserSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is LikeSetCard -> LikeSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is BoostSetCard -> BoostSetCompose( - item, - isInnerNote = false, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is MultiSetCard -> MultiSetCompose( - item, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is BadgeCard -> BadgeCompose( - item, - accountViewModel = accountViewModel, - navController = navController, - routeForLastRead = routeForLastRead - ) - is MessageSetCard -> MessageSetCompose( - messageSetCard = item, - routeForLastRead = routeForLastRead, - accountViewModel = accountViewModel, - navController = navController - ) + val (value, elapsed) = measureTimedValue { + when (item) { + is NoteCard -> NoteCompose( + item.note, + isBoostedNote = false, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is ZapSetCard -> ZapSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is ZapUserSetCard -> ZapUserSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is LikeSetCard -> LikeSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is BoostSetCard -> BoostSetCompose( + item, + isInnerNote = false, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is MultiSetCard -> MultiSetCompose( + item, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is BadgeCard -> BadgeCompose( + item, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead + ) + is MessageSetCard -> MessageSetCompose( + messageSetCard = item, + routeForLastRead = routeForLastRead, + accountViewModel = accountViewModel, + navController = navController + ) + } } + Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed") } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 045ce13d8..0fbcd2ecc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -249,7 +249,7 @@ fun NoteMaster( }) ) { NoteAuthorPicture( - note = baseNote, + baseNote = baseNote, navController = navController, userAccount = account.userProfile(), size = 55.dp