From 6d0b05e34d010ddc8aaace59604521234aafb696 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 20 May 2025 15:00:00 -0400 Subject: [PATCH] Activates NIP-22 reply for everything but kind 1s. --- .../amethyst/ui/navigation/AppNavigation.kt | 13 + .../amethyst/ui/navigation/RouteMaker.kt | 33 +- .../amethyst/ui/navigation/Routes.kt | 10 + .../nip22Comments/CommentPostViewModel.kt | 6 +- .../nip22Comments/GenericCommentPostScreen.kt | 471 ++++++++++++++++++ .../loggedIn/geohash/GeoHashPostScreen.kt | 385 +------------- .../loggedIn/hashtag/HashtagPostScreen.kt | 400 +-------------- 7 files changed, 501 insertions(+), 817 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 1df366a33..d10e21a27 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -50,6 +50,7 @@ import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.compose.Disp import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages +import com.vitorpamplona.amethyst.ui.note.nip22Comments.ReplyCommentPostScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout @@ -168,6 +169,18 @@ fun AppNavigation( ) } + composableFromBottomArgs { + ReplyCommentPostScreen( + reply = it.replyTo?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + message = it.message, + attachment = it.attachment?.ifBlank { null }?.toUri(), + quote = it.quote?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + draft = it.draft?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + accountViewModel, + nav, + ) + } + composableFromBottomArgs { NewProductScreen( message = it.message, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt index d45908732..cc3515cb3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/RouteMaker.kt @@ -211,39 +211,10 @@ fun routeReplyTo( } else if (noteEvent.isHashtagScoped()) { Route.HashtagPost(replyTo = note.idHex) } else { - null + Route.GenericCommentPost(replyTo = note.idHex) } } - else -> null - } -} - -fun routeQuote( - note: Note, - asUser: User, -): Route? { - val noteEvent = note.event - return when (noteEvent) { - is TextNoteEvent -> Route.NewPost(baseReplyTo = note.idHex) - is PrivateDmEvent -> - routeToMessage( - room = noteEvent.chatroomKey(asUser.pubkeyHex), - draftMessage = null, - replyId = noteEvent.id, - draftId = null, - fromUser = asUser, - ) - is ChatroomKeyable -> - routeToMessage( - room = noteEvent.chatroomKey(asUser.pubkeyHex), - draftMessage = null, - replyId = noteEvent.id, - draftId = null, - fromUser = asUser, - ) - is CommentEvent -> Route.GeoPost(replyTo = note.idHex) - - else -> null + else -> Route.GenericCommentPost(replyTo = note.idHex) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index ea314a959..eced5ad9c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -159,6 +159,15 @@ sealed class Route { val draft: String? = null, ) : Route() + @Serializable + data class GenericCommentPost( + val message: String? = null, + val attachment: String? = null, + val replyTo: String? = null, + val quote: String? = null, + val draft: String? = null, + ) : Route() + @Serializable data class NewPost( val message: String? = null, @@ -212,6 +221,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? { dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() + dest.hasRoute() -> entry.toRoute() else -> { null 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 43e11b8c8..7bc016c25 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 @@ -221,12 +221,8 @@ open class CommentPostViewModel : } open fun reply(post: Note) { - val accountViewModel = accountViewModel ?: return - val noteEvent = post.event as? CommentEvent ?: return - val scope = noteEvent.scope() ?: return - this.replyingTo = post - this.externalIdentity = scope + this.externalIdentity = (post.event as? CommentEvent)?.scope() } open fun quote(quote: Note) { 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 new file mode 100644 index 000000000..a79ee5d83 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -0,0 +1,471 @@ +/** + * 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.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 +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.imePadding +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 +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 +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +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.RelaySelectionDialogEasy +import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType +import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton +import com.vitorpamplona.amethyst.ui.navigation.Nav +import com.vitorpamplona.amethyst.ui.note.BaseUserPicture +import com.vitorpamplona.amethyst.ui.note.NoteCompose +import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer +import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton +import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.ShowEmojiSuggestionList +import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList +import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton +import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest +import com.vitorpamplona.amethyst.ui.note.creators.location.AddGeoHashButton +import com.vitorpamplona.amethyst.ui.note.creators.location.LocationAsHash +import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField +import com.vitorpamplona.amethyst.ui.note.creators.previews.DisplayPreviews +import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.AddSecretEmojiButton +import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.SecretEmojiRequest +import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription +import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList +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.screen.loggedIn.CloseButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.Notifying +import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size35dp +import com.vitorpamplona.amethyst.ui.theme.Size5dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.ui.theme.replyModifier +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@Composable +fun ReplyCommentPostScreen( + reply: Note? = null, + message: String? = null, + attachment: Uri? = null, + quote: Note? = null, + draft: Note? = null, + accountViewModel: AccountViewModel, + nav: Nav, +) { + val postViewModel: CommentPostViewModel = viewModel() + postViewModel.init(accountViewModel) + + val context = LocalContext.current + + LaunchedEffect(Unit) { + postViewModel.reloadRelaySet() + reply?.let { + postViewModel.reply(it) + } + draft?.let { + postViewModel.editFromDraft(it) + } + quote?.let { + postViewModel.quote(it) + } + message?.ifBlank { null }?.let { + postViewModel.updateMessage(TextFieldValue(it)) + } + attachment?.let { + withContext(Dispatchers.IO) { + val mediaType = context.contentResolver.getType(it) + postViewModel.selectImage(persistentListOf(SelectedMedia(it, mediaType))) + } + } + } + + GenericCommentPostScreen(postViewModel, accountViewModel, nav) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun GenericCommentPostScreen( + postViewModel: CommentPostViewModel, + accountViewModel: AccountViewModel, + nav: Nav, +) { + WatchAndLoadMyEmojiList(accountViewModel) + + Scaffold( + topBar = { + TopAppBar( + title = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = CenterVertically, + ) { + Spacer(modifier = StdHorzSpacer) + + Box { + IconButton( + modifier = Modifier.align(Alignment.Center), + onClick = { postViewModel.showRelaysDialog = true }, + ) { + Icon( + painter = painterRes(R.drawable.relays), + 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 + // function when the postViewModel is released + accountViewModel.viewModelScope.launch(Dispatchers.IO) { + postViewModel.sendPostSync() + nav.popBack() + } + }, + isActive = postViewModel.canPost(), + ) + } + }, + navigationIcon = { + Row { + Spacer(modifier = StdHorzSpacer) + CloseButton( + onPress = { + // uses the accountViewModel scope to avoid cancelling this + // function when the postViewModel is released + accountViewModel.viewModelScope.launch(Dispatchers.IO) { + postViewModel.sendDraftSync() + nav.popBack() + postViewModel.cancel() + } + }, + ) + } + }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + ) + }, + ) { pad -> + if (postViewModel.showRelaysDialog) { + RelaySelectionDialogEasy( + preSelectedList = postViewModel.relayList ?: persistentListOf(), + onClose = { postViewModel.showRelaysDialog = false }, + onPost = { postViewModel.relayList = it.map { it.url }.toImmutableList() }, + accountViewModel = accountViewModel, + nav = nav, + ) + } + Surface( + modifier = + Modifier + .padding(pad) + .consumeWindowInsets(pad) + .imePadding(), + ) { + GenericCommentPostBody( + postViewModel, + accountViewModel, + nav, + ) + } + } +} + +@Composable +private fun GenericCommentPostBody( + postViewModel: CommentPostViewModel, + accountViewModel: AccountViewModel, + nav: Nav, +) { + val scrollState = rememberScrollState() + + Column(Modifier.fillMaxSize()) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding( + start = Size10dp, + end = Size10dp, + ).weight(1f), + ) { + Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) { + postViewModel.externalIdentity?.let { + Row { + DisplayExternalId(it, nav) + Spacer(modifier = StdVertSpacer) + } + } + + postViewModel.replyingTo?.let { + Row { + NoteCompose( + baseNote = it, + modifier = MaterialTheme.colorScheme.replyModifier, + isQuotedNote = true, + unPackReply = false, + makeItShort = true, + quotesLeft = 1, + accountViewModel = accountViewModel, + nav = nav, + ) + Spacer(modifier = StdVertSpacer) + } + } + + Row { + Notifying(postViewModel.notifying?.toImmutableList(), accountViewModel) { + postViewModel.removeFromReplyList(it) + } + } + + Row( + modifier = Modifier.padding(vertical = Size10dp), + ) { + BaseUserPicture( + accountViewModel.userProfile(), + Size35dp, + accountViewModel = accountViewModel, + ) + MessageField( + R.string.what_s_on_your_mind, + postViewModel, + ) + } + + DisplayPreviews(postViewModel.urlPreviews, accountViewModel, nav) + + if (postViewModel.wantsToMarkAsSensitive) { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), + ) { + ContentSensitivityExplainer() + } + } + + if (postViewModel.wantsToAddGeoHash) { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), + ) { + LocationAsHash(postViewModel) + } + } + + if (postViewModel.wantsForwardZapTo) { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp), + ) { + ForwardZapTo(postViewModel, accountViewModel) + } + } + + postViewModel.multiOrchestrator?.let { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), + ) { + val context = LocalContext.current + ImageVideoDescription( + it, + accountViewModel.account.settings.defaultFileServer, + onAdd = { alt, server, sensitiveContent, mediaQuality -> + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) + if (server.type != ServerType.NIP95) { + accountViewModel.account.settings.changeDefaultFileServer(server) + } + }, + onDelete = postViewModel::deleteMediaToUpload, + onCancel = { postViewModel.multiOrchestrator = null }, + accountViewModel = accountViewModel, + ) + } + } + + if (postViewModel.wantsInvoice) { + postViewModel.lnAddress()?.let { lud16 -> + InvoiceRequest( + lud16, + accountViewModel.account.userProfile().pubkeyHex, + accountViewModel, + stringRes(id = R.string.lightning_invoice), + stringRes(id = R.string.lightning_create_and_add_invoice), + onSuccess = { + postViewModel.insertAtCursor(it) + postViewModel.wantsInvoice = false + }, + onError = { title, message -> accountViewModel.toastManager.toast(title, message) }, + ) + } + } + + if (postViewModel.wantsSecretEmoji) { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), + ) { + Column(Modifier.fillMaxWidth()) { + SecretEmojiRequest { + postViewModel.insertAtCursor(it) + postViewModel.wantsSecretEmoji = false + } + } + } + } + + if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) { + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), + ) { + ZapRaiserRequest( + stringRes(id = R.string.zapraiser), + postViewModel, + ) + } + } + } + } + + postViewModel.userSuggestions?.let { + ShowUserSuggestionList( + it, + postViewModel::autocompleteWithUser, + accountViewModel, + modifier = Modifier.heightIn(0.dp, 300.dp), + ) + } + + postViewModel.emojiSuggestions?.let { + ShowEmojiSuggestionList( + it, + postViewModel::autocompleteWithEmoji, + postViewModel::autocompleteWithEmojiUrl, + accountViewModel, + modifier = Modifier.heightIn(0.dp, 300.dp), + ) + } + + BottomRowActions(postViewModel) + } +} + +@Composable +private fun BottomRowActions(postViewModel: CommentPostViewModel) { + val scrollState = rememberScrollState() + Row( + modifier = + Modifier + .horizontalScroll(scrollState) + .fillMaxWidth() + .height(50.dp), + verticalAlignment = CenterVertically, + ) { + SelectFromGallery( + isUploading = postViewModel.isUploadingImage, + tint = MaterialTheme.colorScheme.onBackground, + modifier = Modifier, + ) { + postViewModel.selectImage(it) + } + + TakePictureButton( + onPictureTaken = { + postViewModel.selectImage(it) + }, + ) + + ForwardZapToButton(postViewModel.wantsForwardZapTo) { + postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo + } + + if (postViewModel.canAddZapRaiser) { + AddZapraiserButton(postViewModel.wantsZapraiser) { + postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser + } + } + + MarkAsSensitiveButton(postViewModel.wantsToMarkAsSensitive) { + postViewModel.toggleMarkAsSensitive() + } + + AddGeoHashButton(postViewModel.wantsToAddGeoHash) { + postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash + } + + AddSecretEmojiButton(postViewModel.wantsSecretEmoji) { + postViewModel.wantsSecretEmoji = !postViewModel.wantsSecretEmoji + } + + if (postViewModel.canAddInvoice && postViewModel.hasLnAddress()) { + AddLnInvoiceButton(postViewModel.wantsInvoice) { + postViewModel.wantsInvoice = !postViewModel.wantsInvoice + } + } + } +} 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 af09d7acb..14bd8d82b 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 @@ -21,85 +21,21 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash 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 -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.imePadding -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 -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.unit.dp -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.RelaySelectionDialogEasy -import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia -import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.navigation.Nav -import com.vitorpamplona.amethyst.ui.note.BaseUserPicture -import com.vitorpamplona.amethyst.ui.note.NoteCompose -import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer -import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton -import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.ShowEmojiSuggestionList -import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList -import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton -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.previews.DisplayPreviews -import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.AddSecretEmojiButton -import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.SecretEmojiRequest -import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription -import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList -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.note.nip22Comments.CommentPostViewModel -import com.vitorpamplona.amethyst.ui.note.nip22Comments.DisplayExternalId -import com.vitorpamplona.amethyst.ui.painterRes +import com.vitorpamplona.amethyst.ui.note.nip22Comments.GenericCommentPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.Notifying -import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Size10dp -import com.vitorpamplona.amethyst.ui.theme.Size35dp -import com.vitorpamplona.amethyst.ui.theme.Size5dp -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer -import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @Composable @@ -143,322 +79,5 @@ fun GeoHashPostScreen( } } - NewGeoPostScreenInner(postViewModel, accountViewModel, nav) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun NewGeoPostScreenInner( - postViewModel: CommentPostViewModel, - accountViewModel: AccountViewModel, - nav: Nav, -) { - WatchAndLoadMyEmojiList(accountViewModel) - - Scaffold( - topBar = { - TopAppBar( - title = { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = CenterVertically, - ) { - Spacer(modifier = StdHorzSpacer) - - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { postViewModel.showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays), - 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 - // function when the postViewModel is released - accountViewModel.viewModelScope.launch(Dispatchers.IO) { - postViewModel.sendPostSync() - nav.popBack() - } - }, - isActive = postViewModel.canPost(), - ) - } - }, - navigationIcon = { - Row { - Spacer(modifier = StdHorzSpacer) - CloseButton( - onPress = { - // uses the accountViewModel scope to avoid cancelling this - // function when the postViewModel is released - accountViewModel.viewModelScope.launch(Dispatchers.IO) { - postViewModel.sendDraftSync() - nav.popBack() - postViewModel.cancel() - } - }, - ) - } - }, - colors = - TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) - }, - ) { pad -> - if (postViewModel.showRelaysDialog) { - RelaySelectionDialogEasy( - preSelectedList = postViewModel.relayList ?: persistentListOf(), - onClose = { postViewModel.showRelaysDialog = false }, - onPost = { postViewModel.relayList = it.map { it.url }.toImmutableList() }, - accountViewModel = accountViewModel, - nav = nav, - ) - } - Surface( - modifier = - Modifier - .padding(pad) - .consumeWindowInsets(pad) - .imePadding(), - ) { - NewGeoPostBody( - postViewModel, - accountViewModel, - nav, - ) - } - } -} - -@Composable -private fun NewGeoPostBody( - postViewModel: CommentPostViewModel, - accountViewModel: AccountViewModel, - nav: Nav, -) { - val scrollState = rememberScrollState() - - Column(Modifier.fillMaxSize()) { - Row( - modifier = - Modifier - .fillMaxWidth() - .padding( - start = Size10dp, - end = Size10dp, - ).weight(1f), - ) { - Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) { - postViewModel.externalIdentity?.let { - Row { - DisplayExternalId(it, nav) - Spacer(modifier = StdVertSpacer) - } - } - - postViewModel.replyingTo?.let { - Row { - NoteCompose( - baseNote = it, - modifier = MaterialTheme.colorScheme.replyModifier, - isQuotedNote = true, - unPackReply = false, - makeItShort = true, - quotesLeft = 1, - accountViewModel = accountViewModel, - nav = nav, - ) - Spacer(modifier = StdVertSpacer) - } - } - - Row { - Notifying(postViewModel.notifying?.toImmutableList(), accountViewModel) { - postViewModel.removeFromReplyList(it) - } - } - - Row( - modifier = Modifier.padding(vertical = Size10dp), - ) { - BaseUserPicture( - accountViewModel.userProfile(), - Size35dp, - accountViewModel = accountViewModel, - ) - MessageField( - R.string.what_s_on_your_mind, - postViewModel, - ) - } - - DisplayPreviews(postViewModel.urlPreviews, accountViewModel, nav) - - if (postViewModel.wantsToMarkAsSensitive) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - ContentSensitivityExplainer() - } - } - - if (postViewModel.wantsForwardZapTo) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp), - ) { - ForwardZapTo(postViewModel, accountViewModel) - } - } - - postViewModel.multiOrchestrator?.let { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - val context = LocalContext.current - ImageVideoDescription( - it, - accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) - if (server.type != ServerType.NIP95) { - accountViewModel.account.settings.changeDefaultFileServer(server) - } - }, - onDelete = postViewModel::deleteMediaToUpload, - onCancel = { postViewModel.multiOrchestrator = null }, - accountViewModel = accountViewModel, - ) - } - } - - if (postViewModel.wantsInvoice) { - postViewModel.lnAddress()?.let { lud16 -> - InvoiceRequest( - lud16, - accountViewModel.account.userProfile().pubkeyHex, - accountViewModel, - stringRes(id = R.string.lightning_invoice), - stringRes(id = R.string.lightning_create_and_add_invoice), - onSuccess = { - postViewModel.insertAtCursor(it) - postViewModel.wantsInvoice = false - }, - onError = { title, message -> accountViewModel.toastManager.toast(title, message) }, - ) - } - } - - if (postViewModel.wantsSecretEmoji) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - Column(Modifier.fillMaxWidth()) { - SecretEmojiRequest { - postViewModel.insertAtCursor(it) - postViewModel.wantsSecretEmoji = false - } - } - } - } - - if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - ZapRaiserRequest( - stringRes(id = R.string.zapraiser), - postViewModel, - ) - } - } - } - } - - postViewModel.userSuggestions?.let { - ShowUserSuggestionList( - it, - postViewModel::autocompleteWithUser, - accountViewModel, - modifier = Modifier.heightIn(0.dp, 300.dp), - ) - } - - postViewModel.emojiSuggestions?.let { - ShowEmojiSuggestionList( - it, - postViewModel::autocompleteWithEmoji, - postViewModel::autocompleteWithEmojiUrl, - accountViewModel, - modifier = Modifier.heightIn(0.dp, 300.dp), - ) - } - - BottomRowActions(postViewModel) - } -} - -@Composable -private fun BottomRowActions(postViewModel: CommentPostViewModel) { - val scrollState = rememberScrollState() - Row( - modifier = - Modifier - .horizontalScroll(scrollState) - .fillMaxWidth() - .height(50.dp), - verticalAlignment = CenterVertically, - ) { - SelectFromGallery( - isUploading = postViewModel.isUploadingImage, - tint = MaterialTheme.colorScheme.onBackground, - modifier = Modifier, - ) { - postViewModel.selectImage(it) - } - - TakePictureButton( - onPictureTaken = { - postViewModel.selectImage(it) - }, - ) - - ForwardZapToButton(postViewModel.wantsForwardZapTo) { - postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo - } - - if (postViewModel.canAddZapRaiser) { - AddZapraiserButton(postViewModel.wantsZapraiser) { - postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser - } - } - - MarkAsSensitiveButton(postViewModel.wantsToMarkAsSensitive) { - postViewModel.toggleMarkAsSensitive() - } - - AddSecretEmojiButton(postViewModel.wantsSecretEmoji) { - postViewModel.wantsSecretEmoji = !postViewModel.wantsSecretEmoji - } - - if (postViewModel.canAddInvoice && postViewModel.hasLnAddress()) { - AddLnInvoiceButton(postViewModel.wantsInvoice) { - postViewModel.wantsInvoice = !postViewModel.wantsInvoice - } - } - } + GenericCommentPostScreen(postViewModel, accountViewModel, nav) } 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 e788dc5d1..0a828d70c 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 @@ -21,87 +21,21 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag 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 -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.imePadding -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 -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Alignment.Companion.CenterVertically -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.unit.dp -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.RelaySelectionDialogEasy -import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia -import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.navigation.Nav -import com.vitorpamplona.amethyst.ui.note.BaseUserPicture -import com.vitorpamplona.amethyst.ui.note.NoteCompose -import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.ContentSensitivityExplainer -import com.vitorpamplona.amethyst.ui.note.creators.contentWarning.MarkAsSensitiveButton -import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.ShowEmojiSuggestionList -import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList -import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton -import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest -import com.vitorpamplona.amethyst.ui.note.creators.location.AddGeoHashButton -import com.vitorpamplona.amethyst.ui.note.creators.location.LocationAsHash -import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField -import com.vitorpamplona.amethyst.ui.note.creators.previews.DisplayPreviews -import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.AddSecretEmojiButton -import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.SecretEmojiRequest -import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription -import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList -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.note.nip22Comments.CommentPostViewModel -import com.vitorpamplona.amethyst.ui.note.nip22Comments.DisplayExternalId -import com.vitorpamplona.amethyst.ui.painterRes +import com.vitorpamplona.amethyst.ui.note.nip22Comments.GenericCommentPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.Notifying -import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.Size10dp -import com.vitorpamplona.amethyst.ui.theme.Size35dp -import com.vitorpamplona.amethyst.ui.theme.Size5dp -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer -import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @Composable @@ -145,335 +79,5 @@ fun HashtagPostScreen( } } - HashtagPostScreenInner(postViewModel, accountViewModel, nav) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun HashtagPostScreenInner( - postViewModel: CommentPostViewModel, - accountViewModel: AccountViewModel, - nav: Nav, -) { - WatchAndLoadMyEmojiList(accountViewModel) - - Scaffold( - topBar = { - TopAppBar( - title = { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = CenterVertically, - ) { - Spacer(modifier = StdHorzSpacer) - - Box { - IconButton( - modifier = Modifier.align(Alignment.Center), - onClick = { postViewModel.showRelaysDialog = true }, - ) { - Icon( - painter = painterRes(R.drawable.relays), - 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 - // function when the postViewModel is released - accountViewModel.viewModelScope.launch(Dispatchers.IO) { - postViewModel.sendPostSync() - nav.popBack() - } - }, - isActive = postViewModel.canPost(), - ) - } - }, - navigationIcon = { - Row { - Spacer(modifier = StdHorzSpacer) - CloseButton( - onPress = { - // uses the accountViewModel scope to avoid cancelling this - // function when the postViewModel is released - accountViewModel.viewModelScope.launch(Dispatchers.IO) { - postViewModel.sendDraftSync() - nav.popBack() - postViewModel.cancel() - } - }, - ) - } - }, - colors = - TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) - }, - ) { pad -> - if (postViewModel.showRelaysDialog) { - RelaySelectionDialogEasy( - preSelectedList = postViewModel.relayList ?: persistentListOf(), - onClose = { postViewModel.showRelaysDialog = false }, - onPost = { postViewModel.relayList = it.map { it.url }.toImmutableList() }, - accountViewModel = accountViewModel, - nav = nav, - ) - } - Surface( - modifier = - Modifier - .padding(pad) - .consumeWindowInsets(pad) - .imePadding(), - ) { - HashtagPostBody( - postViewModel, - accountViewModel, - nav, - ) - } - } -} - -@Composable -private fun HashtagPostBody( - postViewModel: CommentPostViewModel, - accountViewModel: AccountViewModel, - nav: Nav, -) { - val scrollState = rememberScrollState() - - Column(Modifier.fillMaxSize()) { - Row( - modifier = - Modifier - .fillMaxWidth() - .padding( - start = Size10dp, - end = Size10dp, - ).weight(1f), - ) { - Column(Modifier.fillMaxWidth().verticalScroll(scrollState)) { - postViewModel.externalIdentity?.let { - Row { - DisplayExternalId(it, nav) - Spacer(modifier = StdVertSpacer) - } - } - - postViewModel.replyingTo?.let { - Row { - NoteCompose( - baseNote = it, - modifier = MaterialTheme.colorScheme.replyModifier, - isQuotedNote = true, - unPackReply = false, - makeItShort = true, - quotesLeft = 1, - accountViewModel = accountViewModel, - nav = nav, - ) - Spacer(modifier = StdVertSpacer) - } - } - - Row { - Notifying(postViewModel.notifying?.toImmutableList(), accountViewModel) { - postViewModel.removeFromReplyList(it) - } - } - - Row( - modifier = Modifier.padding(vertical = Size10dp), - ) { - BaseUserPicture( - accountViewModel.userProfile(), - Size35dp, - accountViewModel = accountViewModel, - ) - MessageField( - R.string.what_s_on_your_mind, - postViewModel, - ) - } - - DisplayPreviews(postViewModel.urlPreviews, accountViewModel, nav) - - if (postViewModel.wantsToMarkAsSensitive) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - ContentSensitivityExplainer() - } - } - - if (postViewModel.wantsToAddGeoHash) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - LocationAsHash(postViewModel) - } - } - - if (postViewModel.wantsForwardZapTo) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp), - ) { - ForwardZapTo(postViewModel, accountViewModel) - } - } - - postViewModel.multiOrchestrator?.let { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - val context = LocalContext.current - ImageVideoDescription( - it, - accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) - if (server.type != ServerType.NIP95) { - accountViewModel.account.settings.changeDefaultFileServer(server) - } - }, - onDelete = postViewModel::deleteMediaToUpload, - onCancel = { postViewModel.multiOrchestrator = null }, - accountViewModel = accountViewModel, - ) - } - } - - if (postViewModel.wantsInvoice) { - postViewModel.lnAddress()?.let { lud16 -> - InvoiceRequest( - lud16, - accountViewModel.account.userProfile().pubkeyHex, - accountViewModel, - stringRes(id = R.string.lightning_invoice), - stringRes(id = R.string.lightning_create_and_add_invoice), - onSuccess = { - postViewModel.insertAtCursor(it) - postViewModel.wantsInvoice = false - }, - onError = { title, message -> accountViewModel.toastManager.toast(title, message) }, - ) - } - } - - if (postViewModel.wantsSecretEmoji) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - Column(Modifier.fillMaxWidth()) { - SecretEmojiRequest { - postViewModel.insertAtCursor(it) - postViewModel.wantsSecretEmoji = false - } - } - } - } - - if (postViewModel.wantsZapraiser && postViewModel.hasLnAddress()) { - Row( - verticalAlignment = CenterVertically, - modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), - ) { - ZapRaiserRequest( - stringRes(id = R.string.zapraiser), - postViewModel, - ) - } - } - } - } - - postViewModel.userSuggestions?.let { - ShowUserSuggestionList( - it, - postViewModel::autocompleteWithUser, - accountViewModel, - modifier = Modifier.heightIn(0.dp, 300.dp), - ) - } - - postViewModel.emojiSuggestions?.let { - ShowEmojiSuggestionList( - it, - postViewModel::autocompleteWithEmoji, - postViewModel::autocompleteWithEmojiUrl, - accountViewModel, - modifier = Modifier.heightIn(0.dp, 300.dp), - ) - } - - BottomRowActions(postViewModel) - } -} - -@Composable -private fun BottomRowActions(postViewModel: CommentPostViewModel) { - val scrollState = rememberScrollState() - Row( - modifier = - Modifier - .horizontalScroll(scrollState) - .fillMaxWidth() - .height(50.dp), - verticalAlignment = CenterVertically, - ) { - SelectFromGallery( - isUploading = postViewModel.isUploadingImage, - tint = MaterialTheme.colorScheme.onBackground, - modifier = Modifier, - ) { - postViewModel.selectImage(it) - } - - TakePictureButton( - onPictureTaken = { - postViewModel.selectImage(it) - }, - ) - - ForwardZapToButton(postViewModel.wantsForwardZapTo) { - postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo - } - - if (postViewModel.canAddZapRaiser) { - AddZapraiserButton(postViewModel.wantsZapraiser) { - postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser - } - } - - MarkAsSensitiveButton(postViewModel.wantsToMarkAsSensitive) { - postViewModel.toggleMarkAsSensitive() - } - - AddGeoHashButton(postViewModel.wantsToAddGeoHash) { - postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash - } - - AddSecretEmojiButton(postViewModel.wantsSecretEmoji) { - postViewModel.wantsSecretEmoji = !postViewModel.wantsSecretEmoji - } - - if (postViewModel.canAddInvoice && postViewModel.hasLnAddress()) { - AddLnInvoiceButton(postViewModel.wantsInvoice) { - postViewModel.wantsInvoice = !postViewModel.wantsInvoice - } - } - } + GenericCommentPostScreen(postViewModel, accountViewModel, nav) }