Add App Info Bottom Sheet (#406)

* Automated update of relay data - Sun Sep  7 06:20:11 UTC 2025

* Refactor(ui): Improve AboutSheet design and layout Ios Like

This commit refactors the `AboutSheet` composable with several UI enhancements:

-   Updated the overall layout and styling for a more modern look and feel.
-   Introduced a collapsing top bar effect on scroll.
-   Replaced `FeatureCard` with individual `Row` layouts for feature descriptions.
-   Adjusted typography, spacing, and padding for better readability.
-   Updated color usage to align with `MaterialTheme.colorScheme`.
-   Made the bottom sheet skip the partially expanded state by default.
-   Added `statusBarsPadding` to the `ModalBottomSheet`.

---------

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
ruslan_yet
2025-09-14 03:37:53 +02:00
committed by GitHub
co-authored by GitHub Action
parent 73c91b9509
commit eba0d36015
@@ -1,7 +1,11 @@
package com.bitchat.android.ui package com.bitchat.android.ui
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bluetooth import androidx.compose.material.icons.filled.Bluetooth
@@ -16,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.style.BaselineShift
@@ -51,110 +56,194 @@ fun AboutSheet(
// Bottom sheet state // Bottom sheet state
val sheetState = rememberModalBottomSheetState( val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = false skipPartiallyExpanded = true
) )
val lazyListState = rememberLazyListState()
val isScrolled by remember {
derivedStateOf {
lazyListState.firstVisibleItemIndex > 0 || lazyListState.firstVisibleItemScrollOffset > 0
}
}
val topBarAlpha by animateFloatAsState(
targetValue = if (isScrolled) 0.95f else 0f,
label = "topBarAlpha"
)
// Color scheme matching LocationChannelsSheet // Color scheme matching LocationChannelsSheet
val colorScheme = MaterialTheme.colorScheme val colorScheme = MaterialTheme.colorScheme
val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f
val standardBlue = Color(0xFF007AFF) // iOS blue
val standardGreen = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D) // iOS green
if (isPresented) { if (isPresented) {
ModalBottomSheet( ModalBottomSheet(
modifier = modifier.statusBarsPadding(),
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
sheetState = sheetState, sheetState = sheetState,
modifier = modifier containerColor = MaterialTheme.colorScheme.background,
dragHandle = null
) { ) {
LazyColumn( Box(modifier = Modifier.fillMaxWidth()) {
modifier = Modifier LazyColumn(
.fillMaxWidth() state = lazyListState,
.padding(horizontal = 16.dp) modifier = Modifier.fillMaxSize(),
.padding(bottom = 24.dp), contentPadding = PaddingValues(top = 80.dp, bottom = 20.dp)
verticalArrangement = Arrangement.spacedBy(16.dp) ) {
) { // Header Section
// Header item(key = "header") {
item { Column(
Column( modifier = Modifier
modifier = Modifier.fillMaxWidth(), .fillMaxWidth()
verticalArrangement = Arrangement.spacedBy(8.dp) .padding(horizontal = 24.dp)
) { .padding(bottom = 16.dp),
Row( verticalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.Bottom
) { ) {
Text( Row(
text = "bitchat", horizontalArrangement = Arrangement.spacedBy(8.dp),
fontSize = 18.sp, verticalAlignment = Alignment.Bottom
fontFamily = FontFamily.Monospace, ) {
fontWeight = FontWeight.Medium, Text(
color = colorScheme.onSurface text = "bitchat",
) style = TextStyle(
fontFamily = FontFamily.Monospace,
Text( fontWeight = FontWeight.Bold,
text = "v$versionName", fontSize = 32.sp
fontSize = 11.sp, ),
fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onBackground
color = colorScheme.onSurface.copy(alpha = 0.5f),
style = MaterialTheme.typography.bodySmall.copy(
baselineShift = BaselineShift(0.1f)
) )
Text(
text = "v$versionName",
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onBackground.copy(alpha = 0.5f),
style = MaterialTheme.typography.bodySmall.copy(
baselineShift = BaselineShift(0.1f)
)
)
}
Text(
text = "decentralized mesh messaging with end-to-end encryption",
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
) )
} }
Text(
text = "decentralized mesh messaging with end-to-end encryption",
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.7f)
)
} }
}
// Features section
item {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
FeatureCard(
icon = Icons.Filled.Bluetooth,
iconColor = standardBlue,
title = "offline mesh chat",
description = "communicate directly via bluetooth le without internet or servers. messages relay through nearby devices to extend range.",
modifier = Modifier.fillMaxWidth()
)
FeatureCard(
icon = Icons.Filled.Public,
iconColor = standardGreen,
title = "online geohash channels",
description = "connect with people in your area using geohash-based channels. extend the mesh using public internet relays.",
modifier = Modifier.fillMaxWidth()
)
FeatureCard(
icon = Icons.Filled.Lock,
iconColor = if (isDark) Color(0xFFFFD60A) else Color(0xFFF5A623),
title = "end-to-end encryption",
description = "private messages are encrypted. channel messages are public.",
modifier = Modifier.fillMaxWidth()
)
}
}
// Appearance section (theme toggle) // Features section
item { item(key = "feature_offline") {
val themePref by com.bitchat.android.ui.theme.ThemePreferenceManager.themeFlow.collectAsState() Row(
Column( verticalAlignment = Alignment.Top,
modifier = Modifier.fillMaxWidth(), modifier = Modifier
verticalArrangement = Arrangement.spacedBy(8.dp) .padding(horizontal = 24.dp)
) { .padding(vertical = 8.dp)
) {
Icon(
imageVector = Icons.Filled.Bluetooth,
contentDescription = "Offline Mesh Chat",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(top = 2.dp)
.size(20.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = "Offline Mesh Chat",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Communicate directly via Bluetooth LE without internet or servers. Messages relay through nearby devices to extend range.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
)
}
}
}
item(key = "feature_geohash") {
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier
.padding(horizontal = 24.dp)
.padding(vertical = 8.dp)
) {
Icon(
imageVector = Icons.Default.Public,
contentDescription = "Online Geohash Channels",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(top = 2.dp)
.size(20.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = "Online Geohash Channels",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Connect with people in your area using geohash-based channels. Extend the mesh using public internet relays.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
)
}
}
}
item(key = "feature_encryption") {
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier
.padding(horizontal = 24.dp)
.padding(vertical = 8.dp)
) {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = "End-to-End Encryption",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(top = 2.dp)
.size(20.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(
text = "End-to-End Encryption",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onBackground
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Private messages are encrypted. Channel messages are public.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
)
}
}
}
// Appearance Section
item(key = "appearance_section") {
Text( Text(
text = "appearance", text = "appearance",
fontSize = 12.sp, style = MaterialTheme.typography.labelLarge,
fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
fontWeight = FontWeight.Medium, modifier = Modifier
color = colorScheme.onSurface.copy(alpha = 0.8f) .padding(horizontal = 24.dp)
.padding(top = 24.dp, bottom = 8.dp)
) )
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { val themePref by com.bitchat.android.ui.theme.ThemePreferenceManager.themeFlow.collectAsState()
Row(
modifier = Modifier.padding(horizontal = 24.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FilterChip( FilterChip(
selected = themePref.isSystem, selected = themePref.isSystem,
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.System) }, onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.System) },
@@ -172,94 +261,181 @@ fun AboutSheet(
) )
} }
} }
} // Proof of Work Section
item(key = "pow_section") {
// Proof of Work section
item {
val context = LocalContext.current
// Initialize PoW preferences if not already done
LaunchedEffect(Unit) {
PoWPreferenceManager.init(context)
}
val powEnabled by PoWPreferenceManager.powEnabled.collectAsState()
val powDifficulty by PoWPreferenceManager.powDifficulty.collectAsState()
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text( Text(
text = "proof of work", text = "proof of work",
fontSize = 12.sp, style = MaterialTheme.typography.labelLarge,
fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
fontWeight = FontWeight.Medium, modifier = Modifier
color = colorScheme.onSurface.copy(alpha = 0.8f) .padding(horizontal = 24.dp)
.padding(top = 24.dp, bottom = 8.dp)
) )
LaunchedEffect(Unit) {
Row( PoWPreferenceManager.init(context)
horizontalArrangement = Arrangement.spacedBy(8.dp), }
verticalAlignment = Alignment.CenterVertically
val powEnabled by PoWPreferenceManager.powEnabled.collectAsState()
val powDifficulty by PoWPreferenceManager.powDifficulty.collectAsState()
Column(
modifier = Modifier.padding(horizontal = 24.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
FilterChip( Row(
selected = !powEnabled, horizontalArrangement = Arrangement.spacedBy(8.dp),
onClick = { PoWPreferenceManager.setPowEnabled(false) }, verticalAlignment = Alignment.CenterVertically
label = { Text("pow off", fontFamily = FontFamily.Monospace) } ) {
FilterChip(
selected = !powEnabled,
onClick = { PoWPreferenceManager.setPowEnabled(false) },
label = { Text("pow off", fontFamily = FontFamily.Monospace) }
)
FilterChip(
selected = powEnabled,
onClick = { PoWPreferenceManager.setPowEnabled(true) },
label = {
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text("pow on", fontFamily = FontFamily.Monospace)
// Show current difficulty
if (powEnabled) {
Surface(
color = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D),
shape = RoundedCornerShape(50)
) { Box(Modifier.size(8.dp)) }
}
}
}
)
}
Text(
text = "add proof of work to geohash messages for spam deterrence.",
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f)
) )
FilterChip(
selected = powEnabled, // Show difficulty slider when enabled
onClick = { PoWPreferenceManager.setPowEnabled(true) }, if (powEnabled) {
label = { Column(
Row( modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(6.dp), verticalArrangement = Arrangement.spacedBy(8.dp)
verticalAlignment = Alignment.CenterVertically ) {
Text(
text = "difficulty: $powDifficulty bits (~${NostrProofOfWork.estimateMiningTime(powDifficulty)})",
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
)
Slider(
value = powDifficulty.toFloat(),
onValueChange = { PoWPreferenceManager.setPowDifficulty(it.toInt()) },
valueRange = 0f..32f,
steps = 33,
colors = SliderDefaults.colors(
thumbColor = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D),
activeTrackColor = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D)
)
)
// Show difficulty description
Surface(
modifier = Modifier.fillMaxWidth(),
color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
shape = RoundedCornerShape(8.dp)
) { ) {
Text("pow on", fontFamily = FontFamily.Monospace) Column(
// Show current difficulty modifier = Modifier.padding(12.dp),
if (powEnabled) { verticalArrangement = Arrangement.spacedBy(4.dp)
Surface( ) {
color = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D), Text(
shape = RoundedCornerShape(50) text = "difficulty $powDifficulty requires ~${NostrProofOfWork.estimateWork(powDifficulty)} hash attempts",
) { Box(Modifier.size(8.dp)) } fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.7f)
)
Text(
text = when {
powDifficulty == 0 -> "no proof of work required"
powDifficulty <= 8 -> "very low - minimal spam protection"
powDifficulty <= 12 -> "low - basic spam protection"
powDifficulty <= 16 -> "medium - good spam protection"
powDifficulty <= 20 -> "high - strong spam protection"
powDifficulty <= 24 -> "very high - may cause delays"
else -> "extreme - significant computation required"
},
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f)
)
} }
} }
} }
) }
} }
}
// Network (Tor) section
item(key = "network_section") {
val torMode = remember { mutableStateOf(com.bitchat.android.net.TorPreferenceManager.get(context)) }
val torStatus by com.bitchat.android.net.TorManager.statusFlow.collectAsState()
Text( Text(
text = "add proof of work to geohash messages for spam deterrence.", text = "network",
fontSize = 10.sp, style = MaterialTheme.typography.labelLarge,
fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
color = colorScheme.onSurface.copy(alpha = 0.6f) modifier = Modifier
.padding(horizontal = 24.dp)
.padding(top = 24.dp, bottom = 8.dp)
) )
Column(modifier = Modifier.padding(horizontal = 24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
// Show difficulty slider when enabled Row(
if (powEnabled) { horizontalArrangement = Arrangement.spacedBy(8.dp),
Column( verticalAlignment = Alignment.CenterVertically
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Text( FilterChip(
text = "difficulty: $powDifficulty bits (~${NostrProofOfWork.estimateMiningTime(powDifficulty)})", selected = torMode.value == com.bitchat.android.net.TorMode.OFF,
fontSize = 11.sp, onClick = {
fontFamily = FontFamily.Monospace, torMode.value = com.bitchat.android.net.TorMode.OFF
color = colorScheme.onSurface.copy(alpha = 0.7f) com.bitchat.android.net.TorPreferenceManager.set(context, torMode.value)
},
label = { Text("tor off", fontFamily = FontFamily.Monospace) }
) )
FilterChip(
Slider( selected = torMode.value == com.bitchat.android.net.TorMode.ON,
value = powDifficulty.toFloat(), onClick = {
onValueChange = { PoWPreferenceManager.setPowDifficulty(it.toInt()) }, torMode.value = com.bitchat.android.net.TorMode.ON
valueRange = 0f..32f, com.bitchat.android.net.TorPreferenceManager.set(context, torMode.value)
steps = 33, // 33 discrete values (0-32) },
colors = SliderDefaults.colors( label = {
thumbColor = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D), Row(
activeTrackColor = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D) horizontalArrangement = Arrangement.spacedBy(6.dp),
) verticalAlignment = Alignment.CenterVertically
) {
Text("Tor On", fontFamily = FontFamily.Monospace)
val statusColor = when {
torStatus.running && torStatus.bootstrapPercent < 100 -> Color(0xFFFF9500)
torStatus.running && torStatus.bootstrapPercent >= 100 -> if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D)
else -> Color.Red
}
Surface(color = statusColor, shape = CircleShape) {
Box(Modifier.size(8.dp))
}
}
}
) )
}
// Show difficulty description Text(
text = "route internet over tor for enhanced privacy.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
if (torMode.value == com.bitchat.android.net.TorMode.ON) {
val statusText = if (torStatus.running) "Running" else "Stopped"
// Debug status (temporary)
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
color = colorScheme.surfaceVariant.copy(alpha = 0.25f), color = colorScheme.surfaceVariant.copy(alpha = 0.25f),
@@ -267,204 +443,124 @@ fun AboutSheet(
) { ) {
Column( Column(
modifier = Modifier.padding(12.dp), modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(4.dp) verticalArrangement = Arrangement.spacedBy(6.dp)
) { ) {
Text( Text(
text = "difficulty $powDifficulty requires ~${NostrProofOfWork.estimateWork(powDifficulty)} hash attempts", text = "tor Status: $statusText, bootstrap ${torStatus.bootstrapPercent}%",
fontSize = 10.sp, style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace, color = colorScheme.onSurface.copy(alpha = 0.75f)
color = colorScheme.onSurface.copy(alpha = 0.7f)
)
Text(
text = when {
powDifficulty == 0 -> "no proof of work required"
powDifficulty <= 8 -> "very low - minimal spam protection"
powDifficulty <= 12 -> "low - basic spam protection"
powDifficulty <= 16 -> "medium - good spam protection"
powDifficulty <= 20 -> "high - strong spam protection"
powDifficulty <= 24 -> "very high - may cause delays"
else -> "extreme - significant computation required"
},
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f)
) )
val lastLog = torStatus.lastLogLine
if (lastLog.isNotEmpty()) {
Text(
text = "Last: ${lastLog.take(160)}",
style = MaterialTheme.typography.labelSmall,
color = colorScheme.onSurface.copy(alpha = 0.6f)
)
}
} }
} }
} }
} }
} }
}
// Network (Tor) section // Emergency Warning Section
item { item(key = "warning_section") {
val ctx = LocalContext.current val colorScheme = MaterialTheme.colorScheme
val torMode = remember { mutableStateOf(com.bitchat.android.net.TorPreferenceManager.get(ctx)) } val errorColor = colorScheme.error
val torStatus by com.bitchat.android.net.TorManager.statusFlow.collectAsState()
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "network",
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
color = colorScheme.onSurface.copy(alpha = 0.8f)
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
FilterChip(
selected = torMode.value == com.bitchat.android.net.TorMode.OFF,
onClick = {
torMode.value = com.bitchat.android.net.TorMode.OFF
com.bitchat.android.net.TorPreferenceManager.set(ctx, torMode.value)
},
label = { Text("tor off", fontFamily = FontFamily.Monospace) }
)
FilterChip(
selected = torMode.value == com.bitchat.android.net.TorMode.ON,
onClick = {
torMode.value = com.bitchat.android.net.TorMode.ON
com.bitchat.android.net.TorPreferenceManager.set(ctx, torMode.value)
},
label = {
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text("tor on", fontFamily = FontFamily.Monospace)
// Status indicator (red/orange/green) moved inside the "tor on" button
val statusColor = when {
torStatus.running && torStatus.bootstrapPercent < 100 -> Color(0xFFFF9500)
torStatus.running && torStatus.bootstrapPercent >= 100 -> if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D)
else -> Color.Red
}
Surface(
color = statusColor,
shape = RoundedCornerShape(50)
) { Box(Modifier.size(8.dp)) }
}
}
)
}
Text(
text = "route internet over tor for enhanced privacy.",
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f)
)
// Debug status (temporary)
Surface( Surface(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
color = colorScheme.surfaceVariant.copy(alpha = 0.25f), .padding(horizontal = 24.dp, vertical = 24.dp)
shape = RoundedCornerShape(8.dp) .fillMaxWidth(),
color = errorColor.copy(alpha = 0.1f),
shape = RoundedCornerShape(12.dp)
) { ) {
Column( Row(
modifier = Modifier.padding(12.dp), modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(6.dp) horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.Top
) { ) {
Text( Icon(
text = "tor status: " + imageVector = Icons.Filled.Warning,
(if (torStatus.running) "running" else "stopped") + contentDescription = "Warning",
", bootstrap=" + torStatus.bootstrapPercent + "%", tint = errorColor,
fontSize = 11.sp, modifier = Modifier.size(16.dp)
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.75f)
) )
val last = torStatus.lastLogLine Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
if (last.isNotEmpty()) {
Text( Text(
text = "last: " + last.take(160), text = "Emergency Data Deletion",
fontSize = 10.sp, fontSize = 12.sp,
fontFamily = FontFamily.Monospace, fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f) fontWeight = FontWeight.Bold,
color = errorColor
)
Text(
text = "Tip: Triple-click the app title to emergency delete all stored data including messages, keys, and settings.",
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
) )
} }
} }
} }
} }
}
// Footer Section
// Emergency warning item(key = "footer") {
item { Column(
Surface( modifier = Modifier
modifier = Modifier.fillMaxWidth(), .padding(horizontal = 24.dp)
color = Color.Red.copy(alpha = 0.08f), .fillMaxWidth(),
shape = RoundedCornerShape(12.dp) horizontalAlignment = Alignment.CenterHorizontally,
) { verticalArrangement = Arrangement.spacedBy(8.dp)
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.Top
) { ) {
Icon( if (onShowDebug != null) {
imageVector = Icons.Filled.Warning, TextButton(
contentDescription = "Warning", onClick = onShowDebug,
tint = Color(0xFFBF1A1A), colors = ButtonDefaults.textButtonColors(
modifier = Modifier.size(16.dp) contentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
) )
) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Text(
Text( text = "Debug Settings",
text = "emergency data deletion", fontSize = 11.sp,
fontSize = 12.sp, fontFamily = FontFamily.Monospace
fontFamily = FontFamily.Monospace, )
fontWeight = FontWeight.Medium, }
color = Color(0xFFBF1A1A)
)
Text(
text = "tip: triple-click the app title to emergency delete all stored data including messages, keys, and settings.",
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.7f)
)
} }
}
}
}
// Debug settings button
item {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Debug button styled to match the app aesthetic
TextButton(
onClick = { onShowDebug?.invoke() },
colors = ButtonDefaults.textButtonColors(
contentColor = colorScheme.onSurface.copy(alpha = 0.6f)
)
) {
Text( Text(
text = "debug settings", text = "Open Source • Privacy First • Decentralized",
fontSize = 11.sp, fontSize = 11.sp,
fontFamily = FontFamily.Monospace, fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f) color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
) )
// Add extra space at bottom for gesture area
Spacer(modifier = Modifier.height(16.dp))
} }
} }
} }
// Version and footer space // TopBar
item { Box(
Column( modifier = Modifier
modifier = Modifier.fillMaxWidth(), .align(Alignment.TopCenter)
horizontalAlignment = Alignment.CenterHorizontally, .fillMaxWidth()
verticalArrangement = Arrangement.spacedBy(8.dp) .height(64.dp)
.background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha))
) {
TextButton(
onClick = onDismiss,
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(horizontal = 16.dp)
) { ) {
Text( Text(
text = "open source • privacy first • decentralized", text = "Close",
fontSize = 10.sp, style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold),
fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onBackground
color = colorScheme.onSurface.copy(alpha = 0.5f)
) )
// Add extra space at bottom for gesture area
Spacer(modifier = Modifier.height(16.dp))
} }
} }
} }
@@ -472,78 +568,6 @@ fun AboutSheet(
} }
} }
@Composable
private fun FeatureCard(
icon: ImageVector,
iconColor: Color,
title: String,
description: String,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.2f),
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.Top
) {
Icon(
imageVector = icon,
contentDescription = title,
tint = iconColor,
modifier = Modifier.size(20.dp)
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = title,
fontSize = 13.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = description,
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
lineHeight = 15.sp
)
}
}
}
}
@Composable
private fun FeatureItem(text: String) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.Top
) {
Text(
text = "",
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = text,
fontSize = 11.sp,
fontFamily = FontFamily.Monospace,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
modifier = Modifier.weight(1f)
)
}
}
/** /**
* Password prompt dialog for password-protected channels * Password prompt dialog for password-protected channels
* Kept as dialog since it requires user input * Kept as dialog since it requires user input