From b5398e2f1db90db2b78fbff31d406dc0ba7dc86c Mon Sep 17 00:00:00 2001 From: k1ngsterr1 Date: Mon, 16 Mar 2026 00:02:27 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=BB=D0=B8=D0=B7=201.2.0:=20?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=83=D1=81=D1=8B,=20=D1=81=D0=BA=D1=80=D0=BE=D0=BB=D0=BB=20?= =?UTF-8?q?=D0=B8=20UI-=D0=B2=D1=8B=D1=80=D0=B0=D0=B2=D0=BD=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Поднята версия приложения до 1.2.0 (versionCode 22)\n- Синхронизированы статусы отправки между чат-листом и диалогом: SENT отображается как часы до delivery, ERROR теперь стабильно приходит в открытый диалог\n- Доработан Telegram-подобный skeleton в диалоге: shimmer, геометрия пузырей, поддержка групповых аватаров\n- Добавлен плавный автоскролл к баннеру подтверждения нового устройства в чат-листе\n- Выровнены verified-галочки с именами в профилях и в сайдбаре\n- Кнопка Copy Seed Phrase в светлой теме приведена к белому тексту\n- Мелкие UI-правки в чате и компонентах ввода/эмодзи --- app/build.gradle.kts | 4 +- .../messenger/data/MessageRepository.kt | 6 + .../messenger/ui/chats/ChatDetailScreen.kt | 5 +- .../messenger/ui/chats/ChatViewModel.kt | 4 + .../messenger/ui/chats/ChatsListScreen.kt | 51 ++++-- .../chats/components/ChatDetailComponents.kt | 170 ++++++++++++------ .../ui/components/AppleEmojiEditText.kt | 75 +++++--- .../messenger/ui/settings/BackupScreen.kt | 6 +- .../ui/settings/OtherProfileScreen.kt | 23 ++- .../messenger/ui/settings/ProfileScreen.kt | 23 ++- 10 files changed, 252 insertions(+), 115 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0c3f303..202290e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,8 +23,8 @@ val gitShortSha = safeGitOutput("rev-parse", "--short", "HEAD") ?: "unknown" // ═══════════════════════════════════════════════════════════ // Rosetta versioning — bump here on each release // ═══════════════════════════════════════════════════════════ -val rosettaVersionName = "1.1.9" -val rosettaVersionCode = 21 // Increment on each release +val rosettaVersionName = "1.2.0" +val rosettaVersionCode = 22 // Increment on each release android { namespace = "com.rosetta.messenger" diff --git a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt index 859e8bd..6f8cd42 100644 --- a/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt +++ b/app/src/main/java/com/rosetta/messenger/data/MessageRepository.kt @@ -600,6 +600,9 @@ class MessageRepository private constructor(private val context: Context) { // При ошибке обновляем статус messageDao.updateDeliveryStatus(account, messageId, DeliveryStatus.ERROR.value) updateMessageStatus(dialogKey, messageId, DeliveryStatus.ERROR) + _deliveryStatusEvents.tryEmit( + DeliveryStatusUpdate(dialogKey, messageId, DeliveryStatus.ERROR) + ) } } @@ -1140,6 +1143,9 @@ class MessageRepository private constructor(private val context: Context) { messageDao.updateDeliveryStatus(account, entity.messageId, DeliveryStatus.ERROR.value) val dialogKey = getDialogKey(entity.toPublicKey) updateMessageStatus(dialogKey, entity.messageId, DeliveryStatus.ERROR) + _deliveryStatusEvents.tryEmit( + DeliveryStatusUpdate(dialogKey, entity.messageId, DeliveryStatus.ERROR) + ) } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt index 91617b0..2b22aa7 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatDetailScreen.kt @@ -1164,7 +1164,7 @@ fun ChatDetailScreen( Row( modifier = Modifier.fillMaxWidth() - .height(56.dp) + .height(64.dp) .padding( horizontal = 4.dp @@ -1336,7 +1336,7 @@ fun ChatDetailScreen( Row( modifier = Modifier.fillMaxWidth() - .height(56.dp) + .height(64.dp) .padding( horizontal = 4.dp @@ -2310,6 +2310,7 @@ fun ChatDetailScreen( isLoading -> { MessageSkeletonList( isDarkTheme = isDarkTheme, + isGroupChat = isGroupChat, modifier = Modifier.fillMaxSize() ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt index 659de76..1e42678 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatViewModel.kt @@ -425,6 +425,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { // Обновляем конкретное сообщение updateMessageStatus(update.messageId, MessageStatus.DELIVERED) } + DeliveryStatus.ERROR -> { + // Синхронизируем ошибку отправки с открытым диалогом + updateMessageStatus(update.messageId, MessageStatus.ERROR) + } DeliveryStatus.READ -> { // Помечаем все исходящие как прочитанные markAllOutgoingAsRead() diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index 39864eb..2f066f6 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -928,26 +928,27 @@ fun ChatsListScreen( // Display name if (accountName.isNotEmpty()) { Row( - verticalAlignment = Alignment.CenterVertically + horizontalArrangement = Arrangement.Start ) { Text( text = accountName, fontSize = 15.sp, fontWeight = FontWeight.Bold, - color = Color.White + color = Color.White, + modifier = Modifier.alignByBaseline() ) if (accountVerified > 0 || isRosettaOfficial || isFreddyOfficial) { - Spacer( + Box( modifier = - Modifier.width( - 6.dp - ) - ) - VerifiedBadge( - verified = if (accountVerified > 0) accountVerified else 1, - size = 15, - badgeTint = if (isDarkTheme) PrimaryBlue else Color.White - ) + Modifier.padding(start = 2.dp) + .alignBy { (it.measuredHeight * 0.78f).toInt() } + ) { + VerifiedBadge( + verified = if (accountVerified > 0) accountVerified else 1, + size = 15, + badgeTint = if (isDarkTheme) PrimaryBlue else Color.White + ) + } } } } @@ -2066,8 +2067,34 @@ fun ChatsListScreen( // Track scroll direction to hide/show Requests val chatListState = rememberLazyListState() var isRequestsVisible by remember { mutableStateOf(true) } + var lastAutoScrolledVerificationId by remember { + mutableStateOf(null) + } val hapticFeedback = LocalHapticFeedback.current + // When a new device confirmation banner appears at the top, + // smoothly bring the list to top so the banner is visible. + LaunchedEffect(pendingDeviceVerification?.deviceId) { + val verificationId = + pendingDeviceVerification?.deviceId + if (verificationId.isNullOrBlank()) { + lastAutoScrolledVerificationId = null + return@LaunchedEffect + } + if (verificationId == lastAutoScrolledVerificationId) { + return@LaunchedEffect + } + + val alreadyAtTop = + chatListState.firstVisibleItemIndex == 0 && + chatListState.firstVisibleItemScrollOffset <= 2 + if (!alreadyAtTop) { + chatListState.animateScrollToItem(0) + } + + lastAutoScrolledVerificationId = verificationId + } + // NestedScroll — ловим направление свайпа даже без скролла // Для появления: накапливаем pull down дельту, нужен сильный жест val requestsNestedScroll = remember(hapticFeedback) { diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt index eb54a12..4b43821 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/components/ChatDetailComponents.kt @@ -1898,12 +1898,12 @@ fun AnimatedMessageStatus( } } else { Icon( - painter = + painter = when (currentStatus) { MessageStatus.SENDING -> TelegramIcons.Clock MessageStatus.SENT -> - TelegramIcons.Done + TelegramIcons.Clock MessageStatus.DELIVERED -> TelegramIcons.Done else -> TelegramIcons.Clock @@ -2573,68 +2573,130 @@ private fun ForwardedImagePreview( /** Message skeleton loader with shimmer animation */ @Composable -fun MessageSkeletonList(isDarkTheme: Boolean, modifier: Modifier = Modifier) { - val skeletonColor = if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE0E0E0) - - val infiniteTransition = rememberInfiniteTransition(label = "shimmer") - val shimmerAlpha by - infiniteTransition.animateFloat( - initialValue = 0.4f, - targetValue = 0.8f, +fun MessageSkeletonList( + isDarkTheme: Boolean, + isGroupChat: Boolean, + modifier: Modifier = Modifier +) { + val transition = rememberInfiniteTransition(label = "telegramSkeleton") + val shimmerProgress by + transition.animateFloat( + initialValue = -1f, + targetValue = 2f, animationSpec = infiniteRepeatable( - animation = tween(800, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse + animation = tween(1300, easing = LinearEasing), + repeatMode = RepeatMode.Restart ), - label = "shimmerAlpha" + label = "telegramSkeletonProgress" ) - Box(modifier = modifier) { + // Telegram-style deterministic pseudo-randomness. + val widthRandom = remember { floatArrayOf(0.18f, 0.74f, 0.32f, 0.61f, 0.27f, 0.84f, 0.49f, 0.12f) } + val heightRandom = remember { floatArrayOf(0.25f, 0.68f, 0.14f, 0.52f, 0.37f, 0.79f, 0.29f, 0.57f) } + + val bubbleShape = + remember { + RoundedCornerShape( + topStart = TelegramBubbleSpec.bubbleRadius, + topEnd = TelegramBubbleSpec.bubbleRadius, + bottomStart = TelegramBubbleSpec.nearRadius, + bottomEnd = TelegramBubbleSpec.bubbleRadius + ) + } + + BoxWithConstraints(modifier = modifier.fillMaxSize()) { + val density = LocalDensity.current + val maxWidthPx = with(density) { maxWidth.toPx() }.coerceAtLeast(1f) + val gradientWidthPx = with(density) { 200.dp.toPx() } + val shimmerX = shimmerProgress * maxWidthPx + + val color0 = + if (isDarkTheme) Color(0x36FFFFFF) else Color(0x1F000000) + val color1 = + if (isDarkTheme) Color(0x1CFFFFFF) else Color(0x12000000) + val outlineColor = + if (isDarkTheme) Color(0x40FFFFFF) else Color(0x24FFFFFF) + + val shimmerBrush = + Brush.linearGradient( + colorStops = + arrayOf( + 0f to color1, + 0.4f to color0, + 0.6f to color0, + 1f to color1 + ), + start = Offset(shimmerX - gradientWidthPx, 0f), + end = Offset(shimmerX, 0f) + ) + + val bottomInset = 58.dp + val containerWidth = this@BoxWithConstraints.maxWidth + val bubbleMaxWidth = (containerWidth * 0.8f) - if (isGroupChat) 42.dp else 0.dp + val avatarGap = 6.dp + + val availableHeight = (maxHeight - bottomInset).coerceAtLeast(0.dp) + var usedHeight = 0.dp + var rowCount = 0 + while (rowCount < 24 && usedHeight < availableHeight) { + val randomHeight = heightRandom[rowCount % heightRandom.size] + usedHeight += (64.dp + (randomHeight * 64f).dp + 3.dp) + rowCount++ + } + if (rowCount < 6) rowCount = 6 + Column( modifier = - Modifier.align(Alignment.BottomCenter) + Modifier.align(Alignment.BottomStart) .fillMaxWidth() - .padding(horizontal = 8.dp) - .padding(bottom = 80.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) + .padding(start = 3.dp, end = 8.dp, bottom = bottomInset), + verticalArrangement = Arrangement.spacedBy(3.dp) ) { - SkeletonBubble(true, 0.45f, skeletonColor, shimmerAlpha) - SkeletonBubble(false, 0.55f, skeletonColor, shimmerAlpha) - SkeletonBubble(true, 0.35f, skeletonColor, shimmerAlpha) - SkeletonBubble(false, 0.50f, skeletonColor, shimmerAlpha) - SkeletonBubble(true, 0.60f, skeletonColor, shimmerAlpha) - SkeletonBubble(false, 0.40f, skeletonColor, shimmerAlpha) - } - } -} - -@Composable -private fun SkeletonBubble( - isOutgoing: Boolean, - widthFraction: Float, - bubbleColor: Color, - alpha: Float -) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = if (isOutgoing) Arrangement.End else Arrangement.Start - ) { - Box( - modifier = - Modifier.fillMaxWidth(widthFraction) - .defaultMinSize(minHeight = 44.dp) - .clip( - RoundedCornerShape( - topStart = 18.dp, - topEnd = 18.dp, - bottomStart = - if (isOutgoing) 18.dp else 6.dp, - bottomEnd = if (isOutgoing) 6.dp else 18.dp - ) + repeat(rowCount) { index -> + val lineWidth = + minOf( + bubbleMaxWidth, + 42.dp + + containerWidth * + (0.4f + + widthRandom[index % widthRandom.size] * + 0.35f) ) - .background(bubbleColor.copy(alpha = alpha)) - .padding(horizontal = 14.dp, vertical = 10.dp) - ) + val lineHeight = + 64.dp + + (heightRandom[index % heightRandom.size] * 64f).dp + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.Bottom + ) { + if (isGroupChat) { + Box( + modifier = + Modifier.size(42.dp) + .clip(CircleShape) + .background(shimmerBrush) + .border(1.dp, outlineColor, CircleShape) + ) + Spacer(modifier = Modifier.width(avatarGap)) + } + + Box( + modifier = + Modifier.width(lineWidth) + .height(lineHeight) + .clip(bubbleShape) + .background(shimmerBrush) + .border( + width = 1.dp, + color = outlineColor, + shape = bubbleShape + ) + ) + } + } + } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt b/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt index 954da89..f555119 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/components/AppleEmojiEditText.kt @@ -4,7 +4,9 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Paint import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable import android.net.Uri import android.text.Editable import android.text.SpannableStringBuilder @@ -12,6 +14,7 @@ import android.text.TextPaint import android.text.TextWatcher import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan +import android.text.style.DynamicDrawableSpan import android.text.style.ForegroundColorSpan import android.text.style.ImageSpan import android.util.AttributeSet @@ -32,6 +35,43 @@ import androidx.compose.foundation.background import androidx.compose.ui.viewinterop.AndroidView import java.util.regex.Pattern +private class TelegramLikeEmojiSpan( + emojiDrawable: Drawable, + private var sourceFontMetrics: Paint.FontMetricsInt? +) : ImageSpan(emojiDrawable, DynamicDrawableSpan.ALIGN_BOTTOM) { + + private var sizePx: Int = resolveSize(sourceFontMetrics) + + private fun resolveSize(metrics: Paint.FontMetricsInt?): Int { + val metricsSize = metrics?.let { kotlin.math.abs(it.descent) + kotlin.math.abs(it.ascent) } ?: 0 + if (metricsSize > 0) return metricsSize + val intrinsic = drawable.intrinsicHeight + return if (intrinsic > 0) intrinsic else 20 + } + + override fun getSize( + paint: Paint, + text: CharSequence, + start: Int, + end: Int, + fm: Paint.FontMetricsInt? + ): Int { + val metrics = sourceFontMetrics ?: paint.fontMetricsInt + sourceFontMetrics = metrics + sizePx = resolveSize(metrics) + val scaledSize = sizePx.coerceAtLeast(1) + drawable.setBounds(0, 0, scaledSize, scaledSize) + fm?.let { + it.ascent = metrics.ascent + it.descent = metrics.descent + it.top = metrics.top + it.bottom = metrics.bottom + it.leading = metrics.leading + } + return scaledSize + } +} + /** * Apple Emoji EditText - кастомный EditText с PNG эмодзи * Заменяет системные эмодзи на Apple PNG изображения из assets @@ -98,9 +138,8 @@ class AppleEmojiEditTextView @JvmOverloads constructor( // 🔥 Паттерн для :emoji_XXXX: формата (как в десктопе) val EMOJI_CODE_PATTERN: Pattern = Pattern.compile(":emoji_([a-fA-F0-9_-]+):") - // Кэш для bitmap и drawable + // Кэш bitmap private val bitmapCache = LruCache(500) - private val drawableCache = LruCache(500) } init { @@ -195,27 +234,17 @@ class AppleEmojiEditTextView @JvmOverloads constructor( if (existingSpans.isNotEmpty()) continue val unified = match.unified - var drawable = drawableCache.get(unified) - - if (drawable == null) { - var bitmap = bitmapCache.get(unified) - if (bitmap == null) { - bitmap = loadFromAssets(unified) - if (bitmap != null) { - bitmapCache.put(unified, bitmap) - } - } - + var bitmap = bitmapCache.get(unified) + if (bitmap == null) { + bitmap = loadFromAssets(unified) if (bitmap != null) { - drawable = BitmapDrawable(getContext().resources, bitmap) - val size = (textSize * 1.15).toInt() - drawable.setBounds(0, 0, size, size) - drawableCache.put(unified, drawable) + bitmapCache.put(unified, bitmap) } } - if (drawable != null && start < editable.length && end <= editable.length) { - val imageSpan = ImageSpan(drawable, ImageSpan.ALIGN_CENTER) + if (bitmap != null && start < editable.length && end <= editable.length) { + val drawable = BitmapDrawable(resources, bitmap) + val imageSpan = TelegramLikeEmojiSpan(drawable, paint.fontMetricsInt) editable.setSpan(imageSpan, start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) } } @@ -577,12 +606,8 @@ class AppleEmojiTextView @JvmOverloads constructor( for (match in emojiMatches) { val bitmap = loadEmojiBitmap(match.unified) if (bitmap != null) { - val size = (textSize * 1.3).toInt() - val scaledBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true) - val drawable = BitmapDrawable(resources, scaledBitmap) - drawable.setBounds(0, 0, size, size) - - val span = ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM) + val drawable = BitmapDrawable(resources, bitmap) + val span = TelegramLikeEmojiSpan(drawable, paint.fontMetricsInt) // Для :emoji_XXXX: заменяем весь текст на пробел + span // Для Unicode эмодзи оставляем символ как есть 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 1e29be9..b2e9868 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 @@ -253,7 +253,8 @@ fun BackupScreen( .fillMaxWidth() .height(52.dp), colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF248AE6) + containerColor = Color(0xFF248AE6), + contentColor = Color.White ), shape = RoundedCornerShape(10.dp) ) { @@ -266,7 +267,8 @@ fun BackupScreen( Text( text = "Copy Seed Phrase", fontSize = 15.sp, - fontWeight = FontWeight.Medium + fontWeight = FontWeight.Medium, + color = Color.White ) } diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt index f947017..feeef34 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/OtherProfileScreen.kt @@ -2138,9 +2138,9 @@ private fun CollapsingOtherProfileHeader( Modifier.align(Alignment.TopCenter).offset(y = textY), horizontalAlignment = Alignment.CenterHorizontally ) { + val verifiedBadgeSize = (nameFontSize.value * 0.8f).toInt() // Name + Verified Badge Row( - verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( @@ -2150,18 +2150,23 @@ private fun CollapsingOtherProfileHeader( color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.widthIn(max = 220.dp), + modifier = Modifier.widthIn(max = 220.dp).alignByBaseline(), textAlign = TextAlign.Center ) if (verified > 0 || isRosettaOfficial || isFreddyOfficial) { - Spacer(modifier = Modifier.width(4.dp)) - VerifiedBadge( - verified = if (verified > 0) verified else 1, - size = (nameFontSize.value * 0.8f).toInt(), - isDarkTheme = isDarkTheme, - badgeTint = if (isDarkTheme) Color.White else PrimaryBlue - ) + Box( + modifier = + Modifier.padding(start = 4.dp) + .alignBy { (it.measuredHeight * 0.78f).toInt() } + ) { + VerifiedBadge( + verified = if (verified > 0) verified else 1, + size = verifiedBadgeSize, + isDarkTheme = isDarkTheme, + badgeTint = Color.White + ) + } } } diff --git a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt index 43036a9..64ea794 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/settings/ProfileScreen.kt @@ -1389,8 +1389,8 @@ private fun CollapsingProfileHeader( Modifier.align(Alignment.TopCenter).offset(y = textY), horizontalAlignment = Alignment.CenterHorizontally ) { + val verifiedBadgeSize = (nameFontSize.value * 0.8f).toInt() Row( - verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( @@ -1400,17 +1400,22 @@ private fun CollapsingProfileHeader( color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.widthIn(max = 220.dp), + modifier = Modifier.widthIn(max = 220.dp).alignByBaseline(), textAlign = TextAlign.Center ) if (verified > 0 || isRosettaOfficial || isFreddyOfficial) { - Spacer(modifier = Modifier.width(4.dp)) - VerifiedBadge( - verified = if (verified > 0) verified else 2, - size = (nameFontSize.value * 0.8f).toInt(), - isDarkTheme = isDarkTheme, - badgeTint = if (isDarkTheme) Color.White else PrimaryBlue - ) + Box( + modifier = + Modifier.padding(start = 4.dp) + .alignBy { (it.measuredHeight * 0.78f).toInt() } + ) { + VerifiedBadge( + verified = if (verified > 0) verified else 2, + size = verifiedBadgeSize, + isDarkTheme = isDarkTheme, + badgeTint = Color.White + ) + } } }