NoteCompose refactoring

This commit is contained in:
Vitor Pamplona
2023-05-06 11:49:53 -04:00
parent fbbb9b0d72
commit 79da9cee01
13 changed files with 665 additions and 462 deletions
@@ -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)
@@ -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
}
}
}
@@ -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 }
)
@@ -74,7 +74,7 @@ fun HiddenNote(reports: Set<Note>, 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
@@ -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
@@ -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
@@ -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,
@@ -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
@@ -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}")
}
}
@@ -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
@@ -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
@@ -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")
}
}
}
@@ -249,7 +249,7 @@ fun NoteMaster(
})
) {
NoteAuthorPicture(
note = baseNote,
baseNote = baseNote,
navController = navController,
userAccount = account.userProfile(),
size = 55.dp