v1.2.7: поиск сообщений, скелетон, анимация перехода и правка бейджа Requests
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user