diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt index 78e9138..c5cd0e9 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/ConfirmSeedPhraseScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -144,7 +146,7 @@ fun ConfirmSeedPhraseScreen( verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = onBack) { - Icon(Icons.Default.ArrowBack, "Back", tint = textColor) + Icon(TablerIcons.ChevronLeft, "Back", tint = textColor) } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt index d9acf42..d5b144f 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/ImportSeedPhraseScreen.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -64,7 +66,7 @@ fun ImportSeedPhraseScreen( verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = onBack) { - Icon(Icons.Default.ArrowBack, "Back", tint = textColor) + Icon(TablerIcons.ChevronLeft, "Back", tint = textColor) } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt index 2e93ad4..1acad29 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/SeedPhraseScreen.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -65,7 +67,7 @@ fun SeedPhraseScreen( verticalAlignment = Alignment.CenterVertically ) { IconButton(onClick = onBack) { - Icon(Icons.Default.ArrowBack, "Back", tint = textColor) + Icon(TablerIcons.ChevronLeft, "Back", tint = textColor) } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/SetProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/SetProfileScreen.kt index 2e37f6b..722499f 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/SetProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/SetProfileScreen.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -84,9 +86,40 @@ fun SetProfileScreen( var isSaving by remember { mutableStateOf(false) } var visible by remember { mutableStateOf(false) } + // Username availability check + var usernameAvailable by remember { mutableStateOf(null) } + var isCheckingUsername by remember { mutableStateOf(false) } + + LaunchedEffect(username) { + val trimmed = username.trim() + if (trimmed.length < USERNAME_MIN_LENGTH) { + usernameAvailable = null + return@LaunchedEffect + } + if (validateUsername(trimmed) != null) { + usernameAvailable = null + return@LaunchedEffect + } + usernameAvailable = null + isCheckingUsername = true + delay(600) // debounce + try { + val results = ProtocolManager.searchUsers(trimmed, 3000) + val taken = results.any { it.username.equals(trimmed, ignoreCase = true) } + usernameAvailable = !taken + } catch (_: Exception) { + usernameAvailable = null + } + isCheckingUsername = false + } + val nameError = if (nameTouched) validateName(name.trim()) else null - val usernameError = if (usernameTouched) validateUsername(username.trim()) else null - val isFormValid = validateName(name.trim()) == null && validateUsername(username.trim()) == null + val localUsernameError = if (usernameTouched) validateUsername(username.trim()) else null + val usernameError = localUsernameError + ?: if (usernameTouched && usernameAvailable == false) "Username is already taken" else null + val isFormValid = validateName(name.trim()) == null + && validateUsername(username.trim()) == null + && usernameAvailable != false LaunchedEffect(Unit) { visible = true } @@ -276,11 +309,42 @@ fun SetProfileScreen( prefix = { Text("@", color = secondaryText, fontSize = 16.sp) }, + trailingIcon = { + when { + isCheckingUsername && username.trim().length >= USERNAME_MIN_LENGTH -> { + CircularProgressIndicator( + modifier = Modifier.size(18.dp), + strokeWidth = 2.dp, + color = secondaryText + ) + } + usernameAvailable == true && usernameError == null -> { + Icon( + imageVector = TablerIcons.Check, + contentDescription = "Available", + tint = Color(0xFF4CAF50), + modifier = Modifier.size(20.dp) + ) + } + usernameAvailable == false -> { + Icon( + imageVector = TablerIcons.X, + contentDescription = "Taken", + tint = ErrorRed, + modifier = Modifier.size(20.dp) + ) + } + } + }, isError = usernameError != null, modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(14.dp)), singleLine = true, + keyboardOptions = androidx.compose.foundation.text.KeyboardOptions( + capitalization = androidx.compose.ui.text.input.KeyboardCapitalization.None, + autoCorrect = false + ), colors = TextFieldDefaults.colors( focusedTextColor = textColor, unfocusedTextColor = textColor, @@ -300,6 +364,13 @@ fun SetProfileScreen( color = ErrorRed, modifier = Modifier.padding(start = 16.dp, top = 4.dp) ) + } else if (usernameAvailable == true) { + Text( + text = "Username is available!", + fontSize = 12.sp, + color = Color(0xFF4CAF50), + modifier = Modifier.padding(start = 16.dp, top = 4.dp) + ) } else { Text( text = "Username is optional. People can use it to find you without sharing your key.", diff --git a/app/src/main/java/com/rosetta/messenger/ui/auth/WelcomeScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/auth/WelcomeScreen.kt index 901c33f..04a1077 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/auth/WelcomeScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/auth/WelcomeScreen.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -81,7 +83,7 @@ fun WelcomeScreen( if (hasExistingAccount) { IconButton(onClick = onBack, modifier = Modifier.statusBarsPadding().padding(4.dp)) { Icon( - Icons.Default.ArrowBack, + TablerIcons.ChevronLeft, contentDescription = "Back", tint = textColor.copy(alpha = 0.6f) ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/GroupInfoScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/GroupInfoScreen.kt index dde24d0..2d4fc14 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/GroupInfoScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/GroupInfoScreen.kt @@ -47,7 +47,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material.icons.filled.ExitToApp import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Message @@ -998,7 +999,7 @@ fun GroupInfoScreen( ) { IconButton(onClick = onBack, modifier = Modifier.align(Alignment.TopStart)) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Back", tint = Color.White ) @@ -1752,7 +1753,7 @@ private fun GroupEncryptionKeyPage( ) { IconButton(onClick = onBack) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Back", tint = Color.White ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertComponents.kt index cce885b..c28c4ba 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertComponents.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.lazy.grid.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Check import androidx.compose.material3.* import androidx.compose.runtime.* @@ -48,6 +47,7 @@ import com.rosetta.messenger.ui.icons.TelegramIcons import com.rosetta.messenger.ui.onboarding.PrimaryBlue import compose.icons.TablerIcons import compose.icons.tablericons.ChevronDown +import compose.icons.tablericons.ChevronLeft import compose.icons.tablericons.PhotoOff import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay @@ -730,7 +730,7 @@ internal fun ExpandedHeader( modifier = Modifier.size(40.dp) ) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Close gallery", tint = textColor ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertFileLayout.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertFileLayout.kt index 0f74285..9b9fc97 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertFileLayout.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/attach/AttachAlertFileLayout.kt @@ -13,7 +13,8 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -438,7 +439,7 @@ private fun FilePickerTopBar( if (showBack) { IconButton(onClick = onBackClick) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Back", tint = textColor ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt index 73bf365..c68511f 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ImageViewerScreen.kt @@ -530,7 +530,7 @@ fun ImageViewerScreen( modifier = Modifier.align(Alignment.CenterStart) ) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Close", tint = Color.White ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt index 79ab820..80f4532 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/MediaPickerBottomSheet.kt @@ -888,7 +888,7 @@ fun MediaPickerBottomSheet( modifier = Modifier.size(40.dp) ) { Icon( - imageVector = Icons.Default.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Close gallery", tint = textColor ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt index da6df62..7753baa 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/input/ChatDetailInput.kt @@ -683,7 +683,8 @@ fun MessageInputBar( } else if (msg.text.isEmpty() && hasImageAttachment) { "Photo" } else { - val shortText = msg.text.take(40) + val codePoints = msg.text.codePoints().limit(40).toArray() + val shortText = String(codePoints, 0, codePoints.size) if (shortText.length < msg.text.length) "$shortText..." else shortText } } else "${panelReplyMessages.size} messages", diff --git a/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt index 5f2d5c4..f009f30 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt @@ -7,7 +7,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Delete @@ -84,7 +85,7 @@ fun CrashLogsScreen( title = { Text("Crash Logs") }, navigationIcon = { IconButton(onClick = onBackClick) { - Icon(Icons.Default.ArrowBack, contentDescription = "Back") + Icon(TablerIcons.ChevronLeft, contentDescription = "Back") } }, actions = { @@ -293,7 +294,7 @@ private fun CrashDetailScreen( title = { Text("Crash Details") }, navigationIcon = { IconButton(onClick = onBackClick) { - Icon(Icons.Default.ArrowBack, contentDescription = "Back") + Icon(TablerIcons.ChevronLeft, contentDescription = "Back") } }, actions = { diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/BackupScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/BackupScreen.kt index b2e9868..33bf518 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/BackupScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/BackupScreen.kt @@ -14,7 +14,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.SolidColor import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import compose.icons.TablerIcons +import compose.icons.tablericons.* import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff @@ -67,7 +68,7 @@ fun BackupScreen( ) { IconButton(onClick = onBack) { Icon( - imageVector = Icons.Filled.ArrowBack, + imageVector = TablerIcons.ChevronLeft, contentDescription = "Back", tint = textColor )