mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
feat(composer): add "pick on map" location to the short-note composer
The composer could only attach the current GPS location (fixed ~5km), which fails when GPS is denied/unavailable and can't tag a post with a different place. Add a "pick a place on the map" action that opens the shared GeohashLocationPickerDialog and stores the result in ShortNotePostViewModel.pickedGeoHash, which overrides the GPS fix at build time and round-trips through drafts. Picking a place also skips the GPS permission prompt. The existing "use my location" GPS flow is unchanged; this is additive. Scoped to the short-note composer for now; the other ILocationGrabber composers (long-form, classifieds, DMs, comments, channel/nest messages) can adopt the same pickedGeoHash override + section in a follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh
This commit is contained in:
+83
-13
@@ -37,6 +37,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -106,6 +107,8 @@ 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.messagefield.MessageField
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.notify.Notifying
|
||||
@@ -527,19 +530,7 @@ private fun NewPostScreenBody(
|
||||
}
|
||||
|
||||
if (postViewModel.wantsToAddGeoHash) {
|
||||
Row(
|
||||
verticalAlignment = CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp),
|
||||
) {
|
||||
LocationAsHash(postViewModel) {
|
||||
SettingsRow(
|
||||
R.string.geohash_exclusive,
|
||||
R.string.geohash_exclusive_explainer,
|
||||
) {
|
||||
Switch(postViewModel.wantsExclusiveGeoPost, onCheckedChange = { postViewModel.wantsExclusiveGeoPost = it })
|
||||
}
|
||||
}
|
||||
}
|
||||
GeoHashPostSection(postViewModel)
|
||||
}
|
||||
|
||||
if (postViewModel.wantsForwardZapTo) {
|
||||
@@ -881,6 +872,85 @@ 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
|
||||
|
||||
+17
-1
@@ -329,6 +329,12 @@ open class ShortNotePostViewModel :
|
||||
// GeoHash
|
||||
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)
|
||||
var wantsExclusiveGeoPost by mutableStateOf(false)
|
||||
|
||||
// ZapRaiser
|
||||
@@ -753,6 +759,7 @@ open class ShortNotePostViewModel :
|
||||
|
||||
val geohash = draftEvent.getGeoHash()
|
||||
wantsToAddGeoHash = geohash != null
|
||||
pickedGeoHash = geohash
|
||||
if (geohash != null) {
|
||||
wantsExclusiveGeoPost = draftEvent.kind == CommentEvent.KIND
|
||||
}
|
||||
@@ -858,6 +865,7 @@ open class ShortNotePostViewModel :
|
||||
|
||||
val geohash = draftEvent.getGeoHash()
|
||||
wantsToAddGeoHash = geohash != null
|
||||
pickedGeoHash = geohash
|
||||
if (geohash != null) {
|
||||
wantsExclusiveGeoPost = draftEvent.kind == CommentEvent.KIND
|
||||
}
|
||||
@@ -931,6 +939,7 @@ open class ShortNotePostViewModel :
|
||||
|
||||
val geohash = draftEvent.getGeoHash()
|
||||
wantsToAddGeoHash = geohash != null
|
||||
pickedGeoHash = geohash
|
||||
if (geohash != null) {
|
||||
wantsExclusiveGeoPost = draftEvent.kind == CommentEvent.KIND
|
||||
}
|
||||
@@ -1236,7 +1245,13 @@ open class ShortNotePostViewModel :
|
||||
|
||||
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) {
|
||||
// A map-picked geohash wins over the live GPS fix.
|
||||
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)
|
||||
@@ -1563,6 +1578,7 @@ open class ShortNotePostViewModel :
|
||||
wantsToMarkAsSensitive = false
|
||||
contentWarningDescription = ""
|
||||
wantsToAddGeoHash = false
|
||||
pickedGeoHash = null
|
||||
wantsExclusiveGeoPost = false
|
||||
wantsSecretEmoji = false
|
||||
wantsAnonymousPost = false
|
||||
|
||||
@@ -2149,6 +2149,8 @@
|
||||
<string name="geohash_explainer">Adds a Geohash of your location to the post. The public will know you are within 5km (3mi) of the current location</string>
|
||||
<string name="geohash_teleport_title">Teleport</string>
|
||||
<string name="geohash_teleport_action">✈ Teleport here</string>
|
||||
<string name="location_pick_on_map">Pick a place on the map</string>
|
||||
<string name="location_change_place">Change place on the map</string>
|
||||
|
||||
<string name="geohash_exclusive">Location-exclusive Post</string>
|
||||
<string name="geohash_exclusive_explainer">Only followers of the location will see it. Your general followers won\'t see it.</string>
|
||||
|
||||
Reference in New Issue
Block a user