From fea9bcb8ea5239f5441171146eff5b78fc237441 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 23:29:46 +0000 Subject: [PATCH] feat(composer): extend map location picker to all composers Lift pickedGeoHash into the shared ILocationGrabber interface and add a shared GeoHashPostSection composable (GPS default + "pick on map"), then wire it into every remaining composer: long-form, classifieds, private DMs, public messages, NIP-22 comments, channel messages, and nest messages. Each ViewModel now honors the map-picked geohash over GPS at build time and round-trips it through drafts; each screen renders the shared section in place of the GPS-only LocationAsHash. The short-note composer is refactored onto the same shared section (its local copy removed). The geohash-chat "New location channel" screen already reaches the picker via its Teleport card, which now uses the shared, polished picker. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh --- .../creators/location/GeoHashPostSection.kt | 127 ++++++++++++++++++ .../creators/location/ILocationGrabber.kt | 7 + .../nip22Comments/CommentPostViewModel.kt | 6 +- .../nip22Comments/GenericCommentPostScreen.kt | 4 +- .../privateDM/send/ChatNewMessageViewModel.kt | 5 +- .../chats/privateDM/send/NewGroupDMScreen.kt | 4 +- .../send/ChannelNewMessageViewModel.kt | 5 +- .../nip23LongForm/LongFormPostScreen.kt | 4 +- .../nip23LongForm/LongFormPostViewModel.kt | 5 +- .../nip99Classifieds/NewProductScreen.kt | 4 +- .../nip99Classifieds/NewProductViewModel.kt | 5 +- .../loggedIn/home/ShortNotePostScreen.kt | 92 ++----------- .../loggedIn/home/ShortNotePostViewModel.kt | 6 +- .../room/chat/NestNewMessageViewModel.kt | 5 +- .../publicMessages/NewPublicMessageScreen.kt | 4 +- .../NewPublicMessageViewModel.kt | 5 +- 16 files changed, 183 insertions(+), 105 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/GeoHashPostSection.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/GeoHashPostSection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/GeoHashPostSection.kt new file mode 100644 index 0000000000..5196bf7284 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/GeoHashPostSection.kt @@ -0,0 +1,127 @@ +/* + * 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.creators.location + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +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.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.icons.symbols.Icon +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size10dp + +/** + * The shared location section for post composers. Defaults to the device-GPS flow + * ([LocationAsHash]), and adds a "pick a place on the map" action that opens + * [GeohashLocationPickerDialog] and stores the chosen geohash in + * [ILocationGrabber.pickedGeoHash] — which each composer's build step then prefers + * over the live GPS fix. Picking a place also skips the GPS permission prompt. + * + * [innerContent] is a slot for composer-specific extras rendered beneath the + * location readout (e.g. the "location-exclusive post" switch). + */ +@Composable +fun GeoHashPostSection( + model: ILocationGrabber, + innerContent: @Composable () -> Unit = {}, +) { + var showPicker by remember { mutableStateOf(false) } + val picked = model.pickedGeoHash + + Column( + modifier = Modifier.fillMaxWidth().padding(vertical = Size10dp, horizontal = Size10dp), + ) { + if (picked != null) { + // A map-picked place: show it, and let the user clear back to GPS. + Row(verticalAlignment = CenterVertically, modifier = Modifier.fillMaxWidth()) { + Icon( + symbol = MaterialSymbols.LocationOn, + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringRes(R.string.geohash_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.W500, + modifier = Modifier.padding(start = 10.dp), + ) + DisplayLocationInTitle(geohash = picked) + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = { model.pickedGeoHash = null }) { + Icon( + symbol = MaterialSymbols.Close, + contentDescription = stringRes(R.string.remove_location), + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + HorizontalDivider() + innerContent() + } else { + // GPS mode (unchanged): current device location + the composer's extras. + LocationAsHash(model, innerContent) + } + + TextButton(onClick = { showPicker = true }, modifier = Modifier.padding(top = 4.dp)) { + Icon( + symbol = MaterialSymbols.LocationOn, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = stringRes(if (picked != null) R.string.location_change_place else R.string.location_pick_on_map), + modifier = Modifier.padding(start = 6.dp), + ) + } + } + + if (showPicker) { + GeohashLocationPickerDialog( + initialGeohash = picked, + onDismiss = { showPicker = false }, + onConfirm = { cell -> + model.pickedGeoHash = cell + showPicker = false + }, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/ILocationGrabber.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/ILocationGrabber.kt index 105ca1c223..6940f786ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/ILocationGrabber.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/location/ILocationGrabber.kt @@ -27,4 +27,11 @@ interface ILocationGrabber { fun locationManager(): LocationState fun locationFlow(): StateFlow + + /** + * A geohash the user picked on the map (via [GeohashLocationPickerDialog]), which + * overrides the live GPS location at build time. Null means "use my current GPS + * location" — the default behavior. Implementers back this with a Compose state. + */ + var pickedGeoHash: String? } 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 79bc61906c..d52c964d8c 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 @@ -82,6 +82,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash +import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash import com.vitorpamplona.quartz.nip01Core.tags.geohash.hasGeohashes import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.people.PTag @@ -242,6 +243,7 @@ open class CommentPostViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null // ZapRaiser @@ -510,6 +512,7 @@ open class CommentPostViewModel : replyingTo?.let { observeCommunityRules(it) } wantsToAddGeoHash = draftEvent.hasGeohashes() + pickedGeoHash = draftEvent.getGeoHash() notifying = draftEvent.rootAuthorKeys().mapNotNull { LocalCache.checkGetOrCreateUser(it) } + draftEvent.replyAuthorKeys().mapNotNull { LocalCache.checkGetOrCreateUser(it) } @@ -641,7 +644,7 @@ open class CommentPostViewModel : ) tagger.run() - val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() + val geoHash = (if (wantsToAddGeoHash) pickedGeoHash else null) ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() val emojis = account.emoji.findEmojiTags(tagger.message) val urls = findURLs(tagger.message) @@ -867,6 +870,7 @@ open class CommentPostViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null wantsSecretEmoji = false wantsAnonymousPost = false anonymousSignerCache = null 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 e67b48fc9e..12d9ea78d8 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 @@ -72,7 +72,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePick 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.location.GeoHashPostSection import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField import com.vitorpamplona.amethyst.ui.note.creators.notify.Notifying import com.vitorpamplona.amethyst.ui.note.creators.pow.PowOverrideButton @@ -330,7 +330,7 @@ private fun GenericCommentPostBody( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp), ) { - LocationAsHash(postViewModel) + GeoHashPostSection(postViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt index 2d92030b7b..b100b957b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt @@ -246,6 +246,7 @@ class ChatNewMessageViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null // ZapRaiser @@ -370,6 +371,7 @@ class ChatNewMessageViewModel : val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null + pickedGeoHash = geohash val zapraiser = draftEvent.zapraiserAmount() wantsZapraiser = zapraiser != null @@ -579,7 +581,7 @@ class ChatNewMessageViewModel : val urls = findURLs(messageText) val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet()) val emojis = accountViewModel.account.emoji.findEmojiTags(messageText) - val geoHash = if (wantsToAddGeoHash) (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() else null + val geoHash = if (wantsToAddGeoHash) (pickedGeoHash ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()) else null val message = messageText val contentWarningReason = if (wantsToMarkAsSensitive) contentWarningDescription else null @@ -649,6 +651,7 @@ class ChatNewMessageViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null wantsSecretEmoji = false forwardZapTo.value = SplitBuilder() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index 8941be093f..d87f54987d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -100,7 +100,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePick import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton import com.vitorpamplona.amethyst.ui.note.creators.invoice.NewPostInvoiceRequest 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.location.GeoHashPostSection import com.vitorpamplona.amethyst.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField import com.vitorpamplona.amethyst.ui.note.creators.previews.PreviewUrl @@ -250,7 +250,7 @@ fun GroupDMScreenContent( } if (postViewModel.wantsToAddGeoHash) { - LocationAsHash(postViewModel) + GeoHashPostSection(postViewModel) } if (postViewModel.wantsForwardZapTo) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt index 17a13aee09..4fd7a5b155 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt @@ -193,6 +193,7 @@ open class ChannelNewMessageViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null // Geohash location chat (Bitchat interop): messages are signed with an anonymous per-cell @@ -299,6 +300,7 @@ open class ChannelNewMessageViewModel : val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null + pickedGeoHash = geohash val zapraiser = draftEvent.zapraiserAmount() wantsZapraiser = zapraiser != null @@ -507,7 +509,7 @@ open class ChannelNewMessageViewModel : val emojis = accountViewModel.account.emoji.findEmojiTags(messageText) val channelRelays = channel.relays() - val geoHash = if (wantsToAddGeoHash) (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() else null + val geoHash = if (wantsToAddGeoHash) (pickedGeoHash ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()) else null val contentWarningReason = if (wantsToMarkAsSensitive) contentWarningDescription else null val localExpirationDate = if (wantsExpirationDate) expirationDate else null @@ -731,6 +733,7 @@ open class ChannelNewMessageViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null forwardZapTo = SplitBuilder() forwardZapToEditting.clearText() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index 93c8a6b0ae..2465929d40 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -106,7 +106,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePick 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.location.GeoHashPostSection 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 @@ -442,7 +442,7 @@ private fun MarkdownPostScreenBody( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp), ) { - LocationAsHash(postViewModel) { + GeoHashPostSection(postViewModel) { SettingsRow( R.string.geohash_exclusive, R.string.geohash_exclusive_explainer, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt index 47686de456..7dd5c3c036 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt @@ -205,6 +205,7 @@ class LongFormPostViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null var wantsExclusiveGeoPost by mutableStateOf(false) @@ -328,6 +329,7 @@ class LongFormPostViewModel : val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null + pickedGeoHash = geohash if (geohash != null) { wantsExclusiveGeoPost = draftEvent.kind == CommentEvent.KIND } @@ -411,7 +413,7 @@ class LongFormPostViewModel : val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null - val geoHash = if (wantsToAddGeoHash) (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() else null + val geoHash = if (wantsToAddGeoHash) (pickedGeoHash ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()) else null val localZapRaiserAmount = if (wantsZapRaiser) zapRaiserAmount.value else null val emojis = account.emoji.findEmojiTags(tagger.message) @@ -631,6 +633,7 @@ class LongFormPostViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null wantsExclusiveGeoPost = false wantsSecretEmoji = false 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 9f32382d97..2e95142599 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 @@ -65,7 +65,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePick 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.location.GeoHashPostSection 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 @@ -250,7 +250,7 @@ private fun NewProductBody( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp), ) { - LocationAsHash(postViewModel) + GeoHashPostSection(postViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt index 6e7554d7dd..fce9567e51 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt @@ -185,6 +185,7 @@ open class NewProductViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null // ZapRaiser @@ -284,6 +285,7 @@ open class NewProductViewModel : val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null + pickedGeoHash = geohash val zapraiser = draftEvent.zapraiserAmount() wantsZapraiser = zapraiser != null @@ -353,7 +355,7 @@ open class NewProductViewModel : val urls = findURLs(tagger.message) val usedAttachments = iMetaDescription.filterIsIn(urls.toSet()) + productImages.map { it.toIMeta() } - val geoHash = if (wantsToAddGeoHash) (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() else null + val geoHash = if (wantsToAddGeoHash) (pickedGeoHash ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()) else null val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null @@ -484,6 +486,7 @@ open class NewProductViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null wantsSecretEmoji = false forwardZapTo.value = SplitBuilder() 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 9e4ebe9d63..db7e95f3be 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 @@ -107,9 +107,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePick 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.DisplayLocationInTitle -import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPickerDialog -import com.vitorpamplona.amethyst.ui.note.creators.location.LocationAsHash +import com.vitorpamplona.amethyst.ui.note.creators.location.GeoHashPostSection import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField import com.vitorpamplona.amethyst.ui.note.creators.notify.Notifying import com.vitorpamplona.amethyst.ui.note.creators.polls.PollOptionsField @@ -530,7 +528,14 @@ private fun NewPostScreenBody( } if (postViewModel.wantsToAddGeoHash) { - GeoHashPostSection(postViewModel) + GeoHashPostSection(postViewModel) { + SettingsRow( + R.string.geohash_exclusive, + R.string.geohash_exclusive_explainer, + ) { + Switch(postViewModel.wantsExclusiveGeoPost, onCheckedChange = { postViewModel.wantsExclusiveGeoPost = it }) + } + } } if (postViewModel.wantsForwardZapTo) { @@ -872,85 +877,6 @@ private fun BottomRowActions( } } -/** - * The composer's location section. Defaults to the device GPS flow ([LocationAsHash]), - * but a "pick on map" action opens the shared [GeohashLocationPickerDialog] and stores - * the chosen geohash in [ShortNotePostViewModel.pickedGeoHash], which then overrides GPS - * at build time. Picking a place also skips the GPS permission prompt. - */ -@Composable -private fun GeoHashPostSection(postViewModel: ShortNotePostViewModel) { - var showPicker by remember { mutableStateOf(false) } - val picked = postViewModel.pickedGeoHash - - Column( - modifier = Modifier.fillMaxWidth().padding(vertical = Size10dp, horizontal = Size10dp), - ) { - if (picked != null) { - // A map-picked place: show it, and let the user clear back to GPS. - Row(verticalAlignment = CenterVertically, modifier = Modifier.fillMaxWidth()) { - Icon( - symbol = MaterialSymbols.LocationOn, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringRes(R.string.geohash_title), - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.W500, - modifier = Modifier.padding(start = 10.dp), - ) - DisplayLocationInTitle(geohash = picked) - Spacer(modifier = Modifier.weight(1f)) - IconButton(onClick = { postViewModel.pickedGeoHash = null }) { - Icon( - symbol = MaterialSymbols.Close, - contentDescription = stringRes(R.string.remove_location), - modifier = Modifier.size(20.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - HorizontalDivider() - SettingsRow(R.string.geohash_exclusive, R.string.geohash_exclusive_explainer) { - Switch(postViewModel.wantsExclusiveGeoPost, onCheckedChange = { postViewModel.wantsExclusiveGeoPost = it }) - } - } else { - // GPS mode (unchanged): current device location + the exclusive-post switch. - LocationAsHash(postViewModel) { - SettingsRow(R.string.geohash_exclusive, R.string.geohash_exclusive_explainer) { - Switch(postViewModel.wantsExclusiveGeoPost, onCheckedChange = { postViewModel.wantsExclusiveGeoPost = it }) - } - } - } - - TextButton(onClick = { showPicker = true }, modifier = Modifier.padding(top = 4.dp)) { - Icon( - symbol = MaterialSymbols.LocationOn, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringRes(if (picked != null) R.string.location_change_place else R.string.location_pick_on_map), - modifier = Modifier.padding(start = 6.dp), - ) - } - } - - if (showPicker) { - GeohashLocationPickerDialog( - initialGeohash = picked, - onDismiss = { showPicker = false }, - onConfirm = { cell -> - postViewModel.pickedGeoHash = cell - showPicker = false - }, - ) - } -} - @SuppressLint("ViewModelConstructorInComposable") @Preview @Composable 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 43e64bbae2..e2d1fd6afb 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 @@ -330,11 +330,7 @@ open class ShortNotePostViewModel : var wantsToAddGeoHash by mutableStateOf(false) var location: StateFlow? = null - /** - * A geohash the user picked on the map, which overrides the live GPS location at - * build time. Null means "use my current GPS location" (the default behavior). - */ - var pickedGeoHash by mutableStateOf(null) + override var pickedGeoHash by mutableStateOf(null) var wantsExclusiveGeoPost by mutableStateOf(false) // ZapRaiser diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt index 63a55b94c4..e7e86b5f3a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt @@ -178,6 +178,7 @@ open class NestNewMessageViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null // ZapRaiser @@ -272,6 +273,7 @@ open class NestNewMessageViewModel : val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null + pickedGeoHash = geohash val zapraiser = draftEvent.zapraiserAmount() wantsZapraiser = zapraiser != null @@ -427,7 +429,7 @@ open class NestNewMessageViewModel : val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet()) val emojis = accountViewModel.account.emoji.findEmojiTags(messageText) - val geoHash = if (wantsToAddGeoHash) (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() else null + val geoHash = if (wantsToAddGeoHash) (pickedGeoHash ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()) else null val contentWarningReason = if (wantsToMarkAsSensitive) contentWarningDescription else null val localExpirationDate = if (wantsExpirationDate) expirationDate else null @@ -482,6 +484,7 @@ open class NestNewMessageViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null forwardZapTo = SplitBuilder() forwardZapToEditting.clearText() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index f10bd8db4e..56e1382b6a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -76,7 +76,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePick import com.vitorpamplona.amethyst.ui.note.creators.invoice.AddLnInvoiceButton import com.vitorpamplona.amethyst.ui.note.creators.invoice.NewPostInvoiceRequest 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.location.GeoHashPostSection 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 @@ -233,7 +233,7 @@ fun PublicMessageScreenContent( } if (postViewModel.wantsToAddGeoHash) { - LocationAsHash(postViewModel) + GeoHashPostSection(postViewModel) } if (postViewModel.wantsForwardZapTo) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt index 68ffbf6006..4007134010 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt @@ -197,6 +197,7 @@ class NewPublicMessageViewModel : // GeoHash var wantsToAddGeoHash by mutableStateOf(false) + override var pickedGeoHash by mutableStateOf(null) var location: StateFlow? = null // ZapRaiser @@ -300,6 +301,7 @@ class NewPublicMessageViewModel : val geohash = draftEvent.getGeoHash() wantsToAddGeoHash = geohash != null + pickedGeoHash = geohash val zapraiser = draftEvent.zapraiserAmount() wantsZapraiser = zapraiser != null @@ -388,7 +390,7 @@ class NewPublicMessageViewModel : val zapReceiver = if (wantsForwardZapTo) forwardZapTo.value.toZapSplitSetup() else null - val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() + val geoHash = (if (wantsToAddGeoHash) pickedGeoHash else null) ?: (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString() val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount.value else null val emojis = account.emoji.findEmojiTags(tagger.message) @@ -528,6 +530,7 @@ class NewPublicMessageViewModel : wantsToMarkAsSensitive = false contentWarningDescription = "" wantsToAddGeoHash = false + pickedGeoHash = null wantsSecretEmoji = false forwardZapTo.value = SplitBuilder()