diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 490846511..726d8db7c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -820,7 +820,7 @@ class Account( return if (event is PrivateDmEvent && loggedIn.privKey != null) { var pubkeyToUse = event.pubKey - val recepientPK = event.recipientPubKey() + val recepientPK = event.verifiedRecipientPubKey() if (note.author == userProfile() && recepientPK != null) { pubkeyToUse = recepientPK diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 2ecfcf63e..20ca49cd8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -359,7 +359,7 @@ object LocalCache { // Already processed this event. if (note.event != null) return - val recipient = event.recipientPubKey()?.let { getOrCreateUser(it) } + val recipient = event.verifiedRecipientPubKey()?.let { getOrCreateUser(it) } // Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}") @@ -407,7 +407,7 @@ object LocalCache { if (deleteNote.event is PrivateDmEvent) { val author = deleteNote.author - val recipient = (deleteNote.event as? PrivateDmEvent)?.recipientPubKey()?.let { checkGetOrCreateUser(it) } + val recipient = (deleteNote.event as? PrivateDmEvent)?.verifiedRecipientPubKey()?.let { checkGetOrCreateUser(it) } if (recipient != null && author != null) { author.removeMessage(recipient, deleteNote) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index 636ee52b9..25498f702 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt @@ -6,7 +6,7 @@ import kotlin.time.measureTimedValue class ThreadAssembler { - fun searchRoot(note: Note, testedNotes: MutableSet = mutableSetOf()): Note? { + private fun searchRoot(note: Note, testedNotes: MutableSet = mutableSetOf()): Note? { if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note testedNotes.add(note) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 13a0cdd93..392cc17f8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -181,7 +181,7 @@ class User(val pubkeyHex: String) { } @Synchronized - fun getOrCreatePrivateChatroom(user: User): Chatroom { + private fun getOrCreatePrivateChatroom(user: User): Chatroom { return privateChatrooms[user] ?: run { val privateChatroom = Chatroom(setOf()) privateChatrooms = privateChatrooms + Pair(user, privateChatroom) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index 23e2196e4..44222d800 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.service -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES @@ -10,13 +9,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") { var user: User? = null - fun loadUserProfile(userId: String?) { - if (userId != null) { - user = LocalCache.getOrCreateUser(userId) - } else { - user = null - } - + fun loadUserProfile(user: User?) { + this.user = user resetFilters() } 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 e67083cc0..c10837f3a 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 @@ -21,7 +21,11 @@ class PrivateDmEvent( * nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used * for initial messages. */ - fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one + fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" } + + fun recipientPubKeyBytes() = recipientPubKey()?.runCatching { Hex.decode(this[1]) }?.getOrNull() + + fun verifiedRecipientPubKey() = recipientPubKey()?.runCatching { Hex.decode(this[1]).toHexKey() }?.getOrNull() // makes sure its a valid one /** * To be fully compatible with nip-04, we read e-tags that are in violation to nip-18. diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index f9117a13e..c94931a40 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -5,15 +5,23 @@ import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.nip19.Nip19 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun ClickableRoute( @@ -21,56 +29,190 @@ fun ClickableRoute( navController: NavController ) { if (nip19.type == Nip19.Type.USER) { - val userBase = LocalCache.getOrCreateUser(nip19.hex) - - val userState by userBase.live().metadata.observeAsState() - val user = userState?.user ?: return - - CreateClickableText(user.toBestDisplayName(), nip19.additionalChars, "User/${nip19.hex}", navController) + DisplayUser(nip19, navController) } else if (nip19.type == Nip19.Type.ADDRESS) { - val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex) + DisplayAddress(nip19, navController) + } else if (nip19.type == Nip19.Type.NOTE) { + DisplayNote(nip19, navController) + } else if (nip19.type == Nip19.Type.EVENT) { + DisplayEvent(nip19, navController) + } else { + Text( + "@${nip19.hex}${nip19.additionalChars} " + ) + } +} - if (noteBase == null) { - Text( - "@${nip19.hex}${nip19.additionalChars} " +@Composable +private fun DisplayEvent( + nip19: Nip19.Return, + navController: NavController +) { + var noteBase by remember { mutableStateOf(null) } + + LaunchedEffect(key1 = nip19.hex) { + withContext(Dispatchers.IO) { + noteBase = LocalCache.checkGetOrCreateNote(nip19.hex) + } + } + + noteBase?.let { + val noteState by it.live().metadata.observeAsState() + val note = noteState?.note ?: return + val channel = note.channel() + + if (note.event is ChannelCreateEvent) { + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Channel/${nip19.hex}", + navController + ) + } else if (note.event is PrivateDmEvent) { + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Room/${note.author?.pubkeyHex}", + navController + ) + } else if (channel != null) { + CreateClickableText( + channel.toBestDisplayName(), + nip19.additionalChars, + "Channel/${note.channel()?.idHex}", + navController ) } else { - val noteState by noteBase.live().metadata.observeAsState() - val note = noteState?.note ?: return - - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController) + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Event/${nip19.hex}", + navController + ) } - } else if (nip19.type == Nip19.Type.NOTE) { - val noteBase = LocalCache.getOrCreateNote(nip19.hex) - val noteState by noteBase.live().metadata.observeAsState() + } + + if (noteBase == null) { + Text( + "@${nip19.hex}${nip19.additionalChars} " + ) + } +} + +@Composable +private fun DisplayNote( + nip19: Nip19.Return, + navController: NavController +) { + var noteBase by remember { mutableStateOf(null) } + + LaunchedEffect(key1 = nip19.hex) { + withContext(Dispatchers.IO) { + noteBase = LocalCache.checkGetOrCreateNote(nip19.hex) + } + } + + noteBase?.let { + val noteState by it.live().metadata.observeAsState() val note = noteState?.note ?: return val channel = note.channel() if (note.event is ChannelCreateEvent) { - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController) + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Channel/${nip19.hex}", + navController + ) } else if (note.event is PrivateDmEvent) { - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController) + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Room/${note.author?.pubkeyHex}", + navController + ) } else if (channel != null) { - CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController) + CreateClickableText( + channel.toBestDisplayName(), + nip19.additionalChars, + "Channel/${note.channel()?.idHex}", + navController + ) } else { - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController) + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Note/${nip19.hex}", + navController + ) } - } else if (nip19.type == Nip19.Type.EVENT) { - val noteBase = LocalCache.getOrCreateNote(nip19.hex) - val noteState by noteBase.live().metadata.observeAsState() - val note = noteState?.note ?: return - val channel = note.channel() + } - if (note.event is ChannelCreateEvent) { - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController) - } else if (note.event is PrivateDmEvent) { - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController) - } else if (channel != null) { - CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController) - } else { - CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Event/${nip19.hex}", navController) + if (noteBase == null) { + Text( + "@${nip19.hex}${nip19.additionalChars} " + ) + } +} + +@Composable +private fun DisplayAddress( + nip19: Nip19.Return, + navController: NavController +) { + var noteBase by remember { mutableStateOf(null) } + + LaunchedEffect(key1 = nip19.hex) { + withContext(Dispatchers.IO) { + noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex) } - } else { + } + + noteBase?.let { + val noteState by it.live().metadata.observeAsState() + val note = noteState?.note ?: return + + CreateClickableText( + note.idDisplayNote(), + nip19.additionalChars, + "Note/${nip19.hex}", + navController + ) + } + + if (noteBase == null) { + Text( + "@${nip19.hex}${nip19.additionalChars} " + ) + } +} + +@Composable +private fun DisplayUser( + nip19: Nip19.Return, + navController: NavController +) { + var userBase by remember { mutableStateOf(null) } + + LaunchedEffect(key1 = nip19.hex) { + withContext(Dispatchers.IO) { + userBase = LocalCache.checkGetOrCreateUser(nip19.hex) + } + } + + userBase?.let { + val userState by it.live().metadata.observeAsState() + val user = userState?.user ?: return + + CreateClickableText( + user.toBestDisplayName(), + nip19.additionalChars, + "User/${nip19.hex}", + navController + ) + } + + if (userBase == null) { Text( "@${nip19.hex}${nip19.additionalChars} " ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index c4305a3c6..7a6441fcb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -60,8 +60,10 @@ import java.io.File public var muted = mutableStateOf(true) @Composable -fun VideoView(localFile: File, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) { - VideoView(localFile.toUri(), description, onDialog) +fun VideoView(localFile: File?, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) { + if (localFile != null) { + VideoView(localFile.toUri(), description, onDialog) + } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 71bae61c6..dbd57c466 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -111,7 +111,7 @@ class ZoomableUrlVideo( ) : ZoomableUrlContent(url, description, hash, dim, uri) abstract class ZoomablePreloadedContent( - val localFile: File, + val localFile: File?, description: String? = null, val mimeType: String? = null, val isVerified: Boolean? = null, @@ -120,7 +120,7 @@ abstract class ZoomablePreloadedContent( ) : ZoomableContent(description, dim) class ZoomableLocalImage( - localFile: File, + localFile: File?, mimeType: String? = null, description: String? = null, val blurhash: String? = null, @@ -130,7 +130,7 @@ class ZoomableLocalImage( ) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri) class ZoomableLocalVideo( - localFile: File, + localFile: File?, mimeType: String? = null, description: String? = null, dim: String? = null, @@ -219,7 +219,7 @@ private fun LocalImageView( } val contentScale = if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth - if (content.localFile.exists()) { + if (content.localFile != null && content.localFile.exists()) { AsyncImage( model = content.localFile, contentDescription = content.description, @@ -246,7 +246,7 @@ private fun LocalImageView( } } - if (imageState is AsyncImagePainter.State.Error || !content.localFile.exists()) { + if (imageState is AsyncImagePainter.State.Error || content.localFile == null || !content.localFile.exists()) { BlankNote() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt index db26758a1..b2c0829fa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileBookmarksFeedFilter.kt @@ -9,9 +9,9 @@ object UserProfileBookmarksFeedFilter : FeedFilter() { lateinit var account: Account var user: User? = null - fun loadUserProfile(accountLoggedIn: Account, userId: String) { + fun loadUserProfile(accountLoggedIn: Account, user: User?) { account = accountLoggedIn - user = LocalCache.users[userId] + this.user = user } override fun feed(): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt index 27ac42c82..681865c78 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileConversationsFeedFilter.kt @@ -1,7 +1,6 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User @@ -9,9 +8,9 @@ object UserProfileConversationsFeedFilter : FeedFilter() { var account: Account? = null var user: User? = null - fun loadUserProfile(accountLoggedIn: Account, userId: String) { + fun loadUserProfile(accountLoggedIn: Account, user: User?) { account = accountLoggedIn - user = LocalCache.checkGetOrCreateUser(userId) + this.user = user } override fun feed(): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt index 2fe984757..d29148a21 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowersFeedFilter.kt @@ -8,9 +8,9 @@ object UserProfileFollowersFeedFilter : FeedFilter() { lateinit var account: Account var user: User? = null - fun loadUserProfile(accountLoggedIn: Account, userId: String) { + fun loadUserProfile(accountLoggedIn: Account, user: User?) { account = accountLoggedIn - user = LocalCache.users[userId] + this.user = user } override fun feed(): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt index 4afb38c7a..fbe4f54ca 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileFollowsFeedFilter.kt @@ -8,9 +8,9 @@ object UserProfileFollowsFeedFilter : FeedFilter() { lateinit var account: Account var user: User? = null - fun loadUserProfile(accountLoggedIn: Account, userId: String) { + fun loadUserProfile(accountLoggedIn: Account, user: User?) { account = accountLoggedIn - user = LocalCache.users[userId] + this.user = user } override fun feed(): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt index 138e08217..327b40917 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt @@ -9,9 +9,9 @@ object UserProfileNewThreadFeedFilter : FeedFilter() { var account: Account? = null var user: User? = null - fun loadUserProfile(accountLoggedIn: Account, userId: String) { + fun loadUserProfile(accountLoggedIn: Account, user: User) { account = accountLoggedIn - user = LocalCache.checkGetOrCreateUser(userId) + this.user = user } override fun feed(): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt index 025143a49..50716ac5b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileReportsFeedFilter.kt @@ -1,14 +1,13 @@ package com.vitorpamplona.amethyst.ui.dal -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User object UserProfileReportsFeedFilter : FeedFilter() { var user: User? = null - fun loadUserProfile(userId: String) { - user = LocalCache.checkGetOrCreateUser(userId) + fun loadUserProfile(user: User?) { + this.user = user } override fun feed(): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt index 2e6b6ee7d..70cb680ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileZapsFeedFilter.kt @@ -1,6 +1,5 @@ package com.vitorpamplona.amethyst.ui.dal -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.zaps.UserZaps @@ -8,8 +7,8 @@ import com.vitorpamplona.amethyst.service.model.zaps.UserZaps object UserProfileZapsFeedFilter : FeedFilter>() { var user: User? = null - fun loadUserProfile(userId: String) { - user = LocalCache.checkGetOrCreateUser(userId) + fun loadUserProfile(user: User?) { + this.user = user } override fun feed(): List> { 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 f51a77899..b4711e887 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 @@ -134,7 +134,7 @@ fun ChatroomCompose( } else { val replyAuthorBase = (note.event as? PrivateDmEvent) - ?.recipientPubKey() + ?.verifiedRecipientPubKey() ?.let { LocalCache.getOrCreateUser(it) } var userToComposeOn = note.author!! 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 2b55b3dd8..b1c4d1307 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 @@ -89,7 +89,6 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.EventInterface import com.vitorpamplona.amethyst.service.model.FileHeaderEvent -import com.vitorpamplona.amethyst.service.model.FileStorageEvent import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent import com.vitorpamplona.amethyst.service.model.HighlightEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent @@ -122,6 +121,7 @@ import com.vitorpamplona.amethyst.ui.theme.Following import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import nostr.postr.toNpub import java.io.File import java.math.BigDecimal import java.net.URL @@ -387,7 +387,7 @@ fun routeFor(note: Note, loggedIn: User): String? { } else if (noteEvent is PrivateDmEvent) { val replyAuthorBase = (note.event as? PrivateDmEvent) - ?.recipientPubKey() + ?.verifiedRecipientPubKey() ?.let { LocalCache.getOrCreateUser(it) } var userToComposeOn = note.author!! @@ -573,13 +573,13 @@ private fun RenderPrivateMessage( } } } else { - val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) } + val recipient = noteEvent.recipientPubKeyBytes()?.toNpub() ?: "Someone" TranslatableRichTextViewer( stringResource( id = R.string.private_conversation_notification, "@${note.author?.pubkeyNpub()}", - "@${recipient?.pubkeyNpub()}" + "@$recipient" ), canPreview = !makeItShort, Modifier.fillMaxWidth(), @@ -797,13 +797,8 @@ private fun ReplyRow( Spacer(modifier = Modifier.height(5.dp)) } else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) { - val sortedMentions = noteEvent.mentions() - .mapNotNull { LocalCache.checkGetOrCreateUser(it) } - .toSet() - .sortedBy { account.isFollowing(it) } - note.channel()?.let { - ReplyInformationChannel(note.replyTo, sortedMentions, it, navController) + ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, navController) } Spacer(modifier = Modifier.height(5.dp)) @@ -1016,11 +1011,19 @@ fun DisplayHighlight( navController ) + var userBase by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + if (authorHex != null) { + userBase = LocalCache.checkGetOrCreateUser(authorHex) + } + } + } + FlowRow() { authorHex?.let { authorHex -> - val userBase = LocalCache.checkGetOrCreateUser(authorHex) - - if (userBase != null) { + userBase?.let { userBase -> val userState by userBase.live().metadata.observeAsState() val user = userState?.user @@ -1307,20 +1310,23 @@ fun FileStorageHeaderDisplay(baseNote: Note) { val appContext = LocalContext.current.applicationContext val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return - val fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) } ?: return + var fileNote by remember { mutableStateOf(null) } - val noteState by fileNote.live().metadata.observeAsState() - val note = noteState?.note + LaunchedEffect(key1 = eventHeader.id) { + withContext(Dispatchers.IO) { + fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) } + } + } - val eventBytes = (note?.event as? FileStorageEvent) + val noteState = fileNote?.live()?.metadata?.observeAsState() + val note = noteState?.value?.note var content by remember { mutableStateOf(null) } - LaunchedEffect(key1 = eventHeader.id, key2 = noteState) { + LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) { withContext(Dispatchers.IO) { val uri = "nostr:" + baseNote.toNEvent() - val localDir = File(File(appContext.externalCacheDir, "NIP95"), fileNote.idHex) - val bytes = eventBytes?.decode() + val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) } val blurHash = eventHeader.blurhash() val dimensions = eventHeader.dimensions() val description = eventHeader.content @@ -1337,18 +1343,14 @@ fun FileStorageHeaderDisplay(baseNote: Note) { uri = uri ) } else { - if (bytes != null) { - ZoomableLocalVideo( - localFile = localDir, - mimeType = mimeType, - description = description, - dim = dimensions, - isVerified = true, - uri = uri - ) - } else { - null - } + ZoomableLocalVideo( + localFile = localDir, + mimeType = mimeType, + description = description, + dim = dimensions, + isVerified = true, + uri = uri + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt index 8c2686973..18446bf03 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt @@ -5,6 +5,7 @@ import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -17,13 +18,23 @@ import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext @Composable fun ReplyInformation(replyTo: List?, mentions: List, account: Account, navController: NavController) { - val dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) } + var dupMentions by remember { mutableStateOf?>(null) } - ReplyInformation(replyTo, dupMentions, account) { - navController.navigate("User/${it.pubkeyHex}") + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) } + } + } + + if (dupMentions != null) { + ReplyInformation(replyTo, dupMentions, account) { + navController.navigate("User/${it.pubkeyHex}") + } } } @@ -102,6 +113,34 @@ fun ReplyInformation(replyTo: List?, dupMentions: List?, account: Ac } } +@Composable +fun ReplyInformationChannel(replyTo: List?, mentions: List, channel: Channel, account: Account, navController: NavController) { + var sortedMentions by remember { mutableStateOf?>(null) } + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + sortedMentions = mentions + .mapNotNull { LocalCache.checkGetOrCreateUser(it) } + .toSet() + .sortedBy { account.isFollowing(it) } + } + } + + if (sortedMentions != null) { + ReplyInformationChannel( + replyTo, + sortedMentions, + channel, + onUserTagClick = { + navController.navigate("User/${it.pubkeyHex}") + }, + onChannelTagClick = { + navController.navigate("Channel/${it.idHex}") + } + ) + } +} + @Composable fun ReplyInformationChannel(replyTo: List?, mentions: List?, channel: Channel, navController: NavController) { ReplyInformationChannel( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index cf9a85301..defff2d01 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -175,7 +175,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { } } - fun refreshFromOldState(newItems: Set) { + private fun refreshFromOldState(newItems: Set) { val oldNotesState = _feedContent.value val thisAccount = (localFilter as? NotificationFeedFilter)?.account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index a62afa8f1..8238ad33e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -104,7 +104,7 @@ private fun FeedLoaded( } else { val replyAuthorBase = (note.event as? PrivateDmEvent) - ?.recipientPubKey() + ?.verifiedRecipientPubKey() ?.let { LocalCache.getOrCreateUser(it) } var userToComposeOn = note.author!! 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 4a9ab3c11..fffd2a126 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 @@ -100,23 +100,42 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal -@OptIn(ExperimentalFoundationApi::class) @Composable fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) { + if (userId == null) return + + var userBase by remember { mutableStateOf(null) } + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + userBase = LocalCache.checkGetOrCreateUser(userId) + } + } + + userBase?.let { + ProfileScreen( + user = it, + accountViewModel = accountViewModel, + navController = navController + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: NavController) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - if (userId == null) return + UserProfileNewThreadFeedFilter.loadUserProfile(account, user) + UserProfileConversationsFeedFilter.loadUserProfile(account, user) + UserProfileFollowersFeedFilter.loadUserProfile(account, user) + UserProfileFollowsFeedFilter.loadUserProfile(account, user) + UserProfileZapsFeedFilter.loadUserProfile(user) + UserProfileReportsFeedFilter.loadUserProfile(user) + UserProfileBookmarksFeedFilter.loadUserProfile(account, user) - UserProfileNewThreadFeedFilter.loadUserProfile(account, userId) - UserProfileConversationsFeedFilter.loadUserProfile(account, userId) - UserProfileFollowersFeedFilter.loadUserProfile(account, userId) - UserProfileFollowsFeedFilter.loadUserProfile(account, userId) - UserProfileZapsFeedFilter.loadUserProfile(userId) - UserProfileReportsFeedFilter.loadUserProfile(userId) - UserProfileBookmarksFeedFilter.loadUserProfile(account, userId) - - NostrUserProfileDataSource.loadUserProfile(userId) + NostrUserProfileDataSource.loadUserProfile(user) val lifeCycleOwner = LocalLifecycleOwner.current @@ -124,7 +143,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { println("Profidle Start") - NostrUserProfileDataSource.loadUserProfile(userId) + NostrUserProfileDataSource.loadUserProfile(user) NostrUserProfileDataSource.start() } if (event == Lifecycle.Event.ON_PAUSE) {