mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 06:05:21 +00:00
Translations (#467)
* english done * de * more extraction * wip strings en * translations work * remove unneeded translations * remove notification message * add languages * new languages
This commit is contained in:
@@ -102,8 +102,8 @@ private fun BatteryOptimizationEnabledContent(
|
||||
.padding(bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -112,8 +112,8 @@ private fun BatteryOptimizationEnabledContent(
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "battery optimization detected",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_detected_title),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
@@ -136,22 +136,22 @@ private fun BatteryOptimizationEnabledContent(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Power,
|
||||
contentDescription = "Battery Optimization",
|
||||
contentDescription = stringResource(R.string.cd_battery_optimization),
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = "Battery Optimization Enabled",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_enabled_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "bitchat needs to run in the background to maintain mesh connections. battery optimization can interrupt these connections.",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_explanation_short),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -176,22 +176,22 @@ private fun BatteryOptimizationEnabledContent(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = "Benefits",
|
||||
contentDescription = stringResource(R.string.cd_benefits),
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(20.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = "Benefits of Disabling",
|
||||
Text(
|
||||
text = stringResource(R.string.benefits_of_disabling),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "• reliable message delivery\n• maintains mesh connectivity\n• prevents connection drops",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_benefits_short),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -222,8 +222,8 @@ private fun BatteryOptimizationEnabledContent(
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
Text(
|
||||
text = "Disable Battery Optimization",
|
||||
Text(
|
||||
text = stringResource(R.string.disable_battery_optimization),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -240,8 +240,8 @@ private fun BatteryOptimizationEnabledContent(
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = !isLoading
|
||||
) {
|
||||
Text(
|
||||
text = "Check Again",
|
||||
Text(
|
||||
text = stringResource(R.string.check_again),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
@@ -256,8 +256,8 @@ private fun BatteryOptimizationEnabledContent(
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = !isLoading
|
||||
) {
|
||||
Text(
|
||||
text = "Skip for Now",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_skip),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
@@ -282,7 +282,7 @@ private fun BatteryOptimizationCheckingContent(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -291,8 +291,8 @@ private fun BatteryOptimizationCheckingContent(
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "battery optimization disabled",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_disabled_title),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
@@ -312,15 +312,15 @@ private fun BatteryOptimizationCheckingContent(
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Filled.BatteryStd,
|
||||
contentDescription = "Checking Battery Optimization",
|
||||
contentDescription = stringResource(R.string.cd_checking_battery_optimization),
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.rotate(rotation),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "bitchat can run reliably in the background",
|
||||
Text(
|
||||
text = stringResource(R.string.battery_optimization_success_message),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
@@ -345,7 +345,7 @@ private fun BatteryOptimizationNotSupportedContent(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -355,7 +355,7 @@ private fun BatteryOptimizationNotSupportedContent(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "battery optimization not required",
|
||||
text = stringResource(R.string.battery_optimization_not_required),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
@@ -364,13 +364,13 @@ private fun BatteryOptimizationNotSupportedContent(
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Filled.CheckCircle,
|
||||
contentDescription = "Battery Optimization Not Supported",
|
||||
contentDescription = stringResource(R.string.cd_not_supported_battery_optimization),
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "your device doesn't require battery optimization settings. bitchat will run normally.",
|
||||
text = stringResource(R.string.battery_optimization_not_supported_message),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
@@ -385,8 +385,8 @@ private fun BatteryOptimizationNotSupportedContent(
|
||||
containerColor = colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Continue",
|
||||
Text(
|
||||
text = stringResource(R.string.continue_btn),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -394,4 +394,4 @@ private fun BatteryOptimizationNotSupportedContent(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Screen shown when checking Bluetooth status or requesting Bluetooth enable
|
||||
@@ -69,13 +71,13 @@ private fun BluetoothDisabledContent(
|
||||
// Bluetooth icon - using Bluetooth outlined icon in app's green color
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Bluetooth,
|
||||
contentDescription = "Bluetooth",
|
||||
contentDescription = stringResource(R.string.cd_bluetooth),
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = Color(0xFF00C851) // App's main green color
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Bluetooth Required",
|
||||
text = stringResource(R.string.bluetooth_required),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -95,8 +97,8 @@ private fun BluetoothDisabledContent(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat needs Bluetooth to:",
|
||||
Text(
|
||||
text = stringResource(R.string.bluetooth_needs_for),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onSurface
|
||||
@@ -105,11 +107,8 @@ private fun BluetoothDisabledContent(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "• Discover nearby users\n" +
|
||||
"• Create mesh network connections\n" +
|
||||
"• Send and receive messages\n" +
|
||||
"• Work without internet or servers",
|
||||
Text(
|
||||
text = stringResource(R.string.bluetooth_needs_bullets),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
@@ -132,8 +131,8 @@ private fun BluetoothDisabledContent(
|
||||
containerColor = Color(0xFF00C851) // App's main green color
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Enable Bluetooth",
|
||||
Text(
|
||||
text = stringResource(R.string.enable_bluetooth),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -177,14 +176,14 @@ private fun BluetoothNotSupportedContent(
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "❌",
|
||||
text = stringResource(R.string.warning_emoji),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Bluetooth Not Supported",
|
||||
text = stringResource(R.string.bluetooth_not_supported),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -201,7 +200,7 @@ private fun BluetoothNotSupportedContent(
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "This device doesn't support Bluetooth Low Energy (BLE), which is required for bitchat to function.\n\nbitchat needs BLE to create mesh networks and communicate with nearby devices without internet.",
|
||||
text = stringResource(R.string.bluetooth_unsupported_explanation),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface
|
||||
@@ -222,7 +221,7 @@ private fun BluetoothCheckingContent(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -234,7 +233,7 @@ private fun BluetoothCheckingContent(
|
||||
BluetoothLoadingIndicator()
|
||||
|
||||
Text(
|
||||
text = "Checking Bluetooth status...",
|
||||
text = stringResource(R.string.checking_bluetooth_status),
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
|
||||
@@ -12,6 +12,8 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Loading screen shown during app initialization after permissions are granted
|
||||
@@ -59,7 +61,7 @@ fun InitializingScreen(modifier: Modifier) {
|
||||
) {
|
||||
// App title
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -88,7 +90,7 @@ fun InitializingScreen(modifier: Modifier) {
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Initializing mesh network",
|
||||
text = stringResource(R.string.initializing_mesh_network),
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
@@ -98,7 +100,7 @@ fun InitializingScreen(modifier: Modifier) {
|
||||
// Animated dots
|
||||
dots.forEach { alpha ->
|
||||
Text(
|
||||
text = ".",
|
||||
text = stringResource(R.string.dot),
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = alpha)
|
||||
@@ -123,7 +125,7 @@ fun InitializingScreen(modifier: Modifier) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "Setting up Bluetooth mesh networking...",
|
||||
text = stringResource(R.string.setting_up_bluetooth),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
@@ -132,7 +134,7 @@ fun InitializingScreen(modifier: Modifier) {
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "This should only take a few seconds",
|
||||
text = stringResource(R.string.should_take_seconds),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
@@ -173,14 +175,14 @@ fun InitializationErrorScreen(
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "⚠️",
|
||||
text = stringResource(R.string.warning_emoji),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "Setup Not Complete",
|
||||
text = stringResource(R.string.setup_not_complete),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -216,7 +218,7 @@ fun InitializationErrorScreen(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "Try Again",
|
||||
text = stringResource(R.string.try_again),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -230,7 +232,7 @@ fun InitializationErrorScreen(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "Open Settings",
|
||||
text = stringResource(R.string.open_settings),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
|
||||
@@ -15,6 +15,8 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Screen shown when checking location services status or requesting location services enable
|
||||
@@ -70,13 +72,13 @@ private fun LocationDisabledContent(
|
||||
// Location icon - using LocationOn outlined icon in app's green color
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.LocationOn,
|
||||
contentDescription = "Location Services",
|
||||
contentDescription = stringResource(R.string.cd_location_services),
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = Color(0xFF00C851) // App's main green color
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Location Services Required",
|
||||
text = stringResource(R.string.location_services_required),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -103,13 +105,13 @@ private fun LocationDisabledContent(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = "Privacy",
|
||||
contentDescription = stringResource(R.string.cd_privacy),
|
||||
tint = Color(0xFF4CAF50),
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Privacy First",
|
||||
Text(
|
||||
text = stringResource(R.string.privacy_first),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.onSurface
|
||||
@@ -117,8 +119,8 @@ private fun LocationDisabledContent(
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "bitchat does NOT track your location.\n\nLocation services are required for Bluetooth scanning and for the Geohash chat feature.",
|
||||
Text(
|
||||
text = stringResource(R.string.location_explanation),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
@@ -127,8 +129,8 @@ private fun LocationDisabledContent(
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = "bitchat needs location services for:",
|
||||
Text(
|
||||
text = stringResource(R.string.location_needs_for),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onSurface
|
||||
@@ -137,11 +139,8 @@ private fun LocationDisabledContent(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "• Bluetooth device scanning\n" +
|
||||
"• Discovering nearby users on mesh network\n" +
|
||||
"• Geohash chat feature\n" +
|
||||
"• No tracking or location collection",
|
||||
Text(
|
||||
text = stringResource(R.string.location_needs_bullets),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
@@ -164,8 +163,8 @@ private fun LocationDisabledContent(
|
||||
containerColor = Color(0xFF00C851) // App's main green color
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Open Location Settings",
|
||||
Text(
|
||||
text = stringResource(R.string.open_location_settings),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -178,8 +177,8 @@ private fun LocationDisabledContent(
|
||||
onClick = onRetry,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "Check Again",
|
||||
Text(
|
||||
text = stringResource(R.string.check_again),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
@@ -202,13 +201,13 @@ private fun LocationNotAvailableContent(
|
||||
// Error icon
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ErrorOutline,
|
||||
contentDescription = "Error",
|
||||
contentDescription = stringResource(R.string.cd_error),
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = colorScheme.error
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Location Services Unavailable",
|
||||
text = stringResource(R.string.location_services_unavailable),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -225,7 +224,7 @@ private fun LocationNotAvailableContent(
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Location services are not available on this device. This is unusual as location services are standard on Android devices.\n\nbitchat needs location services for Bluetooth scanning to work properly (Android requirement). Without this, the app cannot discover nearby users.",
|
||||
text = stringResource(R.string.location_unavailable_explanation),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface
|
||||
@@ -246,7 +245,7 @@ private fun LocationCheckingContent(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -258,7 +257,7 @@ private fun LocationCheckingContent(
|
||||
LocationLoadingIndicator()
|
||||
|
||||
Text(
|
||||
text = "Checking location services...",
|
||||
text = stringResource(R.string.checking_location_services),
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
|
||||
@@ -24,6 +24,8 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Permission explanation screen shown before requesting permissions
|
||||
@@ -65,7 +67,7 @@ fun PermissionExplanationScreen(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -76,7 +78,7 @@ fun PermissionExplanationScreen(
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "decentralized mesh messaging with end-to-end encryption",
|
||||
text = stringResource(R.string.about_tagline),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
@@ -99,7 +101,7 @@ fun PermissionExplanationScreen(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = "Privacy Protected",
|
||||
contentDescription = stringResource(R.string.cd_privacy_protected),
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
@@ -107,16 +109,14 @@ fun PermissionExplanationScreen(
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = "Your Privacy is Protected",
|
||||
text = stringResource(R.string.privacy_protected),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = colorScheme.onBackground
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "• no tracking or data collection\n" +
|
||||
"• Bluetooth mesh chats are fully offline\n" +
|
||||
"• Geohash chats use the internet",
|
||||
text = stringResource(R.string.privacy_bullets),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
@@ -128,7 +128,7 @@ fun PermissionExplanationScreen(
|
||||
|
||||
// Section header
|
||||
Text(
|
||||
text = "permissions",
|
||||
text = stringResource(R.string.permissions_header),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.7f),
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp)
|
||||
@@ -163,7 +163,7 @@ fun PermissionExplanationScreen(
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Grant Permissions",
|
||||
text = stringResource(R.string.grant_permissions),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -218,12 +218,12 @@ private fun PermissionCategoryCard(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = "Warning",
|
||||
contentDescription = stringResource(R.string.cd_warning),
|
||||
tint = Color(0xFFFF9800),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
text = "bitchat does NOT track your location",
|
||||
text = stringResource(R.string.location_tracking_warning),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Medium,
|
||||
|
||||
@@ -29,7 +29,8 @@ import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.nostr.NostrProofOfWork
|
||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||
import com.bitchat.android.ui.debug.DebugSettingsSheet
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
/**
|
||||
* About Sheet for bitchat app information
|
||||
* Matches the design language of LocationChannelsSheet
|
||||
@@ -102,7 +103,7 @@ fun AboutSheet(
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
text = stringResource(R.string.app_name),
|
||||
style = TextStyle(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -112,7 +113,7 @@ fun AboutSheet(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "v$versionName",
|
||||
text = stringResource(R.string.version_prefix, versionName?:""),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onBackground.copy(alpha = 0.5f),
|
||||
@@ -123,7 +124,7 @@ fun AboutSheet(
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "decentralized mesh messaging with end-to-end encryption",
|
||||
text = stringResource(R.string.about_tagline),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
@@ -141,7 +142,7 @@ fun AboutSheet(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Bluetooth,
|
||||
contentDescription = "Offline Mesh Chat",
|
||||
contentDescription = stringResource(R.string.cd_offline_mesh_chat),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
@@ -150,14 +151,14 @@ fun AboutSheet(
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Offline Mesh Chat",
|
||||
text = stringResource(R.string.about_offline_mesh_title),
|
||||
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.",
|
||||
text = stringResource(R.string.about_offline_mesh_desc),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -173,7 +174,7 @@ fun AboutSheet(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Public,
|
||||
contentDescription = "Online Geohash Channels",
|
||||
contentDescription = stringResource(R.string.cd_online_geohash_channels),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
@@ -182,14 +183,14 @@ fun AboutSheet(
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "Online Geohash Channels",
|
||||
text = stringResource(R.string.about_online_geohash_title),
|
||||
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.",
|
||||
text = stringResource(R.string.about_online_geohash_desc),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -205,7 +206,7 @@ fun AboutSheet(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Lock,
|
||||
contentDescription = "End-to-End Encryption",
|
||||
contentDescription = stringResource(R.string.cd_end_to_end_encryption),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
@@ -214,14 +215,14 @@ fun AboutSheet(
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "End-to-End Encryption",
|
||||
text = stringResource(R.string.about_e2e_title),
|
||||
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.",
|
||||
text = stringResource(R.string.about_e2e_desc),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -232,7 +233,7 @@ fun AboutSheet(
|
||||
// Appearance Section
|
||||
item(key = "appearance_section") {
|
||||
Text(
|
||||
text = "appearance",
|
||||
text = stringResource(R.string.about_appearance),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
|
||||
modifier = Modifier
|
||||
@@ -247,24 +248,24 @@ fun AboutSheet(
|
||||
FilterChip(
|
||||
selected = themePref.isSystem,
|
||||
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.System) },
|
||||
label = { Text("system", fontFamily = FontFamily.Monospace) }
|
||||
label = { Text(stringResource(R.string.about_system), fontFamily = FontFamily.Monospace) }
|
||||
)
|
||||
FilterChip(
|
||||
selected = themePref.isLight,
|
||||
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.Light) },
|
||||
label = { Text("light", fontFamily = FontFamily.Monospace) }
|
||||
label = { Text(stringResource(R.string.about_light), fontFamily = FontFamily.Monospace) }
|
||||
)
|
||||
FilterChip(
|
||||
selected = themePref.isDark,
|
||||
onClick = { com.bitchat.android.ui.theme.ThemePreferenceManager.set(context, com.bitchat.android.ui.theme.ThemePreference.Dark) },
|
||||
label = { Text("dark", fontFamily = FontFamily.Monospace) }
|
||||
label = { Text(stringResource(R.string.about_dark), fontFamily = FontFamily.Monospace) }
|
||||
)
|
||||
}
|
||||
}
|
||||
// Proof of Work Section
|
||||
item(key = "pow_section") {
|
||||
Text(
|
||||
text = "proof of work",
|
||||
text = stringResource(R.string.about_pow),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
|
||||
modifier = Modifier
|
||||
@@ -289,7 +290,7 @@ fun AboutSheet(
|
||||
FilterChip(
|
||||
selected = !powEnabled,
|
||||
onClick = { PoWPreferenceManager.setPowEnabled(false) },
|
||||
label = { Text("pow off", fontFamily = FontFamily.Monospace) }
|
||||
label = { Text(stringResource(R.string.about_pow_off), fontFamily = FontFamily.Monospace) }
|
||||
)
|
||||
FilterChip(
|
||||
selected = powEnabled,
|
||||
@@ -299,7 +300,7 @@ fun AboutSheet(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("pow on", fontFamily = FontFamily.Monospace)
|
||||
Text(stringResource(R.string.about_pow_on), fontFamily = FontFamily.Monospace)
|
||||
// Show current difficulty
|
||||
if (powEnabled) {
|
||||
Surface(
|
||||
@@ -313,7 +314,7 @@ fun AboutSheet(
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "add proof of work to geohash messages for spam deterrence.",
|
||||
text = stringResource(R.string.about_pow_tip),
|
||||
fontSize = 10.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
@@ -326,7 +327,7 @@ fun AboutSheet(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "difficulty: $powDifficulty bits (~${NostrProofOfWork.estimateMiningTime(powDifficulty)})",
|
||||
text = stringResource(R.string.about_pow_difficulty, powDifficulty, NostrProofOfWork.estimateMiningTime(powDifficulty)),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
)
|
||||
@@ -353,20 +354,20 @@ fun AboutSheet(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "difficulty $powDifficulty requires ~${NostrProofOfWork.estimateWork(powDifficulty)} hash attempts",
|
||||
text = stringResource(R.string.about_pow_difficulty_attempts, powDifficulty, NostrProofOfWork.estimateWork(powDifficulty)),
|
||||
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"
|
||||
powDifficulty == 0 -> stringResource(R.string.about_pow_desc_none)
|
||||
powDifficulty <= 8 -> stringResource(R.string.about_pow_desc_very_low)
|
||||
powDifficulty <= 12 -> stringResource(R.string.about_pow_desc_low)
|
||||
powDifficulty <= 16 -> stringResource(R.string.about_pow_desc_medium)
|
||||
powDifficulty <= 20 -> stringResource(R.string.about_pow_desc_high)
|
||||
powDifficulty <= 24 -> stringResource(R.string.about_pow_desc_very_high)
|
||||
else -> stringResource(R.string.about_pow_desc_extreme)
|
||||
},
|
||||
fontSize = 10.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
@@ -384,7 +385,7 @@ fun AboutSheet(
|
||||
val torMode = remember { mutableStateOf(com.bitchat.android.net.TorPreferenceManager.get(context)) }
|
||||
val torStatus by com.bitchat.android.net.TorManager.statusFlow.collectAsState()
|
||||
Text(
|
||||
text = "network",
|
||||
text = stringResource(R.string.about_network),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
|
||||
modifier = Modifier
|
||||
@@ -429,7 +430,7 @@ fun AboutSheet(
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "route internet over tor for enhanced privacy.",
|
||||
text = stringResource(R.string.about_tor_route),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
@@ -446,14 +447,14 @@ fun AboutSheet(
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "tor Status: $statusText, bootstrap ${torStatus.bootstrapPercent}%",
|
||||
text = stringResource(R.string.about_tor_status, statusText, 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)}",
|
||||
text = stringResource(R.string.about_last, lastLog.take(160)),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
@@ -483,20 +484,20 @@ fun AboutSheet(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Warning,
|
||||
contentDescription = "Warning",
|
||||
contentDescription = stringResource(R.string.cd_warning),
|
||||
tint = errorColor,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = "Emergency Data Deletion",
|
||||
text = stringResource(R.string.about_emergency_title),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = errorColor
|
||||
)
|
||||
Text(
|
||||
text = "Tip: Triple-click the app title to emergency delete all stored data including messages, keys, and settings.",
|
||||
text = stringResource(R.string.about_emergency_tip),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
@@ -523,14 +524,14 @@ fun AboutSheet(
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Debug Settings",
|
||||
text = stringResource(R.string.about_debug_settings),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "Open Source • Privacy First • Decentralized",
|
||||
text = stringResource(R.string.about_footer),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
@@ -557,7 +558,7 @@ fun AboutSheet(
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Close",
|
||||
text = stringResource(R.string.close_plain),
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold),
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
@@ -588,7 +589,7 @@ fun PasswordPromptDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = "Enter Channel Password",
|
||||
text = stringResource(R.string.pwd_prompt_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
@@ -596,7 +597,7 @@ fun PasswordPromptDialog(
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = "Channel $channelName is password protected. Enter the password to join.",
|
||||
text = stringResource(R.string.pwd_prompt_message, channelName ?: ""),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
@@ -605,7 +606,7 @@ fun PasswordPromptDialog(
|
||||
OutlinedTextField(
|
||||
value = passwordInput,
|
||||
onValueChange = onPasswordChange,
|
||||
label = { Text("Password", style = MaterialTheme.typography.bodyMedium) },
|
||||
label = { Text(stringResource(R.string.pwd_label), style = MaterialTheme.typography.bodyMedium) },
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
@@ -619,7 +620,7 @@ fun PasswordPromptDialog(
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) {
|
||||
Text(
|
||||
text = "Join",
|
||||
text = stringResource(R.string.join),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.primary
|
||||
)
|
||||
@@ -628,7 +629,7 @@ fun PasswordPromptDialog(
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(
|
||||
text = "Cancel",
|
||||
text = stringResource(R.string.cancel),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -64,7 +66,7 @@ fun TorStatusIcon(
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Cable,
|
||||
contentDescription = "Tor status",
|
||||
contentDescription = stringResource(R.string.cd_tor_status),
|
||||
modifier = modifier,
|
||||
tint = cableColor
|
||||
)
|
||||
@@ -80,23 +82,23 @@ fun NoiseSessionIcon(
|
||||
"uninitialized" -> Triple(
|
||||
Icons.Outlined.NoEncryption,
|
||||
Color(0x87878700), // Grey - ready to establish
|
||||
"Ready for handshake"
|
||||
stringResource(R.string.cd_ready_for_handshake)
|
||||
)
|
||||
"handshaking" -> Triple(
|
||||
Icons.Outlined.Sync,
|
||||
Color(0x87878700), // Grey - in progress
|
||||
"Handshake in progress"
|
||||
stringResource(R.string.cd_handshake_in_progress)
|
||||
)
|
||||
"established" -> Triple(
|
||||
Icons.Filled.Lock,
|
||||
Color(0xFFFF9500), // Orange - secure
|
||||
"End-to-end encrypted"
|
||||
stringResource(R.string.cd_encrypted)
|
||||
)
|
||||
else -> { // "failed" or any other state
|
||||
Triple(
|
||||
Icons.Outlined.Warning,
|
||||
Color(0xFFFF4444), // Red - error
|
||||
"Handshake failed"
|
||||
stringResource(R.string.cd_handshake_failed)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -129,7 +131,7 @@ fun NicknameEditor(
|
||||
modifier = modifier
|
||||
) {
|
||||
Text(
|
||||
text = "@",
|
||||
text = stringResource(R.string.at_symbol),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.primary.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -193,8 +195,8 @@ fun PeerCounter(
|
||||
Icon(
|
||||
imageVector = Icons.Default.Group,
|
||||
contentDescription = when (selectedLocationChannel) {
|
||||
is com.bitchat.android.geohash.ChannelID.Location -> "Geohash participants"
|
||||
else -> "Connected peers"
|
||||
is com.bitchat.android.geohash.ChannelID.Location -> stringResource(R.string.cd_geohash_participants)
|
||||
else -> stringResource(R.string.cd_connected_peers)
|
||||
},
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = countColor
|
||||
@@ -211,7 +213,7 @@ fun PeerCounter(
|
||||
|
||||
if (joinedChannels.isNotEmpty()) {
|
||||
Text(
|
||||
text = " · ⧉ ${joinedChannels.size}",
|
||||
text = stringResource(R.string.channel_count_prefix) + "${joinedChannels.size}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (isConnected) Color(0xFF00C851) else Color.Red,
|
||||
fontSize = 16.sp,
|
||||
@@ -373,13 +375,13 @@ private fun PrivateChatHeader(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
contentDescription = stringResource(R.string.back),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "back",
|
||||
text = stringResource(R.string.chat_back),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.primary
|
||||
)
|
||||
@@ -405,7 +407,7 @@ private fun PrivateChatHeader(
|
||||
if (showGlobe) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Public,
|
||||
contentDescription = "Nostr reachable",
|
||||
contentDescription = stringResource(R.string.cd_nostr_reachable),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = Color(0xFF9B59B6) // Purple like iOS
|
||||
)
|
||||
@@ -428,7 +430,7 @@ private fun PrivateChatHeader(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isFavorite) Icons.Filled.Star else Icons.Outlined.Star,
|
||||
contentDescription = if (isFavorite) "Remove from favorites" else "Add to favorites",
|
||||
contentDescription = if (isFavorite) stringResource(R.string.cd_remove_favorite) else stringResource(R.string.cd_add_favorite),
|
||||
modifier = Modifier.size(18.dp), // Slightly larger than sidebar icon
|
||||
tint = if (isFavorite) Color(0xFFFFD700) else Color(0x87878700) // Yellow or grey
|
||||
)
|
||||
@@ -463,13 +465,13 @@ private fun ChannelHeader(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
contentDescription = stringResource(R.string.back),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "back",
|
||||
text = stringResource(R.string.chat_back),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = colorScheme.primary
|
||||
)
|
||||
@@ -478,7 +480,7 @@ private fun ChannelHeader(
|
||||
|
||||
// Title - perfectly centered regardless of other elements
|
||||
Text(
|
||||
text = "channel: $channel",
|
||||
text = stringResource(R.string.chat_channel_prefix, channel),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color(0xFFFF9500), // Orange to match input field
|
||||
modifier = Modifier
|
||||
@@ -492,7 +494,7 @@ private fun ChannelHeader(
|
||||
modifier = Modifier.align(Alignment.CenterEnd)
|
||||
) {
|
||||
Text(
|
||||
text = "leave",
|
||||
text = stringResource(R.string.chat_leave),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Red
|
||||
)
|
||||
@@ -534,7 +536,7 @@ private fun MainHeader(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "bitchat/",
|
||||
text = stringResource(R.string.app_brand),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = colorScheme.primary,
|
||||
modifier = Modifier.singleOrTripleClickable(
|
||||
@@ -562,7 +564,7 @@ private fun MainHeader(
|
||||
// Render icon directly to avoid symbol resolution issues
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = "Unread private messages",
|
||||
contentDescription = stringResource(R.string.cd_unread_private_messages),
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.clickable { viewModel.openLatestUnreadPrivateChat() },
|
||||
@@ -593,7 +595,7 @@ private fun MainHeader(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isBookmarked) Icons.Filled.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||
contentDescription = "Toggle bookmark",
|
||||
contentDescription = stringResource(R.string.cd_toggle_bookmark),
|
||||
tint = if (isBookmarked) Color(0xFF00C851) else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.75f),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
@@ -668,7 +670,7 @@ private fun LocationChannelsButton(
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Icon(
|
||||
imageVector = Icons.Default.PinDrop,
|
||||
contentDescription = "Teleported",
|
||||
contentDescription = stringResource(R.string.cd_teleported),
|
||||
modifier = Modifier.size(12.dp),
|
||||
tint = badgeColor
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -294,7 +295,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
IconButton(onClick = { forceScrollToBottom = !forceScrollToBottom }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowDownward,
|
||||
contentDescription = "Scroll to bottom",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_scroll_to_bottom),
|
||||
tint = Color(0xFF00C851)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -61,7 +63,7 @@ fun ChatUserSheet(
|
||||
) {
|
||||
// Header
|
||||
Text(
|
||||
text = "@$targetNickname",
|
||||
text = stringResource(R.string.at_nickname, targetNickname),
|
||||
fontSize = 18.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -69,7 +71,7 @@ fun ChatUserSheet(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = if (selectedMessage != null) "choose an action for this message or user" else "choose an action for this user",
|
||||
text = if (selectedMessage != null) stringResource(R.string.choose_action_message_or_user) else stringResource(R.string.choose_action_user),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
@@ -83,8 +85,8 @@ fun ChatUserSheet(
|
||||
selectedMessage?.let { message ->
|
||||
item {
|
||||
UserActionRow(
|
||||
title = "copy message",
|
||||
subtitle = "copy this message to clipboard",
|
||||
title = stringResource(R.string.action_copy_message_title),
|
||||
subtitle = stringResource(R.string.action_copy_message_subtitle),
|
||||
titleColor = standardGrey,
|
||||
onClick = {
|
||||
// Copy the message content to clipboard
|
||||
@@ -100,8 +102,8 @@ fun ChatUserSheet(
|
||||
// Slap action
|
||||
item {
|
||||
UserActionRow(
|
||||
title = "slap $targetNickname",
|
||||
subtitle = "send a playful slap message",
|
||||
title = stringResource(R.string.action_slap_title, targetNickname),
|
||||
subtitle = stringResource(R.string.action_slap_subtitle),
|
||||
titleColor = standardBlue,
|
||||
onClick = {
|
||||
// Send slap command
|
||||
@@ -114,8 +116,8 @@ fun ChatUserSheet(
|
||||
// Hug action
|
||||
item {
|
||||
UserActionRow(
|
||||
title = "hug $targetNickname",
|
||||
subtitle = "send a friendly hug message",
|
||||
title = stringResource(R.string.action_hug_title, targetNickname),
|
||||
subtitle = stringResource(R.string.action_hug_subtitle),
|
||||
titleColor = standardGreen,
|
||||
onClick = {
|
||||
// Send hug command
|
||||
@@ -128,8 +130,8 @@ fun ChatUserSheet(
|
||||
// Block action
|
||||
item {
|
||||
UserActionRow(
|
||||
title = "block $targetNickname",
|
||||
subtitle = "block all messages from this user",
|
||||
title = stringResource(R.string.action_block_title, targetNickname),
|
||||
subtitle = stringResource(R.string.action_block_subtitle),
|
||||
titleColor = standardRed,
|
||||
onClick = {
|
||||
// Check if we're in a geohash channel
|
||||
@@ -158,7 +160,7 @@ fun ChatUserSheet(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "cancel",
|
||||
text = stringResource(R.string.cancel_lower),
|
||||
fontSize = BASE_FONT_SIZE.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
|
||||
@@ -226,20 +226,6 @@ class ChatViewModel(
|
||||
} catch (_: Exception) { }
|
||||
|
||||
// Note: Mesh service is now started by MainActivity
|
||||
|
||||
// Show welcome message if no peers after delay
|
||||
viewModelScope.launch {
|
||||
delay(10000)
|
||||
if (state.getConnectedPeersValue().isEmpty() && state.getMessagesValue().isEmpty()) {
|
||||
val welcomeMessage = BitchatMessage(
|
||||
sender = "system",
|
||||
content = "get people around you to download bitchat and chat with them here!",
|
||||
timestamp = Date(),
|
||||
isRelay = false
|
||||
)
|
||||
messageManager.addMessage(welcomeMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// BLE receives are inserted by MessageHandler path; no VoiceNoteBus for Tor in this branch.
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||
import java.util.*
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* GeohashPeopleList - iOS-compatible component for displaying geohash participants
|
||||
@@ -66,7 +68,7 @@ fun GeohashPeopleList(
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "PEOPLE",
|
||||
text = stringResource(R.string.geohash_people_header),
|
||||
style = MaterialTheme.typography.labelSmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -78,7 +80,7 @@ fun GeohashPeopleList(
|
||||
if (geohashPeople.isEmpty()) {
|
||||
// Empty state - matches iOS "nobody around..."
|
||||
Text(
|
||||
text = "nobody around...",
|
||||
text = stringResource(R.string.nobody_around),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = BASE_FONT_SIZE.sp
|
||||
@@ -182,7 +184,7 @@ private fun GeohashPersonItem(
|
||||
// Unread DM indicator (orange envelope)
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = "Unread message",
|
||||
contentDescription = stringResource(R.string.cd_unread_message),
|
||||
modifier = Modifier.size(12.dp),
|
||||
tint = Color(0xFFFF9500) // iOS orange
|
||||
)
|
||||
@@ -253,7 +255,7 @@ private fun GeohashPersonItem(
|
||||
// "You" indicator for current user
|
||||
if (isMe) {
|
||||
Text(
|
||||
text = " (you)",
|
||||
text = stringResource(R.string.you_suffix),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = BASE_FONT_SIZE.sp
|
||||
|
||||
@@ -37,6 +37,8 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.bitchat.android.geohash.Geohash
|
||||
import com.bitchat.android.geohash.LocationChannelManager
|
||||
@@ -175,7 +177,7 @@ class GeohashPickerActivity : ComponentActivity() {
|
||||
shadowElevation = 6.dp
|
||||
) {
|
||||
Text(
|
||||
text = "pan and zoom to select a geohash",
|
||||
text = stringResource(R.string.pan_zoom_instruction),
|
||||
fontSize = 12.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
@@ -228,7 +230,7 @@ class GeohashPickerActivity : ComponentActivity() {
|
||||
)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Remove, contentDescription = "Decrease precision")
|
||||
Icon(Icons.Filled.Remove, contentDescription = stringResource(R.string.cd_decrease_precision))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +246,7 @@ class GeohashPickerActivity : ComponentActivity() {
|
||||
)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Increase precision")
|
||||
Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.cd_increase_precision))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +266,10 @@ class GeohashPickerActivity : ComponentActivity() {
|
||||
)
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Check, contentDescription = "Select geohash")
|
||||
Icon(Icons.Filled.Check, contentDescription = stringResource(R.string.cd_select_geohash))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(
|
||||
text = "select",
|
||||
text = stringResource(R.string.select),
|
||||
fontSize = (BASE_FONT_SIZE - 2).sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
|
||||
@@ -217,7 +217,7 @@ fun MessageInput(
|
||||
// Show placeholder when there's no text and not recording
|
||||
if (value.text.isEmpty() && !isRecording) {
|
||||
Text(
|
||||
text = "type a message...",
|
||||
text = stringResource(R.string.type_a_message_placeholder),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
@@ -486,7 +486,7 @@ fun MentionSuggestionItem(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "@$suggestion",
|
||||
text = stringResource(R.string.mention_suggestion_at, suggestion),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
@@ -498,7 +498,7 @@ fun MentionSuggestionItem(
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Text(
|
||||
text = "mention",
|
||||
text = stringResource(R.string.mention),
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -94,7 +95,7 @@ fun LinkPreviewPill(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Link,
|
||||
contentDescription = "Link",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_link),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = Color.Blue
|
||||
)
|
||||
|
||||
@@ -37,6 +37,8 @@ import com.bitchat.android.geohash.GeohashChannelLevel
|
||||
import com.bitchat.android.geohash.LocationChannelManager
|
||||
import com.bitchat.android.geohash.GeohashBookmarksStore
|
||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Location Channels Sheet for selecting geohash-based location channels
|
||||
@@ -133,7 +135,7 @@ fun LocationChannelsSheet(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "#location channels",
|
||||
text = stringResource(R.string.location_channels_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
@@ -141,7 +143,7 @@ fun LocationChannelsSheet(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "chat with people near you using geohash channels. only a coarse geohash is shared, never exact gps. do not screenshot or share this screen to protect your privacy.",
|
||||
text = stringResource(R.string.location_channels_desc),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f)
|
||||
@@ -170,7 +172,7 @@ fun LocationChannelsSheet(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "grant location permission",
|
||||
text = stringResource(R.string.grant_location_permission),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
@@ -180,7 +182,7 @@ fun LocationChannelsSheet(
|
||||
LocationChannelManager.PermissionState.RESTRICTED -> {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = "location permission denied. enable in settings to use location channels.",
|
||||
text = stringResource(R.string.location_permission_denied),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = Color.Red.copy(alpha = 0.8f)
|
||||
@@ -194,7 +196,7 @@ fun LocationChannelsSheet(
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = "open settings",
|
||||
text = stringResource(R.string.open_settings),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
@@ -203,7 +205,7 @@ fun LocationChannelsSheet(
|
||||
}
|
||||
LocationChannelManager.PermissionState.AUTHORIZED -> {
|
||||
Text(
|
||||
text = "✓ location permission granted",
|
||||
text = stringResource(R.string.location_permission_granted),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = standardGreen
|
||||
@@ -216,7 +218,7 @@ fun LocationChannelsSheet(
|
||||
) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(12.dp))
|
||||
Text(
|
||||
text = "checking permissions...",
|
||||
text = stringResource(R.string.checking_permissions),
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
@@ -232,7 +234,7 @@ fun LocationChannelsSheet(
|
||||
item(key = "mesh") {
|
||||
ChannelRow(
|
||||
title = meshTitleWithCount(viewModel),
|
||||
subtitle = "#bluetooth • ${bluetoothRangeString()}",
|
||||
subtitle = stringResource(R.string.location_bluetooth_subtitle, bluetoothRangeString()),
|
||||
isSelected = selectedChannel is ChannelID.Mesh,
|
||||
titleColor = standardBlue,
|
||||
titleBold = meshCount(viewModel) > 0,
|
||||
@@ -262,13 +264,13 @@ fun LocationChannelsSheet(
|
||||
titleColor = standardGreen,
|
||||
titleBold = highlight,
|
||||
trailingContent = {
|
||||
IconButton(onClick = { bookmarksStore.toggle(channel.geohash) }) {
|
||||
Icon(
|
||||
imageVector = if (isBookmarked) Icons.Filled.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||
contentDescription = if (isBookmarked) "Unbookmark" else "Bookmark",
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { bookmarksStore.toggle(channel.geohash) }) {
|
||||
Icon(
|
||||
imageVector = if (isBookmarked) Icons.Filled.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||
contentDescription = if (isBookmarked) stringResource(R.string.cd_remove_bookmark) else stringResource(R.string.cd_add_bookmark),
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
// Selecting a suggested nearby channel is not a teleport
|
||||
@@ -286,7 +288,7 @@ fun LocationChannelsSheet(
|
||||
) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(16.dp))
|
||||
Text(
|
||||
text = "finding nearby channels…",
|
||||
text = stringResource(R.string.finding_nearby_channels),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
@@ -298,7 +300,7 @@ fun LocationChannelsSheet(
|
||||
if (bookmarks.isNotEmpty()) {
|
||||
item(key = "bookmarked_header") {
|
||||
Text(
|
||||
text = "bookmarked",
|
||||
text = stringResource(R.string.bookmarked),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.7f),
|
||||
@@ -328,7 +330,7 @@ fun LocationChannelsSheet(
|
||||
IconButton(onClick = { bookmarksStore.toggle(gh) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Bookmark,
|
||||
contentDescription = "Remove bookmark",
|
||||
contentDescription = stringResource(R.string.cd_remove_bookmark),
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
|
||||
)
|
||||
}
|
||||
@@ -366,7 +368,7 @@ fun LocationChannelsSheet(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "#",
|
||||
text = stringResource(R.string.hash_symbol),
|
||||
fontSize = BASE_FONT_SIZE.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
@@ -409,7 +411,7 @@ fun LocationChannelsSheet(
|
||||
decorationBox = { innerTextField ->
|
||||
if (customGeohash.isEmpty()) {
|
||||
Text(
|
||||
text = "geohash",
|
||||
text = stringResource(R.string.geohash_placeholder),
|
||||
fontSize = BASE_FONT_SIZE.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
|
||||
@@ -435,7 +437,7 @@ fun LocationChannelsSheet(
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Map,
|
||||
contentDescription = "Open map",
|
||||
contentDescription = stringResource(R.string.cd_open_map),
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
@@ -453,7 +455,7 @@ fun LocationChannelsSheet(
|
||||
locationManager.select(ChannelID.Location(channel))
|
||||
onDismiss()
|
||||
} else {
|
||||
customError = "invalid geohash"
|
||||
customError = context.getString(R.string.invalid_geohash)
|
||||
}
|
||||
},
|
||||
enabled = isValid,
|
||||
@@ -467,14 +469,13 @@ fun LocationChannelsSheet(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "teleport",
|
||||
text = stringResource(R.string.teleport),
|
||||
fontSize = BASE_FONT_SIZE.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
// iOS has a face.dashed icon, use closest Material equivalent
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PinDrop,
|
||||
contentDescription = "Teleport",
|
||||
contentDescription = stringResource(R.string.cd_teleport),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
@@ -530,11 +531,7 @@ fun LocationChannelsSheet(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = if (locationServicesEnabled) {
|
||||
"disable location services"
|
||||
} else {
|
||||
"enable location services"
|
||||
},
|
||||
text = if (locationServicesEnabled) stringResource(R.string.disable_location_services) else stringResource(R.string.enable_location_services),
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
@@ -558,7 +555,7 @@ fun LocationChannelsSheet(
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Close",
|
||||
text = stringResource(R.string.close_plain),
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontWeight = FontWeight.Bold),
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
@@ -668,7 +665,7 @@ private fun ChannelRow(
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = "Selected",
|
||||
contentDescription = stringResource(R.string.cd_selected),
|
||||
tint = Color(0xFF32D74B), // iOS green for checkmark
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
@@ -695,10 +692,13 @@ private fun splitTitleAndCount(title: String): Pair<String, String?> {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun meshTitleWithCount(viewModel: ChatViewModel): String {
|
||||
val meshCount = meshCount(viewModel)
|
||||
val noun = if (meshCount == 1) "person" else "people"
|
||||
return "mesh [$meshCount $noun]"
|
||||
val ctx = androidx.compose.ui.platform.LocalContext.current
|
||||
val peopleText = ctx.resources.getQuantityString(com.bitchat.android.R.plurals.people_count, meshCount, meshCount)
|
||||
val meshLabel = stringResource(com.bitchat.android.R.string.mesh_label)
|
||||
return "$meshLabel [$peopleText]"
|
||||
}
|
||||
|
||||
private fun meshCount(viewModel: ChatViewModel): Int {
|
||||
@@ -708,14 +708,25 @@ private fun meshCount(viewModel: ChatViewModel): Int {
|
||||
} ?: 0
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun geohashTitleWithCount(channel: GeohashChannel, participantCount: Int): String {
|
||||
val noun = if (participantCount == 1) "person" else "people"
|
||||
return "${channel.level.displayName.lowercase()} [$participantCount $noun]"
|
||||
val ctx = androidx.compose.ui.platform.LocalContext.current
|
||||
val peopleText = ctx.resources.getQuantityString(com.bitchat.android.R.plurals.people_count, participantCount, participantCount)
|
||||
val levelName = when (channel.level) {
|
||||
com.bitchat.android.geohash.GeohashChannelLevel.BLOCK -> stringResource(com.bitchat.android.R.string.location_level_block)
|
||||
com.bitchat.android.geohash.GeohashChannelLevel.NEIGHBORHOOD -> stringResource(com.bitchat.android.R.string.location_level_neighborhood)
|
||||
com.bitchat.android.geohash.GeohashChannelLevel.CITY -> stringResource(com.bitchat.android.R.string.location_level_city)
|
||||
com.bitchat.android.geohash.GeohashChannelLevel.PROVINCE -> stringResource(com.bitchat.android.R.string.location_level_province)
|
||||
com.bitchat.android.geohash.GeohashChannelLevel.REGION -> stringResource(com.bitchat.android.R.string.location_level_region)
|
||||
}
|
||||
return "$levelName [$peopleText]"
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun geohashHashTitleWithCount(geohash: String, participantCount: Int): String {
|
||||
val noun = if (participantCount == 1) "person" else "people"
|
||||
return "#$geohash [$participantCount $noun]"
|
||||
val ctx = androidx.compose.ui.platform.LocalContext.current
|
||||
val peopleText = ctx.resources.getQuantityString(com.bitchat.android.R.plurals.people_count, participantCount, participantCount)
|
||||
return "#$geohash [$peopleText]"
|
||||
}
|
||||
|
||||
private fun isChannelSelected(channel: GeohashChannel, selectedChannel: ChannelID?): Boolean {
|
||||
|
||||
@@ -43,6 +43,9 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import com.bitchat.android.ui.media.FileMessageItem
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
import com.bitchat.android.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
|
||||
// VoiceNotePlayer moved to com.bitchat.android.ui.media.VoiceNotePlayer
|
||||
|
||||
@@ -334,11 +337,11 @@ fun MessageItem(
|
||||
.clickable { onCancelTransfer?.invoke(message) },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "Cancel", tint = Color.White, modifier = Modifier.size(14.dp))
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(R.string.cd_cancel), tint = Color.White, modifier = Modifier.size(14.dp))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(text = "[file unavailable]", fontFamily = FontFamily.Monospace, color = Color.Gray)
|
||||
Text(text = stringResource(R.string.file_unavailable), fontFamily = FontFamily.Monospace, color = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,7 +474,7 @@ fun DeliveryStatusIcon(status: DeliveryStatus) {
|
||||
when (status) {
|
||||
is DeliveryStatus.Sending -> {
|
||||
Text(
|
||||
text = "○",
|
||||
text = stringResource(R.string.status_sending),
|
||||
fontSize = 10.sp,
|
||||
color = colorScheme.primary.copy(alpha = 0.6f)
|
||||
)
|
||||
@@ -479,7 +482,7 @@ fun DeliveryStatusIcon(status: DeliveryStatus) {
|
||||
is DeliveryStatus.Sent -> {
|
||||
// Use a subtle hollow marker for Sent; single check is reserved for Delivered (iOS parity)
|
||||
Text(
|
||||
text = "○",
|
||||
text = stringResource(R.string.status_pending),
|
||||
fontSize = 10.sp,
|
||||
color = colorScheme.primary.copy(alpha = 0.6f)
|
||||
)
|
||||
@@ -487,14 +490,14 @@ fun DeliveryStatusIcon(status: DeliveryStatus) {
|
||||
is DeliveryStatus.Delivered -> {
|
||||
// Single check for Delivered (matches iOS expectations)
|
||||
Text(
|
||||
text = "✓",
|
||||
text = stringResource(R.string.status_sent),
|
||||
fontSize = 10.sp,
|
||||
color = colorScheme.primary.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
is DeliveryStatus.Read -> {
|
||||
Text(
|
||||
text = "✓✓",
|
||||
text = stringResource(R.string.status_delivered),
|
||||
fontSize = 10.sp,
|
||||
color = Color(0xFF007AFF), // Blue
|
||||
fontWeight = FontWeight.Bold
|
||||
@@ -502,7 +505,7 @@ fun DeliveryStatusIcon(status: DeliveryStatus) {
|
||||
}
|
||||
is DeliveryStatus.Failed -> {
|
||||
Text(
|
||||
text = "⚠",
|
||||
text = stringResource(R.string.status_failed),
|
||||
fontSize = 10.sp,
|
||||
color = Color.Red.copy(alpha = 0.8f)
|
||||
)
|
||||
@@ -510,7 +513,7 @@ fun DeliveryStatusIcon(status: DeliveryStatus) {
|
||||
is DeliveryStatus.PartiallyDelivered -> {
|
||||
// Show a single subdued check without numeric label
|
||||
Text(
|
||||
text = "✓",
|
||||
text = stringResource(R.string.status_sent),
|
||||
fontSize = 10.sp,
|
||||
color = colorScheme.primary.copy(alpha = 0.6f)
|
||||
)
|
||||
|
||||
@@ -258,7 +258,10 @@ class NotificationManager(
|
||||
}
|
||||
|
||||
if (messageCount > 5) {
|
||||
style.setSummaryText("and ${messageCount - 5} more")
|
||||
val extra = messageCount - 5
|
||||
style.setSummaryText(context.resources.getQuantityString(
|
||||
R.plurals.notification_and_more, extra, extra
|
||||
))
|
||||
}
|
||||
|
||||
builder.setStyle(style)
|
||||
@@ -291,11 +294,11 @@ class NotificationManager(
|
||||
)
|
||||
|
||||
// Build notification content
|
||||
val contentTitle = "👥 bitchatters nearby!"
|
||||
val contentTitle = context.getString(R.string.notification_active_peers_title)
|
||||
val contentText = if (peersSize == 1) {
|
||||
"1 person around"
|
||||
context.getString(R.string.notification_active_peers_one)
|
||||
} else {
|
||||
"$peersSize people around"
|
||||
context.getString(R.string.notification_active_peers_many, peersSize)
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
@@ -331,8 +334,8 @@ class NotificationManager(
|
||||
|
||||
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("bitchat")
|
||||
.setContentText("$totalMessages messages from $senderCount people")
|
||||
.setContentTitle(context.getString(R.string.app_name))
|
||||
.setContentText(context.getString(R.string.notification_messages_from_people, totalMessages, senderCount))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
@@ -342,7 +345,7 @@ class NotificationManager(
|
||||
|
||||
// Add inbox style showing recent senders
|
||||
val style = NotificationCompat.InboxStyle()
|
||||
.setBigContentTitle("New Messages")
|
||||
.setBigContentTitle(context.getString(R.string.notification_new_location_messages))
|
||||
|
||||
pendingNotifications.entries.take(5).forEach { (peerID, notifications) ->
|
||||
val latestNotif = notifications.last()
|
||||
@@ -356,7 +359,7 @@ class NotificationManager(
|
||||
}
|
||||
|
||||
if (pendingNotifications.size > 5) {
|
||||
style.setSummaryText("and ${pendingNotifications.size - 5} more conversations")
|
||||
style.setSummaryText(context.getString(R.string.notification_more_conversations, pendingNotifications.size - 5))
|
||||
}
|
||||
|
||||
builder.setStyle(style)
|
||||
@@ -459,15 +462,15 @@ class NotificationManager(
|
||||
// Build notification content with location name if available
|
||||
val geohashDisplay = latestNotification.locationName?.let { "$it (#$geohash)" } ?: "#$geohash"
|
||||
val contentTitle = when {
|
||||
mentionCount > 0 && firstMessageCount > 0 && messageCount > 1 -> "Mentioned in $geohashDisplay (+${messageCount - 1} more)"
|
||||
mentionCount > 0 -> if (mentionCount == 1) "Mentioned in $geohashDisplay" else "$mentionCount mentions in $geohashDisplay"
|
||||
firstMessageCount > 0 -> "New activity in $geohashDisplay"
|
||||
else -> "Messages in $geohashDisplay"
|
||||
mentionCount > 0 && firstMessageCount > 0 && messageCount > 1 -> context.getString(R.string.notification_mentions_in_more, geohashDisplay, messageCount - 1)
|
||||
mentionCount > 0 -> if (mentionCount == 1) context.getString(R.string.notification_mentions_in, geohashDisplay) else context.getString(R.string.notification_mentions_in_plural, mentionCount, geohashDisplay)
|
||||
firstMessageCount > 0 -> context.getString(R.string.notification_new_activity_in, geohashDisplay)
|
||||
else -> context.getString(R.string.notification_messages_in, geohashDisplay)
|
||||
}
|
||||
|
||||
val contentText = when {
|
||||
latestNotification.isMention -> "${latestNotification.senderNickname}: ${latestNotification.messageContent}"
|
||||
latestNotification.isFirstMessage -> "${latestNotification.senderNickname} joined the conversation"
|
||||
latestNotification.isFirstMessage -> context.getString(R.string.notification_joined_conversation, latestNotification.senderNickname)
|
||||
else -> "${latestNotification.senderNickname}: ${latestNotification.messageContent}"
|
||||
}
|
||||
|
||||
@@ -503,7 +506,8 @@ class NotificationManager(
|
||||
}
|
||||
|
||||
if (messageCount > 5) {
|
||||
style.setSummaryText("and ${messageCount - 5} more")
|
||||
val extra = messageCount - 5
|
||||
style.setSummaryText(context.resources.getQuantityString(R.plurals.notification_and_more, extra, extra))
|
||||
}
|
||||
|
||||
builder.setStyle(style)
|
||||
@@ -543,12 +547,12 @@ class NotificationManager(
|
||||
)
|
||||
|
||||
val contentTitle = if (totalMentions > 0) {
|
||||
"bitchat - $totalMentions mentions"
|
||||
context.getString(R.string.notification_geohash_summary_title_mentions, totalMentions)
|
||||
} else {
|
||||
"bitchat - location chats"
|
||||
context.getString(R.string.notification_geohash_summary_title)
|
||||
}
|
||||
|
||||
val contentText = "$totalMessages messages from $geohashCount locations"
|
||||
val contentText = context.getString(R.string.notification_geohash_summary_text, totalMessages, geohashCount)
|
||||
|
||||
val builder = NotificationCompat.Builder(context, GEOHASH_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
@@ -563,7 +567,7 @@ class NotificationManager(
|
||||
|
||||
// Add inbox style showing recent geohashes
|
||||
val style = NotificationCompat.InboxStyle()
|
||||
.setBigContentTitle("New Location Messages")
|
||||
.setBigContentTitle(context.getString(R.string.notification_new_messages))
|
||||
|
||||
pendingGeohashNotifications.entries.take(5).forEach { (geohash, notifications) ->
|
||||
val mentionCount = notifications.count { it.isMention }
|
||||
@@ -579,7 +583,7 @@ class NotificationManager(
|
||||
}
|
||||
|
||||
if (pendingGeohashNotifications.size > 5) {
|
||||
style.setSummaryText("and ${pendingGeohashNotifications.size - 5} more locations")
|
||||
style.setSummaryText(context.getString(R.string.notification_more_locations, pendingGeohashNotifications.size - 5))
|
||||
}
|
||||
|
||||
builder.setStyle(style)
|
||||
@@ -676,9 +680,9 @@ class NotificationManager(
|
||||
|
||||
// Build notification content
|
||||
val contentTitle = if (messageCount == 1) {
|
||||
"Mentioned in Mesh Chat"
|
||||
context.getString(R.string.notification_mesh_mention_title_singular)
|
||||
} else {
|
||||
"$messageCount mentions in Mesh Chat"
|
||||
context.getString(R.string.notification_mesh_mention_title_plural, messageCount)
|
||||
}
|
||||
|
||||
val contentText = "${latestNotification.senderNickname}: ${latestNotification.messageContent}"
|
||||
@@ -710,7 +714,8 @@ class NotificationManager(
|
||||
}
|
||||
|
||||
if (messageCount > 5) {
|
||||
style.setSummaryText("and ${messageCount - 5} more")
|
||||
val extra = messageCount - 5
|
||||
style.setSummaryText(context.resources.getQuantityString(R.plurals.notification_and_more, extra, extra))
|
||||
}
|
||||
|
||||
builder.setStyle(style)
|
||||
|
||||
@@ -15,6 +15,8 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.nostr.NostrProofOfWork
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||
|
||||
/**
|
||||
@@ -54,7 +56,7 @@ fun PoWStatusIndicator(
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = "Mining PoW",
|
||||
contentDescription = stringResource(R.string.cd_mining_pow),
|
||||
tint = Color(0xFFFF9500), // Orange for mining
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
@@ -63,7 +65,7 @@ fun PoWStatusIndicator(
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = "PoW Enabled",
|
||||
contentDescription = stringResource(R.string.cd_pow_enabled),
|
||||
tint = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D), // Green when ready
|
||||
modifier = Modifier.size(12.dp)
|
||||
)
|
||||
@@ -85,7 +87,7 @@ fun PoWStatusIndicator(
|
||||
// PoW icon
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = "Proof of Work",
|
||||
contentDescription = stringResource(R.string.cd_proof_of_work),
|
||||
tint = if (isMining) Color(0xFFFF9500) else {
|
||||
if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D)
|
||||
},
|
||||
@@ -95,9 +97,9 @@ fun PoWStatusIndicator(
|
||||
// Status text
|
||||
Text(
|
||||
text = if (isMining) {
|
||||
"mining..."
|
||||
stringResource(R.string.pow_mining_ellipsis)
|
||||
} else {
|
||||
"pow: ${powDifficulty}bit"
|
||||
stringResource(R.string.pow_label_format, powDifficulty)
|
||||
},
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
@@ -109,7 +111,7 @@ fun PoWStatusIndicator(
|
||||
// Time estimate
|
||||
if (!isMining && powDifficulty > 0) {
|
||||
Text(
|
||||
text = "(~${NostrProofOfWork.estimateMiningTime(powDifficulty)})",
|
||||
text = stringResource(R.string.pow_time_estimate, NostrProofOfWork.estimateMiningTime(powDifficulty)),
|
||||
fontSize = 9.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
|
||||
@@ -237,7 +237,7 @@ fun ChannelsSection(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "Leave channel",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_leave_channel),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
)
|
||||
@@ -552,7 +552,7 @@ private fun PeerItem(
|
||||
// Show mail icon for unread DMs (iOS orange)
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = "Unread message",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_unread_message),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color(0xFFFF9500) // iOS orange
|
||||
)
|
||||
@@ -562,7 +562,7 @@ private fun PeerItem(
|
||||
// Purple globe to indicate Nostr availability
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Public,
|
||||
contentDescription = "Reachable via Nostr",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_reachable_via_nostr),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color(0xFF9C27B0) // Purple
|
||||
)
|
||||
@@ -571,7 +571,7 @@ private fun PeerItem(
|
||||
Icon(
|
||||
//painter = androidx.compose.ui.res.painterResource(id = R.drawable.ic_offline_favorite),
|
||||
imageVector = Icons.Outlined.Circle,
|
||||
contentDescription = "Offline favorite",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_offline_favorite),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.Gray
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -129,7 +130,7 @@ fun VoiceRecordButton(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Mic,
|
||||
contentDescription = "Record voice note",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_record_voice),
|
||||
tint = Color.Black,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
||||
@@ -25,6 +25,8 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -97,10 +99,10 @@ fun DebugSettingsSheet(
|
||||
item {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.BugReport, contentDescription = null, tint = Color(0xFFFF9500))
|
||||
Text("debug tools", fontFamily = FontFamily.Monospace, fontSize = 18.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_tools), fontFamily = FontFamily.Monospace, fontSize = 18.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
Text(
|
||||
text = "developer utilities for diagnostics and control",
|
||||
text = stringResource(R.string.debug_tools_desc),
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 12.sp,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
@@ -113,12 +115,12 @@ fun DebugSettingsSheet(
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.SettingsEthernet, contentDescription = null, tint = Color(0xFF00C851))
|
||||
Text("verbose logging", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_verbose_logging), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Switch(checked = verboseLogging, onCheckedChange = { manager.setVerboseLoggingEnabled(it) })
|
||||
}
|
||||
Text(
|
||||
"logs peer joins/leaves, connection direction, packet routing and relays",
|
||||
stringResource(R.string.debug_verbose_hint),
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 11.sp,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
@@ -133,10 +135,10 @@ fun DebugSettingsSheet(
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.Bluetooth, contentDescription = null, tint = Color(0xFF007AFF))
|
||||
Text("bluetooth roles", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_bluetooth_roles), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("gatt server", fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
|
||||
Text(stringResource(R.string.debug_gatt_server), fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
|
||||
Switch(checked = gattServerEnabled, onCheckedChange = {
|
||||
manager.setGattServerEnabled(it)
|
||||
scope.launch {
|
||||
@@ -145,9 +147,9 @@ fun DebugSettingsSheet(
|
||||
})
|
||||
}
|
||||
val serverCount = connectedDevices.count { it.connectionType == ConnectionType.GATT_SERVER }
|
||||
Text("connections: $serverCount / $maxServer", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_connections_fmt, serverCount, maxServer), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("max server", fontFamily = FontFamily.Monospace, modifier = Modifier.width(90.dp))
|
||||
Text(stringResource(R.string.debug_max_server), fontFamily = FontFamily.Monospace, modifier = Modifier.width(90.dp))
|
||||
Slider(
|
||||
value = maxServer.toFloat(),
|
||||
onValueChange = { manager.setMaxServerConnections(it.toInt().coerceAtLeast(1)) },
|
||||
@@ -156,7 +158,7 @@ fun DebugSettingsSheet(
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("gatt client", fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
|
||||
Text(stringResource(R.string.debug_gatt_client), fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
|
||||
Switch(checked = gattClientEnabled, onCheckedChange = {
|
||||
manager.setGattClientEnabled(it)
|
||||
scope.launch {
|
||||
@@ -165,9 +167,9 @@ fun DebugSettingsSheet(
|
||||
})
|
||||
}
|
||||
val clientCount = connectedDevices.count { it.connectionType == ConnectionType.GATT_CLIENT }
|
||||
Text("connections: $clientCount / $maxClient", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_connections_fmt, clientCount, maxClient), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("max client", fontFamily = FontFamily.Monospace, modifier = Modifier.width(90.dp))
|
||||
Text(stringResource(R.string.debug_max_client), fontFamily = FontFamily.Monospace, modifier = Modifier.width(90.dp))
|
||||
Slider(
|
||||
value = maxClient.toFloat(),
|
||||
onValueChange = { manager.setMaxClientConnections(it.toInt().coerceAtLeast(1)) },
|
||||
@@ -176,9 +178,9 @@ fun DebugSettingsSheet(
|
||||
)
|
||||
}
|
||||
val overallCount = connectedDevices.size
|
||||
Text("connections: $overallCount / $maxOverall", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_overall_connections_fmt, overallCount, maxOverall), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("max overall", fontFamily = FontFamily.Monospace, modifier = Modifier.width(90.dp))
|
||||
Text(stringResource(R.string.debug_max_overall), fontFamily = FontFamily.Monospace, modifier = Modifier.width(90.dp))
|
||||
Slider(
|
||||
value = maxOverall.toFloat(),
|
||||
onValueChange = { manager.setMaxConnectionsOverall(it.toInt().coerceAtLeast(1)) },
|
||||
@@ -187,7 +189,7 @@ fun DebugSettingsSheet(
|
||||
)
|
||||
}
|
||||
Text(
|
||||
"turn roles on/off and close all connections when disabled",
|
||||
stringResource(R.string.debug_roles_hint),
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 11.sp,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
@@ -202,12 +204,12 @@ fun DebugSettingsSheet(
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.PowerSettingsNew, contentDescription = null, tint = Color(0xFFFF9500))
|
||||
Text("packet relay", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_packet_relay), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Switch(checked = packetRelayEnabled, onCheckedChange = { manager.setPacketRelayEnabled(it) })
|
||||
}
|
||||
Text("since start: ${relayStats.totalRelaysCount}", fontFamily = FontFamily.Monospace, fontSize = 11.sp)
|
||||
Text("last 10s: ${relayStats.last10SecondRelays} • 1m: ${relayStats.lastMinuteRelays} • 15m: ${relayStats.last15MinuteRelays}", fontFamily = FontFamily.Monospace, fontSize = 11.sp)
|
||||
Text(stringResource(R.string.debug_since_start_fmt, relayStats.totalRelaysCount), fontFamily = FontFamily.Monospace, fontSize = 11.sp)
|
||||
Text(stringResource(R.string.debug_relays_window_fmt, relayStats.last10SecondRelays, relayStats.lastMinuteRelays, relayStats.last15MinuteRelays), fontFamily = FontFamily.Monospace, fontSize = 11.sp)
|
||||
// Realtime graph: per-second relays, full-width canvas, bottom-up bars, fast decay
|
||||
var series by remember { mutableStateOf(List(60) { 0f }) }
|
||||
LaunchedEffect(isPresented) {
|
||||
@@ -295,15 +297,15 @@ fun DebugSettingsSheet(
|
||||
Icon(Icons.Filled.SettingsEthernet, contentDescription = null, tint = Color(0xFF9C27B0))
|
||||
Text("sync settings", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
Text("max packets per sync: $seenCapacity", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_max_packets_per_sync_fmt, seenCapacity), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Slider(value = seenCapacity.toFloat(), onValueChange = { manager.setSeenPacketCapacity(it.toInt()) }, valueRange = 10f..1000f, steps = 99)
|
||||
Text("max GCS filter size: $gcsMaxBytes bytes (128–1024)", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_max_gcs_filter_size_fmt, gcsMaxBytes), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Slider(value = gcsMaxBytes.toFloat(), onValueChange = { manager.setGcsMaxBytes(it.toInt()) }, valueRange = 128f..1024f, steps = 0)
|
||||
Text("target FPR: ${String.format("%.2f", gcsFpr)}%", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_target_fpr_fmt, String.format("%.2f", gcsFpr)), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Slider(value = gcsFpr.toFloat(), onValueChange = { manager.setGcsFprPercent(it.toDouble()) }, valueRange = 0.1f..5.0f, steps = 49)
|
||||
val p = remember(gcsFpr) { com.bitchat.android.sync.GCSFilter.deriveP(gcsFpr / 100.0) }
|
||||
val nmax = remember(gcsFpr, gcsMaxBytes) { com.bitchat.android.sync.GCSFilter.estimateMaxElementsForSize(gcsMaxBytes, p) }
|
||||
Text("derived P: $p • est. max elements: $nmax", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_derived_p_fmt, p, nmax), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,10 +316,10 @@ fun DebugSettingsSheet(
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.Devices, contentDescription = null, tint = Color(0xFF4CAF50))
|
||||
Text("connected devices", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_connected_devices), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
val localAddr = remember { meshService.connectionManager.getLocalAdapterAddress() }
|
||||
Text("our device id: ${localAddr ?: "unknown"}", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text(stringResource(R.string.debug_our_device_id_fmt, localAddr ?: stringResource(R.string.unknown)), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
if (connectedDevices.isEmpty()) {
|
||||
Text("none", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.6f))
|
||||
} else {
|
||||
@@ -325,11 +327,11 @@ fun DebugSettingsSheet(
|
||||
Surface(shape = RoundedCornerShape(8.dp), color = colorScheme.surface.copy(alpha = 0.6f)) {
|
||||
Row(Modifier.fillMaxWidth().padding(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("${dev.peerID ?: "unknown"} • ${dev.deviceAddress}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
|
||||
val roleLabel = if (dev.connectionType == ConnectionType.GATT_SERVER) "as server (we host)" else "as client (we connect)"
|
||||
Text("${dev.nickname ?: ""} • RSSI: ${dev.rssi ?: "?"} • $roleLabel${if (dev.isDirectConnection) " • direct" else ""}", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text((dev.peerID ?: stringResource(R.string.unknown)) + " • ${dev.deviceAddress}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
|
||||
val roleLabel = if (dev.connectionType == ConnectionType.GATT_SERVER) stringResource(R.string.debug_role_server) else stringResource(R.string.debug_role_client)
|
||||
Text("${dev.nickname ?: ""} • " + stringResource(R.string.debug_rssi_fmt, dev.rssi ?: stringResource(R.string.debug_question_mark)) + " • $roleLabel" + (if (dev.isDirectConnection) stringResource(R.string.debug_direct_suffix) else ""), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
}
|
||||
Text("disconnect", color = Color(0xFFBF1A1A), fontFamily = FontFamily.Monospace, modifier = Modifier.clickable {
|
||||
Text(stringResource(R.string.debug_disconnect), color = Color(0xFFBF1A1A), fontFamily = FontFamily.Monospace, modifier = Modifier.clickable {
|
||||
meshService.connectionManager.disconnectAddress(dev.deviceAddress)
|
||||
})
|
||||
}
|
||||
@@ -346,19 +348,19 @@ fun DebugSettingsSheet(
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.Bluetooth, contentDescription = null, tint = Color(0xFF007AFF))
|
||||
Text("recent scan results", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_recent_scan_results), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
if (scanResults.isEmpty()) {
|
||||
Text("none", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.6f))
|
||||
Text(stringResource(R.string.debug_none), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.6f))
|
||||
} else {
|
||||
scanResults.forEach { res ->
|
||||
Surface(shape = RoundedCornerShape(8.dp), color = colorScheme.surface.copy(alpha = 0.6f)) {
|
||||
Row(Modifier.fillMaxWidth().padding(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("${res.peerID ?: "unknown"} • ${res.deviceAddress}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
|
||||
Text("${res.deviceName ?: ""} • RSSI: ${res.rssi}", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Text((res.peerID ?: stringResource(R.string.unknown)) + " • ${res.deviceAddress}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
|
||||
Text(stringResource(R.string.debug_rssi_fmt, res.rssi.toString()), fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
}
|
||||
Text("connect", color = Color(0xFF00C851), fontFamily = FontFamily.Monospace, modifier = Modifier.clickable {
|
||||
Text(stringResource(R.string.debug_connect), color = Color(0xFF00C851), fontFamily = FontFamily.Monospace, modifier = Modifier.clickable {
|
||||
meshService.connectionManager.connectToAddress(res.deviceAddress)
|
||||
})
|
||||
}
|
||||
@@ -375,9 +377,9 @@ fun DebugSettingsSheet(
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.BugReport, contentDescription = null, tint = Color(0xFFFF9500))
|
||||
Text("debug console", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Text(stringResource(R.string.debug_debug_console), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Text("clear", color = Color(0xFFBF1A1A), fontFamily = FontFamily.Monospace, modifier = Modifier.clickable {
|
||||
Text(stringResource(R.string.debug_clear), color = Color(0xFFBF1A1A), fontFamily = FontFamily.Monospace, modifier = Modifier.clickable {
|
||||
manager.clearDebugMessages()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import androidx.compose.material3.ColorScheme
|
||||
@@ -91,7 +93,7 @@ fun AudioMessageItem(
|
||||
.clickable { onCancelTransfer?.invoke(message) },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "Cancel", tint = Color.White, modifier = Modifier.size(16.dp))
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(R.string.cd_cancel), tint = Color.White, modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -28,6 +30,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.features.file.FileUtils
|
||||
import com.bitchat.android.model.BitchatFilePacket
|
||||
@@ -60,7 +63,7 @@ fun FileMessageItem(
|
||||
// File icon
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Description,
|
||||
contentDescription = "File",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_file),
|
||||
tint = getFileIconColor(packet.fileName),
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
@@ -70,13 +73,13 @@ fun FileMessageItem(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
// File name
|
||||
Text(
|
||||
text = packet.fileName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = packet.fileName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
// File details
|
||||
Row(
|
||||
|
||||
@@ -14,6 +14,8 @@ import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.features.file.FileUtils
|
||||
|
||||
@Composable
|
||||
@@ -44,7 +46,7 @@ fun FilePickerButton(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Attachment,
|
||||
contentDescription = "Pick file",
|
||||
contentDescription = stringResource(R.string.cd_pick_file),
|
||||
tint = Color.Gray,
|
||||
modifier = Modifier.size(20.dp).rotate(90f)
|
||||
)
|
||||
|
||||
@@ -28,6 +28,9 @@ import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
@@ -77,7 +80,7 @@ fun FileSendingAnimation(
|
||||
// File icon
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Description,
|
||||
contentDescription = "File",
|
||||
contentDescription = stringResource(R.string.cd_file),
|
||||
tint = Color(0xFF00C851), // Green like app theme
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
@@ -102,7 +105,7 @@ fun FileSendingAnimation(
|
||||
// Blinking cursor (only if not fully revealed)
|
||||
if (animatedChars < fileName.length && showCursor) {
|
||||
androidx.compose.material3.Text(
|
||||
text = "_",
|
||||
text = stringResource(R.string.underscore),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
||||
color = Color.White
|
||||
@@ -132,10 +135,12 @@ private fun FileProgressBars(
|
||||
val filledBars = (progress * bars).toInt()
|
||||
|
||||
// Create a matrix-style progress bar string
|
||||
val ctx = LocalContext.current
|
||||
val progressString = buildString {
|
||||
val brackets = ctx.getString(R.string.progress_bar_brackets, "", 0)
|
||||
append("[")
|
||||
for (i in 0 until bars) {
|
||||
append(if (i < filledBars) "█" else "░")
|
||||
append(if (i < filledBars) ctx.getString(R.string.progress_filled) else ctx.getString(R.string.progress_empty))
|
||||
}
|
||||
append("] ")
|
||||
append("${(progress * 100).toInt()}%")
|
||||
|
||||
@@ -20,9 +20,11 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.features.file.FileUtils
|
||||
import com.bitchat.android.model.BitchatFilePacket
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -54,7 +56,7 @@ fun FileViewerDialog(
|
||||
) {
|
||||
// File received header
|
||||
Text(
|
||||
text = "📎 File Received",
|
||||
text = stringResource(R.string.file_viewer_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
@@ -65,17 +67,17 @@ fun FileViewerDialog(
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Text(
|
||||
text = "📄 ${packet.fileName}",
|
||||
text = stringResource(R.string.file_viewer_name, packet.fileName),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = "📏 Size: ${FileUtils.formatFileSize(packet.fileSize)}",
|
||||
text = stringResource(R.string.file_viewer_size, FileUtils.formatFileSize(packet.fileSize)),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = "🏷️ Type: ${packet.mimeType}",
|
||||
text = stringResource(R.string.file_viewer_type, packet.mimeType),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
@@ -108,7 +110,7 @@ fun FileViewerDialog(
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
) {
|
||||
Text("📂 Open / Save")
|
||||
Text(stringResource(R.string.file_viewer_open_save))
|
||||
}
|
||||
|
||||
// Dismiss button
|
||||
@@ -119,13 +121,13 @@ fun FileViewerDialog(
|
||||
containerColor = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
) {
|
||||
Text("❌ Close")
|
||||
Text(stringResource(R.string.close_with_emoji))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open a file using system viewers or save to device
|
||||
|
||||
@@ -32,6 +32,8 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -75,13 +77,13 @@ fun FullScreenImageViewer(imagePaths: List<String>, initialIndex: Int = 0, onClo
|
||||
bmp?.let {
|
||||
androidx.compose.foundation.Image(
|
||||
bitmap = it.asImageBitmap(),
|
||||
contentDescription = "Image ${page + 1} of ${imagePaths.size}",
|
||||
contentDescription = stringResource(R.string.cd_image_index_of, page + 1, imagePaths.size),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
} ?: run {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(text = "Image unavailable", color = Color.White)
|
||||
Text(text = stringResource(R.string.image_unavailable), color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +98,7 @@ fun FullScreenImageViewer(imagePaths: List<String>, initialIndex: Int = 0, onClo
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "${(pagerState.currentPage ?: 0) + 1} / ${imagePaths.size}",
|
||||
text = stringResource(R.string.image_counter, (pagerState.currentPage ?: 0) + 1, imagePaths.size),
|
||||
color = Color.White,
|
||||
fontSize = 14.sp,
|
||||
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
|
||||
@@ -118,7 +120,7 @@ fun FullScreenImageViewer(imagePaths: List<String>, initialIndex: Int = 0, onClo
|
||||
.clickable { saveToDownloads(context, imagePaths[pagerState.currentPage].toString()) },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.material3.Icon(Icons.Filled.Download, "Save current image", tint = Color.White)
|
||||
androidx.compose.material3.Icon(Icons.Filled.Download, stringResource(R.string.cd_save_current_image), tint = Color.White)
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Box(
|
||||
@@ -128,7 +130,7 @@ fun FullScreenImageViewer(imagePaths: List<String>, initialIndex: Int = 0, onClo
|
||||
.clickable { onClose() },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.material3.Icon(Icons.Filled.Close, "Close", tint = Color.White)
|
||||
androidx.compose.material3.Icon(Icons.Filled.Close, stringResource(R.string.cd_close), tint = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,10 +163,10 @@ private fun saveToDownloads(context: android.content.Context, path: String) {
|
||||
context.contentResolver.update(uri, v2, null, null)
|
||||
}
|
||||
// Show toast message indicating the image has been saved
|
||||
Toast.makeText(context, "Image saved to Downloads", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.toast_image_saved), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}.onFailure {
|
||||
// Optionally handle failure case (e.g., show error toast)
|
||||
Toast.makeText(context, "Failed to save image", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.toast_failed_to_save_image), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,17 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
@@ -113,7 +115,7 @@ fun ImageMessageItem(
|
||||
// Fully revealed image
|
||||
Image(
|
||||
bitmap = img,
|
||||
contentDescription = "Image",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_image),
|
||||
modifier = Modifier
|
||||
.widthIn(max = 300.dp)
|
||||
.aspectRatio(aspect)
|
||||
@@ -137,13 +139,13 @@ fun ImageMessageItem(
|
||||
.clickable { onCancelTransfer?.invoke(message) },
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "Cancel", tint = Color.White, modifier = Modifier.size(14.dp))
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(com.bitchat.android.R.string.cd_cancel), tint = Color.White, modifier = Modifier.size(14.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(text = "[image unavailable]", fontFamily = FontFamily.Monospace, color = Color.Gray)
|
||||
Text(text = stringResource(com.bitchat.android.R.string.image_unavailable), fontFamily = FontFamily.Monospace, color = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.features.media.ImageUtils
|
||||
|
||||
@@ -35,7 +36,7 @@ fun ImagePickerButton(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Photo,
|
||||
contentDescription = "Pick image",
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.pick_image),
|
||||
tint = Color.Gray,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
||||
@@ -26,6 +26,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
|
||||
/**
|
||||
* Media picker that offers image and file options
|
||||
@@ -53,7 +55,7 @@ fun MediaPickerOptions(
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = "Pick media",
|
||||
contentDescription = stringResource(R.string.cd_pick_media),
|
||||
tint = Color.Black,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
@@ -98,7 +100,7 @@ fun MediaPickerOptions(
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
androidx.compose.material3.Text(
|
||||
text = "Image",
|
||||
text = stringResource(R.string.media_type_image),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
@@ -126,7 +128,7 @@ fun MediaPickerOptions(
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
androidx.compose.material3.Text(
|
||||
text = "File",
|
||||
text = stringResource(R.string.media_type_file),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user