mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-24 23:45:19 +00:00
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user