From 296fa201f764d0fdac5d16b8ded131f0a504facc Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 8 Jul 2025 11:28:30 -0400 Subject: [PATCH] Delete Relay Selection dialog from all new post screens. --- .../vitorpamplona/amethyst/model/Account.kt | 62 +++- .../amethyst/ui/actions/EditPostView.kt | 34 +-- .../amethyst/ui/actions/EditPostViewModel.kt | 15 +- .../amethyst/ui/actions/NewMediaModel.kt | 25 +- .../amethyst/ui/actions/NewMediaView.kt | 37 +-- .../ui/actions/NewPollVoteValueRange.kt | 4 +- .../ui/actions/RelaySelectionDialog.kt | 278 ------------------ .../nip22Comments/CommentPostViewModel.kt | 91 +++--- .../nip22Comments/GenericCommentPostScreen.kt | 30 -- .../nip99Classifieds/NewProductScreen.kt | 32 -- .../loggedIn/geohash/GeoHashPostScreen.kt | 1 - .../loggedIn/hashtag/HashtagPostScreen.kt | 1 - .../loggedIn/home/ShortNotePostScreen.kt | 34 +-- .../loggedIn/home/ShortNotePostViewModel.kt | 148 +++++----- .../nip01Core/relay/client/NostrClient.kt | 10 +- 15 files changed, 185 insertions(+), 617 deletions(-) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 84b08bb37..6ac6a139d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -92,6 +92,7 @@ import com.vitorpamplona.quartz.experimental.profileGallery.dimension import com.vitorpamplona.quartz.experimental.profileGallery.fromEvent import com.vitorpamplona.quartz.experimental.profileGallery.hash import com.vitorpamplona.quartz.experimental.profileGallery.mimeType +import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray @@ -1085,14 +1086,15 @@ class Account( fun consumeAndSendNip95( data: FileStorageEvent, signedEvent: FileStorageHeaderEvent, - relayList: List, ): Note? { if (!isWriteable()) return null - client.send(data, relayList = relayList.toSet()) + val relayList = computeRelayListToBroadcast(signedEvent) + + client.send(data, relayList = relayList) cache.justConsumeMyOwnEvent(data) - client.send(signedEvent, relayList = relayList.toSet()) + client.send(signedEvent, relayList = relayList) cache.justConsumeMyOwnEvent(signedEvent) return cache.getNoteIfExists(signedEvent.id) @@ -1161,7 +1163,6 @@ class Account( urlHeaderInfo: Map, caption: String?, contentWarningReason: String?, - relayList: Set, onReady: (Note) -> Unit, ) { val iMetas = @@ -1180,7 +1181,7 @@ class Account( ) } - signer.sign( + val template = PictureEvent.build(iMetas, caption ?: "") { caption?.let { hashtags(findHashtags(it)) @@ -1192,9 +1193,10 @@ class Account( // add geohashes // add title contentWarningReason?.let { contentWarning(contentWarningReason) } - }, - ) { - sendHeader(it, relayList = relayList, onReady) + } + + signAndComputeBroadcast(template) { event -> + cache.getNoteIfExists(event.id)?.let { onReady(it) } } } @@ -1205,7 +1207,6 @@ class Account( alt: String?, contentWarningReason: String?, originalHash: String? = null, - relayList: Set, onReady: (Note) -> Unit, ) { if (!isWriteable()) return @@ -1273,8 +1274,8 @@ class Account( } } - signer.sign(template) { - sendHeader(it, relayList = relayList, onReady) + signAndComputeBroadcast(template) { event -> + cache.getNoteIfExists(event.id)?.let { onReady(it) } } } @@ -1305,6 +1306,30 @@ class Account( } } + fun signAndComputeBroadcast( + template: EventTemplate, + broadcast: List = emptyList(), + onDone: (T) -> Unit = {}, + ) { + signer.sign(template) { event -> + cache.justConsumeMyOwnEvent(event) + val note = + if (event is AddressableEvent) { + cache.getOrCreateAddressableNote(event.address()) + } else { + cache.getOrCreateNote(event.id) + } + + val relayList = computeRelayListToBroadcast(note) + + client.send(event, relayList) + + broadcast.forEach { client.send(it, relayList) } + + onDone(event) + } + } + fun signAndSend( template: EventTemplate, relayList: Set, @@ -1581,7 +1606,7 @@ class Account( originalNote: Note, notify: HexKey?, summary: String? = null, - relayList: List, + broadcast: List, ) { if (!isWriteable()) return @@ -1593,9 +1618,14 @@ class Account( notify = notify, summary = summary, signer = signer, - ) { - cache.justConsumeMyOwnEvent(it) - client.send(it, relayList = relayList.toSet()) + ) { event -> + cache.justConsumeMyOwnEvent(event) + val note = cache.getOrCreateNote(event.id) + val relayList = computeRelayListToBroadcast(note) + + client.send(event, relayList = relayList) + + broadcast.forEach { client.send(it, relayList) } } } @@ -1715,7 +1745,7 @@ class Account( )?.relays()?.ifEmpty { null }?.toSet() if (relayList != null) { - client.send(signedEvent = wrap, relayList = relayList) + client.send(event = wrap, relayList = relayList) } else { val taggedUserInboxRelays = wrap.taggedUserIds().flatMapTo(mutableSetOf()) { pubkey -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index 557c8a878..6a7781ece 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.actions import androidx.compose.foundation.border import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -96,7 +95,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList -import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange @@ -108,7 +106,6 @@ import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.amethyst.ui.theme.subtleBorder -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -128,12 +125,6 @@ fun EditPostView( val scrollState = rememberScrollState() val scope = rememberCoroutineScope() - var showRelaysDialog by remember { mutableStateOf(false) } - var relayList = - remember { - accountViewModel.account.outboxRelays.flow.value - .toImmutableList() - } LaunchedEffect(Unit) { postViewModel.load(edit, versionLookingAt, accountViewModel) @@ -148,16 +139,6 @@ fun EditPostView( decorFitsSystemWindows = false, ), ) { - if (showRelaysDialog) { - RelaySelectionDialog( - preSelectedList = relayList, - onClose = { showRelaysDialog = false }, - onPost = { relayList = it }, - accountViewModel = accountViewModel, - nav = nav, - ) - } - Scaffold( topBar = { TopAppBar( @@ -169,22 +150,9 @@ fun EditPostView( ) { Spacer(modifier = StdHorzSpacer) - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays, 2), - contentDescription = stringRes(id = R.string.relay_list_selector), - modifier = Modifier.height(25.dp), - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } PostButton( onPost = { - postViewModel.sendPost(relayList = relayList) + postViewModel.sendPost() scope.launch { delay(100) onClose() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt index 2ede4d54b..5d10cc634 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt @@ -47,7 +47,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip92IMeta.IMetaTag import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder import com.vitorpamplona.quartz.nip94FileMetadata.alt @@ -120,18 +120,21 @@ open class EditPostViewModel : ViewModel() { editedFromNote = edit } - fun sendPost(relayList: List) { - viewModelScope.launch(Dispatchers.IO) { innerSendPost(relayList) } + fun sendPost() { + viewModelScope.launch(Dispatchers.IO) { innerSendPost() } } - suspend fun innerSendPost(relayList: List) { + suspend fun innerSendPost() { if (accountViewModel == null) { cancel() return } + val extraNotesToBroadcast = mutableListOf() + nip95attachments.forEach { - account?.sendNip95(it.first, it.second, relayList.toSet()) + extraNotesToBroadcast.add(it.first) + extraNotesToBroadcast.add(it.second) } val notify = @@ -147,7 +150,7 @@ open class EditPostViewModel : ViewModel() { originalNote = editedFromNote!!, notify = notify, summary = subject.text.ifBlank { null }, - relayList = relayList, + extraNotesToBroadcast, ) cancel() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index 320793b38..4e6e8a1cb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -39,7 +39,6 @@ import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.joinAll @@ -82,13 +81,11 @@ open class NewMediaModel : ViewModel() { fun upload( context: Context, - relayList: List, onSucess: () -> Unit, onError: (String, String) -> Unit, ) { viewModelScope.launch { val myAccount = account ?: return@launch - if (relayList.isEmpty()) return@launch val serverToUse = selectedServer ?: return@launch val myMultiOrchestrator = multiOrchestrator ?: return@launch @@ -141,7 +138,7 @@ open class NewMediaModel : ViewModel() { withTimeoutOrNull(30000) { suspendCancellableCoroutine { continuation -> account?.createNip95(it.bytes, headerInfo = it.fileHeader, caption, if (sensitiveContent) "" else null) { nip95 -> - account?.consumeAndSendNip95(nip95.first, nip95.second, relayList) + account?.consumeAndSendNip95(nip95.first, nip95.second) continuation.resume(true) } } @@ -156,13 +153,12 @@ open class NewMediaModel : ViewModel() { withTimeoutOrNull(30000) { suspendCancellableCoroutine { continuation -> account?.sendHeader( - it.url, - it.magnet, - it.fileHeader, - caption, - if (sensitiveContent) "" else null, - it.uploadedHash, - relayList.toSet(), + url = it.url, + magnetUri = it.magnet, + headerInfo = it.fileHeader, + alt = caption, + contentWarningReason = if (sensitiveContent) "" else null, + originalHash = it.uploadedHash, ) { continuation.resume(true) } @@ -178,10 +174,9 @@ open class NewMediaModel : ViewModel() { withTimeoutOrNull(30000) { suspendCancellableCoroutine { continuation -> account?.sendAllAsOnePictureEvent( - imageUrls, - caption, - if (sensitiveContent) "" else null, - relayList.toSet(), + urlHeaderInfo = imageUrls, + caption = caption, + contentWarningReason = if (sensitiveContent) "" else null, ) { continuation.resume(true) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 9252a51dc..f5a6a49e8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -35,8 +35,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -49,7 +47,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -70,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.buttons.CloseButton import com.vitorpamplona.amethyst.ui.note.buttons.PostButton import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.SettingSwitchItem -import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer @@ -100,13 +96,6 @@ fun NewMediaView( postViewModel.load(account, uris) } - var showRelaysDialog by remember { mutableStateOf(false) } - var relayList = - remember { - accountViewModel.account.outboxRelays.flow.value - .toImmutableList() - } - Dialog( onDismissRequest = { onClose() }, properties = @@ -128,23 +117,9 @@ fun NewMediaView( ) { Spacer(modifier = StdHorzSpacer) - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays, 3), - contentDescription = stringRes(id = R.string.relay_list_selector), - modifier = Modifier.height(25.dp), - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } - PostButton( onPost = { - postViewModel.upload(context, relayList, onClose, accountViewModel.toastManager::toast) + postViewModel.upload(context, onClose, accountViewModel.toastManager::toast) postViewModel.selectedServer?.let { if (it.type != ServerType.NIP95) { account.settings.changeDefaultFileServer(it) @@ -173,16 +148,6 @@ fun NewMediaView( ) }, ) { pad -> - if (showRelaysDialog) { - RelaySelectionDialog( - preSelectedList = relayList, - onClose = { showRelaysDialog = false }, - onPost = { relayList = it }, - accountViewModel = accountViewModel, - nav = nav, - ) - } - Surface( modifier = Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt index 99393183f..1f97a0599 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPollVoteValueRange.kt @@ -66,7 +66,7 @@ fun NewPollVoteValueRange(pollViewModel: ShortNotePostViewModel) { onValueChange = { pollViewModel.updateMinZapAmountForPoll(it) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.weight(1f), - colors = if (pollViewModel.isValidvalueMinimum.value) colorValid else colorInValid, + colors = if (pollViewModel.isValidValueMinimum.value) colorValid else colorInValid, label = { Text( text = stringRes(R.string.poll_zap_value_min), @@ -88,7 +88,7 @@ fun NewPollVoteValueRange(pollViewModel: ShortNotePostViewModel) { onValueChange = { pollViewModel.updateMaxZapAmountForPoll(it) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.weight(1f), - colors = if (pollViewModel.isValidvalueMaximum.value) colorValid else colorInValid, + colors = if (pollViewModel.isValidValueMaximum.value) colorValid else colorInValid, label = { Text( text = stringRes(R.string.poll_zap_value_max), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt deleted file mode 100644 index 0b737a14e..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.ui.actions - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.service.Nip11Retriever -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.navigation.INav -import com.vitorpamplona.amethyst.ui.note.buttons.CloseButton -import com.vitorpamplona.amethyst.ui.note.buttons.SaveButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.FeedPadding -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlin.collections.map - -data class RelayList( - val relay: NormalizedRelayUrl, - val isSelected: Boolean, -) - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun RelaySelectionDialog( - preSelectedList: ImmutableList, - onClose: () -> Unit, - onPost: (list: ImmutableList) -> Unit, - accountViewModel: AccountViewModel, - nav: INav, -) { - var relays by remember { - mutableStateOf( - accountViewModel.account.client.allAvailableRelays().map { - RelayList( - relay = it, - isSelected = preSelectedList.any { relayUrl -> it == relayUrl }, - ) - }, - ) - } - - val hasSelectedRelay by remember { derivedStateOf { relays.any { it.isSelected } } } - - var selected by remember { mutableStateOf(true) } - - Dialog( - onDismissRequest = { onClose() }, - properties = - DialogProperties( - usePlatformDefaultWidth = false, - dismissOnClickOutside = false, - decorFitsSystemWindows = false, - ), - ) { - SetDialogToEdgeToEdge() - - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - TopAppBar( - actions = { - SaveButton( - onPost = { - val selectedRelays = relays.filter { it.isSelected }.map { it.relay }.toImmutableList() - onPost(selectedRelays) - onClose() - }, - isActive = hasSelectedRelay, - ) - Spacer(modifier = StdHorzSpacer) - }, - title = {}, - navigationIcon = { - Row { - Spacer(modifier = StdHorzSpacer) - CloseButton( - onPress = { onClose() }, - ) - } - }, - ) - }, - ) { pad -> - Column( - modifier = - Modifier.padding(pad).padding(start = 10.dp, end = 10.dp, top = 10.dp), - ) { - RelaySwitch( - text = stringRes(R.string.select_deselect_all), - checked = selected, - onClick = { - selected = !selected - relays = relays.mapIndexed { _, item -> item.copy(isSelected = selected) } - }, - ) - - LazyColumn( - contentPadding = FeedPadding, - ) { - itemsIndexed( - relays, - key = { _, item -> item.relay.url }, - ) { index, item -> - RenderRelaySwitch( - item, - onClick = { - relays = - relays.mapIndexed { j, item -> - if (index == j) { - item.copy(isSelected = !item.isSelected) - } else { - item - } - } - }, - accountViewModel, - nav, - ) - } - } - } - } - } -} - -@Composable -fun RenderRelaySwitch( - item: RelayList, - onClick: () -> Unit, - accountViewModel: AccountViewModel, - nav: INav, -) { - val context = LocalContext.current - - val relayInfo by loadRelayInfo(item.relay, accountViewModel) - - var openRelayDialog by remember { mutableStateOf(false) } - - if (openRelayDialog) { - RelayInformationDialog( - onClose = { openRelayDialog = false }, - relayInfo = relayInfo, - relay = item.relay, - accountViewModel = accountViewModel, - nav = nav, - ) - } - - RelaySwitch( - text = item.relay.displayUrl(), - checked = item.isSelected, - onClick = onClick, - onLongPress = { - openRelayDialog = true - accountViewModel.retrieveRelayDocument( - relay = item.relay, - onInfo = { }, - onError = { relay, errorCode, exceptionMessage -> - val msg = - when (errorCode) { - Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> - stringRes( - context, - R.string.relay_information_document_error_assemble_url, - relay.url, - exceptionMessage, - ) - Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> - stringRes( - context, - R.string.relay_information_document_error_assemble_url, - relay.url, - exceptionMessage, - ) - Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> - stringRes( - context, - R.string.relay_information_document_error_assemble_url, - relay.url, - exceptionMessage, - ) - Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> - stringRes( - context, - R.string.relay_information_document_error_assemble_url, - relay.url, - exceptionMessage, - ) - } - - accountViewModel.toastManager.toast( - stringRes(context, R.string.unable_to_download_relay_document), - msg, - ) - }, - ) - }, - ) -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun RelaySwitch( - text: String, - checked: Boolean, - onClick: () -> Unit, - onLongPress: () -> Unit = {}, -) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier.combinedClickable( - onClick = onClick, - onLongClick = onLongPress, - ), - ) { - Text( - modifier = Modifier.weight(1f), - text = text, - ) - Switch( - checked = checked, - onCheckedChange = { onClick() }, - ) - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt index 1b049286b..a28cb0839 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt @@ -64,7 +64,6 @@ import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash import com.vitorpamplona.quartz.nip01Core.tags.geohash.hasGeohashes @@ -99,13 +98,14 @@ import com.vitorpamplona.quartz.nip94FileMetadata.mimeType import com.vitorpamplona.quartz.nip94FileMetadata.originalHash import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent import com.vitorpamplona.quartz.nip94FileMetadata.size +import com.vitorpamplona.quartz.utils.tryAndWait import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlin.collections.plus +import kotlin.coroutines.resume @Stable open class CommentPostViewModel : @@ -131,7 +131,7 @@ open class CommentPostViewModel : var account: Account? = null var externalIdentity by mutableStateOf(null) - var replyingTo: Note? by mutableStateOf(null) + var replyingTo: Note? by mutableStateOf(null) val iMetaAttachments = IMetaAttachments() var nip95attachments by mutableStateOf>>( @@ -177,9 +177,6 @@ open class CommentPostViewModel : var wantsZapraiser by mutableStateOf(false) override val zapRaiserAmount = mutableStateOf(null) - var showRelaysDialog by mutableStateOf(false) - var relayList by mutableStateOf?>(null) - fun lnAddress(): String? = account?.userProfile()?.info?.lnAddress() fun hasLnAddress(): Boolean = account?.userProfile()?.info?.lnAddress() != null @@ -270,15 +267,15 @@ open class CommentPostViewModel : canAddZapRaiser = accountViewModel?.userProfile()?.info?.lnAddress() != null multiOrchestrator = null - val localfowardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" } + val localForwardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" } forwardZapTo.value = SplitBuilder() - localfowardZapTo.forEach { + localForwardZapTo.forEach { val user = LocalCache.getOrCreateUser(it[1]) val value = it.last().toFloatOrNull() ?: 0f forwardZapTo.value.addItem(user, value) } forwardZapToEditting.value = TextFieldValue("") - wantsForwardZapTo = localfowardZapTo.isNotEmpty() + wantsForwardZapTo = localForwardZapTo.isNotEmpty() wantsToMarkAsSensitive = draftEvent.isSensitive() @@ -311,21 +308,23 @@ open class CommentPostViewModel : suspend fun sendPostSync() { val template = createTemplate() ?: return - val relayList = relayList + val extraNotesToBroadcast = mutableListOf() - if (nip95attachments.isNotEmpty() && relayList != null && relayList.isNotEmpty()) { + if (nip95attachments.isNotEmpty()) { val usedImages = template.tags.taggedQuoteIds().toSet() nip95attachments.forEach { if (usedImages.contains(it.second.id)) { - account?.sendNip95(it.first, it.second, relayList.toSet()) + extraNotesToBroadcast.add(it.first) + extraNotesToBroadcast.add(it.second) } } } - accountViewModel?.account?.signAndSendPrivatelyOrBroadcast( - template, - relayList = { relayList }, - ) + tryAndWait { continuation -> + accountViewModel?.account?.signAndComputeBroadcast(template, extraNotesToBroadcast) { + continuation.resume(it) + } + } accountViewModel?.deleteDraft(draftTag.current) @@ -378,7 +377,7 @@ open class CommentPostViewModel : msg = tagger.message, replyingTo = eventHint, ) { - tagger.pTags?.let { notify(it.map { it.toPTag() }) } + tagger.pTags?.let { pTagList -> notify(pTagList.map { it.toPTag() }) } hashtags(findHashtags(tagger.message)) references(findURLs(tagger.message)) @@ -398,7 +397,7 @@ open class CommentPostViewModel : msg = tagger.message, extId = externalIdentity, ) { - tagger.pTags?.let { notify(it.map { it.toPTag() }) } + tagger.pTags?.let { pTagList -> notify(pTagList.map { it.toPTag() }) } hashtags(findHashtags(tagger.message)) references(findURLs(tagger.message)) @@ -458,9 +457,9 @@ open class CommentPostViewModel : ) if (results.allGood) { - results.successful.forEach { - if (it.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - account?.createNip95(it.result.bytes, headerInfo = it.result.fileHeader, alt, contentWarningReason) { nip95 -> + results.successful.forEach { state -> + if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { + account?.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason) { nip95 -> nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) } @@ -469,20 +468,20 @@ open class CommentPostViewModel : urlPreviews.update(message) } } - } else if (it.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + } else if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(it.result.url) + IMetaTagBuilder(state.result.url) .apply { - hash(it.result.fileHeader.hash) - size(it.result.fileHeader.size) - it.result.fileHeader.mimeType + hash(state.result.fileHeader.hash) + size(state.result.fileHeader.size) + state.result.fileHeader.mimeType ?.let { mimeType(it) } - it.result.fileHeader.dim + state.result.fileHeader.dim ?.let { dims(it) } - it.result.fileHeader.blurHash + state.result.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - it.result.magnet?.let { magnet(it) } - it.result.uploadedHash?.let { originalHash(it) } + state.result.magnet?.let { magnet(it) } + state.result.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } contentWarningReason?.let { sensitiveContent(contentWarningReason) } @@ -490,7 +489,7 @@ open class CommentPostViewModel : iMetaAttachments.replace(iMeta.url, iMeta) - message = message.insertUrlAtCursor(it.result.url) + message = message.insertUrlAtCursor(state.result.url) urlPreviews.update(message) } } @@ -546,21 +545,9 @@ open class CommentPostViewModel : emojiSuggestions?.reset() - showRelaysDialog = false - - reloadRelaySet() - draftTag.rotate() } - fun reloadRelaySet() { - val account = accountViewModel?.account ?: return - - relayList = - account.outboxRelays.flow.value - .toImmutableList() - } - fun deleteMediaToUpload(selected: SelectedMediaProcessing) { this.multiOrchestrator?.remove(selected) } @@ -569,8 +556,8 @@ open class CommentPostViewModel : notifying = notifying?.filter { it != userToRemove } } - override fun updateMessage(it: TextFieldValue) { - message = it + override fun updateMessage(newMessage: TextFieldValue) { + message = newMessage urlPreviews.update(message) if (message.selection.collapsed) { @@ -627,13 +614,13 @@ open class CommentPostViewModel : val wordToInsert = item.link.url + " " viewModelScope.launch(Dispatchers.IO) { - iMetaAttachments.downloadAndPrepare( - item.link.url, - { - Amethyst.Companion.instance.okHttpClients - .getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) - }, - ) + iMetaAttachments.downloadAndPrepare(item.link.url) { + Amethyst.Companion.instance.okHttpClients + .getHttpClient( + accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) + ?: false, + ) + } } message = message.replaceCurrentWord(wordToInsert) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index 1fbdc21c7..f91fb778c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.note.nip22Comments import android.net.Uri import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -37,8 +36,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -46,7 +43,6 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -56,7 +52,6 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.RelaySelectionDialog import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia @@ -85,7 +80,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.AddZapraiserButton import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.ZapRaiserRequest import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton -import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp @@ -116,7 +110,6 @@ fun ReplyCommentPostScreen( val context = LocalContext.current LaunchedEffect(Unit) { - postViewModel.reloadRelaySet() reply?.let { postViewModel.reply(it) } @@ -159,20 +152,6 @@ fun GenericCommentPostScreen( verticalAlignment = CenterVertically, ) { Spacer(modifier = StdHorzSpacer) - - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { postViewModel.showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays, 5), - contentDescription = stringRes(id = R.string.relay_list_selector), - modifier = Modifier.height(25.dp), - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } PostButton( onPost = { // uses the accountViewModel scope to avoid cancelling this @@ -209,15 +188,6 @@ fun GenericCommentPostScreen( ) }, ) { pad -> - if (postViewModel.showRelaysDialog) { - RelaySelectionDialog( - preSelectedList = postViewModel.relayList ?: persistentListOf(), - onClose = { postViewModel.showRelaysDialog = false }, - onPost = { postViewModel.relayList = it }, - accountViewModel = accountViewModel, - nav = nav, - ) - } Surface( modifier = Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index 580f31de0..94cfbce29 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import android.net.Uri import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -37,8 +36,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -47,10 +44,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -60,7 +54,6 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.RelaySelectionDialog import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia @@ -88,7 +81,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.AddZapraiserButton import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.ZapRaiserRequest import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton -import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size10dp @@ -151,8 +143,6 @@ fun NewProductScreen( ) { WatchAndLoadMyEmojiList(accountViewModel) - var showRelaysDialog by remember { mutableStateOf(false) } - Scaffold( topBar = { TopAppBar( @@ -164,19 +154,6 @@ fun NewProductScreen( ) { Spacer(modifier = StdHorzSpacer) - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays, 1), - contentDescription = stringRes(id = R.string.relay_list_selector), - modifier = Modifier.height(25.dp), - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } PostButton( onPost = { accountViewModel.viewModelScope.launch(Dispatchers.IO) { @@ -210,15 +187,6 @@ fun NewProductScreen( ) }, ) { pad -> - if (showRelaysDialog) { - RelaySelectionDialog( - preSelectedList = postViewModel.relayList ?: persistentListOf(), - onClose = { showRelaysDialog = false }, - onPost = { postViewModel.relayList = it }, - accountViewModel = accountViewModel, - nav = nav, - ) - } Surface( modifier = Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt index 9d56277f2..368e021d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt @@ -54,7 +54,6 @@ fun GeoHashPostScreen( val context = LocalContext.current LaunchedEffect(Unit) { - postViewModel.reloadRelaySet() geohash?.let { postViewModel.newPostFor(GeohashId(it)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt index fd0934ac3..d96a6a847 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt @@ -54,7 +54,6 @@ fun HashtagPostScreen( val context = LocalContext.current LaunchedEffect(Unit) { - postViewModel.reloadRelaySet() hashtag?.let { postViewModel.newPostFor(HashtagId(it)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index 1e22671f5..c3203f6dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -25,7 +25,6 @@ import android.net.Uri import android.os.Parcelable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -50,8 +49,6 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -62,7 +59,6 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.RelaySelectionDialog import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia @@ -173,8 +169,6 @@ private fun NewPostScreenInner( accountViewModel: AccountViewModel, nav: Nav, ) { - val scope = rememberCoroutineScope() - WatchAndLoadMyEmojiList(accountViewModel) Scaffold( @@ -188,19 +182,6 @@ private fun NewPostScreenInner( ) { Spacer(modifier = StdHorzSpacer) - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { postViewModel.showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays, 6), - contentDescription = stringRes(id = R.string.relay_list_selector), - modifier = Modifier.height(25.dp), - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } PostButton( onPost = { // uses the accountViewModel scope to avoid cancelling this @@ -238,15 +219,6 @@ private fun NewPostScreenInner( ) }, ) { pad -> - if (postViewModel.showRelaysDialog) { - RelaySelectionDialog( - preSelectedList = postViewModel.relayList ?: persistentListOf(), - onClose = { postViewModel.showRelaysDialog = false }, - onPost = { postViewModel.relayList = it }, - accountViewModel = accountViewModel, - nav = nav, - ) - } Surface( modifier = Modifier @@ -419,7 +391,7 @@ private fun NewPostScreenBody( } } - if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) { + if (postViewModel.wantsZapRaiser && postViewModel.hasLnAddress()) { Row( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), @@ -495,8 +467,8 @@ private fun BottomRowActions(postViewModel: ShortNotePostViewModel) { } if (postViewModel.canAddZapRaiser) { - AddZapraiserButton(postViewModel.wantsZapraiser) { - postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser + AddZapraiserButton(postViewModel.wantsZapRaiser) { + postViewModel.wantsZapRaiser = !postViewModel.wantsZapRaiser } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index 90d5280f4..af371a87c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -74,7 +74,6 @@ import com.vitorpamplona.quartz.experimental.zapPolls.tags.PollOptionTag import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash @@ -115,12 +114,13 @@ import com.vitorpamplona.quartz.nip94FileMetadata.mimeType import com.vitorpamplona.quartz.nip94FileMetadata.originalHash import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent import com.vitorpamplona.quartz.nip94FileMetadata.size +import com.vitorpamplona.quartz.utils.tryAndWait import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlin.coroutines.resume enum class UserSuggestionAnchor { MAIN_MESSAGE, @@ -151,8 +151,8 @@ open class ShortNotePostViewModel : var accountViewModel: AccountViewModel? = null var account: Account? = null - var originalNote: Note? by mutableStateOf(null) - var forkedFromNote: Note? by mutableStateOf(null) + var originalNote: Note? by mutableStateOf(null) + var forkedFromNote: Note? by mutableStateOf(null) var pTags by mutableStateOf?>(null) var eTags by mutableStateOf?>(null) @@ -185,9 +185,8 @@ open class ShortNotePostViewModel : var consensusThreshold: Int? = null var closedAt: Long? = null - var isValidRecipients = mutableStateOf(true) - var isValidvalueMaximum = mutableStateOf(true) - var isValidvalueMinimum = mutableStateOf(true) + var isValidValueMaximum = mutableStateOf(true) + var isValidValueMinimum = mutableStateOf(true) var isValidConsensusThreshold = mutableStateOf(true) var isValidClosedAt = mutableStateOf(true) @@ -212,12 +211,9 @@ open class ShortNotePostViewModel : // ZapRaiser var canAddZapRaiser by mutableStateOf(false) - var wantsZapraiser by mutableStateOf(false) + var wantsZapRaiser by mutableStateOf(false) override val zapRaiserAmount = mutableStateOf(null) - var showRelaysDialog by mutableStateOf(false) - var relayList by mutableStateOf?>(null) - fun lnAddress(): String? = account?.userProfile()?.info?.lnAddress() fun hasLnAddress(): Boolean = account?.userProfile()?.info?.lnAddress() != null @@ -295,10 +291,10 @@ open class ShortNotePostViewModel : canUsePoll = originalNote == null multiOrchestrator = null - quote?.let { - message = TextFieldValue(message.text + "\nnostr:${it.toNEvent()}") + quote?.let { quotedNote -> + message = TextFieldValue(message.text + "\nnostr:${quotedNote.toNEvent()}") - it.author?.let { quotedUser -> + quotedNote.author?.let { quotedUser -> if (quotedUser.pubkeyHex != accountViewModel.userProfile().pubkeyHex) { if (forwardZapTo.value.items.none { it.key.pubkeyHex == quotedUser.pubkeyHex }) { forwardZapTo.value.addItem(quotedUser) @@ -313,21 +309,21 @@ open class ShortNotePostViewModel : } } - fork?.let { - message = TextFieldValue(version?.event?.content ?: it.event?.content ?: "") + fork?.let { forkedNoted -> + message = TextFieldValue(version?.event?.content ?: forkedNoted.event?.content ?: "") - it.event?.isSensitiveOrNSFW()?.let { + forkedNoted.event?.isSensitiveOrNSFW()?.let { if (it) wantsToMarkAsSensitive = true } - it.event?.zapraiserAmount()?.let { + forkedNoted.event?.zapraiserAmount()?.let { zapRaiserAmount.value = it } - it.event?.zapSplitSetup()?.let { - val totalWeight = it.sumOf { if (it is ZapSplitSetupLnAddress) 0.0 else it.weight } + forkedNoted.event?.zapSplitSetup()?.let { setup -> + val totalWeight = setup.sumOf { if (it is ZapSplitSetupLnAddress) 0.0 else it.weight } - it.forEach { + setup.forEach { if (it is ZapSplitSetup) { forwardZapTo.value.addItem(LocalCache.getOrCreateUser(it.pubKeyHex), (it.weight / totalWeight).toFloat()) } @@ -336,7 +332,7 @@ open class ShortNotePostViewModel : // Only adds if it is not already set up. if (forwardZapTo.value.items.isEmpty()) { - it.author?.let { forkedAuthor -> + forkedNoted.author?.let { forkedAuthor -> if (forkedAuthor.pubkeyHex != accountViewModel.userProfile().pubkeyHex) { if (forwardZapTo.value.items.none { it.key.pubkeyHex == forkedAuthor.pubkeyHex }) forwardZapTo.value.addItem(forkedAuthor) if (forwardZapTo.value.items.none { it.key.pubkeyHex == accountViewModel.userProfile().pubkeyHex }) forwardZapTo.value.addItem(accountViewModel.userProfile()) @@ -347,7 +343,7 @@ open class ShortNotePostViewModel : } } - it.author?.let { + forkedNoted.author?.let { if (this.pTags == null) { this.pTags = listOf(it) } else if (this.pTags?.contains(it) != true) { @@ -355,7 +351,7 @@ open class ShortNotePostViewModel : } } - forkedFromNote = it + forkedFromNote = forkedNoted } ?: run { forkedFromNote = null } @@ -380,15 +376,15 @@ open class ShortNotePostViewModel : canAddZapRaiser = accountViewModel?.userProfile()?.info?.lnAddress() != null multiOrchestrator = null - val localfowardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" } + val localForwardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" } forwardZapTo.value = SplitBuilder() - localfowardZapTo.forEach { + localForwardZapTo.forEach { val user = LocalCache.getOrCreateUser(it[1]) val value = it.last().toFloatOrNull() ?: 0f forwardZapTo.value.addItem(user, value) } forwardZapToEditting.value = TextFieldValue("") - wantsForwardZapTo = localfowardZapTo.isNotEmpty() + wantsForwardZapTo = localForwardZapTo.isNotEmpty() wantsToMarkAsSensitive = draftEvent.isSensitive() @@ -398,11 +394,11 @@ open class ShortNotePostViewModel : wantsExclusiveGeoPost = draftEvent.kind == CommentEvent.KIND } - val zapraiser = draftEvent.zapraiserAmount() - wantsZapraiser = zapraiser != null + val zapRaiser = draftEvent.zapraiserAmount() + wantsZapRaiser = zapRaiser != null zapRaiserAmount.value = null - if (zapraiser != null) { - zapRaiserAmount.value = zapraiser + if (zapRaiser != null) { + zapRaiserAmount.value = zapRaiser } eTags = @@ -416,7 +412,7 @@ open class ShortNotePostViewModel : LocalCache.getOrCreateUser(it[1]) } - draftEvent.tags.filter { it.size > 3 && (it[0] == "e" || it[0] == "a") && it.get(3) == "fork" }.forEach { + draftEvent.tags.filter { it.size > 3 && (it[0] == "e" || it[0] == "a") && it[3] == "fork" }.forEach { val note = LocalCache.checkGetOrCreateNote(it[1]) forkedFromNote = note } @@ -470,21 +466,23 @@ open class ShortNotePostViewModel : suspend fun sendPostSync() { val template = createTemplate() ?: return - val relayList = relayList + val extraNotesToBroadcast = mutableListOf() - if (nip95attachments.isNotEmpty() && relayList != null && relayList.isNotEmpty()) { + if (nip95attachments.isNotEmpty()) { val usedImages = template.tags.taggedQuoteIds().toSet() nip95attachments.forEach { if (usedImages.contains(it.second.id)) { - account?.sendNip95(it.first, it.second, relayList.toSet()) + extraNotesToBroadcast.add(it.first) + extraNotesToBroadcast.add(it.second) } } } - accountViewModel?.account?.signAndSendPrivatelyOrBroadcast( - template, - relayList = { relayList }, - ) + tryAndWait { continuation -> + accountViewModel?.account?.signAndComputeBroadcast(template, extraNotesToBroadcast) { + continuation.resume(it) + } + } accountViewModel?.deleteDraft(draftTag.current) @@ -526,7 +524,7 @@ open class ShortNotePostViewModel : val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() - val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null + val localZapRaiserAmount = if (wantsZapRaiser) zapRaiserAmount.value else null val emojis = findEmoji(tagger.message, account?.emoji?.myEmojis?.value) val urls = findURLs(tagger.message) @@ -565,7 +563,7 @@ open class ShortNotePostViewModel : replyingTo = originalNote?.toEventHint(), forkingFrom = forkedFromNote?.toEventHint(), ) { - tagger.pTags?.let { notify(it.map { it.toPTag() }) } + tagger.pTags?.let { pTagList -> notify(pTagList.map { it.toPTag() }) } hashtags(findHashtags(tagger.message)) references(findURLs(tagger.message)) @@ -619,9 +617,9 @@ open class ShortNotePostViewModel : ) if (results.allGood) { - results.successful.forEach { - if (it.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - account?.createNip95(it.result.bytes, headerInfo = it.result.fileHeader, alt, contentWarningReason) { nip95 -> + results.successful.forEach { state -> + if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { + account?.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason) { nip95 -> nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) } @@ -630,20 +628,20 @@ open class ShortNotePostViewModel : urlPreviews.update(message) } } - } else if (it.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + } else if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(it.result.url) + IMetaTagBuilder(state.result.url) .apply { - hash(it.result.fileHeader.hash) - size(it.result.fileHeader.size) - it.result.fileHeader.mimeType + hash(state.result.fileHeader.hash) + size(state.result.fileHeader.size) + state.result.fileHeader.mimeType ?.let { mimeType(it) } - it.result.fileHeader.dim + state.result.fileHeader.dim ?.let { dims(it) } - it.result.fileHeader.blurHash + state.result.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - it.result.magnet?.let { magnet(it) } - it.result.uploadedHash?.let { originalHash(it) } + state.result.magnet?.let { magnet(it) } + state.result.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } contentWarningReason?.let { sensitiveContent(contentWarningReason) } @@ -651,7 +649,7 @@ open class ShortNotePostViewModel : iMetaAttachments.replace(iMeta.url, iMeta) - message = message.insertUrlAtCursor(it.result.url) + message = message.insertUrlAtCursor(state.result.url) urlPreviews.update(message) } } @@ -677,7 +675,7 @@ open class ShortNotePostViewModel : pTags = null wantsPoll = false - zapRecipients = mutableStateListOf() + zapRecipients = mutableStateListOf() pollOptions = newStateMapPollOptions() valueMaximum = null valueMinimum = null @@ -685,7 +683,7 @@ open class ShortNotePostViewModel : closedAt = null wantsInvoice = false - wantsZapraiser = false + wantsZapRaiser = false zapRaiserAmount.value = null wantsForwardZapTo = false @@ -706,21 +704,9 @@ open class ShortNotePostViewModel : emojiSuggestions?.reset() - showRelaysDialog = false - - reloadRelaySet() - draftTag.rotate() } - fun reloadRelaySet() { - val account = accountViewModel?.account ?: return - - relayList = - account.outboxRelays.flow.value - .toImmutableList() - } - fun deleteMediaToUpload(selected: SelectedMediaProcessing) { this.multiOrchestrator?.remove(selected) } @@ -733,8 +719,8 @@ open class ShortNotePostViewModel : updateMessage(TextFieldValue(message.text + " " + it)) } - override fun updateMessage(it: TextFieldValue) { - message = it + override fun updateMessage(newMessage: TextFieldValue) { + message = newMessage urlPreviews.update(message) if (message.selection.collapsed) { @@ -793,8 +779,12 @@ open class ShortNotePostViewModel : viewModelScope.launch(Dispatchers.IO) { iMetaAttachments.downloadAndPrepare( item.link.url, - { Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) }, - ) + ) { + Amethyst.instance.okHttpClients.getHttpClient( + accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) + ?: false, + ) + } } message = message.replaceCurrentWord(wordToInsert) @@ -811,13 +801,13 @@ open class ShortNotePostViewModel : message.text.isNotBlank() && !isUploadingImage && !wantsInvoice && - (!wantsZapraiser || zapRaiserAmount.value != null) && + (!wantsZapRaiser || zapRaiserAmount.value != null) && ( !wantsPoll || ( pollOptions.values.all { it.isNotEmpty() } && - isValidvalueMinimum.value && - isValidvalueMaximum.value + isValidValueMinimum.value && + isValidValueMaximum.value ) ) && multiOrchestrator == null @@ -857,11 +847,11 @@ open class ShortNotePostViewModel : fun checkMinMax() { if ((valueMinimum ?: 0) > (valueMaximum ?: Long.MAX_VALUE)) { - isValidvalueMinimum.value = false - isValidvalueMaximum.value = false + isValidValueMinimum.value = false + isValidValueMaximum.value = false } else { - isValidvalueMinimum.value = true - isValidvalueMaximum.value = true + isValidValueMinimum.value = true + isValidValueMaximum.value = true } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt index a0641bd82..144c5273c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt @@ -237,18 +237,18 @@ class NostrClient( } fun sendIfExists( - signedEvent: Event, + event: Event, connectedRelay: NormalizedRelayUrl, ) { - relayPool.getRelay(connectedRelay)?.send(signedEvent) + relayPool.getRelay(connectedRelay)?.send(event) } fun send( - signedEvent: Event, + event: Event, relayList: Set, ) { - eventOutbox.markAsSending(signedEvent, relayList) - relayPool.send(signedEvent, relayList) + eventOutbox.markAsSending(event, relayList) + relayPool.send(event, relayList) } fun close(subscriptionId: String) {