mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 10:25:19 +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:
@@ -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