From 974de992f0ce226233a6110238632c92e74e77b0 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 27 Jul 2026 03:29:09 +0200 Subject: [PATCH] notes --- .../java/com/bitchat/android/ui/ChatHeader.kt | 96 ++++++------------- .../bitchat/android/ui/LocationNotesButton.kt | 10 +- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt index c6cb546f..9d831592 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt @@ -5,16 +5,9 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.* -import android.util.Log -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* @@ -39,10 +32,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.foundation.Canvas -import androidx.compose.ui.geometry.Offset import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bitchat.android.core.ui.component.button.BitChatBrandButton +import com.bitchat.android.net.ArtiTorManager +import com.bitchat.android.net.TorMode import com.bitchat.android.ui.theme.BitchatMotion import com.bitchat.android.ui.theme.LocalBitchatPalette @@ -103,53 +96,30 @@ private fun HeaderIconButton( } /** - * Connection status indicator. + * Smooth tint for header glyphs that should reflect Tor connection state. * - * Only visible while the connection is still coming up (or has failed). A steady green "all - * good" light is noise: the absence of the indicator already means everything is fine, and the - * eye stops registering an always-on dot anyway. It fades and scales in/out so appearing and - * disappearing reads as a state change rather than a glitch. + * When Tor is off or fully bootstrapped, [normal] is used. While connecting the tint goes + * orange; if Tor is enabled but not running it goes red. Cross-fades so Bootstrap percent + * flips do not flash the whole header. */ @Composable -fun TorStatusDot( - modifier: Modifier = Modifier -) { +internal fun torConnectionTint(normal: Color): Color { val palette = LocalBitchatPalette.current - val torProvider = remember { com.bitchat.android.net.ArtiTorManager.getInstance() } - val torStatus by torProvider.statusFlow.collectAsState() + val torStatus by remember { ArtiTorManager.getInstance() }.statusFlow.collectAsState() - val isEnabled = torStatus.mode != com.bitchat.android.net.TorMode.OFF - val isEstablished = torStatus.running && torStatus.bootstrapPercent >= 100 - val isVisible = isEnabled && !isEstablished - - AnimatedVisibility( - visible = isVisible, - enter = fadeIn(tween(BitchatMotion.STANDARD_MS)) + - scaleIn( - initialScale = 0.4f, - animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing) - ), - exit = fadeOut(tween(BitchatMotion.QUICK_MS)) + - scaleOut( - targetScale = 0.4f, - animationSpec = tween(BitchatMotion.QUICK_MS, easing = FastOutSlowInEasing) - ), - ) { - // Bootstrapping vs. failed. Cross-faded, because Tor flips between these frequently and - // a hard colour cut in the corner of the screen reads as a rendering fault. - val dotColor by animateColorAsState( - targetValue = if (torStatus.running) palette.accentOrange else palette.accentRed, - animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing), - label = "torDotColor" - ) - Canvas(modifier = modifier) { - drawCircle( - color = dotColor, - radius = size.minDimension / 2, - center = Offset(size.width / 2, size.height / 2) - ) - } + val target = when { + torStatus.mode == TorMode.OFF -> normal + torStatus.running && torStatus.bootstrapPercent >= 100 -> normal + torStatus.running -> palette.accentOrange + else -> palette.accentRed } + + val animated by animateColorAsState( + targetValue = target, + animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing), + label = "torConnectionTint" + ) + return animated } @Composable @@ -477,24 +447,14 @@ private fun MainHeader( // MARK: - Status cluster. // - // Order, left to right: connection state, unread DMs, notes, channel, people. - // The connection indicator leads because it is the only item that can invalidate - // everything to its right, and because it appears and disappears on its own — a fixed - // leftmost slot means the rest of the cluster never shifts when it does. + // Order, left to right: unread DMs, notes, channel, people. + // Tor health is read from the location channel / notes icon colour rather than a + // dedicated status dot. Row( verticalAlignment = Alignment.CenterVertically, // Tight, because every child below is its own >=44.dp tap target. horizontalArrangement = Arrangement.spacedBy(2.dp) ) { - // Connection status. The Box holds the slot open at a constant width so the icons to - // its right stay put whether or not the dot is currently showing. - Box( - modifier = Modifier.size(HeaderIconSize), - contentAlignment = Alignment.Center - ) { - TorStatusDot(modifier = Modifier.size(8.dp)) - } - // Unread private messages badge (click to open most recent DM) if (hasUnreadPrivateMessages.isNotEmpty()) { HeaderIconButton( @@ -569,7 +529,9 @@ private fun LocationChannelsButton( // is, so it is plain "mesh". else -> stringResource(R.string.mesh_label) } - val badgeColor = if (isLocation) palette.accentGreen else palette.accentBlue + val channelColor = if (isLocation) palette.accentGreen else palette.accentBlue + // Icon alone carries Tor health so the geohash / "mesh" label stays channel-coloured. + val iconColor = torConnectionTint(normal = channelColor) val badgeIcon = if (isLocation) Icons.Outlined.Public else Icons.Filled.Bluetooth Row( @@ -585,9 +547,9 @@ private fun LocationChannelsButton( ) { Icon( imageVector = badgeIcon, - contentDescription = null, + contentDescription = stringResource(R.string.cd_tor_status), modifier = Modifier.size(HeaderIconSize), - tint = badgeColor + tint = iconColor ) Text( @@ -595,7 +557,7 @@ private fun LocationChannelsButton( style = MaterialTheme.typography.bodyMedium, fontSize = HeaderTextSize, fontWeight = FontWeight.Medium, - color = badgeColor, + color = channelColor, maxLines = 1 ) } diff --git a/app/src/main/java/com/bitchat/android/ui/LocationNotesButton.kt b/app/src/main/java/com/bitchat/android/ui/LocationNotesButton.kt index 185a053e..4819deef 100644 --- a/app/src/main/java/com/bitchat/android/ui/LocationNotesButton.kt +++ b/app/src/main/java/com/bitchat/android/ui/LocationNotesButton.kt @@ -26,8 +26,9 @@ import com.bitchat.android.ui.theme.LocalBitchatPalette /** * Location Notes button component for MainHeader - * Shows in mesh mode when location permission granted AND services enabled - * Icon turns primary color when notes exist, gray otherwise + * Shows in mesh mode when location permission granted AND services enabled. + * Base tint is primary when notes exist, secondary otherwise; Tor health may + * override that color (orange connecting, red failed) via [torConnectionTint]. */ @Composable fun LocationNotesButton( @@ -36,6 +37,7 @@ fun LocationNotesButton( modifier: Modifier = Modifier ) { val colorScheme = MaterialTheme.colorScheme + val palette = LocalBitchatPalette.current val context = LocalContext.current // Get channel and permission state @@ -57,6 +59,8 @@ fun LocationNotesButton( if (selectedLocationChannel is ChannelID.Mesh && locationEnabled) { val hasNotes = notesCount > 0 val contentDescription = stringResource(R.string.cd_location_notes) + val normalTint = if (hasNotes) colorScheme.primary else palette.textSecondary + val tint = torConnectionTint(normal = normalTint) // Match other header icon buttons: 44.dp target, no Material IconButton min-size padding // that pushed the notes glyph farther from the mesh badge than sibling gaps. Box( @@ -70,7 +74,7 @@ fun LocationNotesButton( imageVector = Icons.Outlined.Description, contentDescription = contentDescription, modifier = Modifier.size(HeaderIconSize), - tint = if (hasNotes) colorScheme.primary else LocalBitchatPalette.current.textSecondary + tint = tint ) } }