Compare commits
11 Commits
618b9d720e
...
d5b6ca3a7e
| Author | SHA1 | Date | |
|---|---|---|---|
| d5b6ca3a7e | |||
| 9e7a2e4998 | |||
| 822f982332 | |||
| 64c767823c | |||
| dc16ada30b | |||
| 297309db1f | |||
| b01b2902b3 | |||
| 398f460a60 | |||
| 89f3561358 | |||
| 9c5c92eab6 | |||
| 4fd73f23ea |
19
RELEASE_NOTES.md
Normal file
19
RELEASE_NOTES.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Release Notes
|
||||||
|
|
||||||
|
## 1.2.0 (обновление с 1.1.9)
|
||||||
|
|
||||||
|
- Синхронизированы индикаторы отправки между чат-листом и диалогом:
|
||||||
|
- до фактической доставки показываются часы;
|
||||||
|
- ошибки отправки корректно отображаются внутри диалога.
|
||||||
|
- Доработан skeleton загрузки сообщений в диалоге в стиле Telegram:
|
||||||
|
- shimmer-анимация;
|
||||||
|
- более реалистичные размеры и форма пузырей;
|
||||||
|
- поддержка групповых аватаров в skeleton.
|
||||||
|
- Добавлен плавный автоскролл вверх в чат-листе при появлении плашки подтверждения нового устройства.
|
||||||
|
- Выровнены verified-галочки по имени:
|
||||||
|
- в моем профиле;
|
||||||
|
- в чужом профиле;
|
||||||
|
- в боковом меню (sidebar).
|
||||||
|
- Улучшено выравнивание Apple-like emoji в тексте сообщений.
|
||||||
|
- В светлой теме кнопка `Copy Seed Phrase` отображается с белым текстом.
|
||||||
|
- Выполнены дополнительные UI-правки и полировка отображения в чате и профилях.
|
||||||
@@ -23,8 +23,8 @@ val gitShortSha = safeGitOutput("rev-parse", "--short", "HEAD") ?: "unknown"
|
|||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
// Rosetta versioning — bump here on each release
|
// Rosetta versioning — bump here on each release
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════
|
||||||
val rosettaVersionName = "1.1.9"
|
val rosettaVersionName = "1.2.0"
|
||||||
val rosettaVersionCode = 21 // Increment on each release
|
val rosettaVersionCode = 22 // Increment on each release
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.rosetta.messenger"
|
namespace = "com.rosetta.messenger"
|
||||||
|
|||||||
@@ -600,6 +600,9 @@ class MessageRepository private constructor(private val context: Context) {
|
|||||||
// При ошибке обновляем статус
|
// При ошибке обновляем статус
|
||||||
messageDao.updateDeliveryStatus(account, messageId, DeliveryStatus.ERROR.value)
|
messageDao.updateDeliveryStatus(account, messageId, DeliveryStatus.ERROR.value)
|
||||||
updateMessageStatus(dialogKey, messageId, DeliveryStatus.ERROR)
|
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)
|
messageDao.updateDeliveryStatus(account, entity.messageId, DeliveryStatus.ERROR.value)
|
||||||
val dialogKey = getDialogKey(entity.toPublicKey)
|
val dialogKey = getDialogKey(entity.toPublicKey)
|
||||||
updateMessageStatus(dialogKey, entity.messageId, DeliveryStatus.ERROR)
|
updateMessageStatus(dialogKey, entity.messageId, DeliveryStatus.ERROR)
|
||||||
|
_deliveryStatusEvents.tryEmit(
|
||||||
|
DeliveryStatusUpdate(dialogKey, entity.messageId, DeliveryStatus.ERROR)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,23 +17,18 @@ object ReleaseNotes {
|
|||||||
val RELEASE_NOTICE = """
|
val RELEASE_NOTICE = """
|
||||||
Update v$VERSION_PLACEHOLDER
|
Update v$VERSION_PLACEHOLDER
|
||||||
|
|
||||||
|
Отправка сообщений
|
||||||
|
- Синхронизированы индикаторы отправки между чат-листом и диалогом: до фактической доставки показываются часы; ошибки отправки корректно отображаются внутри диалога
|
||||||
|
|
||||||
|
Загрузка сообщений
|
||||||
|
- Доработан skeleton загрузки сообщений в диалоге в стиле Telegram: shimmer-анимация, более реалистичные размеры и форма пузырей, поддержка групповых аватаров в skeleton
|
||||||
|
|
||||||
Интерфейс
|
Интерфейс
|
||||||
- Исправлен цвет галочки верификации в сайдбаре в зависимости от темы
|
- Добавлен плавный автоскролл вверх в чат-листе при появлении плашки подтверждения нового устройства
|
||||||
- Исправлен цвет галочки верификации в профилях и попапах в светлой теме
|
- Выровнены verified-галочки по имени: в моем профиле, в чужом профиле, в боковом меню (sidebar)
|
||||||
- Исправлен черный gesture navigation bar при fullscreen фото
|
- Улучшено выравнивание Apple-like emoji в тексте сообщений
|
||||||
|
- В светлой теме кнопка Copy Seed Phrase отображается с белым текстом
|
||||||
Фото и редактор
|
- Дополнительные UI-правки и полировка отображения в чате и профилях
|
||||||
- При рисовании на фото теперь скрываются инпут и лишние оверлеи
|
|
||||||
- Синхронизировано поведение системных баров в полноэкранном фото-режиме
|
|
||||||
|
|
||||||
Сообщения
|
|
||||||
- Логика read-индикаторов внутри диалога приведена к логике чат-листа
|
|
||||||
- Убраны неверные переходы статусов сообщений при read/delivered событиях
|
|
||||||
|
|
||||||
Обновления приложения
|
|
||||||
- Загрузка апдейта переведена на системный DownloadManager
|
|
||||||
- Скачивание обновления продолжается после сворачивания/выхода из приложения
|
|
||||||
- Прогресс и состояние установки апдейта восстанавливаются после повторного запуска
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
fun getNotice(version: String): String =
|
fun getNotice(version: String): String =
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ import com.rosetta.messenger.ui.components.AvatarImage
|
|||||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
import com.rosetta.messenger.ui.settings.ThemeWallpapers
|
import com.rosetta.messenger.ui.settings.ThemeWallpapers
|
||||||
|
import com.rosetta.messenger.ui.utils.NavigationModeUtils
|
||||||
import com.rosetta.messenger.ui.utils.SystemBarsStyleUtils
|
import com.rosetta.messenger.ui.utils.SystemBarsStyleUtils
|
||||||
import com.rosetta.messenger.utils.MediaUtils
|
import com.rosetta.messenger.utils.MediaUtils
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -209,7 +210,7 @@ fun ChatDetailScreen(
|
|||||||
val chatWallpaperResId = remember(chatWallpaperId) { ThemeWallpapers.drawableResOrNull(chatWallpaperId) }
|
val chatWallpaperResId = remember(chatWallpaperId) { ThemeWallpapers.drawableResOrNull(chatWallpaperId) }
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
|
||||||
val dateHeaderTextColor = if (isDarkTheme) Color.White else secondaryTextColor
|
val dateHeaderTextColor = if (isDarkTheme || chatWallpaperResId != null) Color.White else secondaryTextColor
|
||||||
val headerIconColor = Color.White
|
val headerIconColor = Color.White
|
||||||
|
|
||||||
// 🔥 Keyboard & Emoji Coordinator
|
// 🔥 Keyboard & Emoji Coordinator
|
||||||
@@ -332,6 +333,12 @@ fun ChatDetailScreen(
|
|||||||
val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
|
val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
|
||||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
ic.isAppearanceLightStatusBars = false
|
ic.isAppearanceLightStatusBars = false
|
||||||
|
NavigationModeUtils.applyNavigationBarVisibility(
|
||||||
|
window = window,
|
||||||
|
insetsController = ic,
|
||||||
|
context = context,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,6 +351,12 @@ fun ChatDetailScreen(
|
|||||||
val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
|
val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
|
||||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||||
ic.isAppearanceLightStatusBars = false
|
ic.isAppearanceLightStatusBars = false
|
||||||
|
NavigationModeUtils.applyNavigationBarVisibility(
|
||||||
|
window = window,
|
||||||
|
insetsController = ic,
|
||||||
|
context = context,
|
||||||
|
isDarkTheme = isDarkTheme
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1151,7 +1164,7 @@ fun ChatDetailScreen(
|
|||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier.fillMaxWidth()
|
||||||
.height(56.dp)
|
.height(64.dp)
|
||||||
.padding(
|
.padding(
|
||||||
horizontal =
|
horizontal =
|
||||||
4.dp
|
4.dp
|
||||||
@@ -1323,7 +1336,7 @@ fun ChatDetailScreen(
|
|||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier.fillMaxWidth()
|
||||||
.height(56.dp)
|
.height(64.dp)
|
||||||
.padding(
|
.padding(
|
||||||
horizontal =
|
horizontal =
|
||||||
4.dp
|
4.dp
|
||||||
@@ -2297,6 +2310,7 @@ fun ChatDetailScreen(
|
|||||||
isLoading -> {
|
isLoading -> {
|
||||||
MessageSkeletonList(
|
MessageSkeletonList(
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
|
isGroupChat = isGroupChat,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxSize()
|
Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
@@ -2385,7 +2399,7 @@ fun ChatDetailScreen(
|
|||||||
"No messages yet",
|
"No messages yet",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color =
|
color =
|
||||||
secondaryTextColor,
|
dateHeaderTextColor,
|
||||||
fontWeight =
|
fontWeight =
|
||||||
FontWeight
|
FontWeight
|
||||||
.Medium
|
.Medium
|
||||||
@@ -2405,7 +2419,7 @@ fun ChatDetailScreen(
|
|||||||
"Send a message to start the conversation",
|
"Send a message to start the conversation",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color =
|
color =
|
||||||
secondaryTextColor
|
dateHeaderTextColor
|
||||||
.copy(
|
.copy(
|
||||||
alpha =
|
alpha =
|
||||||
0.7f
|
0.7f
|
||||||
|
|||||||
@@ -425,6 +425,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Обновляем конкретное сообщение
|
// Обновляем конкретное сообщение
|
||||||
updateMessageStatus(update.messageId, MessageStatus.DELIVERED)
|
updateMessageStatus(update.messageId, MessageStatus.DELIVERED)
|
||||||
}
|
}
|
||||||
|
DeliveryStatus.ERROR -> {
|
||||||
|
// Синхронизируем ошибку отправки с открытым диалогом
|
||||||
|
updateMessageStatus(update.messageId, MessageStatus.ERROR)
|
||||||
|
}
|
||||||
DeliveryStatus.READ -> {
|
DeliveryStatus.READ -> {
|
||||||
// Помечаем все исходящие как прочитанные
|
// Помечаем все исходящие как прочитанные
|
||||||
markAllOutgoingAsRead()
|
markAllOutgoingAsRead()
|
||||||
@@ -440,9 +444,19 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private fun markAllOutgoingAsRead() {
|
private fun markAllOutgoingAsRead() {
|
||||||
_messages.value =
|
_messages.value =
|
||||||
_messages.value.map { msg ->
|
_messages.value.map { msg ->
|
||||||
if (msg.isOutgoing && msg.status != MessageStatus.READ) {
|
if (!msg.isOutgoing) return@map msg
|
||||||
msg.copy(status = MessageStatus.READ)
|
|
||||||
} else msg
|
val nextStatus =
|
||||||
|
when (msg.status) {
|
||||||
|
// Read event can promote only already-sent/delivered messages.
|
||||||
|
MessageStatus.SENT,
|
||||||
|
MessageStatus.DELIVERED,
|
||||||
|
MessageStatus.READ -> MessageStatus.READ
|
||||||
|
MessageStatus.SENDING,
|
||||||
|
MessageStatus.ERROR -> msg.status
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextStatus != msg.status) msg.copy(status = nextStatus) else msg
|
||||||
}
|
}
|
||||||
updateCacheFromCurrentMessages()
|
updateCacheFromCurrentMessages()
|
||||||
}
|
}
|
||||||
@@ -479,7 +493,17 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
private fun updateMessageStatus(messageId: String, status: MessageStatus) {
|
private fun updateMessageStatus(messageId: String, status: MessageStatus) {
|
||||||
_messages.value =
|
_messages.value =
|
||||||
_messages.value.map { msg ->
|
_messages.value.map { msg ->
|
||||||
if (msg.id == messageId) msg.copy(status = status) else msg
|
if (msg.id != messageId) return@map msg
|
||||||
|
|
||||||
|
// Keep read status monotonic: late DELIVERED must not downgrade READ.
|
||||||
|
val mergedStatus =
|
||||||
|
when (status) {
|
||||||
|
MessageStatus.DELIVERED ->
|
||||||
|
if (msg.status == MessageStatus.READ) MessageStatus.READ
|
||||||
|
else MessageStatus.DELIVERED
|
||||||
|
else -> status
|
||||||
|
}
|
||||||
|
if (mergedStatus != msg.status) msg.copy(status = mergedStatus) else msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Также обновляем кэш!
|
// 🔥 Также обновляем кэш!
|
||||||
|
|||||||
@@ -928,29 +928,30 @@ fun ChatsListScreen(
|
|||||||
// Display name
|
// Display name
|
||||||
if (accountName.isNotEmpty()) {
|
if (accountName.isNotEmpty()) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
horizontalArrangement = Arrangement.Start
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = accountName,
|
text = accountName,
|
||||||
fontSize = 15.sp,
|
fontSize = 15.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = Color.White
|
color = Color.White,
|
||||||
|
modifier = Modifier.alignByBaseline()
|
||||||
)
|
)
|
||||||
if (accountVerified > 0 || isRosettaOfficial || isFreddyOfficial) {
|
if (accountVerified > 0 || isRosettaOfficial || isFreddyOfficial) {
|
||||||
Spacer(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.width(
|
Modifier.padding(start = 2.dp)
|
||||||
6.dp
|
.alignBy { (it.measuredHeight * 0.78f).toInt() }
|
||||||
)
|
) {
|
||||||
)
|
|
||||||
VerifiedBadge(
|
VerifiedBadge(
|
||||||
verified = if (accountVerified > 0) accountVerified else 1,
|
verified = if (accountVerified > 0) accountVerified else 1,
|
||||||
size = 15,
|
size = 15,
|
||||||
badgeTint = if (isDarkTheme) Color.White else PrimaryBlue
|
badgeTint = if (isDarkTheme) PrimaryBlue else Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Username
|
// Username
|
||||||
if (accountUsername.isNotEmpty()) {
|
if (accountUsername.isNotEmpty()) {
|
||||||
@@ -2066,8 +2067,34 @@ fun ChatsListScreen(
|
|||||||
// Track scroll direction to hide/show Requests
|
// Track scroll direction to hide/show Requests
|
||||||
val chatListState = rememberLazyListState()
|
val chatListState = rememberLazyListState()
|
||||||
var isRequestsVisible by remember { mutableStateOf(true) }
|
var isRequestsVisible by remember { mutableStateOf(true) }
|
||||||
|
var lastAutoScrolledVerificationId by remember {
|
||||||
|
mutableStateOf<String?>(null)
|
||||||
|
}
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
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 — ловим направление свайпа даже без скролла
|
// NestedScroll — ловим направление свайпа даже без скролла
|
||||||
// Для появления: накапливаем pull down дельту, нужен сильный жест
|
// Для появления: накапливаем pull down дельту, нужен сильный жест
|
||||||
val requestsNestedScroll = remember(hapticFeedback) {
|
val requestsNestedScroll = remember(hapticFeedback) {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import com.rosetta.messenger.repository.AvatarRepository
|
import com.rosetta.messenger.repository.AvatarRepository
|
||||||
@@ -224,7 +226,13 @@ fun SearchScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize().pointerInput(Unit) {
|
||||||
|
detectHorizontalDragGestures { _, dragAmount ->
|
||||||
|
if (dragAmount > 10f) {
|
||||||
|
hideKeyboardInstantly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
// Хедер как в Telegram: стрелка назад + поле ввода
|
// Хедер как в Telegram: стрелка назад + поле ввода
|
||||||
|
|||||||
@@ -752,18 +752,23 @@ fun MessageBubble(
|
|||||||
280.dp
|
280.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasGroupSenderName = showGroupSenderLabel && !message.isOutgoing && senderName.isNotBlank()
|
||||||
val bubbleWidthModifier =
|
val bubbleWidthModifier =
|
||||||
if (isSafeSystemMessage) {
|
if (isSafeSystemMessage) {
|
||||||
Modifier.widthIn(min = 220.dp, max = 320.dp)
|
Modifier.widthIn(min = 220.dp, max = 320.dp)
|
||||||
} else if (isStandaloneGroupInvite) {
|
} else if (isStandaloneGroupInvite) {
|
||||||
Modifier.widthIn(min = 180.dp, max = 260.dp)
|
Modifier.widthIn(min = 180.dp, max = 260.dp)
|
||||||
} else if (hasImageWithCaption || hasOnlyMedia) {
|
} else if (hasImageWithCaption || hasOnlyMedia) {
|
||||||
|
if (hasGroupSenderName) {
|
||||||
|
Modifier.widthIn(min = photoWidth)
|
||||||
|
} else {
|
||||||
Modifier.width(
|
Modifier.width(
|
||||||
photoWidth
|
photoWidth
|
||||||
) // 🔥 Фиксированная ширина = размер фото (убирает лишний
|
) // 🔥 Фиксированная ширина = размер фото (убирает лишний
|
||||||
// отступ)
|
// отступ)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Modifier.widthIn(min = 60.dp, max = 280.dp)
|
Modifier.widthIn(min = if (hasGroupSenderName) 120.dp else 60.dp, max = 280.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -1903,7 +1908,7 @@ fun AnimatedMessageStatus(
|
|||||||
MessageStatus.SENDING ->
|
MessageStatus.SENDING ->
|
||||||
TelegramIcons.Clock
|
TelegramIcons.Clock
|
||||||
MessageStatus.SENT ->
|
MessageStatus.SENT ->
|
||||||
TelegramIcons.Done
|
TelegramIcons.Clock
|
||||||
MessageStatus.DELIVERED ->
|
MessageStatus.DELIVERED ->
|
||||||
TelegramIcons.Done
|
TelegramIcons.Done
|
||||||
else -> TelegramIcons.Clock
|
else -> TelegramIcons.Clock
|
||||||
@@ -2573,68 +2578,130 @@ private fun ForwardedImagePreview(
|
|||||||
|
|
||||||
/** Message skeleton loader with shimmer animation */
|
/** Message skeleton loader with shimmer animation */
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageSkeletonList(isDarkTheme: Boolean, modifier: Modifier = Modifier) {
|
fun MessageSkeletonList(
|
||||||
val skeletonColor = if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFE0E0E0)
|
isDarkTheme: Boolean,
|
||||||
|
isGroupChat: Boolean,
|
||||||
val infiniteTransition = rememberInfiniteTransition(label = "shimmer")
|
modifier: Modifier = Modifier
|
||||||
val shimmerAlpha by
|
) {
|
||||||
infiniteTransition.animateFloat(
|
val transition = rememberInfiniteTransition(label = "telegramSkeleton")
|
||||||
initialValue = 0.4f,
|
val shimmerProgress by
|
||||||
targetValue = 0.8f,
|
transition.animateFloat(
|
||||||
|
initialValue = -1f,
|
||||||
|
targetValue = 2f,
|
||||||
animationSpec =
|
animationSpec =
|
||||||
infiniteRepeatable(
|
infiniteRepeatable(
|
||||||
animation = tween(800, easing = FastOutSlowInEasing),
|
animation = tween(1300, easing = LinearEasing),
|
||||||
repeatMode = RepeatMode.Reverse
|
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(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.align(Alignment.BottomCenter)
|
Modifier.align(Alignment.BottomStart)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(start = 3.dp, end = 8.dp, bottom = bottomInset),
|
||||||
.padding(bottom = 80.dp),
|
verticalArrangement = Arrangement.spacedBy(3.dp)
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
|
||||||
) {
|
) {
|
||||||
SkeletonBubble(true, 0.45f, skeletonColor, shimmerAlpha)
|
repeat(rowCount) { index ->
|
||||||
SkeletonBubble(false, 0.55f, skeletonColor, shimmerAlpha)
|
val lineWidth =
|
||||||
SkeletonBubble(true, 0.35f, skeletonColor, shimmerAlpha)
|
minOf(
|
||||||
SkeletonBubble(false, 0.50f, skeletonColor, shimmerAlpha)
|
bubbleMaxWidth,
|
||||||
SkeletonBubble(true, 0.60f, skeletonColor, shimmerAlpha)
|
42.dp +
|
||||||
SkeletonBubble(false, 0.40f, skeletonColor, shimmerAlpha)
|
containerWidth *
|
||||||
}
|
(0.4f +
|
||||||
}
|
widthRandom[index % widthRandom.size] *
|
||||||
}
|
0.35f)
|
||||||
|
)
|
||||||
|
val lineHeight =
|
||||||
|
64.dp +
|
||||||
|
(heightRandom[index % heightRandom.size] * 64f).dp
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SkeletonBubble(
|
|
||||||
isOutgoing: Boolean,
|
|
||||||
widthFraction: Float,
|
|
||||||
bubbleColor: Color,
|
|
||||||
alpha: Float
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = if (isOutgoing) Arrangement.End else Arrangement.Start
|
verticalAlignment = Alignment.Bottom
|
||||||
) {
|
) {
|
||||||
|
if (isGroupChat) {
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth(widthFraction)
|
Modifier.size(42.dp)
|
||||||
.defaultMinSize(minHeight = 44.dp)
|
.clip(CircleShape)
|
||||||
.clip(
|
.background(shimmerBrush)
|
||||||
RoundedCornerShape(
|
.border(1.dp, outlineColor, CircleShape)
|
||||||
topStart = 18.dp,
|
)
|
||||||
topEnd = 18.dp,
|
Spacer(modifier = Modifier.width(avatarGap))
|
||||||
bottomStart =
|
}
|
||||||
if (isOutgoing) 18.dp else 6.dp,
|
|
||||||
bottomEnd = if (isOutgoing) 6.dp else 18.dp
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier.width(lineWidth)
|
||||||
|
.height(lineHeight)
|
||||||
|
.clip(bubbleShape)
|
||||||
|
.background(shimmerBrush)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = outlineColor,
|
||||||
|
shape = bubbleShape
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.background(bubbleColor.copy(alpha = alpha))
|
}
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp)
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
localCaption = value
|
localCaption = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val isDrawingModeActive = currentTool == EditorTool.DRAW
|
||||||
|
|
||||||
LaunchedEffect(imageUri, sourceThumbnail) {
|
LaunchedEffect(imageUri, sourceThumbnail) {
|
||||||
localCaption = caption
|
localCaption = caption
|
||||||
@@ -394,6 +395,15 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isDrawingModeActive) {
|
||||||
|
if (isDrawingModeActive) {
|
||||||
|
showEmojiPicker = false
|
||||||
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
|
focusManager.clearFocus(force = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun hideKeyboard() {
|
fun hideKeyboard() {
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
@@ -851,6 +861,7 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.padding(horizontal = 4.dp, vertical = 8.dp)
|
.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
if (!isDrawingModeActive) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { closeViewer() },
|
onClick = { closeViewer() },
|
||||||
modifier = Modifier.align(Alignment.CenterStart)
|
modifier = Modifier.align(Alignment.CenterStart)
|
||||||
@@ -863,6 +874,7 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = currentTool == EditorTool.DRAW && showColorPicker,
|
visible = currentTool == EditorTool.DRAW && showColorPicker,
|
||||||
@@ -990,6 +1002,11 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = !isDrawingModeActive,
|
||||||
|
enter = fadeIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier.fillMaxWidth()
|
||||||
@@ -1108,7 +1125,9 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDrawingModeActive) {
|
||||||
AnimatedKeyboardTransition(
|
AnimatedKeyboardTransition(
|
||||||
coordinator = coordinator,
|
coordinator = coordinator,
|
||||||
showEmojiPicker = showEmojiPicker
|
showEmojiPicker = showEmojiPicker
|
||||||
@@ -1124,4 +1143,5 @@ private fun SimpleFullscreenPhotoContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Paint
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
@@ -12,6 +14,7 @@ import android.text.TextPaint
|
|||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
|
import android.text.style.DynamicDrawableSpan
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
import android.text.style.ImageSpan
|
import android.text.style.ImageSpan
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
@@ -32,6 +35,43 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import java.util.regex.Pattern
|
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 Emoji EditText - кастомный EditText с PNG эмодзи
|
||||||
* Заменяет системные эмодзи на Apple PNG изображения из assets
|
* Заменяет системные эмодзи на Apple PNG изображения из assets
|
||||||
@@ -98,9 +138,8 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
|
|||||||
// 🔥 Паттерн для :emoji_XXXX: формата (как в десктопе)
|
// 🔥 Паттерн для :emoji_XXXX: формата (как в десктопе)
|
||||||
val EMOJI_CODE_PATTERN: Pattern = Pattern.compile(":emoji_([a-fA-F0-9_-]+):")
|
val EMOJI_CODE_PATTERN: Pattern = Pattern.compile(":emoji_([a-fA-F0-9_-]+):")
|
||||||
|
|
||||||
// Кэш для bitmap и drawable
|
// Кэш bitmap
|
||||||
private val bitmapCache = LruCache<String, Bitmap>(500)
|
private val bitmapCache = LruCache<String, Bitmap>(500)
|
||||||
private val drawableCache = LruCache<String, BitmapDrawable>(500)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -195,9 +234,6 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
|
|||||||
if (existingSpans.isNotEmpty()) continue
|
if (existingSpans.isNotEmpty()) continue
|
||||||
|
|
||||||
val unified = match.unified
|
val unified = match.unified
|
||||||
var drawable = drawableCache.get(unified)
|
|
||||||
|
|
||||||
if (drawable == null) {
|
|
||||||
var bitmap = bitmapCache.get(unified)
|
var bitmap = bitmapCache.get(unified)
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
bitmap = loadFromAssets(unified)
|
bitmap = loadFromAssets(unified)
|
||||||
@@ -206,16 +242,9 @@ class AppleEmojiEditTextView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bitmap != null) {
|
if (bitmap != null && start < editable.length && end <= editable.length) {
|
||||||
drawable = BitmapDrawable(getContext().resources, bitmap)
|
val drawable = BitmapDrawable(resources, bitmap)
|
||||||
val size = (textSize * 1.15).toInt()
|
val imageSpan = TelegramLikeEmojiSpan(drawable, paint.fontMetricsInt)
|
||||||
drawable.setBounds(0, 0, size, size)
|
|
||||||
drawableCache.put(unified, drawable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawable != null && start < editable.length && end <= editable.length) {
|
|
||||||
val imageSpan = ImageSpan(drawable, ImageSpan.ALIGN_CENTER)
|
|
||||||
editable.setSpan(imageSpan, start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
editable.setSpan(imageSpan, start, end, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,12 +606,8 @@ class AppleEmojiTextView @JvmOverloads constructor(
|
|||||||
for (match in emojiMatches) {
|
for (match in emojiMatches) {
|
||||||
val bitmap = loadEmojiBitmap(match.unified)
|
val bitmap = loadEmojiBitmap(match.unified)
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
val size = (textSize * 1.3).toInt()
|
val drawable = BitmapDrawable(resources, bitmap)
|
||||||
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true)
|
val span = TelegramLikeEmojiSpan(drawable, paint.fontMetricsInt)
|
||||||
val drawable = BitmapDrawable(resources, scaledBitmap)
|
|
||||||
drawable.setBounds(0, 0, size, size)
|
|
||||||
|
|
||||||
val span = ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM)
|
|
||||||
|
|
||||||
// Для :emoji_XXXX: заменяем весь текст на пробел + span
|
// Для :emoji_XXXX: заменяем весь текст на пробел + span
|
||||||
// Для Unicode эмодзи оставляем символ как есть
|
// Для Unicode эмодзи оставляем символ как есть
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fun VerifiedBadge(
|
|||||||
|
|
||||||
// Цвет верификации: в тёмной теме — как индикаторы прочтения (PrimaryBlue), в светлой — #ACD2F9
|
// Цвет верификации: в тёмной теме — как индикаторы прочтения (PrimaryBlue), в светлой — #ACD2F9
|
||||||
val badgeColor =
|
val badgeColor =
|
||||||
badgeTint ?: if (isDarkTheme) PrimaryBlue else Color(0xFFACD2F9)
|
badgeTint ?: PrimaryBlue
|
||||||
|
|
||||||
// Текст аннотации
|
// Текст аннотации
|
||||||
val annotationText = when (verified) {
|
val annotationText = when (verified) {
|
||||||
|
|||||||
@@ -253,7 +253,8 @@ fun BackupScreen(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(52.dp),
|
.height(52.dp),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = Color(0xFF248AE6)
|
containerColor = Color(0xFF248AE6),
|
||||||
|
contentColor = Color.White
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(10.dp)
|
shape = RoundedCornerShape(10.dp)
|
||||||
) {
|
) {
|
||||||
@@ -266,7 +267,8 @@ fun BackupScreen(
|
|||||||
Text(
|
Text(
|
||||||
text = "Copy Seed Phrase",
|
text = "Copy Seed Phrase",
|
||||||
fontSize = 15.sp,
|
fontSize = 15.sp,
|
||||||
fontWeight = FontWeight.Medium
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2138,9 +2138,9 @@ private fun CollapsingOtherProfileHeader(
|
|||||||
Modifier.align(Alignment.TopCenter).offset(y = textY),
|
Modifier.align(Alignment.TopCenter).offset(y = textY),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
val verifiedBadgeSize = (nameFontSize.value * 0.8f).toInt()
|
||||||
// Name + Verified Badge
|
// Name + Verified Badge
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -2150,20 +2150,25 @@ private fun CollapsingOtherProfileHeader(
|
|||||||
color = textColor,
|
color = textColor,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.widthIn(max = 220.dp),
|
modifier = Modifier.widthIn(max = 220.dp).alignByBaseline(),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|
||||||
if (verified > 0 || isRosettaOfficial || isFreddyOfficial) {
|
if (verified > 0 || isRosettaOfficial || isFreddyOfficial) {
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier.padding(start = 4.dp)
|
||||||
|
.alignBy { (it.measuredHeight * 0.78f).toInt() }
|
||||||
|
) {
|
||||||
VerifiedBadge(
|
VerifiedBadge(
|
||||||
verified = if (verified > 0) verified else 1,
|
verified = if (verified > 0) verified else 1,
|
||||||
size = (nameFontSize.value * 0.8f).toInt(),
|
size = verifiedBadgeSize,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
badgeTint = Color.White
|
badgeTint = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
|
|||||||
@@ -1389,8 +1389,8 @@ private fun CollapsingProfileHeader(
|
|||||||
Modifier.align(Alignment.TopCenter).offset(y = textY),
|
Modifier.align(Alignment.TopCenter).offset(y = textY),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
val verifiedBadgeSize = (nameFontSize.value * 0.8f).toInt()
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -1400,18 +1400,24 @@ private fun CollapsingProfileHeader(
|
|||||||
color = textColor,
|
color = textColor,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.widthIn(max = 220.dp),
|
modifier = Modifier.widthIn(max = 220.dp).alignByBaseline(),
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
if (verified > 0 || isRosettaOfficial || isFreddyOfficial) {
|
if (verified > 0 || isRosettaOfficial || isFreddyOfficial) {
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier.padding(start = 4.dp)
|
||||||
|
.alignBy { (it.measuredHeight * 0.78f).toInt() }
|
||||||
|
) {
|
||||||
VerifiedBadge(
|
VerifiedBadge(
|
||||||
verified = if (verified > 0) verified else 2,
|
verified = if (verified > 0) verified else 2,
|
||||||
size = (nameFontSize.value * 0.8f).toInt(),
|
size = verifiedBadgeSize,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme,
|
||||||
|
badgeTint = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user