diff --git a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt index 3caccf322..b0e740dbb 100644 --- a/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/AboutSheet.kt @@ -1,7 +1,11 @@ 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.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons 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.vector.ImageVector 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.FontWeight import androidx.compose.ui.text.style.BaselineShift @@ -51,110 +56,194 @@ fun AboutSheet( // Bottom sheet state 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 val colorScheme = MaterialTheme.colorScheme 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) { ModalBottomSheet( + modifier = modifier.statusBarsPadding(), onDismissRequest = onDismiss, sheetState = sheetState, - modifier = modifier + containerColor = MaterialTheme.colorScheme.background, + dragHandle = null ) { - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = 24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - // Header - item { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.Bottom + Box(modifier = Modifier.fillMaxWidth()) { + LazyColumn( + state = lazyListState, + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(top = 80.dp, bottom = 20.dp) + ) { + // Header Section + item(key = "header") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(bottom = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.Bottom + ) { + Text( + text = "bitchat", + style = TextStyle( + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + fontSize = 32.sp + ), + color = MaterialTheme.colorScheme.onBackground + ) + + 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 = "bitchat", - fontSize = 18.sp, + text = "decentralized mesh messaging with end-to-end encryption", + fontSize = 12.sp, fontFamily = FontFamily.Monospace, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f) ) - - Text( - text = "v$versionName", - fontSize = 11.sp, - fontFamily = FontFamily.Monospace, - color = colorScheme.onSurface.copy(alpha = 0.5f), - style = MaterialTheme.typography.bodySmall.copy( - baselineShift = BaselineShift(0.1f) + } + } + + // Features section + item(key = "feature_offline") { + Row( + verticalAlignment = Alignment.Top, + modifier = Modifier + .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) + ) + } } - - 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() - ) + 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 (theme toggle) - item { - val themePref by com.bitchat.android.ui.theme.ThemePreferenceManager.themeFlow.collectAsState() - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { + // Appearance Section + item(key = "appearance_section") { Text( text = "appearance", - fontSize = 12.sp, - fontFamily = FontFamily.Monospace, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface.copy(alpha = 0.8f) + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f), + modifier = Modifier + .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( selected = themePref.isSystem, 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 { - 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) - ) { + // Proof of Work Section + item(key = "pow_section") { Text( text = "proof of work", - fontSize = 12.sp, - fontFamily = FontFamily.Monospace, - fontWeight = FontWeight.Medium, - color = colorScheme.onSurface.copy(alpha = 0.8f) + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f), + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(top = 24.dp, bottom = 8.dp) ) - - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically + LaunchedEffect(Unit) { + PoWPreferenceManager.init(context) + } + + 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( - selected = !powEnabled, - onClick = { PoWPreferenceManager.setPowEnabled(false) }, - label = { Text("pow off", fontFamily = FontFamily.Monospace) } + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + 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, - onClick = { PoWPreferenceManager.setPowEnabled(true) }, - label = { - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically + + // Show difficulty slider when enabled + if (powEnabled) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + 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) - // Show current difficulty - if (powEnabled) { - Surface( - color = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D), - shape = RoundedCornerShape(50) - ) { Box(Modifier.size(8.dp)) } + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = "difficulty $powDifficulty requires ~${NostrProofOfWork.estimateWork(powDifficulty)} hash attempts", + 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 = "add proof of work to geohash messages for spam deterrence.", - fontSize = 10.sp, - fontFamily = FontFamily.Monospace, - color = colorScheme.onSurface.copy(alpha = 0.6f) + text = "network", + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f), + modifier = Modifier + .padding(horizontal = 24.dp) + .padding(top = 24.dp, bottom = 8.dp) ) - - // Show difficulty slider when enabled - if (powEnabled) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp) + Column(modifier = Modifier.padding(horizontal = 24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically ) { - Text( - text = "difficulty: $powDifficulty bits (~${NostrProofOfWork.estimateMiningTime(powDifficulty)})", - fontSize = 11.sp, - fontFamily = FontFamily.Monospace, - color = colorScheme.onSurface.copy(alpha = 0.7f) + 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(context, torMode.value) + }, + label = { Text("tor off", fontFamily = FontFamily.Monospace) } ) - - Slider( - value = powDifficulty.toFloat(), - onValueChange = { PoWPreferenceManager.setPowDifficulty(it.toInt()) }, - valueRange = 0f..32f, - steps = 33, // 33 discrete values (0-32) - colors = SliderDefaults.colors( - thumbColor = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D), - activeTrackColor = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D) - ) + 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(context, torMode.value) + }, + label = { + Row( + 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( modifier = Modifier.fillMaxWidth(), color = colorScheme.surfaceVariant.copy(alpha = 0.25f), @@ -267,204 +443,124 @@ fun AboutSheet( ) { Column( modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(4.dp) + verticalArrangement = Arrangement.spacedBy(6.dp) ) { Text( - text = "difficulty $powDifficulty requires ~${NostrProofOfWork.estimateWork(powDifficulty)} hash attempts", - 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) + text = "tor Status: $statusText, bootstrap ${torStatus.bootstrapPercent}%", + style = MaterialTheme.typography.bodySmall, + color = colorScheme.onSurface.copy(alpha = 0.75f) ) + 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 - item { - val ctx = LocalContext.current - val torMode = remember { mutableStateOf(com.bitchat.android.net.TorPreferenceManager.get(ctx)) } - 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) - ) + // Emergency Warning Section + item(key = "warning_section") { + val colorScheme = MaterialTheme.colorScheme + val errorColor = colorScheme.error - // Debug status (temporary) Surface( - modifier = Modifier.fillMaxWidth(), - color = colorScheme.surfaceVariant.copy(alpha = 0.25f), - shape = RoundedCornerShape(8.dp) + modifier = Modifier + .padding(horizontal = 24.dp, vertical = 24.dp) + .fillMaxWidth(), + color = errorColor.copy(alpha = 0.1f), + shape = RoundedCornerShape(12.dp) ) { - Column( - modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.Top ) { - Text( - text = "tor status: " + - (if (torStatus.running) "running" else "stopped") + - ", bootstrap=" + torStatus.bootstrapPercent + "%", - fontSize = 11.sp, - fontFamily = FontFamily.Monospace, - color = colorScheme.onSurface.copy(alpha = 0.75f) + Icon( + imageVector = Icons.Filled.Warning, + contentDescription = "Warning", + tint = errorColor, + modifier = Modifier.size(16.dp) ) - val last = torStatus.lastLogLine - if (last.isNotEmpty()) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Text( - text = "last: " + last.take(160), - fontSize = 10.sp, + text = "Emergency Data Deletion", + fontSize = 12.sp, 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) ) } } } } - } - - // Emergency warning - item { - Surface( - modifier = Modifier.fillMaxWidth(), - color = Color.Red.copy(alpha = 0.08f), - shape = RoundedCornerShape(12.dp) - ) { - Row( - modifier = Modifier.padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.Top + + // Footer Section + item(key = "footer") { + Column( + modifier = Modifier + .padding(horizontal = 24.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Icon( - imageVector = Icons.Filled.Warning, - contentDescription = "Warning", - tint = Color(0xFFBF1A1A), - modifier = Modifier.size(16.dp) - ) - - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - Text( - text = "emergency data deletion", - fontSize = 12.sp, - 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) - ) + if (onShowDebug != null) { + TextButton( + onClick = onShowDebug, + colors = ButtonDefaults.textButtonColors( + contentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + ) + ) { + Text( + text = "Debug Settings", + fontSize = 11.sp, + fontFamily = FontFamily.Monospace + ) + } } - } - } - } - - // 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 = "debug settings", + text = "Open Source • Privacy First • Decentralized", fontSize = 11.sp, 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 - item { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) + // TopBar + Box( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .height(64.dp) + .background(MaterialTheme.colorScheme.background.copy(alpha = topBarAlpha)) + ) { + TextButton( + onClick = onDismiss, + modifier = Modifier + .align(Alignment.CenterEnd) + .padding(horizontal = 16.dp) ) { Text( - text = "open source • privacy first • decentralized", - fontSize = 10.sp, - fontFamily = FontFamily.Monospace, - color = colorScheme.onSurface.copy(alpha = 0.5f) + text = "Close", + style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold), + color = MaterialTheme.colorScheme.onBackground ) - - // 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 * Kept as dialog since it requires user input