Релиз 1.2.4: фиксы чатов, медиа и release notes
This commit is contained in:
@@ -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.2.3"
|
val rosettaVersionName = "1.2.4"
|
||||||
val rosettaVersionCode = 25 // Increment on each release
|
val rosettaVersionCode = 26 // Increment on each release
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.rosetta.messenger"
|
namespace = "com.rosetta.messenger"
|
||||||
|
|||||||
@@ -19,14 +19,31 @@ object ReleaseNotes {
|
|||||||
|
|
||||||
Что обновлено после версии 1.2.3
|
Что обновлено после версии 1.2.3
|
||||||
|
|
||||||
Группы и медиа
|
Чат-лист и Requests
|
||||||
- Исправлено отображение групповых баблов и стеков сообщений
|
- Полностью переработано поведение блока Requests: pull-жест, раскрытие и скрытие как у архива в Telegram
|
||||||
- Исправлено позиционирование аватарки: имя и аватар в группе теперь не разъезжаются
|
- Доработана вытягивающаяся анимация: requests сразу появляются первым элементом при pull вниз
|
||||||
- Исправлена обрезка имени отправителя в медиа-баблах
|
- Убраны рывки и прыжки списка чатов при анимациях и при пустом списке запросов
|
||||||
- Исправлено растяжение фото в forwarded/media-пузырях
|
|
||||||
|
|
||||||
Интерфейс
|
Чаты и группы
|
||||||
- Убрана лишняя рамка вокруг аватарки в боковом меню
|
- Исправлены групповые баблы и аватарки в стеках сообщений, устранены кривые состояния в медиа-блоках
|
||||||
|
- Исправлена обрезка имени отправителя в групповых медиа-сообщениях
|
||||||
|
- Плашки даты в диалоге приведены к Telegram-стилю, добавлена плавающая верхняя дата при скролле
|
||||||
|
- Сообщение «you joined the group» теперь белого цвета в тёмной теме и на обоях
|
||||||
|
|
||||||
|
Медиа и локальные данные
|
||||||
|
- Исправлена отправка нескольких фото: добавлен корректный optimistic UI и стабильное отображение до/после перезахода
|
||||||
|
- Экран редактирования фото после камеры унифицирован с редактором фото из галереи
|
||||||
|
- Удалённые сообщения теперь корректно удаляются локально и не возвращаются после открытия диалога
|
||||||
|
|
||||||
|
Обои и темы
|
||||||
|
- Разделены наборы обоев для светлой и тёмной темы
|
||||||
|
- Исправлено поведение обоев на разных разрешениях: убраны повторения/растяжения, фон отображается стабильнее
|
||||||
|
|
||||||
|
Навигация и UI
|
||||||
|
- Back-свайп теперь везде скрывает клавиатуру (как на экране поиска)
|
||||||
|
- На экране группы выровнены размеры иконок Encryption Key и Add Members
|
||||||
|
- Улучшен back-свайп на экране Encryption Key: возврат во внутреннюю страницу группы
|
||||||
|
- Приведён к нормальному размер индикатор ошибки в чат-листе
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
fun getNotice(version: String): String =
|
fun getNotice(version: String): String =
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ import com.rosetta.messenger.network.SearchUser
|
|||||||
import com.rosetta.messenger.repository.AvatarRepository
|
import com.rosetta.messenger.repository.AvatarRepository
|
||||||
import com.rosetta.messenger.ui.chats.attach.ChatAttachAlert
|
import com.rosetta.messenger.ui.chats.attach.ChatAttachAlert
|
||||||
import com.rosetta.messenger.ui.chats.components.*
|
import com.rosetta.messenger.ui.chats.components.*
|
||||||
import com.rosetta.messenger.ui.chats.components.ImageEditorScreen
|
|
||||||
import com.rosetta.messenger.ui.chats.components.InAppCameraScreen
|
import com.rosetta.messenger.ui.chats.components.InAppCameraScreen
|
||||||
import com.rosetta.messenger.ui.chats.components.MultiImageEditorScreen
|
import com.rosetta.messenger.ui.chats.components.MultiImageEditorScreen
|
||||||
import com.rosetta.messenger.ui.chats.input.*
|
import com.rosetta.messenger.ui.chats.input.*
|
||||||
@@ -234,6 +233,8 @@ fun ChatDetailScreen(
|
|||||||
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 || hasChatWallpaper) Color.White else secondaryTextColor
|
val dateHeaderTextColor = if (isDarkTheme || hasChatWallpaper) Color.White else secondaryTextColor
|
||||||
|
val dateHeaderBackgroundColor =
|
||||||
|
if (isDarkTheme || hasChatWallpaper) Color(0x80212121) else Color(0xCCE8E8ED)
|
||||||
val headerIconColor = Color.White
|
val headerIconColor = Color.White
|
||||||
|
|
||||||
// 🔥 Keyboard & Emoji Coordinator
|
// 🔥 Keyboard & Emoji Coordinator
|
||||||
@@ -391,6 +392,7 @@ fun ChatDetailScreen(
|
|||||||
var pendingCameraPhotoUri by remember {
|
var pendingCameraPhotoUri by remember {
|
||||||
mutableStateOf<Uri?>(null)
|
mutableStateOf<Uri?>(null)
|
||||||
} // Фото для редактирования
|
} // Фото для редактирования
|
||||||
|
var pendingCameraPhotoCaption by remember { mutableStateOf("") }
|
||||||
|
|
||||||
// 📷 Показать встроенную камеру (без системного превью)
|
// 📷 Показать встроенную камеру (без системного превью)
|
||||||
var showInAppCamera by remember { mutableStateOf(false) }
|
var showInAppCamera by remember { mutableStateOf(false) }
|
||||||
@@ -1156,6 +1158,37 @@ fun ChatDetailScreen(
|
|||||||
val showScrollToBottomButton by remember(messagesWithDates, isAtBottom, isSendingMessage) {
|
val showScrollToBottomButton by remember(messagesWithDates, isAtBottom, isSendingMessage) {
|
||||||
derivedStateOf { messagesWithDates.isNotEmpty() && !isAtBottom && !isSendingMessage }
|
derivedStateOf { messagesWithDates.isNotEmpty() && !isAtBottom && !isSendingMessage }
|
||||||
}
|
}
|
||||||
|
val floatingDateText by remember(messagesWithDates, listState) {
|
||||||
|
derivedStateOf {
|
||||||
|
if (messagesWithDates.isEmpty()) {
|
||||||
|
return@derivedStateOf null
|
||||||
|
}
|
||||||
|
val layoutInfo = listState.layoutInfo
|
||||||
|
val visibleItems = layoutInfo.visibleItemsInfo
|
||||||
|
if (visibleItems.isEmpty()) {
|
||||||
|
return@derivedStateOf null
|
||||||
|
}
|
||||||
|
val topVisibleItem =
|
||||||
|
visibleItems.minByOrNull { itemInfo ->
|
||||||
|
kotlin.math.abs(itemInfo.offset - layoutInfo.viewportStartOffset)
|
||||||
|
} ?: return@derivedStateOf null
|
||||||
|
val messageIndex = topVisibleItem.index
|
||||||
|
if (messageIndex !in messagesWithDates.indices) {
|
||||||
|
return@derivedStateOf null
|
||||||
|
}
|
||||||
|
getDateText(messagesWithDates[messageIndex].first.timestamp.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val showFloatingDateHeader by
|
||||||
|
remember(messagesWithDates, floatingDateText, isAtBottom, listState) {
|
||||||
|
derivedStateOf {
|
||||||
|
messagesWithDates.isNotEmpty() &&
|
||||||
|
floatingDateText != null &&
|
||||||
|
!isAtBottom &&
|
||||||
|
(listState.isScrollInProgress ||
|
||||||
|
listState.firstVisibleItemIndex > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Telegram-style: Прокрутка ТОЛЬКО при новых сообщениях (не при пагинации)
|
// Telegram-style: Прокрутка ТОЛЬКО при новых сообщениях (не при пагинации)
|
||||||
// 🔥 Скроллим только если изменился ID самого нового сообщения
|
// 🔥 Скроллим только если изменился ID самого нового сообщения
|
||||||
@@ -2555,12 +2588,41 @@ fun ChatDetailScreen(
|
|||||||
isMessageBoundary(message, prevMessage)
|
isMessageBoundary(message, prevMessage)
|
||||||
val isGroupStart =
|
val isGroupStart =
|
||||||
isMessageBoundary(message, nextMessage)
|
isMessageBoundary(message, nextMessage)
|
||||||
|
val runHeadIndex =
|
||||||
|
messageRunNewestIndex.getOrNull(
|
||||||
|
index
|
||||||
|
) ?: index
|
||||||
|
val runTailIndex =
|
||||||
|
messageRunOldestIndexByHead
|
||||||
|
.getOrNull(
|
||||||
|
runHeadIndex
|
||||||
|
)
|
||||||
|
?: runHeadIndex
|
||||||
|
val isHeadPhase =
|
||||||
|
incomingRunAvatarUiState
|
||||||
|
.showOnRunHeads
|
||||||
|
.contains(
|
||||||
|
runHeadIndex
|
||||||
|
)
|
||||||
|
val isTailPhase =
|
||||||
|
incomingRunAvatarUiState
|
||||||
|
.showOnRunTails
|
||||||
|
.contains(
|
||||||
|
runHeadIndex
|
||||||
|
)
|
||||||
val showIncomingGroupAvatar =
|
val showIncomingGroupAvatar =
|
||||||
isGroupChat &&
|
isGroupChat &&
|
||||||
!message.isOutgoing &&
|
!message.isOutgoing &&
|
||||||
senderPublicKeyForMessage
|
senderPublicKeyForMessage
|
||||||
.isNotBlank() &&
|
.isNotBlank() &&
|
||||||
isGroupStart
|
((index ==
|
||||||
|
runHeadIndex &&
|
||||||
|
isHeadPhase &&
|
||||||
|
showTail) ||
|
||||||
|
(index ==
|
||||||
|
runTailIndex &&
|
||||||
|
isTailPhase &&
|
||||||
|
isGroupStart))
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
if (showDate
|
if (showDate
|
||||||
@@ -2571,8 +2633,10 @@ fun ChatDetailScreen(
|
|||||||
message.timestamp
|
message.timestamp
|
||||||
.time
|
.time
|
||||||
),
|
),
|
||||||
secondaryTextColor =
|
textColor =
|
||||||
dateHeaderTextColor
|
dateHeaderTextColor,
|
||||||
|
backgroundColor =
|
||||||
|
dateHeaderBackgroundColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val selectionKey =
|
val selectionKey =
|
||||||
@@ -2943,6 +3007,42 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
|
visible =
|
||||||
|
showFloatingDateHeader &&
|
||||||
|
!isLoading &&
|
||||||
|
!isSelectionMode,
|
||||||
|
enter =
|
||||||
|
fadeIn(animationSpec = tween(120)) +
|
||||||
|
slideInVertically(
|
||||||
|
animationSpec =
|
||||||
|
tween(120)
|
||||||
|
) { height ->
|
||||||
|
-height / 2
|
||||||
|
},
|
||||||
|
exit =
|
||||||
|
fadeOut(animationSpec = tween(100)) +
|
||||||
|
slideOutVertically(
|
||||||
|
animationSpec =
|
||||||
|
tween(100)
|
||||||
|
) { height ->
|
||||||
|
-height / 2
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier.align(Alignment.TopCenter)
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.zIndex(3f)
|
||||||
|
) {
|
||||||
|
floatingDateText?.let { dateText ->
|
||||||
|
DateHeader(
|
||||||
|
dateText = dateText,
|
||||||
|
textColor = dateHeaderTextColor,
|
||||||
|
backgroundColor =
|
||||||
|
dateHeaderBackgroundColor,
|
||||||
|
verticalPadding = 0.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (incomingRunAvatarUiState.overlays.isNotEmpty()) {
|
if (incomingRunAvatarUiState.overlays.isNotEmpty()) {
|
||||||
val avatarInsetPx =
|
val avatarInsetPx =
|
||||||
with(density) {
|
with(density) {
|
||||||
@@ -3560,7 +3660,8 @@ fun ChatDetailScreen(
|
|||||||
InAppCameraScreen(
|
InAppCameraScreen(
|
||||||
onDismiss = { showInAppCamera = false },
|
onDismiss = { showInAppCamera = false },
|
||||||
onPhotoTaken = { photoUri ->
|
onPhotoTaken = { photoUri ->
|
||||||
// Сначала редактор (skipEnterAnimation=1f), потом убираем камеру
|
// После камеры открываем тот же fullscreen-редактор,
|
||||||
|
// что и для фото из галереи.
|
||||||
pendingCameraPhotoUri = photoUri
|
pendingCameraPhotoUri = photoUri
|
||||||
showInAppCamera = false
|
showInAppCamera = false
|
||||||
}
|
}
|
||||||
@@ -3569,26 +3670,25 @@ fun ChatDetailScreen(
|
|||||||
|
|
||||||
// 📷 Image Editor для фото с камеры (с caption как в Telegram)
|
// 📷 Image Editor для фото с камеры (с caption как в Telegram)
|
||||||
pendingCameraPhotoUri?.let { uri ->
|
pendingCameraPhotoUri?.let { uri ->
|
||||||
ImageEditorScreen(
|
SimpleFullscreenPhotoOverlay(
|
||||||
imageUri = uri,
|
imageUri = uri,
|
||||||
onDismiss = {
|
modifier = Modifier.fillMaxSize().zIndex(100f),
|
||||||
pendingCameraPhotoUri = null
|
showCaptionInput = true,
|
||||||
inputFocusTrigger++
|
caption = pendingCameraPhotoCaption,
|
||||||
},
|
onCaptionChange = { pendingCameraPhotoCaption = it },
|
||||||
onSave = { editedUri ->
|
isDarkTheme = isDarkTheme,
|
||||||
// 🚀 Мгновенный optimistic UI - фото появляется СРАЗУ
|
onSend = { editedUri, caption ->
|
||||||
viewModel.sendImageFromUri(editedUri, "")
|
|
||||||
showMediaPicker = false
|
|
||||||
},
|
|
||||||
onSaveWithCaption = { editedUri, caption ->
|
|
||||||
// 🚀 Мгновенный optimistic UI - фото появляется СРАЗУ
|
|
||||||
viewModel.sendImageFromUri(editedUri, caption)
|
viewModel.sendImageFromUri(editedUri, caption)
|
||||||
showMediaPicker = false
|
showMediaPicker = false
|
||||||
|
pendingCameraPhotoUri = null
|
||||||
|
pendingCameraPhotoCaption = ""
|
||||||
|
inputFocusTrigger++
|
||||||
},
|
},
|
||||||
isDarkTheme = isDarkTheme,
|
onDismiss = {
|
||||||
showCaptionInput = true,
|
pendingCameraPhotoUri = null
|
||||||
recipientName = user.title,
|
pendingCameraPhotoCaption = ""
|
||||||
skipEnterAnimation = true // Из камеры — мгновенно, без fade
|
inputFocusTrigger++
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2169,15 +2169,27 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
/** 🔥 Удалить сообщение (для ошибки отправки) */
|
/** 🔥 Удалить сообщение (для ошибки отправки) */
|
||||||
fun deleteMessage(messageId: String) {
|
fun deleteMessage(messageId: String) {
|
||||||
|
val account = myPublicKey ?: return
|
||||||
|
val opponent = opponentKey ?: return
|
||||||
|
val dialogKey = getDialogKey(account, opponent)
|
||||||
|
|
||||||
// Удаляем из UI сразу на main
|
// Удаляем из UI сразу на main
|
||||||
_messages.value = _messages.value.filter { it.id != messageId }
|
val updatedMessages = _messages.value.filter { it.id != messageId }
|
||||||
|
_messages.value = updatedMessages
|
||||||
|
// Синхронизируем глобальный кэш диалога, иначе удалённые сообщения могут вернуться
|
||||||
|
// при повторном открытии чата из stale cache.
|
||||||
|
updateCacheWithLimit(account, dialogKey, updatedMessages)
|
||||||
|
messageRepository.clearDialogCache(opponent)
|
||||||
|
|
||||||
// Удаляем из БД в IO + удаляем pin если был
|
// Удаляем из БД в IO + удаляем pin если был
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val account = myPublicKey ?: return@launch
|
|
||||||
val dialogKey = opponentKey ?: return@launch
|
|
||||||
pinnedMessageDao.removePin(account, dialogKey, messageId)
|
pinnedMessageDao.removePin(account, dialogKey, messageId)
|
||||||
messageDao.deleteMessage(account, messageId)
|
messageDao.deleteMessage(account, messageId)
|
||||||
|
if (account == opponent) {
|
||||||
|
dialogDao.updateSavedMessagesDialogFromMessages(account)
|
||||||
|
} else {
|
||||||
|
dialogDao.updateDialogFromMessages(account, opponent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4462,17 +4462,17 @@ fun DialogItemContent(
|
|||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.size(22.dp)
|
Modifier.size(16.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(Color(0xFFE53935)),
|
.background(Color(0xFFE53935)),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "!",
|
text = "!",
|
||||||
fontSize = 13.sp,
|
fontSize = 10.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
lineHeight = 13.sp,
|
lineHeight = 10.sp,
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ import com.rosetta.messenger.ui.chats.components.ViewableImage
|
|||||||
import com.rosetta.messenger.ui.components.AppleEmojiText
|
import com.rosetta.messenger.ui.components.AppleEmojiText
|
||||||
import com.rosetta.messenger.ui.components.AvatarImage
|
import com.rosetta.messenger.ui.components.AvatarImage
|
||||||
import com.rosetta.messenger.ui.components.SharedMediaFastScrollOverlay
|
import com.rosetta.messenger.ui.components.SharedMediaFastScrollOverlay
|
||||||
|
import com.rosetta.messenger.ui.components.SwipeBackContainer
|
||||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||||
import com.rosetta.messenger.ui.icons.TelegramIcons
|
import com.rosetta.messenger.ui.icons.TelegramIcons
|
||||||
import com.rosetta.messenger.ui.settings.FullScreenAvatarViewer
|
import com.rosetta.messenger.ui.settings.FullScreenAvatarViewer
|
||||||
@@ -339,6 +340,7 @@ fun GroupInfoScreen(
|
|||||||
val accentColor = if (isDarkTheme) Color(0xFF5AA5FF) else Color(0xFF228BE6)
|
val accentColor = if (isDarkTheme) Color(0xFF5AA5FF) else Color(0xFF228BE6)
|
||||||
val actionContentColor = if (isDarkTheme) Color.White else Color(0xFF1C1C1E)
|
val actionContentColor = if (isDarkTheme) Color.White else Color(0xFF1C1C1E)
|
||||||
val groupActionButtonBlue = if (isDarkTheme) Color(0xFF4A4A4D) else Color(0xFF2478C2)
|
val groupActionButtonBlue = if (isDarkTheme) Color(0xFF4A4A4D) else Color(0xFF2478C2)
|
||||||
|
val groupMenuTrailingIconSize = 22.dp
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
@@ -805,8 +807,8 @@ fun GroupInfoScreen(
|
|||||||
swipedMemberKey = null
|
swipedMemberKey = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(swipedMemberKey) {
|
LaunchedEffect(swipedMemberKey, showEncryptionPage) {
|
||||||
onSwipeBackEnabledChanged(swipedMemberKey == null)
|
onSwipeBackEnabledChanged(swipedMemberKey == null && !showEncryptionPage)
|
||||||
}
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
@@ -1207,7 +1209,7 @@ fun GroupInfoScreen(
|
|||||||
imageVector = Icons.Default.PersonAdd,
|
imageVector = Icons.Default.PersonAdd,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = accentColor,
|
tint = accentColor,
|
||||||
modifier = Modifier.size(22.dp)
|
modifier = Modifier.size(groupMenuTrailingIconSize)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,7 +1235,7 @@ fun GroupInfoScreen(
|
|||||||
)
|
)
|
||||||
if (encryptionKeyLoading) {
|
if (encryptionKeyLoading) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(groupMenuTrailingIconSize),
|
||||||
strokeWidth = 2.dp,
|
strokeWidth = 2.dp,
|
||||||
color = accentColor
|
color = accentColor
|
||||||
)
|
)
|
||||||
@@ -1241,12 +1243,12 @@ fun GroupInfoScreen(
|
|||||||
val identiconKey = encryptionKey.ifBlank { dialogPublicKey }
|
val identiconKey = encryptionKey.ifBlank { dialogPublicKey }
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(34.dp)
|
.size(groupMenuTrailingIconSize)
|
||||||
.clip(RoundedCornerShape(6.dp))
|
.clip(RoundedCornerShape(3.dp))
|
||||||
) {
|
) {
|
||||||
TelegramStyleIdenticon(
|
TelegramStyleIdenticon(
|
||||||
keyRender = identiconKey,
|
keyRender = identiconKey,
|
||||||
size = 34.dp,
|
size = groupMenuTrailingIconSize,
|
||||||
isDarkTheme = isDarkTheme
|
isDarkTheme = isDarkTheme
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1565,11 +1567,12 @@ fun GroupInfoScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
SwipeBackContainer(
|
||||||
visible = showEncryptionPage,
|
isVisible = showEncryptionPage,
|
||||||
enter = fadeIn(animationSpec = tween(durationMillis = 260)),
|
onBack = { showEncryptionPage = false },
|
||||||
exit = fadeOut(animationSpec = tween(durationMillis = 200)),
|
isDarkTheme = isDarkTheme,
|
||||||
modifier = Modifier.fillMaxSize()
|
layer = 3,
|
||||||
|
propagateBackgroundProgress = false
|
||||||
) {
|
) {
|
||||||
val displayLines = remember(encryptionKey) { encodeGroupKeyForDisplay(encryptionKey) }
|
val displayLines = remember(encryptionKey) { encodeGroupKeyForDisplay(encryptionKey) }
|
||||||
GroupEncryptionKeyPage(
|
GroupEncryptionKeyPage(
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import androidx.compose.ui.text.withStyle
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.DpOffset
|
import androidx.compose.ui.unit.DpOffset
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
@@ -191,37 +192,33 @@ fun TelegramStyleMessageContent(
|
|||||||
|
|
||||||
private data class LayoutResult(val width: Int, val height: Int, val timeX: Int, val timeY: Int)
|
private data class LayoutResult(val width: Int, val height: Int, val timeX: Int, val timeY: Int)
|
||||||
|
|
||||||
/** Date header with fade-in animation */
|
/** Telegram-like date header chip (inline separator and floating top badge). */
|
||||||
@Composable
|
@Composable
|
||||||
fun DateHeader(dateText: String, secondaryTextColor: Color) {
|
fun DateHeader(
|
||||||
var isVisible by remember { mutableStateOf(false) }
|
dateText: String,
|
||||||
val alpha by
|
textColor: Color,
|
||||||
animateFloatAsState(
|
backgroundColor: Color,
|
||||||
targetValue = if (isVisible) 1f else 0f,
|
modifier: Modifier = Modifier,
|
||||||
animationSpec = tween(durationMillis = 250, easing = TelegramEasing),
|
verticalPadding: Dp = 12.dp
|
||||||
label = "dateAlpha"
|
) {
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(dateText) { isVisible = true }
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth().padding(vertical = 12.dp).graphicsLayer {
|
modifier
|
||||||
this.alpha = alpha
|
.fillMaxWidth()
|
||||||
},
|
.padding(vertical = verticalPadding),
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = dateText,
|
text = dateText,
|
||||||
fontSize = 13.sp,
|
fontSize = 12.sp,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
color = secondaryTextColor,
|
color = textColor,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.background(
|
Modifier.background(
|
||||||
color = secondaryTextColor.copy(alpha = 0.1f),
|
color = backgroundColor,
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(10.dp)
|
||||||
)
|
)
|
||||||
.padding(horizontal = 12.dp, vertical = 4.dp)
|
.padding(horizontal = 10.dp, vertical = 3.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user