mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-29 07:16:08 +00:00
notes
This commit is contained in:
parent
02a737fad6
commit
974de992f0
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user