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
Some checks failed
Android Kernel Build / build (push) Failing after 19m6s
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user