From c2beaf5f80d52dd8f9985ee56c041c09e4eb9d4c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 19 Sep 2023 15:26:16 -0400 Subject: [PATCH] Starting a Refactoring of LocalCache away from a Singleton instance. --- .../amethyst/ui/actions/NewMessageTagger.kt | 23 +-- .../amethyst/ui/actions/NewPostView.kt | 8 +- .../amethyst/ui/actions/NewPostViewModel.kt | 26 +++- .../ui/actions/RelayInformationDialog.kt | 2 +- .../ui/buttons/NewCommunityNoteButton.kt | 2 +- .../amethyst/ui/components/ClickableRoute.kt | 62 ++++---- .../amethyst/ui/components/RichTextViewer.kt | 20 +-- .../amethyst/ui/navigation/AppTopBar.kt | 8 +- .../amethyst/ui/navigation/DrawerContent.kt | 2 +- .../amethyst/ui/note/ChannelCardCompose.kt | 2 +- .../amethyst/ui/note/ChatroomHeaderCompose.kt | 36 ++--- .../ui/note/NIP05VerificationDisplay.kt | 6 +- .../amethyst/ui/note/NoteCompose.kt | 91 +++++------- .../ui/note/UpdateReactionTypeDialog.kt | 5 +- .../amethyst/ui/note/UserProfilePicture.kt | 22 +-- .../amethyst/ui/screen/UserFeedViewModel.kt | 10 +- .../ui/screen/loggedIn/AccountViewModel.kt | 135 ++++++++++++++++++ .../ui/screen/loggedIn/ChannelScreen.kt | 12 +- .../ui/screen/loggedIn/ChatroomScreen.kt | 7 +- .../ui/screen/loggedIn/CommunityScreen.kt | 2 +- .../ui/screen/loggedIn/ProfileScreen.kt | 6 +- .../components/TranslatableRichTextViewer.kt | 26 +--- 22 files changed, 308 insertions(+), 205 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMessageTagger.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMessageTagger.kt index f8741da59..a7cd5ab0b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMessageTagger.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMessageTagger.kt @@ -1,8 +1,8 @@ package com.vitorpamplona.amethyst.ui.actions -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.Nip19 @@ -13,7 +13,8 @@ class NewMessageTagger( var message: String, var mentions: List? = null, var replyTos: List? = null, - var channelHex: String? = null + var channelHex: String? = null, + var accountViewModel: AccountViewModel ) { val directMentions = mutableSetOf() @@ -40,20 +41,20 @@ class NewMessageTagger( return (if (channelHex != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0) } - fun run() { + suspend fun run() { // adds all references to mentions and reply tos message.split('\n').forEach { paragraph: String -> paragraph.split(' ').forEach { word: String -> val results = parseDirtyWordForKey(word) if (results?.key?.type == Nip19.Type.USER) { - addUserToMentions(LocalCache.getOrCreateUser(results.key.hex)) + addUserToMentions(accountViewModel.getOrCreateUser(results.key.hex)) } else if (results?.key?.type == Nip19.Type.NOTE) { - addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex)) + addNoteToReplyTos(accountViewModel.getOrCreateNote(results.key.hex)) } else if (results?.key?.type == Nip19.Type.EVENT) { - addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex)) + addNoteToReplyTos(accountViewModel.getOrCreateNote(results.key.hex)) } else if (results?.key?.type == Nip19.Type.ADDRESS) { - val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex) + val note = accountViewModel.checkGetOrCreateAddressableNote(results.key.hex) if (note != null) { addNoteToReplyTos(note) } @@ -66,19 +67,19 @@ class NewMessageTagger( paragraph.split(' ').map { word: String -> val results = parseDirtyWordForKey(word) if (results?.key?.type == Nip19.Type.USER) { - val user = LocalCache.getOrCreateUser(results.key.hex) + val user = accountViewModel.getOrCreateUser(results.key.hex) "nostr:${user.pubkeyNpub()}${results.restOfWord}" } else if (results?.key?.type == Nip19.Type.NOTE) { - val note = LocalCache.getOrCreateNote(results.key.hex) + val note = accountViewModel.getOrCreateNote(results.key.hex) "nostr:${note.toNEvent()}${results.restOfWord}" } else if (results?.key?.type == Nip19.Type.EVENT) { - val note = LocalCache.getOrCreateNote(results.key.hex) + val note = accountViewModel.getOrCreateNote(results.key.hex) "nostr:${note.toNEvent()}${results.restOfWord}" } else if (results?.key?.type == Nip19.Type.ADDRESS) { - val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex) + val note = accountViewModel.checkGetOrCreateAddressableNote(results.key.hex) if (note != null) { "nostr:${note.idNote()}${results.restOfWord}" } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index cf3193090..4bbbd8449 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -142,7 +142,7 @@ fun NewPostView( var relayList = account.activeWriteRelays() LaunchedEffect(Unit) { - postViewModel.load(account, baseReplyTo, quote) + postViewModel.load(accountViewModel, baseReplyTo, quote) launch(Dispatchers.IO) { postViewModel.imageUploadingError.collect { error -> @@ -211,10 +211,8 @@ fun NewPostView( } PostButton( onPost = { - scope.launch(Dispatchers.IO) { - postViewModel.sendPost(relayList = relayList) - onClose() - } + postViewModel.sendPost(relayList = relayList) + onClose() }, isActive = postViewModel.canPost() ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index 165ebb50b..b7d82e07c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -23,6 +23,7 @@ import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.Split import com.vitorpamplona.amethyst.ui.components.isValidURL +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.events.AddressableEvent import com.vitorpamplona.quartz.events.BaseTextNoteEvent @@ -45,6 +46,7 @@ enum class UserSuggestionAnchor { @Stable open class NewPostViewModel() : ViewModel() { + var accountViewModel: AccountViewModel? = null var account: Account? = null var requiresNIP24: Boolean = false @@ -115,7 +117,7 @@ open class NewPostViewModel() : ViewModel() { // NIP24 Wrapped DMs / Group messages var nip24 by mutableStateOf(false) - open fun load(account: Account, replyingTo: Note?, quote: Note?) { + open fun load(accountViewModel: AccountViewModel, replyingTo: Note?, quote: Note?) { originalNote = replyingTo replyingTo?.let { replyNote -> if (replyNote.event is BaseTextNoteEvent) { @@ -147,8 +149,8 @@ open class NewPostViewModel() : ViewModel() { urlPreview = findUrlInMessage() } - canAddInvoice = account.userProfile().info?.lnAddress() != null - canAddZapRaiser = account.userProfile().info?.lnAddress() != null + canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null + canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channelHex() == null contentToAddUrl = null @@ -160,14 +162,26 @@ open class NewPostViewModel() : ViewModel() { forwardZapTo = Split() forwardZapToEditting = TextFieldValue("") - this.account = account + this.accountViewModel = accountViewModel + this.account = accountViewModel.account } fun sendPost(relayList: List? = null) { - val tagger = NewMessageTagger(message.text, mentions, replyTos, originalNote?.channelHex()) + viewModelScope.launch(Dispatchers.IO) { + innerSendPost(relayList) + } + } + + suspend fun innerSendPost(relayList: List? = null) { + if (accountViewModel == null) { + cancel() + return + } + + val tagger = NewMessageTagger(message.text, mentions, replyTos, originalNote?.channelHex(), accountViewModel!!) tagger.run() - val toUsersTagger = NewMessageTagger(toUsers.text, null, null, null) + val toUsersTagger = NewMessageTagger(toUsers.text, null, null, null, accountViewModel!!) toUsersTagger.run() val dmUsers = toUsersTagger.mentions diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt index 9fbaf2463..ad1f6fde1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt @@ -248,7 +248,7 @@ private fun DisplayOwnerInformation( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadUser(baseUserHex = userHex) { + LoadUser(baseUserHex = userHex, accountViewModel) { Crossfade(it) { if (it != null) { UserCompose(baseUser = it, accountViewModel = accountViewModel, showDiviser = false, nav = nav) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt index ec0e9593e..dc0e0bb4f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/buttons/NewCommunityNoteButton.kt @@ -24,7 +24,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable fun NewCommunityNoteButton(communityIdHex: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - LoadNote(baseNoteHex = communityIdHex) { + LoadNote(baseNoteHex = communityIdHex, accountViewModel) { it?.let { NewCommunityNoteButton(it, accountViewModel, nav) } 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 0e5370aec..33e770783 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 @@ -40,12 +40,12 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NIP30Parser import com.vitorpamplona.amethyst.ui.note.LoadChannel import com.vitorpamplona.amethyst.ui.note.toShortenHex +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.encoders.Nip19 import com.vitorpamplona.quartz.events.ChannelCreateEvent import com.vitorpamplona.quartz.events.ImmutableListOfLists @@ -61,20 +61,21 @@ import kotlinx.coroutines.launch @Composable fun ClickableRoute( nip19: Nip19.Return, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { when (nip19.type) { Nip19.Type.USER -> { - DisplayUser(nip19, nav) + DisplayUser(nip19, accountViewModel, nav) } Nip19.Type.ADDRESS -> { - DisplayAddress(nip19, nav) + DisplayAddress(nip19, accountViewModel, nav) } Nip19.Type.NOTE -> { - DisplayNote(nip19, nav) + DisplayNote(nip19, accountViewModel, nav) } Nip19.Type.EVENT -> { - DisplayEvent(nip19, nav) + DisplayEvent(nip19, accountViewModel, nav) } else -> { Text( @@ -86,34 +87,15 @@ fun ClickableRoute( } } -@Composable -private fun LoadNote( - hex: String, - content: @Composable (Note) -> Unit -) { - var noteBase by remember(hex) { mutableStateOf(LocalCache.getNoteIfExists(hex)) } - - if (noteBase == null) { - LaunchedEffect(key1 = hex) { - launch(Dispatchers.IO) { - noteBase = LocalCache.checkGetOrCreateNote(hex) - } - } - } - - noteBase?.let { - content(it) - } -} - @Composable private fun DisplayEvent( nip19: Nip19.Return, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadNote(nip19.hex) { + LoadNote(nip19.hex, accountViewModel) { if (it != null) { - DisplayNoteLink(it, nip19, nav) + DisplayNoteLink(it, nip19, accountViewModel, nav) } else { CreateClickableText( clickablePart = remember(nip19) { "@${nip19.hex.toShortenHex()}" }, @@ -128,11 +110,12 @@ private fun DisplayEvent( @Composable private fun DisplayNote( nip19: Nip19.Return, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadNote(nip19.hex) { + LoadNote(nip19.hex, accountViewModel = accountViewModel) { if (it != null) { - DisplayNoteLink(it, nip19, nav) + DisplayNoteLink(it, nip19, accountViewModel, nav) } else { CreateClickableText( clickablePart = remember(nip19) { "@${nip19.hex.toShortenHex()}" }, @@ -148,6 +131,7 @@ private fun DisplayNote( private fun DisplayNoteLink( it: Note, nip19: Nip19.Return, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { val noteState by it.live().metadata.observeAsState() @@ -177,7 +161,7 @@ private fun DisplayNoteLink( nav = nav ) } else if (channelHex != null) { - LoadChannel(baseChannelHex = channelHex) { baseChannel -> + LoadChannel(baseChannelHex = channelHex, accountViewModel) { baseChannel -> val channelState by baseChannel.live.observeAsState() val channelDisplayName by remember(channelState) { derivedStateOf { @@ -205,14 +189,15 @@ private fun DisplayNoteLink( @Composable private fun DisplayAddress( nip19: Nip19.Return, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - var noteBase by remember(nip19) { mutableStateOf(LocalCache.getNoteIfExists(nip19.hex)) } + var noteBase by remember(nip19) { mutableStateOf(accountViewModel.getNoteIfExists(nip19.hex)) } if (noteBase == null) { LaunchedEffect(key1 = nip19.hex) { - launch(Dispatchers.IO) { - noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex) + accountViewModel.checkGetOrCreateAddressableNote(nip19.hex) { + noteBase = it } } } @@ -244,14 +229,19 @@ private fun DisplayAddress( @Composable private fun DisplayUser( nip19: Nip19.Return, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - var userBase by remember(nip19) { mutableStateOf(LocalCache.getUserIfExists(nip19.hex)) } + var userBase by remember(nip19) { + mutableStateOf( + accountViewModel.getUserIfExists(nip19.hex) + ) + } if (userBase == null) { LaunchedEffect(key1 = nip19.hex) { - launch(Dispatchers.IO) { - userBase = LocalCache.checkGetOrCreateUser(nip19.hex) + accountViewModel.checkGetOrCreateUser(nip19.hex) { + userBase = it } } } 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 df9a345e7..06a01e12d 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 @@ -243,7 +243,7 @@ private fun RenderWordWithoutPreview( is PhoneSegment -> ClickablePhone(word.segmentText) is BechSegment -> BechLink(word.segmentText, false, backgroundColor, accountViewModel, nav) is HashTagSegment -> HashTag(word, nav) - is HashIndexUserSegment -> TagLink(word, nav) + is HashIndexUserSegment -> TagLink(word, accountViewModel, nav) is HashIndexEventSegment -> TagLink(word, false, backgroundColor, accountViewModel, nav) is SchemelessUrlSegment -> NoProtocolUrlRenderer(word) is RegularTextSegment -> NormalWord(word.segmentText, style) @@ -270,7 +270,7 @@ private fun RenderWordWithPreview( is PhoneSegment -> ClickablePhone(word.segmentText) is BechSegment -> BechLink(word.segmentText, true, backgroundColor, accountViewModel, nav) is HashTagSegment -> HashTag(word, nav) - is HashIndexUserSegment -> TagLink(word, nav) + is HashIndexUserSegment -> TagLink(word, accountViewModel, nav) is HashIndexEventSegment -> TagLink(word, true, backgroundColor, accountViewModel, nav) is SchemelessUrlSegment -> NoProtocolUrlRenderer(word) is RegularTextSegment -> NormalWord(word.segmentText, style) @@ -654,7 +654,7 @@ fun BechLink(word: String, canPreview: Boolean, backgroundColor: MutableState Unit) { - LoadUser(baseUserHex = word.hex) { +fun TagLink(word: HashIndexUserSegment, accountViewModel: AccountViewModel, nav: (String) -> Unit) { + LoadUser(baseUserHex = word.hex, accountViewModel) { if (it == null) { Text(text = word.segmentText) } else { @@ -776,15 +776,15 @@ fun TagLink(word: HashIndexUserSegment, nav: (String) -> Unit) { } @Composable -fun LoadNote(baseNoteHex: String, content: @Composable (Note?) -> Unit) { +fun LoadNote(baseNoteHex: String, accountViewModel: AccountViewModel, content: @Composable (Note?) -> Unit) { var note by remember(baseNoteHex) { - mutableStateOf(LocalCache.getNoteIfExists(baseNoteHex)) + mutableStateOf(accountViewModel.getNoteIfExists(baseNoteHex)) } if (note == null) { LaunchedEffect(key1 = baseNoteHex) { - launch(Dispatchers.IO) { - note = LocalCache.checkGetOrCreateNote(baseNoteHex) + accountViewModel.checkGetOrCreateNote(baseNoteHex) { + note = it } } } @@ -794,7 +794,7 @@ fun LoadNote(baseNoteHex: String, content: @Composable (Note?) -> Unit) { @Composable fun TagLink(word: HashIndexEventSegment, canPreview: Boolean, backgroundColor: MutableState, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - LoadNote(baseNoteHex = word.hex) { + LoadNote(baseNoteHex = word.hex, accountViewModel) { if (it == null) { Text(text = remember { word.segmentText.toShortenHex() }) } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index a9793d2d4..20254536a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -227,7 +227,7 @@ private fun CommunityTopBar( nav: (String) -> Unit, navPopBack: () -> Unit ) { - LoadAddressableNote(aTagHex = id) { baseNote -> + LoadAddressableNote(aTagHex = id, accountViewModel) { baseNote -> if (baseNote != null) { FlexibleTopBarWithBackButton( title = { @@ -288,7 +288,7 @@ private fun RenderRoomTopBar( if (room.users.size == 1) { FlexibleTopBarWithBackButton( title = { - LoadUser(baseUserHex = room.users.first()) { baseUser -> + LoadUser(baseUserHex = room.users.first(), accountViewModel) { baseUser -> if (baseUser != null) { ClickableUserPicture( baseUser = baseUser, @@ -303,7 +303,7 @@ private fun RenderRoomTopBar( } }, extendableRow = { - LoadUser(baseUserHex = room.users.first()) { + LoadUser(baseUserHex = room.users.first(), accountViewModel) { if (it != null) { UserCompose( baseUser = it, @@ -348,7 +348,7 @@ private fun ChannelTopBar( nav: (String) -> Unit, navPopBack: () -> Unit ) { - LoadChannel(baseChannelHex = id) { baseChannel -> + LoadChannel(baseChannelHex = id, accountViewModel) { baseChannel -> FlexibleTopBarWithBackButton( prefixRow = { if (baseChannel is LiveActivitiesChannel) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index c567d0481..d6074f760 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -257,7 +257,7 @@ private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewMo val scope = rememberCoroutineScope() val focusManager = LocalFocusManager.current - LoadStatuses(user = baseAccountUser) { statuses -> + LoadStatuses(user = baseAccountUser, accountViewModel) { statuses -> if (statuses.isEmpty()) { val currentStatus = remember { mutableStateOf("") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index 00352c989..a56015433 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -646,7 +646,7 @@ fun RenderCommunitiesThumb(baseNote: Note, accountViewModel: AccountViewModel, n fun RenderChannelThumb(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val noteEvent = baseNote.event as? ChannelCreateEvent ?: return - LoadChannel(baseChannelHex = baseNote.idHex) { + LoadChannel(baseChannelHex = baseNote.idHex, accountViewModel) { RenderChannelThumb(baseNote = baseNote, channel = it, accountViewModel, nav) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt index 5db559973..f623852fc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt @@ -47,7 +47,6 @@ import androidx.lifecycle.map import com.patrykandpatrick.vico.core.extension.forEachIndexedExtended import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Channel -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji @@ -99,7 +98,7 @@ fun ChatroomComposeChannelOrUser( } if (channelHex != null) { - ChatroomChannel(channelHex, baseNote, accountViewModel, nav) + ChatroomChannel(channelHex!!, baseNote, accountViewModel, nav) } else { ChatroomPrivateMessages(baseNote, accountViewModel, nav) } @@ -134,12 +133,12 @@ private fun ChatroomPrivateMessages( @Composable private fun ChatroomChannel( - channelHex: HexKey?, + channelHex: HexKey, baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadChannel(baseChannelHex = channelHex!!) { channel -> + LoadChannel(baseChannelHex = channelHex, accountViewModel) { channel -> ChannelRoomCompose(baseNote, channel, accountViewModel, nav) } } @@ -277,7 +276,7 @@ private fun UserRoomCompose( ) }, channelTitle = { - RoomNameDisplay(room, it, accountViewModel.userProfile()) + RoomNameDisplay(room, it, accountViewModel) }, channelLastTime = createAt, channelLastContent = content, @@ -287,20 +286,20 @@ private fun UserRoomCompose( } @Composable -fun RoomNameDisplay(room: ChatroomKey, modifier: Modifier, loggedInUser: User) { - val roomSubject by loggedInUser.live().messages.map { +fun RoomNameDisplay(room: ChatroomKey, modifier: Modifier, accountViewModel: AccountViewModel) { + val roomSubject by accountViewModel.userProfile().live().messages.map { it.user.privateChatrooms[room]?.subject - }.distinctUntilChanged().observeAsState(loggedInUser.privateChatrooms[room]?.subject) + }.distinctUntilChanged().observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject) Crossfade(targetState = roomSubject, modifier) { if (it != null && it.isNotBlank()) { if (room.users.size > 1) { DisplayRoomSubject(it) } else { - DisplayUserAndSubject(room.users.first(), it) + DisplayUserAndSubject(room.users.first(), it, accountViewModel) } } else { - DisplayUserSetAsSubject(room) + DisplayUserSetAsSubject(room, accountViewModel) } } } @@ -308,7 +307,8 @@ fun RoomNameDisplay(room: ChatroomKey, modifier: Modifier, loggedInUser: User) { @Composable private fun DisplayUserAndSubject( user: HexKey, - subject: String + subject: String, + accountViewModel: AccountViewModel ) { Row() { Text( @@ -322,7 +322,7 @@ private fun DisplayUserAndSubject( fontWeight = FontWeight.Bold, maxLines = 1 ) - LoadUser(baseUserHex = user) { + LoadUser(baseUserHex = user, accountViewModel = accountViewModel) { it?.let { UsernameDisplay(it, Modifier.weight(1f)) } @@ -333,6 +333,7 @@ private fun DisplayUserAndSubject( @Composable fun DisplayUserSetAsSubject( room: ChatroomKey, + accountViewModel: AccountViewModel, fontWeight: FontWeight = FontWeight.Bold ) { val userList = remember(room) { @@ -342,7 +343,7 @@ fun DisplayUserSetAsSubject( if (userList.size == 1) { // Regular Design Row() { - LoadUser(baseUserHex = userList[0]) { + LoadUser(baseUserHex = userList[0], accountViewModel) { it?.let { UsernameDisplay(it, Modifier.weight(1f), fontWeight = fontWeight) } @@ -351,7 +352,7 @@ fun DisplayUserSetAsSubject( } else { Row() { userList.take(4).forEachIndexedExtended { index, isFirst, isLast, value -> - LoadUser(baseUserHex = value) { + LoadUser(baseUserHex = value, accountViewModel) { it?.let { ShortUsernameDisplay(baseUser = it, fontWeight = fontWeight) } @@ -416,15 +417,14 @@ private fun WatchNotificationChanges( } @Composable -fun LoadUser(baseUserHex: String, content: @Composable (User?) -> Unit) { +fun LoadUser(baseUserHex: String, accountViewModel: AccountViewModel, content: @Composable (User?) -> Unit) { var user by remember(baseUserHex) { - mutableStateOf(LocalCache.getUserIfExists(baseUserHex)) + mutableStateOf(accountViewModel.getUserIfExists(baseUserHex)) } if (user == null) { LaunchedEffect(key1 = baseUserHex) { - launch(Dispatchers.IO) { - val newUser = LocalCache.checkGetOrCreateUser(baseUserHex) + accountViewModel.checkGetOrCreateUser(baseUserHex) { newUser -> if (user != newUser) { user = newUser } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index 81a0d49c6..7ac6bbbeb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -106,7 +106,7 @@ fun ObserveDisplayNip05Status( ) { val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05()) - LoadStatuses(baseUser) { statuses -> + LoadStatuses(baseUser, accountViewModel) { statuses -> Crossfade(targetState = nip05, modifier = columnModifier, label = "ObserveDisplayNip05StatusCrossfade") { VerifyAndDisplayNIP05OrStatusLine(it, statuses, baseUser, columnModifier, accountViewModel, nav) } @@ -233,7 +233,7 @@ fun DisplayStatus( ) } } else if (nostrATag != null) { - LoadAddressableNote(nostrATag) { note -> + LoadAddressableNote(nostrATag, accountViewModel) { note -> if (note != null) { Spacer(modifier = StdHorzSpacer) IconButton( @@ -255,7 +255,7 @@ fun DisplayStatus( } } } else if (nostrHexID != null) { - LoadNote(baseNoteHex = nostrHexID) { + LoadNote(baseNoteHex = nostrHexID, accountViewModel) { if (it != null) { Spacer(modifier = StdHorzSpacer) IconButton( 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 9a51519ff..3b5b6d2ae 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 @@ -85,7 +85,6 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.ConnectivityType -import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.model.User @@ -674,14 +673,13 @@ fun LongCommunityHeader( } LaunchedEffect(key1 = noteState) { - launch(Dispatchers.IO) { - val noteEvent = (noteState?.note?.event as? CommunityDefinitionEvent) - val newParticipantUsers = noteEvent?.moderators()?.mapNotNull { part -> - LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } - }?.toImmutableList() + val participants = (noteState?.note?.event as? CommunityDefinitionEvent)?.moderators() - if (newParticipantUsers != null && !equalImmutableLists(newParticipantUsers, participantUsers)) { - participantUsers = newParticipantUsers + if (participants != null) { + accountViewModel.loadParticipants(participants) { newParticipantUsers -> + if (newParticipantUsers != null && !equalImmutableLists(newParticipantUsers, participantUsers)) { + participantUsers = newParticipantUsers + } } } } @@ -1817,9 +1815,8 @@ fun DisplayPeopleList( ) { val noteEvent = baseNote.event as? PeopleListEvent ?: return - var members by remember { mutableStateOf>(listOf()) } + var members by remember { mutableStateOf>(persistentListOf()) } - val account = accountViewModel.userProfile() var expanded by remember { mutableStateOf(false) } @@ -1848,10 +1845,8 @@ fun DisplayPeopleList( ) LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - members = noteEvent.bookmarkedPeople().mapNotNull { hex -> - LocalCache.checkGetOrCreateUser(hex) - }.sortedBy { account.isFollowing(it) }.reversed() + accountViewModel.loadUsers(noteEvent.bookmarkedPeople()) { + members = it } } @@ -1899,15 +1894,11 @@ private fun RenderBadgeAward( val noteEvent = note.event as? BadgeAwardEvent ?: return var awardees by remember { mutableStateOf>(listOf()) } - val account = accountViewModel.userProfile() - Text(text = stringResource(R.string.award_granted_to)) LaunchedEffect(key1 = note) { - launch(Dispatchers.IO) { - awardees = noteEvent.awardees().mapNotNull { hex -> - LocalCache.checkGetOrCreateUser(hex) - }.sortedBy { account.isFollowing(it) }.reversed() + accountViewModel.loadUsers(noteEvent.awardees()) { + awardees = it } } @@ -2016,7 +2007,7 @@ fun RenderPostApproval( Column(Modifier.fillMaxWidth()) { noteEvent.communities().forEach { - LoadAddressableNote(it) { + LoadAddressableNote(it, accountViewModel) { it?.let { NoteCompose( it, @@ -2055,15 +2046,14 @@ fun RenderPostApproval( } @Composable -fun LoadAddressableNote(aTagHex: String, content: @Composable (AddressableNote?) -> Unit) { +fun LoadAddressableNote(aTagHex: String, accountViewModel: AccountViewModel, content: @Composable (AddressableNote?) -> Unit) { var note by remember(aTagHex) { - mutableStateOf(LocalCache.getAddressableNoteIfExists(aTagHex)) + mutableStateOf(accountViewModel.getAddressableNoteIfExists(aTagHex)) } if (note == null) { LaunchedEffect(key1 = aTagHex) { - launch(Dispatchers.IO) { - val newNote = LocalCache.checkGetOrCreateAddressableNote(aTagHex) + accountViewModel.checkGetOrCreateAddressableNote(aTagHex) { newNote -> if (newNote != note) { note = newNote } @@ -2075,15 +2065,14 @@ fun LoadAddressableNote(aTagHex: String, content: @Composable (AddressableNote?) } @Composable -fun LoadAddressableNote(aTag: ATag, content: @Composable (AddressableNote?) -> Unit) { +fun LoadAddressableNote(aTag: ATag, accountViewModel: AccountViewModel, content: @Composable (AddressableNote?) -> Unit) { var note by remember(aTag) { - mutableStateOf(LocalCache.getAddressableNoteIfExists(aTag.toTag())) + mutableStateOf(accountViewModel.getAddressableNoteIfExists(aTag.toTag())) } if (note == null) { LaunchedEffect(key1 = aTag) { - launch(Dispatchers.IO) { - val newNote = LocalCache.getOrCreateAddressableNote(aTag) + accountViewModel.getOrCreateAddressableNote(aTag) { newNote -> if (newNote != note) { note = newNote } @@ -2215,7 +2204,8 @@ private fun EmojiListOptions( accountViewModel.userProfile().pubkeyHex, "", null - ) + ), + accountViewModel ) { it?.let { usersEmojiList -> val hasAddedThis by usersEmojiList.live().metadata.map { @@ -2567,6 +2557,7 @@ fun SecondUserInfoRow( @Composable fun LoadStatuses( user: User, + accountViewModel: AccountViewModel, content: @Composable (ImmutableList) -> Unit ) { var statuses: ImmutableList by remember { @@ -2576,9 +2567,7 @@ fun LoadStatuses( val userStatus by user.live().statuses.observeAsState() LaunchedEffect(key1 = userStatus) { - launch(Dispatchers.IO) { - val myUser = userStatus?.user ?: return@launch - val newStatuses = LocalCache.findStatusesForUser(myUser) + accountViewModel.findStatusesForUser(userStatus?.user ?: user) { newStatuses -> if (!equalImmutableLists(statuses, newStatuses)) { statuses = newStatuses } @@ -2776,7 +2765,7 @@ private fun RenderAuthorImages( if (isChannel) { val baseChannelHex = remember { baseNote.channelHex() } if (baseChannelHex != null) { - LoadChannel(baseChannelHex) { channel -> + LoadChannel(baseChannelHex, accountViewModel) { channel -> ChannelNotePicture(channel) } } @@ -2784,15 +2773,14 @@ private fun RenderAuthorImages( } @Composable -fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit) { +fun LoadChannel(baseChannelHex: String, accountViewModel: AccountViewModel, content: @Composable (Channel) -> Unit) { var channel by remember(baseChannelHex) { - mutableStateOf(LocalCache.getChannelIfExists(baseChannelHex)) + mutableStateOf(accountViewModel.getChannelIfExists(baseChannelHex)) } if (channel == null) { LaunchedEffect(key1 = baseChannelHex) { - launch(Dispatchers.IO) { - val newChannel = LocalCache.checkGetOrCreateChannel(baseChannelHex) + accountViewModel.checkGetOrCreateChannel(baseChannelHex) { newChannel -> launch(Dispatchers.Main) { channel = newChannel } @@ -2909,15 +2897,12 @@ private fun DisplayQuoteAuthor( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - var userBase by remember { mutableStateOf(LocalCache.getUserIfExists(authorHex)) } + var userBase by remember { mutableStateOf(accountViewModel.getUserIfExists(authorHex)) } - LaunchedEffect(Unit) { - if (userBase == null) { - launch(Dispatchers.IO) { - val newUserBase = LocalCache.checkGetOrCreateUser(authorHex) - launch(Dispatchers.Main) { - userBase = newUserBase - } + if (userBase == null) { + LaunchedEffect(Unit) { + accountViewModel.checkGetOrCreateUser(authorHex) { newUserBase -> + userBase = newUserBase } } } @@ -2941,7 +2926,7 @@ private fun DisplayQuoteAuthor( @Composable private fun LoadAndDisplayPost(postAddress: ATag, accountViewModel: AccountViewModel, nav: (String) -> Unit) { - LoadAddressableNote(aTag = postAddress) { + LoadAddressableNote(aTag = postAddress, accountViewModel) { it?.let { note -> val noteEvent by note.live().metadata.map { it.note.event @@ -3387,7 +3372,7 @@ fun FileStorageHeaderDisplay(baseNote: Note, roundedCorner: Boolean, accountView val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return val dataEventId = eventHeader.dataEventId() ?: return - LoadNote(baseNoteHex = dataEventId) { contentNote -> + LoadNote(baseNoteHex = dataEventId, accountViewModel) { contentNote -> if (contentNote != null) { ObserverAndRenderNIP95(baseNote, contentNote, roundedCorner, accountViewModel) } @@ -3462,8 +3447,8 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, accountViewModel: A var participantUsers by remember { mutableStateOf>>(emptyList()) } LaunchedEffect(key1 = participants) { - launch(Dispatchers.IO) { - participantUsers = participants.mapNotNull { part -> LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } } + accountViewModel.loadParticipants(participants) { + participantUsers = it } } @@ -3672,11 +3657,7 @@ fun RenderLiveActivityEventInner(baseNote: Note, accountViewModel: AccountViewMo } LaunchedEffect(key1 = eventUpdates) { - launch(Dispatchers.IO) { - val newParticipantUsers = participants.mapNotNull { part -> - LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } - }.toImmutableList() - + accountViewModel.loadParticipants(participants) { newParticipantUsers -> if (!equalImmutableLists(newParticipantUsers, participantUsers)) { participantUsers = newParticipantUsers } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index a1296f4f1..b2705c9cb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -314,7 +314,8 @@ private fun EmojiSelector(accountViewModel: AccountViewModel, nav: (String) -> U accountViewModel.userProfile().pubkeyHex, "", null - ) + ), + accountViewModel ) { emptyNote -> emptyNote?.let { usersEmojiList -> val collections by usersEmojiList.live().metadata.map { @@ -339,7 +340,7 @@ fun EmojiCollectionGallery(emojiCollections: List, accountViewModel: Accou state = listState ) { itemsIndexed(emojiCollections, key = { _, item -> item.toTag() }) { _, item -> - LoadAddressableNote(aTag = item) { + LoadAddressableNote(aTag = item, accountViewModel) { it?.let { WatchAndRenderNote(it, bgColor, accountViewModel, nav, onClick) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt index 56e7c58fb..4fedca29f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserProfilePicture.kt @@ -112,7 +112,7 @@ fun UserPicture( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadUser(baseUserHex = userHex) { + LoadUser(baseUserHex = userHex, accountViewModel) { if (it != null) { UserPicture( user = it, @@ -216,57 +216,57 @@ fun NonClickableUserPictures( when (userList.size) { 0 -> {} - 1 -> LoadUser(baseUserHex = userList[0]) { + 1 -> LoadUser(baseUserHex = userList[0], accountViewModel) { it?.let { BaseUserPicture(it, size, accountViewModel, outerModifier = Modifier) } } 2 -> { - LoadUser(baseUserHex = userList[0]) { + LoadUser(baseUserHex = userList[0], accountViewModel) { it?.let { BaseUserPicture(it, size.div(1.5f), accountViewModel, outerModifier = Modifier.align(Alignment.CenterStart)) } } - LoadUser(baseUserHex = userList[1]) { + LoadUser(baseUserHex = userList[1], accountViewModel) { it?.let { BaseUserPicture(it, size.div(1.5f), accountViewModel, outerModifier = Modifier.align(Alignment.CenterEnd)) } } } 3 -> { - LoadUser(baseUserHex = userList[0]) { + LoadUser(baseUserHex = userList[0], accountViewModel) { it?.let { BaseUserPicture(it, size.div(1.8f), accountViewModel, outerModifier = Modifier.align(Alignment.BottomStart)) } } - LoadUser(baseUserHex = userList[1]) { + LoadUser(baseUserHex = userList[1], accountViewModel) { it?.let { BaseUserPicture(it, size.div(1.8f), accountViewModel, outerModifier = Modifier.align(Alignment.TopCenter)) } } - LoadUser(baseUserHex = userList[2]) { + LoadUser(baseUserHex = userList[2], accountViewModel) { it?.let { BaseUserPicture(it, size.div(1.8f), accountViewModel, outerModifier = Modifier.align(Alignment.BottomEnd)) } } } else -> { - LoadUser(baseUserHex = userList[0]) { + LoadUser(baseUserHex = userList[0], accountViewModel) { it?.let { BaseUserPicture(it, size.div(2f), accountViewModel, outerModifier = Modifier.align(Alignment.BottomStart)) } } - LoadUser(baseUserHex = userList[1]) { + LoadUser(baseUserHex = userList[1], accountViewModel) { it?.let { BaseUserPicture(it, size.div(2f), accountViewModel, outerModifier = Modifier.align(Alignment.TopStart)) } } - LoadUser(baseUserHex = userList[2]) { + LoadUser(baseUserHex = userList[2], accountViewModel) { it?.let { BaseUserPicture(it, size.div(2f), accountViewModel, outerModifier = Modifier.align(Alignment.BottomEnd)) } } - LoadUser(baseUserHex = userList[3]) { + LoadUser(baseUserHex = userList[3], accountViewModel) { it?.let { BaseUserPicture(it, size.div(2f), accountViewModel, outerModifier = Modifier.align(Alignment.TopEnd)) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 18aaacb0b..b076e54b5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -101,10 +101,12 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel(), In private val bundler = BundledUpdate(250, Dispatchers.IO) override fun invalidateData(ignoreIfDoing: Boolean) { - bundler.invalidate(ignoreIfDoing) { - // adds the time to perform the refresh into this delay - // holding off new updates in case of heavy refresh routines. - refreshSuspended() + viewModelScope.launch(Dispatchers.IO) { + bundler.invalidate(ignoreIfDoing) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 7abf1adff..05d2268a6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AccountState import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.ConnectivityType import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -24,15 +25,19 @@ import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.service.ZapPaymentHandler +import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService +import com.vitorpamplona.amethyst.ui.components.TranslationConfig import com.vitorpamplona.amethyst.ui.components.UrlPreviewState import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus import com.vitorpamplona.amethyst.ui.note.showAmount +import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.GiftWrapEvent import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.LnZapRequestEvent +import com.vitorpamplona.quartz.events.Participant import com.vitorpamplona.quartz.events.ReportEvent import com.vitorpamplona.quartz.events.SealedGossipEvent import com.vitorpamplona.quartz.events.UserMetadata @@ -40,6 +45,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -523,6 +529,135 @@ class AccountViewModel(val account: Account) : ViewModel() { } } + fun translate(content: String, onTranslated: (TranslationConfig) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + LanguageTranslatorService.autoTranslate( + content, + account.dontTranslateFrom, + account.translateTo + ).addOnCompleteListener { task -> + if (task.isSuccessful && !content.equals(task.result.result, true)) { + if (task.result.sourceLang != null && task.result.targetLang != null) { + val preference = account.preferenceBetween(task.result.sourceLang!!, task.result.targetLang!!) + val newConfig = TranslationConfig( + result = task.result.result, + sourceLang = task.result.sourceLang, + targetLang = task.result.targetLang, + showOriginal = preference == task.result.sourceLang + ) + + onTranslated(newConfig) + } + } + } + } + } + + suspend fun checkGetOrCreateUser(key: HexKey): User? { + return LocalCache.checkGetOrCreateUser(key) + } + + suspend fun getOrCreateUser(key: HexKey): User { + return LocalCache.getOrCreateUser(key) + } + + fun checkGetOrCreateUser(key: HexKey, onResult: (User?) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + onResult(checkGetOrCreateUser(key)) + } + } + + fun getUserIfExists(hex: HexKey): User? { + return LocalCache.getUserIfExists(hex) + } + + suspend fun checkGetOrCreateNote(key: HexKey): Note? { + return LocalCache.checkGetOrCreateNote(key) + } + + suspend fun getOrCreateNote(key: HexKey): Note { + return LocalCache.getOrCreateNote(key) + } + + fun checkGetOrCreateNote(key: HexKey, onResult: (Note?) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + onResult(checkGetOrCreateNote(key)) + } + } + + fun getNoteIfExists(hex: HexKey): Note? { + return LocalCache.getNoteIfExists(hex) + } + + suspend fun checkGetOrCreateAddressableNote(key: HexKey): AddressableNote? { + return LocalCache.checkGetOrCreateAddressableNote(key) + } + + fun checkGetOrCreateAddressableNote(key: HexKey, onResult: (AddressableNote?) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + onResult(checkGetOrCreateAddressableNote(key)) + } + } + + suspend fun getOrCreateAddressableNote(key: ATag): AddressableNote? { + return LocalCache.getOrCreateAddressableNote(key) + } + + fun getOrCreateAddressableNote(key: ATag, onResult: (AddressableNote?) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + onResult(getOrCreateAddressableNote(key)) + } + } + + fun getAddressableNoteIfExists(key: String): AddressableNote? { + return LocalCache.addressables[key] + } + + fun findStatusesForUser(myUser: User, onResult: (ImmutableList) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + onResult(LocalCache.findStatusesForUser(myUser)) + } + } + + suspend fun checkGetOrCreateChannel(key: HexKey): Channel? { + return LocalCache.checkGetOrCreateChannel(key) + } + + fun checkGetOrCreateChannel(key: HexKey, onResult: (Channel?) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + onResult(checkGetOrCreateChannel(key)) + } + } + + fun getChannelIfExists(hex: HexKey): Channel? { + return LocalCache.getChannelIfExists(hex) + } + + fun loadParticipants(participants: List, onReady: (ImmutableList>) -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + val participantUsers = participants.mapNotNull { part -> + checkGetOrCreateUser(part.key)?.let { + Pair( + part, + it + ) + } + }.toImmutableList() + + onReady(participantUsers) + } + } + + fun loadUsers(hexList: List, onReady: (ImmutableList) -> Unit) { + viewModelScope.launch { + onReady( + hexList.mapNotNull { hex -> + checkGetOrCreateUser(hex) + }.sortedBy { account.isFollowing(it) }.reversed().toImmutableList() + ) + } + } + class Factory(val account: Account) : ViewModelProvider.Factory { override fun create(modelClass: Class): AccountViewModel { return AccountViewModel(account) as AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 46fe8e705..c3a7c26e4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -149,7 +149,7 @@ fun ChannelScreen( ) { if (channelId == null) return - LoadChannel(channelId) { + LoadChannel(channelId, accountViewModel) { PrepareChannelViewModels( baseChannel = it, accountViewModel = accountViewModel, @@ -169,6 +169,7 @@ fun PrepareChannelViewModels(baseChannel: Channel, accountViewModel: AccountView ) val channelScreenModel: NewPostViewModel = viewModel() + channelScreenModel.accountViewModel = accountViewModel channelScreenModel.account = accountViewModel.account ChannelScreen( @@ -267,7 +268,8 @@ fun ChannelScreen( message = newPostModel.message.text, mentions = listOfNotNull(replyTo.value?.author), replyTos = listOfNotNull(replyTo.value), - channelHex = channel.idHex + channelHex = channel.idHex, + accountViewModel = accountViewModel ) tagger.run() if (channel is PublicChatChannel) { @@ -530,7 +532,7 @@ fun ChannelHeader( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadChannel(channelHex) { + LoadChannel(channelHex, accountViewModel) { ChannelHeader( it, showVideo, @@ -783,7 +785,7 @@ fun LongChannelHeader( } } - LoadNote(baseNoteHex = channel.idHex) { loadingNote -> + LoadNote(baseNoteHex = channel.idHex, accountViewModel) { loadingNote -> loadingNote?.let { note -> Row( lineModifier, @@ -886,7 +888,7 @@ private fun ShortChannelActionOptions( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - LoadNote(baseNoteHex = channel.idHex) { + LoadNote(baseNoteHex = channel.idHex, accountViewModel) { it?.let { var popupExpanded by remember { mutableStateOf(false) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index 6b51062c5..7f02e78eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -185,6 +185,7 @@ fun PrepareChatroomViewModels( ) val newPostModel: NewPostViewModel = viewModel() + newPostModel.accountViewModel = accountViewModel newPostModel.account = accountViewModel.account newPostModel.requiresNIP24 = room.users.size > 1 if (newPostModel.requiresNIP24) { @@ -482,7 +483,7 @@ fun ChatroomHeader( nav: (String) -> Unit ) { if (room.users.size == 1) { - LoadUser(baseUserHex = room.users.first()) { baseUser -> + LoadUser(baseUserHex = room.users.first(), accountViewModel) { baseUser -> if (baseUser != null) { ChatroomHeader(baseUser = baseUser, modifier = modifier, accountViewModel = accountViewModel, nav = nav) } @@ -559,7 +560,7 @@ fun GroupChatroomHeader( Column(modifier = Modifier.padding(start = 10.dp)) { RoomNameOnlyDisplay(room, Modifier, FontWeight.Bold, accountViewModel.userProfile()) - DisplayUserSetAsSubject(room, FontWeight.Normal) + DisplayUserSetAsSubject(room, accountViewModel, FontWeight.Normal) } } @@ -733,7 +734,7 @@ fun LongRoomHeader( state = rememberLazyListState() ) { itemsIndexed(list, key = { _, item -> item }) { _, item -> - LoadUser(baseUserHex = item) { + LoadUser(baseUserHex = item, accountViewModel) { if (it != null) { UserCompose( baseUser = it, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt index 069a4f468..011c6dd6b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt @@ -22,7 +22,7 @@ import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView fun CommunityScreen(aTagHex: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) { if (aTagHex == null) return - LoadAddressableNote(aTagHex = aTagHex) { + LoadAddressableNote(aTagHex = aTagHex, accountViewModel) { it?.let { PrepareViewModelsCommunityScreen( note = it, 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 a34b48251..3e035b97b 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 @@ -940,7 +940,7 @@ private fun DrawAdditionalInfo( } } - DisplayBadges(baseUser, nav) + DisplayBadges(baseUser, accountViewModel, nav) DisplayNip05ProfileStatus(user, accountViewModel) @@ -1174,6 +1174,7 @@ private fun WatchApp(baseApp: Note, nav: (String) -> Unit) { @Composable private fun DisplayBadges( baseUser: User, + accountViewModel: AccountViewModel, nav: (String) -> Unit ) { LoadAddressableNote( @@ -1182,7 +1183,8 @@ private fun DisplayBadges( baseUser.pubkeyHex, BadgeProfilesEvent.standardDTAg, null - ) + ), + accountViewModel ) { if (it != null) { val badgeList by it.live().metadata.map { diff --git a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index 47fac7211..f0d4c2da9 100644 --- a/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/app/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.core.os.ConfigurationCompat import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.quartz.events.ImmutableListOfLists @@ -312,31 +311,8 @@ private fun TranslationMessage( @Composable fun TranslateAndWatchLanguageChanges(content: String, accountViewModel: AccountViewModel, onTranslated: (TranslationConfig) -> Unit) { val accountState by accountViewModel.accountLanguagesLiveData.observeAsState() - val account = remember(accountState) { accountState?.account } ?: return LaunchedEffect(accountState) { - launch(Dispatchers.IO) { - LanguageTranslatorService.autoTranslate( - content, - account.dontTranslateFrom, - account.translateTo - ).addOnCompleteListener { task -> - if (task.isSuccessful && !content.equals(task.result.result, true)) { - if (task.result.sourceLang != null && task.result.targetLang != null) { - val preference = account.preferenceBetween(task.result.sourceLang!!, task.result.targetLang!!) - val newConfig = TranslationConfig( - result = task.result.result, - sourceLang = task.result.sourceLang, - targetLang = task.result.targetLang, - showOriginal = preference == task.result.sourceLang - ) - - // withContext(Dispatchers.Main) { - onTranslated(newConfig) - // } - } - } - } - } + accountViewModel.translate(content, onTranslated) } }