mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 16:33:27 +00:00
feat: collapse geohash composer identity into a tappable avatar
Replaces the always-on nickname field + "post as me" chip in the location-chat composer with a single leading identity avatar, next to the text field: - Shows the account's profile picture when posting as yourself, or an incognito avatar (person-off) when posting under the anonymous per-cell key. - Tap toggles anonymous ↔ me (routing through the existing "post as your real account?" warning when switching to your real identity). - Long-press opens a small dialog to set/edit the per-cell nickname, so the nickname field no longer has to sit in the chat all the time. The read-only "✈ Teleported" indicator moves to its own line above the composer, shown only when the app has determined you're not in the cell. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
This commit is contained in:
+121
-37
@@ -21,8 +21,10 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.geohashChat
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -33,9 +35,9 @@ 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.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
@@ -50,6 +52,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@@ -61,6 +64,8 @@ import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.geohashChat.GeohashChatChannel
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
@@ -227,55 +232,134 @@ private fun GeohashChatRoom(
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
GeohashComposerOptions(
|
||||
model = newMessageModel,
|
||||
onRequestPostAsSelf = { showPostAsSelfWarning = true },
|
||||
if (newMessageModel.geohashTeleported) {
|
||||
TeleportIndicatorRow()
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
GeohashIdentityAvatar(
|
||||
model = newMessageModel,
|
||||
accountViewModel = accountViewModel,
|
||||
onRequestPostAsSelf = { showPostAsSelfWarning = true },
|
||||
)
|
||||
Box(Modifier.weight(1f)) {
|
||||
EditFieldRow(
|
||||
channelScreenModel = newMessageModel,
|
||||
accountViewModel = accountViewModel,
|
||||
onSendNewMessage = feedViewModel.feedState::sendToTop,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The composer's identity control — the one affordance that replaces the always-on nickname field and
|
||||
* "post as me" chip. Shows the account's picture when posting as yourself, or an incognito avatar when
|
||||
* posting under the anonymous per-cell key. Tap flips between the two (routing through the "post as me"
|
||||
* warning when switching to your real identity); long-press opens the nickname editor.
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun GeohashIdentityAvatar(
|
||||
model: ChannelNewMessageViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
onRequestPostAsSelf: () -> Unit,
|
||||
) {
|
||||
var showNickname by remember { mutableStateOf(false) }
|
||||
if (showNickname) {
|
||||
GeohashNicknameDialog(model) { showNickname = false }
|
||||
}
|
||||
|
||||
val postAsSelf = model.geohashPostAsSelf
|
||||
val avatarModifier =
|
||||
Modifier
|
||||
.padding(start = 10.dp, end = 4.dp, bottom = 6.dp)
|
||||
.size(36.dp)
|
||||
.clip(CircleShape)
|
||||
.combinedClickable(
|
||||
onClick = { if (postAsSelf) model.geohashPostAsSelf = false else onRequestPostAsSelf() },
|
||||
onLongClick = { showNickname = true },
|
||||
)
|
||||
EditFieldRow(
|
||||
channelScreenModel = newMessageModel,
|
||||
accountViewModel = accountViewModel,
|
||||
onSendNewMessage = feedViewModel.feedState::sendToTop,
|
||||
nav = nav,
|
||||
|
||||
if (postAsSelf) {
|
||||
val picture by observeUserPicture(accountViewModel.userProfile(), accountViewModel)
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = accountViewModel.userProfile().pubkeyHex,
|
||||
model = picture,
|
||||
contentDescription = "Posting as yourself — tap to go anonymous, long-press for a nickname",
|
||||
modifier = avatarModifier,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||
autoPlayGif = false,
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
avatarModifier.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
SymbolIcon(
|
||||
symbol = MaterialSymbols.PersonOff,
|
||||
contentDescription = "Posting anonymously — tap to post as yourself, long-press for a nickname",
|
||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets/edits the Bitchat `n` nickname carried on this cell's messages — reached by long-pressing the avatar. */
|
||||
@Composable
|
||||
private fun GeohashComposerOptions(
|
||||
private fun GeohashNicknameDialog(
|
||||
model: ChannelNewMessageViewModel,
|
||||
onRequestPostAsSelf: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = model.geohashNickname,
|
||||
onValueChange = { model.geohashNickname = it },
|
||||
singleLine = true,
|
||||
label = { Text("Nickname (optional)") },
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
)
|
||||
Row(
|
||||
Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(horizontal = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
FilterChip(
|
||||
selected = model.geohashPostAsSelf,
|
||||
onClick = {
|
||||
if (model.geohashPostAsSelf) model.geohashPostAsSelf = false else onRequestPostAsSelf()
|
||||
},
|
||||
label = { Text("Post as me") },
|
||||
)
|
||||
// Read-only: the app sets teleport from your location (see GeohashChatRoom). Shown only when
|
||||
// you're not in the cell, so you know your messages carry the ✈ marker — it isn't a toggle.
|
||||
if (model.geohashTeleported) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
var text by remember { mutableStateOf(model.geohashNickname) }
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
model.geohashNickname = text.trim()
|
||||
onDismiss()
|
||||
}) { Text("Save") }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } },
|
||||
title = { Text("Nickname") },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
"✈ Teleported",
|
||||
"Shown next to your messages in this location channel. Leave blank to stay unnamed.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
singleLine = true,
|
||||
placeholder = { Text("e.g. river-otter") },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** A read-only line telling the user their messages carry the ✈ teleport marker (the app sets this). */
|
||||
@Composable
|
||||
private fun TeleportIndicatorRow() {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"✈ Teleported — you're not in this cell",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user