New about sheet (#294)

* about sheet

* version

* about page
This commit is contained in:
callebtc
2025-08-23 12:04:21 +02:00
committed by GitHub
parent e92266c025
commit 26520991cf
3 changed files with 372 additions and 130 deletions
@@ -0,0 +1,368 @@
package com.bitchat.android.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Public
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* About Sheet for bitchat app information
* Matches the design language of LocationChannelsSheet
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AboutSheet(
isPresented: Boolean,
onDismiss: () -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
// Get version name from package info
val versionName = remember {
try {
context.packageManager.getPackageInfo(context.packageName, 0).versionName
} catch (e: Exception) {
"1.0.0" // fallback version
}
}
// Bottom sheet state
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = false
)
// 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(
onDismissRequest = onDismiss,
sheetState = sheetState,
modifier = modifier
) {
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
) {
Text(
text = "bitchat",
fontSize = 18.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
color = colorScheme.onSurface
)
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)
)
)
}
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 = "geohash channels",
description = "internet-based location channels using coarse geohash coordinates. connect with people in your area while preserving privacy.",
modifier = Modifier.fillMaxWidth()
)
FeatureCard(
icon = Icons.Filled.Lock,
iconColor = if (isDark) Color(0xFFFFD60A) else Color(0xFFF5A623),
title = "end-to-end encryption",
description = "all direct messages use noise protocol encryption. channel messages are protected with optional passwords.",
modifier = Modifier.fillMaxWidth()
)
}
}
// Additional features
item {
Surface(
modifier = Modifier.fillMaxWidth(),
color = colorScheme.surfaceVariant.copy(alpha = 0.3f),
shape = RoundedCornerShape(12.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "additional features",
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
color = colorScheme.onSurface.copy(alpha = 0.8f)
)
FeatureItem("store-and-forward messaging for offline peers")
FeatureItem("ephemeral messaging with automatic cleanup")
FeatureItem("peer discovery and identity verification")
FeatureItem("minimal metadata leakage")
}
}
}
// 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
) {
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)
)
}
}
}
}
// Version and footer space
item {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "open source • privacy first • decentralized",
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.5f)
)
// Add extra space at bottom for gesture area
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}
}
}
@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
*/
@Composable
fun PasswordPromptDialog(
show: Boolean,
channelName: String?,
passwordInput: String,
onPasswordChange: (String) -> Unit,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
if (show && channelName != null) {
val colorScheme = MaterialTheme.colorScheme
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = "Enter Channel Password",
style = MaterialTheme.typography.titleMedium,
color = colorScheme.onSurface
)
},
text = {
Column {
Text(
text = "Channel $channelName is password protected. Enter the password to join.",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = passwordInput,
onValueChange = onPasswordChange,
label = { Text("Password", style = MaterialTheme.typography.bodyMedium) },
textStyle = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace
),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = colorScheme.primary,
unfocusedBorderColor = colorScheme.outline
)
)
}
},
confirmButton = {
TextButton(onClick = onConfirm) {
Text(
text = "Join",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.primary
)
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(
text = "Cancel",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.onSurface
)
}
},
containerColor = colorScheme.surface,
tonalElevation = 8.dp
)
}
}
@@ -22,7 +22,7 @@ import androidx.compose.ui.zIndex
* - MessageComponents: Message display and formatting
* - InputComponents: Message input and command suggestions
* - SidebarComponents: Navigation drawer with channels and people
* - DialogComponents: Password prompts and modals
* - AboutSheet: App info and password prompts
* - ChatUIUtils: Utility functions for formatting and colors
*/
@Composable
@@ -399,9 +399,9 @@ private fun ChatDialogs(
onDismiss = onPasswordDismiss
)
// App info dialog
AppInfoDialog(
show = showAppInfo,
// About sheet
AboutSheet(
isPresented = showAppInfo,
onDismiss = onAppInfoDismiss
)
@@ -1,126 +0,0 @@
package com.bitchat.android.ui
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
/**
* Dialog components for ChatScreen
* Extracted from ChatScreen.kt for better organization
*/
@Composable
fun PasswordPromptDialog(
show: Boolean,
channelName: String?,
passwordInput: String,
onPasswordChange: (String) -> Unit,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
if (show && channelName != null) {
val colorScheme = MaterialTheme.colorScheme
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = "Enter Channel Password",
style = MaterialTheme.typography.titleMedium,
color = colorScheme.onSurface
)
},
text = {
Column {
Text(
text = "Channel $channelName is password protected. Enter the password to join.",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = passwordInput,
onValueChange = onPasswordChange,
label = { Text("Password", style = MaterialTheme.typography.bodyMedium) },
textStyle = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace
),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = colorScheme.primary,
unfocusedBorderColor = colorScheme.outline
)
)
}
},
confirmButton = {
TextButton(onClick = onConfirm) {
Text(
text = "Join",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.primary
)
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(
text = "Cancel",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.onSurface
)
}
},
containerColor = colorScheme.surface,
tonalElevation = 8.dp
)
}
}
@Composable
fun AppInfoDialog(
show: Boolean,
onDismiss: () -> Unit
) {
if (show) {
val colorScheme = MaterialTheme.colorScheme
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = "About bitchat",
style = MaterialTheme.typography.titleMedium,
color = colorScheme.onSurface
)
},
text = {
Text(
text = "Decentralized mesh messaging over Bluetooth LE\n\n" +
"• No servers or internet required\n" +
"• End-to-end encrypted private messages\n" +
"• Password-protected channels\n" +
"• Store-and-forward for offline peers\n\n" +
"Triple-click title to emergency clear all data",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.onSurface
)
},
confirmButton = {
TextButton(onClick = onDismiss) {
Text(
text = "OK",
style = MaterialTheme.typography.bodyMedium,
color = colorScheme.primary
)
}
},
containerColor = colorScheme.surface,
tonalElevation = 8.dp
)
}
}