mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 22:45:20 +00:00
Move PrivateChatSheet hosting into ChatDialogs and unify sheet state (#586)
* Automated update of relay data - Sun Sep 21 06:21:05 UTC 2025 * Automated update of relay data - Sun Sep 28 06:20:40 UTC 2025 * refactor: new close button like ios(but not liquid glass) * Automated update of relay data - Sun Oct 5 06:20:09 UTC 2025 * Automated update of relay data - Sun Oct 12 06:20:12 UTC 2025 * Automated update of relay data - Sun Oct 19 06:21:51 UTC 2025 * Automated update of relay data - Sun Oct 26 06:21:31 UTC 2025 * Automated update of relay data - Sun Nov 2 06:22:16 UTC 2025 * Automated update of relay data - Sun Nov 9 06:21:43 UTC 2025 * Automated update of relay data - Sun Nov 16 06:22:37 UTC 2025 * Automated update of relay data - Sun Nov 23 06:22:51 UTC 2025 * Automated update of relay data - Sun Nov 30 06:24:08 UTC 2025 * Automated update of relay data - Sun Dec 7 06:22:59 UTC 2025 * Automated update of relay data - Sun Dec 14 06:24:33 UTC 2025 * Automated update of relay data - Sun Dec 21 06:24:49 UTC 2025 * Automated update of relay data - Sun Dec 28 06:25:38 UTC 2025 * Automated update of relay data - Sun Jan 4 06:26:28 UTC 2026 * Automated update of relay data - Sun Jan 11 06:26:19 UTC 2026 * feat: Show private chat in sheet from notification * Refactor: Hoist private chat sheet state to ChatViewModel * remove icon * remove old bottom sheet --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
co-authored by
GitHub Action
callebtc
parent
829fb52a04
commit
94b28b2107
@@ -774,8 +774,9 @@ class MainActivity : OrientationAwareActivity() {
|
||||
if (peerID != null) {
|
||||
Log.d("MainActivity", "Opening private chat with $senderNickname (peerID: $peerID) from notification")
|
||||
|
||||
// Open the private chat with this peer
|
||||
chatViewModel.startPrivateChat(peerID)
|
||||
// Open the private chat sheet with this peer
|
||||
chatViewModel.showMeshPeerList()
|
||||
chatViewModel.showPrivateChatSheet(peerID)
|
||||
|
||||
// Clear notifications for this sender since user is now viewing the chat
|
||||
chatViewModel.clearNotificationsForSender(peerID)
|
||||
|
||||
@@ -37,22 +37,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Reactive helper to compute favorite state from fingerprint mapping
|
||||
* This eliminates the need for static isFavorite parameters and makes
|
||||
* the UI reactive to fingerprint manager changes
|
||||
*/
|
||||
@Composable
|
||||
fun isFavoriteReactive(
|
||||
peerID: String,
|
||||
peerFingerprints: Map<String, String>,
|
||||
favoritePeers: Set<String>
|
||||
): Boolean {
|
||||
return remember(peerID, peerFingerprints, favoritePeers) {
|
||||
val fingerprint = peerFingerprints[peerID]
|
||||
fingerprint != null && favoritePeers.contains(fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun TorStatusDot(
|
||||
@@ -246,39 +231,6 @@ fun ChatHeaderContent(
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
when {
|
||||
selectedPrivatePeer != null -> {
|
||||
// Private chat header - Fully reactive state tracking
|
||||
val favoritePeers by viewModel.favoritePeers.collectAsStateWithLifecycle()
|
||||
val peerFingerprints by viewModel.peerFingerprints.collectAsStateWithLifecycle()
|
||||
val peerSessionStates by viewModel.peerSessionStates.collectAsStateWithLifecycle()
|
||||
val peerNicknames by viewModel.peerNicknames.collectAsStateWithLifecycle()
|
||||
|
||||
// Reactive favorite computation - no more static lookups!
|
||||
val isFavorite = isFavoriteReactive(
|
||||
peerID = selectedPrivatePeer,
|
||||
peerFingerprints = peerFingerprints,
|
||||
favoritePeers = favoritePeers
|
||||
)
|
||||
val sessionState = peerSessionStates[selectedPrivatePeer]
|
||||
|
||||
Log.d("ChatHeader", "Header recomposing: peer=$selectedPrivatePeer, isFav=$isFavorite, sessionState=$sessionState")
|
||||
|
||||
// Pass geohash context and people for NIP-17 chat title formatting
|
||||
val selectedLocationChannel by viewModel.selectedLocationChannel.collectAsStateWithLifecycle()
|
||||
val geohashPeople by viewModel.geohashPeople.collectAsStateWithLifecycle()
|
||||
|
||||
PrivateChatHeader(
|
||||
peerID = selectedPrivatePeer,
|
||||
peerNicknames = peerNicknames,
|
||||
isFavorite = isFavorite,
|
||||
sessionState = sessionState,
|
||||
selectedLocationChannel = selectedLocationChannel,
|
||||
geohashPeople = geohashPeople,
|
||||
onBackClick = onBackClick,
|
||||
onToggleFavorite = { viewModel.toggleFavorite(selectedPrivatePeer) },
|
||||
viewModel = viewModel
|
||||
)
|
||||
}
|
||||
currentChannel != null -> {
|
||||
// Channel header
|
||||
ChannelHeader(
|
||||
@@ -304,168 +256,7 @@ fun ChatHeaderContent(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrivateChatHeader(
|
||||
peerID: String,
|
||||
peerNicknames: Map<String, String>,
|
||||
isFavorite: Boolean,
|
||||
sessionState: String?,
|
||||
selectedLocationChannel: com.bitchat.android.geohash.ChannelID?,
|
||||
geohashPeople: List<GeoPerson>,
|
||||
onBackClick: () -> Unit,
|
||||
onToggleFavorite: () -> Unit,
|
||||
viewModel: ChatViewModel
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val isNostrDM = peerID.startsWith("nostr_") || peerID.startsWith("nostr:")
|
||||
val verifiedFingerprints by viewModel.verifiedFingerprints.collectAsStateWithLifecycle()
|
||||
val isVerified = remember(peerID, verifiedFingerprints) {
|
||||
viewModel.isPeerVerified(peerID, verifiedFingerprints)
|
||||
}
|
||||
// Determine mutual favorite state for this peer (supports mesh ephemeral 16-hex via favorites lookup)
|
||||
val isMutualFavorite = remember(peerID, peerNicknames) {
|
||||
try {
|
||||
if (isNostrDM) return@remember false
|
||||
if (peerID.length == 64 && peerID.matches(Regex("^[0-9a-fA-F]+$"))) {
|
||||
val noiseKeyBytes = peerID.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(noiseKeyBytes)?.isMutual == true
|
||||
} else if (peerID.length == 16 && peerID.matches(Regex("^[0-9a-fA-F]+$"))) {
|
||||
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(peerID)?.isMutual == true
|
||||
} else false
|
||||
} catch (_: Exception) { false }
|
||||
}
|
||||
|
||||
// Compute title text: for NIP-17 chats show "#geohash/@username" (iOS parity)
|
||||
val titleText: String = if (isNostrDM) {
|
||||
// For geohash DMs, get the actual source geohash and proper display name
|
||||
val (conversationGeohash, baseName) = try {
|
||||
val repoField = com.bitchat.android.ui.GeohashViewModel::class.java.getDeclaredField("repo")
|
||||
repoField.isAccessible = true
|
||||
val repo = repoField.get(viewModel.geohashViewModel) as com.bitchat.android.nostr.GeohashRepository
|
||||
val gh = repo.getConversationGeohash(peerID) ?: "geohash"
|
||||
val fullPubkey = com.bitchat.android.nostr.GeohashAliasRegistry.get(peerID) ?: ""
|
||||
val displayName = if (fullPubkey.isNotEmpty()) {
|
||||
repo.displayNameForGeohashConversation(fullPubkey, gh)
|
||||
} else {
|
||||
peerNicknames[peerID] ?: "unknown"
|
||||
}
|
||||
Pair(gh, displayName)
|
||||
} catch (e: Exception) {
|
||||
Pair("geohash", peerNicknames[peerID] ?: "unknown")
|
||||
}
|
||||
|
||||
"#$conversationGeohash/@$baseName"
|
||||
} else {
|
||||
// Prefer live mesh nickname; fallback to favorites nickname (supports 16-hex), finally short key
|
||||
peerNicknames[peerID] ?: run {
|
||||
val titleFromFavorites = try {
|
||||
if (peerID.length == 64 && peerID.matches(Regex("^[0-9a-fA-F]+$"))) {
|
||||
val noiseKeyBytes = peerID.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(noiseKeyBytes)?.peerNickname
|
||||
} else if (peerID.length == 16 && peerID.matches(Regex("^[0-9a-fA-F]+$"))) {
|
||||
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(peerID)?.peerNickname
|
||||
} else null
|
||||
} catch (_: Exception) { null }
|
||||
titleFromFavorites ?: peerID.take(12)
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
// Back button - positioned all the way to the left with minimal margin
|
||||
Button(
|
||||
onClick = onBackClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = colorScheme.primary
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 4.dp, vertical = 4.dp), // Reduced horizontal padding
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.offset(x = (-8).dp) // Move even further left to minimize margin
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.chat_back),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Title - perfectly centered regardless of other elements
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = titleText,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFFFF9500) // Orange
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
// Show a globe when chatting via Nostr alias, or when mesh session not established but mutual favorite exists
|
||||
val showGlobe = isNostrDM || (sessionState != "established" && isMutualFavorite)
|
||||
val securityModifier = if (!isNostrDM) {
|
||||
Modifier.clickable { viewModel.showSecurityVerificationSheet() }
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
if (showGlobe) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Public,
|
||||
contentDescription = stringResource(R.string.cd_nostr_reachable),
|
||||
modifier = Modifier.size(14.dp).then(securityModifier),
|
||||
tint = Color(0xFF9B59B6) // Purple like iOS
|
||||
)
|
||||
} else {
|
||||
NoiseSessionIcon(
|
||||
sessionState = sessionState,
|
||||
modifier = Modifier.size(14.dp).then(securityModifier)
|
||||
)
|
||||
}
|
||||
|
||||
if (isVerified) {
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Verified,
|
||||
contentDescription = stringResource(R.string.verify_title),
|
||||
modifier = Modifier.size(14.dp).then(securityModifier),
|
||||
tint = Color(0xFF32D74B)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Favorite button - positioned on the right
|
||||
IconButton(
|
||||
onClick = {
|
||||
Log.d("ChatHeader", "Header toggle favorite: peerID=$peerID, currentFavorite=$isFavorite")
|
||||
onToggleFavorite()
|
||||
},
|
||||
modifier = Modifier.align(Alignment.CenterEnd)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isFavorite) Icons.Filled.Star else Icons.Outlined.Star,
|
||||
contentDescription = if (isFavorite) stringResource(R.string.cd_remove_favorite) else stringResource(R.string.cd_add_favorite),
|
||||
modifier = Modifier.size(18.dp), // Slightly larger than sidebar icon
|
||||
tint = if (isFavorite) Color(0xFFFFD700) else Color(0x87878700) // Yellow or grey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelHeader(
|
||||
|
||||
@@ -56,6 +56,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
val mentionSuggestions by viewModel.mentionSuggestions.collectAsStateWithLifecycle()
|
||||
val showAppInfo by viewModel.showAppInfo.collectAsStateWithLifecycle()
|
||||
val showMeshPeerListSheet by viewModel.showMeshPeerList.collectAsStateWithLifecycle()
|
||||
val privateChatSheetPeer by viewModel.privateChatSheetPeer.collectAsStateWithLifecycle()
|
||||
val showVerificationSheet by viewModel.showVerificationSheet.collectAsStateWithLifecycle()
|
||||
val showSecurityVerificationSheet by viewModel.showSecurityVerificationSheet.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -86,8 +87,8 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
val selectedLocationChannel by viewModel.selectedLocationChannel.collectAsStateWithLifecycle()
|
||||
|
||||
// Determine what messages to show based on current context (unified timelines)
|
||||
// Legacy private chat timeline removed - private chats now exclusively use PrivateChatSheet
|
||||
val displayMessages = when {
|
||||
selectedPrivatePeer != null -> privateChats[selectedPrivatePeer] ?: emptyList()
|
||||
currentChannel != null -> channelMessages[currentChannel] ?: emptyList()
|
||||
else -> {
|
||||
val locationChannel = selectedLocationChannel
|
||||
@@ -102,7 +103,6 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
|
||||
// Determine whether to show media buttons (only hide in geohash location chats)
|
||||
val showMediaButtons = when {
|
||||
selectedPrivatePeer != null -> true
|
||||
currentChannel != null -> true
|
||||
else -> selectedLocationChannel !is com.bitchat.android.geohash.ChannelID.Location
|
||||
}
|
||||
@@ -232,7 +232,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
selection = TextRange(mentionText.length)
|
||||
)
|
||||
},
|
||||
selectedPrivatePeer = selectedPrivatePeer,
|
||||
selectedPrivatePeer = null,
|
||||
currentChannel = currentChannel,
|
||||
nickname = nickname,
|
||||
colorScheme = colorScheme,
|
||||
@@ -243,7 +243,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
// Floating header - positioned absolutely at top, ignores keyboard
|
||||
ChatFloatingHeader(
|
||||
headerHeight = headerHeight,
|
||||
selectedPrivatePeer = selectedPrivatePeer,
|
||||
selectedPrivatePeer = null,
|
||||
currentChannel = currentChannel,
|
||||
nickname = nickname,
|
||||
viewModel = viewModel,
|
||||
@@ -490,6 +490,8 @@ private fun ChatDialogs(
|
||||
showMeshPeerListSheet: Boolean,
|
||||
onMeshPeerListDismiss: () -> Unit,
|
||||
) {
|
||||
val privateChatSheetPeer by viewModel.privateChatSheetPeer.collectAsStateWithLifecycle()
|
||||
|
||||
// Password dialog
|
||||
PasswordPromptDialog(
|
||||
show = showPasswordDialog,
|
||||
@@ -570,4 +572,16 @@ private fun ChatDialogs(
|
||||
viewModel = viewModel
|
||||
)
|
||||
}
|
||||
|
||||
if (privateChatSheetPeer != null) {
|
||||
PrivateChatSheet(
|
||||
isPresented = true,
|
||||
peerID = privateChatSheetPeer!!,
|
||||
viewModel = viewModel,
|
||||
onDismiss = {
|
||||
viewModel.hidePrivateChatSheet()
|
||||
viewModel.endPrivateChat()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,9 @@ class ChatState(
|
||||
private val _showMeshPeerList = MutableStateFlow(false)
|
||||
val showMeshPeerList: StateFlow<Boolean> = _showMeshPeerList.asStateFlow()
|
||||
|
||||
private val _privateChatSheetPeer = MutableStateFlow<String?>(null)
|
||||
val privateChatSheetPeer: StateFlow<String?> = _privateChatSheetPeer.asStateFlow()
|
||||
|
||||
private val _showVerificationSheet = MutableStateFlow(false)
|
||||
val showVerificationSheet: StateFlow<Boolean> = _showVerificationSheet.asStateFlow()
|
||||
|
||||
@@ -188,6 +191,7 @@ class ChatState(
|
||||
fun getGeohashPeopleValue() = _geohashPeople.value
|
||||
|
||||
fun getShowMeshPeerListValue() = _showMeshPeerList.value
|
||||
fun getPrivateChatSheetPeerValue() = _privateChatSheetPeer.value
|
||||
|
||||
fun getTeleportedGeoValue() = _teleportedGeo.value
|
||||
fun getGeohashParticipantCountsValue() = _geohashParticipantCounts.value
|
||||
@@ -337,4 +341,8 @@ class ChatState(
|
||||
fun setShowMeshPeerList(show: Boolean) {
|
||||
_showMeshPeerList.value = show
|
||||
}
|
||||
|
||||
fun setPrivateChatSheetPeer(peerID: String?) {
|
||||
_privateChatSheetPeer.value = peerID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@ class ChatViewModel(
|
||||
val peerDirect: StateFlow<Map<String, Boolean>> = state.peerDirect
|
||||
val showAppInfo: StateFlow<Boolean> = state.showAppInfo
|
||||
val showMeshPeerList: StateFlow<Boolean> = state.showMeshPeerList
|
||||
val privateChatSheetPeer: StateFlow<String?> = state.privateChatSheetPeer
|
||||
val showVerificationSheet: StateFlow<Boolean> = state.showVerificationSheet
|
||||
val showSecurityVerificationSheet: StateFlow<Boolean> = state.showSecurityVerificationSheet
|
||||
val selectedLocationChannel: StateFlow<com.bitchat.android.geohash.ChannelID?> = state.selectedLocationChannel
|
||||
@@ -403,6 +404,8 @@ class ChatViewModel(
|
||||
setCurrentPrivateChatPeer(null)
|
||||
// Clear mesh mention notifications since user is now back in mesh chat
|
||||
clearMeshMentionNotifications()
|
||||
// Ensure sheet is hidden
|
||||
hidePrivateChatSheet()
|
||||
}
|
||||
|
||||
// MARK: - Open Latest Unread Private Chat
|
||||
@@ -451,7 +454,7 @@ class ChatViewModel(
|
||||
canonical ?: targetKey
|
||||
}
|
||||
|
||||
startPrivateChat(openPeer)
|
||||
showPrivateChatSheet(openPeer)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "openLatestUnreadPrivateChat failed: ${e.message}")
|
||||
}
|
||||
@@ -796,6 +799,18 @@ class ChatViewModel(
|
||||
|
||||
fun hideMeshPeerList() {
|
||||
state.setShowMeshPeerList(false)
|
||||
if (state.getPrivateChatSheetPeerValue() != null) {
|
||||
endPrivateChat()
|
||||
}
|
||||
hidePrivateChatSheet()
|
||||
}
|
||||
|
||||
fun showPrivateChatSheet(peerID: String) {
|
||||
state.setPrivateChatSheetPeer(peerID)
|
||||
}
|
||||
|
||||
fun hidePrivateChatSheet() {
|
||||
state.setPrivateChatSheetPeer(null)
|
||||
}
|
||||
|
||||
fun getPeerFingerprintForDisplay(peerID: String): String? {
|
||||
@@ -1006,7 +1021,7 @@ class ChatViewModel(
|
||||
*/
|
||||
fun startGeohashDM(pubkeyHex: String) {
|
||||
geohashViewModel.startGeohashDM(pubkeyHex) { convKey ->
|
||||
startPrivateChat(convKey)
|
||||
showPrivateChatSheet(convKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1049,7 +1064,7 @@ class ChatViewModel(
|
||||
true
|
||||
}
|
||||
// Exit private chat
|
||||
state.getSelectedPrivateChatPeerValue() != null -> {
|
||||
state.getSelectedPrivateChatPeerValue() != null || state.getPrivateChatSheetPeerValue() != null -> {
|
||||
endPrivateChat()
|
||||
true
|
||||
}
|
||||
|
||||
@@ -57,10 +57,6 @@ fun MeshPeerListSheet(
|
||||
val peerRSSI by viewModel.peerRSSI.collectAsStateWithLifecycle()
|
||||
val selectedLocationChannel by viewModel.selectedLocationChannel.collectAsStateWithLifecycle()
|
||||
|
||||
// Track nested private chat sheet state
|
||||
var showPrivateChatSheet by remember { mutableStateOf(false) }
|
||||
var privateChatPeerID by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
// Bottom sheet state
|
||||
val sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
@@ -124,9 +120,8 @@ fun MeshPeerListSheet(
|
||||
val peerName = channel.removePrefix("@")
|
||||
val peerID =
|
||||
peerNicknames.entries.firstOrNull { it.value == peerName }?.key
|
||||
if (peerID != null) {
|
||||
privateChatPeerID = peerID
|
||||
showPrivateChatSheet = true
|
||||
if (peerID != null) {
|
||||
viewModel.showPrivateChatSheet(peerID)
|
||||
}
|
||||
} else {
|
||||
// Regular channel switch
|
||||
@@ -164,9 +159,7 @@ fun MeshPeerListSheet(
|
||||
selectedPrivatePeer = selectedPrivatePeer,
|
||||
viewModel = viewModel,
|
||||
onPrivateChatStart = { peerID ->
|
||||
viewModel.startPrivateChat(peerID)
|
||||
privateChatPeerID = peerID
|
||||
showPrivateChatSheet = true
|
||||
viewModel.showPrivateChatSheet(peerID)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -223,19 +216,6 @@ fun MeshPeerListSheet(
|
||||
}
|
||||
}
|
||||
|
||||
// Nested Private Chat Sheet (iOS-style)
|
||||
if (showPrivateChatSheet && privateChatPeerID != null) {
|
||||
PrivateChatSheet(
|
||||
isPresented = showPrivateChatSheet,
|
||||
peerID = privateChatPeerID!!,
|
||||
viewModel = viewModel,
|
||||
onDismiss = {
|
||||
showPrivateChatSheet = false
|
||||
privateChatPeerID = null
|
||||
viewModel.endPrivateChat()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,16 +275,6 @@ private fun ChannelRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Selection indicator
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.cd_selected),
|
||||
tint = Color(0xFF32D74B), // iOS green
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Leave channel button
|
||||
CloseButton(
|
||||
onClick = onLeaveChannel,
|
||||
@@ -715,16 +685,6 @@ private fun PeerItem(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Selection indicator
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(R.string.cd_selected),
|
||||
tint = Color(0xFF32D74B), // iOS green
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Favorite star with proper filled/outlined states
|
||||
IconButton(
|
||||
onClick = onToggleFavorite,
|
||||
@@ -802,7 +762,7 @@ private fun convertRSSIToSignalStrength(rssi: Int?): Int {
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PrivateChatSheet(
|
||||
fun PrivateChatSheet(
|
||||
isPresented: Boolean,
|
||||
peerID: String,
|
||||
viewModel: ChatViewModel,
|
||||
|
||||
Reference in New Issue
Block a user