feat: update version to 1.1.1 and enhance group chat features, sync stability, and UI improvements
Some checks failed
Android Kernel Build / build (push) Failing after 19m6s

This commit is contained in:
2026-03-01 19:16:45 +05:00
parent a0569648e8
commit 7bf3db52a6
6 changed files with 64 additions and 226 deletions

View File

@@ -99,7 +99,6 @@ class MessageRepository private constructor(private val context: Context) {
/** Desktop parity: MESSAGE_MAX_TIME_TO_DELEVERED_S = 80 (seconds) */
private const val MESSAGE_MAX_TIME_TO_DELIVERED_MS = 80_000L
private const val MAX_SYNC_FUTURE_DRIFT_MS = 86_400_000L // 24h
const val SYSTEM_SAFE_PUBLIC_KEY = "0x000000000000000000000000000000000000000002"
const val SYSTEM_SAFE_TITLE = "Safe"
@@ -385,43 +384,13 @@ class MessageRepository private constructor(private val context: Context) {
suspend fun getLastSyncTimestamp(): Long {
val account = currentAccount ?: return 0L
val stored = syncTimeDao.getLastSync(account) ?: 0L
val normalized = normalizeSyncTimestamp(stored)
if (normalized != stored) {
syncTimeDao.upsert(AccountSyncTimeEntity(account = account, lastSync = normalized))
if (stored > 0) {
android.util.Log.w(
"MessageRepository",
"⚠️ Normalized invalid last_sync for account=${account.take(10)}...: $stored -> $normalized"
)
ProtocolManager.addLog("⚠️ SYNC cursor normalized: $stored -> $normalized")
}
}
return normalized
return syncTimeDao.getLastSync(account) ?: 0L
}
suspend fun updateLastSyncTimestamp(timestamp: Long) {
if (timestamp <= 0) return
val account = currentAccount ?: return
val normalized = normalizeSyncTimestamp(timestamp)
if (normalized <= 0) return
// Desktop parity: allow moving sync cursor backward if needed.
syncTimeDao.upsert(AccountSyncTimeEntity(account = account, lastSync = normalized))
}
private fun normalizeSyncTimestamp(rawTimestamp: Long): Long {
if (rawTimestamp <= 0) return 0L
val now = System.currentTimeMillis()
val maxAllowed = now + MAX_SYNC_FUTURE_DRIFT_MS
var normalized = rawTimestamp
// Heal common corruption where extra decimal places appear in timestamp.
while (normalized > maxAllowed) {
normalized /= 10L
if (normalized <= 0L) return 0L
}
return if (normalized > maxAllowed) 0L else normalized
// Desktop parity: store raw cursor value from sync/update events.
syncTimeDao.upsert(AccountSyncTimeEntity(account = account, lastSync = timestamp))
}
/** Получить поток сообщений для диалога */
@@ -953,10 +922,26 @@ class MessageRepository private constructor(private val context: Context) {
// 1) from=opponent, to=account -> собеседник прочитал НАШИ сообщения (double check)
// 2) from=account, to=opponent -> sync с другого нашего устройства (мы прочитали входящие)
val isOwnReadSync = fromPublicKey == account
// Desktop parity (group): from=groupMember, to=groupId -> mark own group messages as read.
if (!isOwnReadSync && isGroupDialogKey(toPublicKey)) {
// Group read receipts are currently not mapped to per-message states.
val dialogKey = getDialogKey(toPublicKey)
messageDao.markAllAsRead(account, toPublicKey)
val readCount = messageCache[dialogKey]?.value?.count { it.isFromMe && !it.isRead } ?: 0
messageCache[dialogKey]?.let { flow ->
flow.value =
flow.value.map { msg ->
if (msg.isFromMe && !msg.isRead) msg.copy(isRead = true) else msg
}
}
_deliveryStatusEvents.tryEmit(DeliveryStatusUpdate(dialogKey, "", DeliveryStatus.READ))
MessageLogger.logReadStatus(fromPublicKey = toPublicKey, messagesCount = readCount)
dialogDao.updateDialogFromMessages(account, toPublicKey)
return
}
val opponentKey = if (isOwnReadSync) toPublicKey else fromPublicKey
if (opponentKey.isBlank()) return

View File

@@ -17,9 +17,28 @@ object ReleaseNotes {
val RELEASE_NOTICE = """
Update v$VERSION_PLACEHOLDER
Синхронизация сообщений
- Исправлен бесконечный цикл синхронизации, когда сервер возвращал пустые батчи с неизменным курсором
- Вынесена общая логика завершения sync-цикла для единообразной обработки всех сценариев
Группы и интерфейс
- Полностью обновлен экран группы в стиле приложения (по паритету с desktop логикой)
- В участниках добавлены верификации, админ-метка и тултип администратора
- Добавлен просмотр Encryption Key с QR-кодом
- Улучшены секции Media/Files/Links: корректные пустые состояния и выравнивание медиа-сетки
Сообщения и списки
- Group Invite теперь отображается как invite-карточка вместо хэша (в чате и в chat list)
- Для групп в chat list показывается иконка и автор последнего сообщения (You/имя отправителя)
- Исправлено выравнивание превью вида "You: Photo"
- Системные события группы (например joined the group) приведены к desktop-стилю
Модерация групп
- Добавлены свайп и long-press действия по участникам (Kick)
- Улучшены цвета, haptic и размеры action-кнопки; исправлен конфликт свайпа item vs экран
- Для групп в chat list добавлены swipe-actions: Pin, Leave, Delete
Синхронизация и стабильность
- Исправлены пропуски сообщений при массовой синхронизации личных и групповых чатов
- Sync теперь не продвигает курсор батча при ошибках обработки и делает безопасные ретраи
- Исправлены кейсы, где requests зависели от состояния устройства, а не аккаунта
- Rosetta Updates и Safe исключены из requests
""".trimIndent()
fun getNotice(version: String): String =