v1.2.7: поиск сообщений, скелетон, анимация перехода и правка бейджа Requests

This commit is contained in:
2026-03-21 21:48:36 +05:00
parent 8fdbfb4e5f
commit 8e743e710a
5 changed files with 157 additions and 35 deletions

View File

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

View File

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

View File

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

View File

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

View File

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