diff --git a/app/build.gradle.kts b/app/build.gradle.kts index be7a6c9..53e2e4e 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.2.6" -val rosettaVersionCode = 28 // Increment on each release +val rosettaVersionName = "1.2.7" +val rosettaVersionCode = 29 // Increment on each release android { namespace = "com.rosetta.messenger" diff --git a/app/src/main/java/com/rosetta/messenger/data/ReleaseNotes.kt b/app/src/main/java/com/rosetta/messenger/data/ReleaseNotes.kt index b9d67d6..80fa5f4 100644 --- a/app/src/main/java/com/rosetta/messenger/data/ReleaseNotes.kt +++ b/app/src/main/java/com/rosetta/messenger/data/ReleaseNotes.kt @@ -17,18 +17,30 @@ object ReleaseNotes { val RELEASE_NOTICE = """ Update v$VERSION_PLACEHOLDER - Синхронизация статусов (desktop parity) - - Исправлено ложное «ошибка отправки» для сообщений, пришедших в sync с других устройств - - Синхронизированные исходящие теперь сразу помечаются как доставленные, как в desktop - - Добавлена авто-нормализация старых sync-сообщений со статусами WAITING/ERROR в DELIVERED + Поиск + - Добавлена вкладка Messages в поиске: поиск по тексту сообщений по всем чатам + - Реализованы быстрые сниппеты с подсветкой найденного текста и переходом в нужный чат + - Добавлены алиасы для Saved Messages в поиске (saved / saved messages / избранное и др.) - Emoji-подсказки в поле ввода - - Переработан UI подсказок: отдельный плавающий пузырёк над инпутом без изменения его высоты - - Исправлен клиппинг/обрезание пузырька внизу экрана - - Рендер эмодзи в подсказках приведён к Apple-like отображению + Тэги и навигация + - Исправлены клики по @тэгам в сообщениях: теперь открывается чат пользователя + - Добавлен устойчивый резолв @username (локальный диалог -> кэш -> сервер) + - Устранен конфликт клика по тэгу с контекстным меню пузырька - Полировка интерфейса - - В списке чатов выровнен текст после «Draft:» по одной line-height линии + Чаты и UI + - Улучшен пустой экран Saved Messages на обоях: добавлена подложка и повышена читаемость + - Стабилизировано отображение verified-бейджа в хедере личного чата + - Подправлено положение галочки в сайдбаре + - В тёмной теме цвет цифры в бейдже Requests возле бургер-меню приведен к цвету шапки + + Темы и обои + - Добавлены пары обоев для светлой и темной темы + - Обои теперь автоматически синхронизируются при переключении темы + - Выбор обоев сохраняется отдельно для light/dark + + Безопасность и система + - Если устройство не поддерживает отпечаток пальца, биометрия больше не предлагается + - Удалена неиспользуемая зависимость jsoup """.trimIndent() fun getNotice(version: String): String = 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 4a66fb7..9fbc2ac 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 @@ -1377,8 +1377,35 @@ fun ChatDetailScreen( isDarkTheme ) + // Smooth transition when switching to another dialog from in-message mentions/tags. + // SwipeBackContainer stays mounted, so we animate content change locally. + var runDialogSwitchEnterAnimation by remember(user.publicKey) { mutableStateOf(false) } + LaunchedEffect(user.publicKey) { + runDialogSwitchEnterAnimation = false + withFrameNanos { } + runDialogSwitchEnterAnimation = true + } + val dialogSwitchAlpha by + animateFloatAsState( + targetValue = if (runDialogSwitchEnterAnimation) 1f else 0.9f, + animationSpec = tween(durationMillis = 240), + label = "dialogSwitchAlpha" + ) + val dialogSwitchOffsetX by + animateDpAsState( + targetValue = if (runDialogSwitchEnterAnimation) 0.dp else 24.dp, + animationSpec = tween(durationMillis = 240), + label = "dialogSwitchOffsetX" + ) + // 🚀 Весь контент (swipe-back обрабатывается в SwipeBackContainer) - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = + Modifier.fillMaxSize().graphicsLayer { + alpha = dialogSwitchAlpha + translationX = with(density) { dialogSwitchOffsetX.toPx() } + } + ) { // Telegram-style solid header background (без blur) val headerBackground = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFF228BE6) 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 3f2ef33..8957fcf 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 @@ -1612,6 +1612,8 @@ fun ChatsListScreen( } } val badgeBg = Color.White + val badgeTextColor = + if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFF228BE6) val badgeShape = RoundedCornerShape(50) Box( modifier = Modifier @@ -1630,7 +1632,7 @@ fun ChatsListScreen( text = badgeText, fontSize = 10.sp, fontWeight = FontWeight.Bold, - color = Color(0xFF228BE6), + color = badgeTextColor, lineHeight = 10.sp, modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp) ) diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/SearchScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/SearchScreen.kt index 5436b28..a4a100b 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/SearchScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/SearchScreen.kt @@ -1146,27 +1146,7 @@ private fun MessagesTabContent( } } isSearching -> { - // Loading state - Box( - modifier = Modifier - .fillMaxSize() - .imePadding(), - contentAlignment = Alignment.Center - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - CircularProgressIndicator( - modifier = Modifier.size(32.dp), - color = PrimaryBlue, - strokeWidth = 2.5.dp - ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = "Searching messages...", - fontSize = 14.sp, - color = secondaryTextColor - ) - } - } + MessageSearchSkeleton(isDarkTheme = isDarkTheme) } results.isEmpty() -> { // No results @@ -1236,6 +1216,107 @@ private fun MessagesTabContent( } } +@Composable +private fun MessageSearchSkeleton(isDarkTheme: Boolean) { + val shimmerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8) + val highlightColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFF5F5F5) + val dividerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8) + + val transition = rememberInfiniteTransition(label = "message_search_shimmer") + val translateAnim by transition.animateFloat( + initialValue = 0f, + targetValue = 1000f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 1200, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ), + label = "message_search_shimmer_translate" + ) + + val shimmerBrush = androidx.compose.ui.graphics.Brush.linearGradient( + colors = listOf(shimmerColor, highlightColor, shimmerColor), + start = androidx.compose.ui.geometry.Offset(translateAnim - 200f, 0f), + end = androidx.compose.ui.geometry.Offset(translateAnim, 0f) + ) + + LazyColumn( + modifier = Modifier.fillMaxSize().imePadding(), + contentPadding = PaddingValues(vertical = 4.dp) + ) { + items(8) { index -> + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = + Modifier.size(48.dp) + .clip(CircleShape) + .background(shimmerBrush) + ) + + Spacer(modifier = Modifier.width(12.dp)) + + Column(modifier = Modifier.weight(1f)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = + Modifier.fillMaxWidth( + fraction = if (index % 2 == 0) 0.48f else 0.62f + ) + .height(14.dp) + .clip(RoundedCornerShape(4.dp)) + .background(shimmerBrush) + ) + Spacer(modifier = Modifier.width(10.dp)) + Box( + modifier = + Modifier.width(if (index % 2 == 0) 58.dp else 50.dp) + .height(12.dp) + .clip(RoundedCornerShape(4.dp)) + .background(shimmerBrush) + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Box( + modifier = + Modifier.fillMaxWidth( + fraction = if (index % 2 == 0) 0.86f else 0.74f + ) + .height(12.dp) + .clip(RoundedCornerShape(4.dp)) + .background(shimmerBrush) + ) + Spacer(modifier = Modifier.height(6.dp)) + Box( + modifier = + Modifier.fillMaxWidth( + fraction = if (index % 2 == 0) 0.67f else 0.79f + ) + .height(12.dp) + .clip(RoundedCornerShape(4.dp)) + .background(shimmerBrush) + ) + } + } + + Divider( + color = dividerColor, + thickness = 0.5.dp, + modifier = Modifier.padding(start = 76.dp) + ) + } + } +} + @Composable private fun MessageSearchResultItem( result: MessageSearchResult,