mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh
This commit is contained in:
+127
@@ -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
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+7
@@ -27,4 +27,11 @@ interface ILocationGrabber {
|
||||
fun locationManager(): LocationState
|
||||
|
||||
fun locationFlow(): StateFlow<LocationState.LocationResult>
|
||||
|
||||
/**
|
||||
* 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?
|
||||
}
|
||||
|
||||
+5
-1
@@ -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<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -246,6 +246,7 @@ class ChatNewMessageViewModel :
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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()
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+4
-1
@@ -193,6 +193,7 @@ open class ChannelNewMessageViewModel :
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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()
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+4
-1
@@ -205,6 +205,7 @@ class LongFormPostViewModel :
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -185,6 +185,7 @@ open class NewProductViewModel :
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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()
|
||||
|
||||
+9
-83
@@ -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
|
||||
|
||||
+1
-5
@@ -330,11 +330,7 @@ open class ShortNotePostViewModel :
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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<String?>(null)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var wantsExclusiveGeoPost by mutableStateOf(false)
|
||||
|
||||
// ZapRaiser
|
||||
|
||||
+4
-1
@@ -178,6 +178,7 @@ open class NestNewMessageViewModel :
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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()
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+4
-1
@@ -197,6 +197,7 @@ class NewPublicMessageViewModel :
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
override var pickedGeoHash by mutableStateOf<String?>(null)
|
||||
var location: StateFlow<LocationState.LocationResult>? = 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()
|
||||
|
||||
Reference in New Issue
Block a user