UI: унифицированы иконки навигации (ChevronLeft), фикс обрезки эмодзи в reply-превью, проверка доступности username при регистрации, отдельный экран биометрии, клавиатура прячется при скролле профиля и навигации, плавная анимация navbar при смене темы, аватарки в поиске.

This commit is contained in:
2026-04-08 09:22:27 +05:00
parent 299c84cb89
commit 1e259f52ee
13 changed files with 105 additions and 21 deletions

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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<Boolean?>(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.",

View File

@@ -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)
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -530,7 +530,7 @@ fun ImageViewerScreen(
modifier = Modifier.align(Alignment.CenterStart)
) {
Icon(
imageVector = Icons.Default.ArrowBack,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Close",
tint = Color.White
)

View File

@@ -888,7 +888,7 @@ fun MediaPickerBottomSheet(
modifier = Modifier.size(40.dp)
) {
Icon(
imageVector = Icons.Default.ArrowBack,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Close gallery",
tint = textColor
)

View File

@@ -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",

View File

@@ -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 = {

View File

@@ -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
)