diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 4c0f36102..8d398eb89 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -267,7 +267,20 @@ final class ChatModel: ObservableObject { func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) { // update previews if let i = getChatIndex(cInfo.id) { - chats[i].chatItems = [cItem] + chats[i].chatItems = switch cInfo { + case .group: + if let currentPreviewItem = chats[i].chatItems.first { + if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs { + [cItem] + } else { + [currentPreviewItem] + } + } else { + [cItem] + } + default: + [cItem] + } if case .rcvNew = cItem.meta.itemStatus { chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1 increaseUnreadCounter(user: currentUser!) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index ad0e5ee10..9128f67f2 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -613,7 +613,7 @@ public enum ChatResponse: Decodable, Error { case remoteCtrlConnecting(remoteCtrl_: RemoteCtrlInfo?, ctrlAppInfo: CtrlAppInfo, appVersion: String) case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String) case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo) - case remoteCtrlStopped + case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) // misc case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) case cmdOk(user: UserRef?) @@ -1552,6 +1552,13 @@ public enum RemoteCtrlSessionState: Decodable { case connected(sessionCode: String) } +public enum RemoteCtrlStopReason: Decodable { + case discoveryFailed(chatError: ChatError) + case connectionFailed(chatError: ChatError) + case setupFailed(chatError: ChatError) + case disconnected +} + public struct CtrlAppInfo: Decodable { public var appVersionRange: AppVersionRange public var deviceName: String diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 76c2f39fb..60b70c0ae 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -222,8 +222,23 @@ object ChatModel { val chat: Chat if (i >= 0) { chat = chats[i] + val newPreviewItem = when (cInfo) { + is ChatInfo.Group -> { + val currentPreviewItem = chat.chatItems.firstOrNull() + if (currentPreviewItem != null) { + if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) { + cItem + } else { + currentPreviewItem + } + } else { + cItem + } + } + else -> cItem + } chats[i] = chat.copy( - chatItems = arrayListOf(cItem), + chatItems = arrayListOf(newPreviewItem), chatStats = if (cItem.meta.itemStatus is CIStatus.RcvNew) { val minUnreadId = if(chat.chatStats.minUnreadItemId == 0L) cItem.id else chat.chatStats.minUnreadItemId @@ -2945,6 +2960,14 @@ sealed class RemoteCtrlSessionState { @Serializable @SerialName("connected") data class Connected(val sessionCode: String): RemoteCtrlSessionState() } +@Serializable +sealed class RemoteCtrlStopReason { + @Serializable @SerialName("discoveryFailed") class DiscoveryFailed(val chatError: ChatError): RemoteCtrlStopReason() + @Serializable @SerialName("connectionFailed") class ConnectionFailed(val chatError: ChatError): RemoteCtrlStopReason() + @Serializable @SerialName("setupFailed") class SetupFailed(val chatError: ChatError): RemoteCtrlStopReason() + @Serializable @SerialName("disconnected") object Disconnected: RemoteCtrlStopReason() +} + sealed class UIRemoteCtrlSessionState { object Starting: UIRemoteCtrlSessionState() object Searching: UIRemoteCtrlSessionState() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 727e0d2a3..83ae90cb2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3581,6 +3581,13 @@ sealed class RemoteHostSessionState { @Serializable @SerialName("connected") data class Connected(val sessionCode: String): RemoteHostSessionState() } +@Serializable +sealed class RemoteHostStopReason { + @Serializable @SerialName("connectionFailed") data class ConnectionFailed(val chatError: ChatError): RemoteHostStopReason() + @Serializable @SerialName("crashed") data class Crashed(val chatError: ChatError): RemoteHostStopReason() + @Serializable @SerialName("disconnected") object Disconnected: RemoteHostStopReason() +} + val json = Json { prettyPrint = true ignoreUnknownKeys = true @@ -3804,7 +3811,7 @@ sealed class CR { @Serializable @SerialName("remoteHostSessionCode") class RemoteHostSessionCode(val remoteHost_: RemoteHostInfo?, val sessionCode: String): CR() @Serializable @SerialName("newRemoteHost") class NewRemoteHost(val remoteHost: RemoteHostInfo): CR() @Serializable @SerialName("remoteHostConnected") class RemoteHostConnected(val remoteHost: RemoteHostInfo): CR() - @Serializable @SerialName("remoteHostStopped") class RemoteHostStopped(val remoteHostId_: Long?): CR() + @Serializable @SerialName("remoteHostStopped") class RemoteHostStopped(val remoteHostId_: Long?, val rhsState: RemoteHostSessionState, val rhStopReason: RemoteHostStopReason): CR() @Serializable @SerialName("remoteFileStored") class RemoteFileStored(val remoteHostId: Long, val remoteFileSource: CryptoFile): CR() // remote events (mobile) @Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List): CR() @@ -3812,7 +3819,7 @@ sealed class CR { @Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrl_: RemoteCtrlInfo?, val ctrlAppInfo: CtrlAppInfo, val appVersion: String): CR() @Serializable @SerialName("remoteCtrlSessionCode") class RemoteCtrlSessionCode(val remoteCtrl_: RemoteCtrlInfo?, val sessionCode: String): CR() @Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrl: RemoteCtrlInfo): CR() - @Serializable @SerialName("remoteCtrlStopped") class RemoteCtrlStopped(): CR() + @Serializable @SerialName("remoteCtrlStopped") class RemoteCtrlStopped(val rcsState: RemoteCtrlSessionState, val rcStopReason: RemoteCtrlStopReason): CR() @Serializable @SerialName("versionInfo") class VersionInfo(val versionInfo: CoreVersionInfo, val chatMigrations: List, val agentMigrations: List): CR() @Serializable @SerialName("cmdOk") class CmdOk(val user: UserRef?): CR() @Serializable @SerialName("chatCmdError") class ChatCmdError(val user_: UserRef?, val chatError: ChatError): CR() diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 32f58b54b..cae17e24a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -1084,8 +1084,8 @@ data RemoteHostError deriving (Show, Exception) data RemoteHostStopReason - = RHSRConnectionFailed ChatError - | RHSRCrashed ChatError + = RHSRConnectionFailed {chatError :: ChatError} + | RHSRCrashed {chatError :: ChatError} | RHSRDisconnected deriving (Show, Exception) @@ -1106,9 +1106,9 @@ data RemoteCtrlError deriving (Show, Exception) data RemoteCtrlStopReason - = RCSRDiscoveryFailed ChatError - | RCSRConnectionFailed ChatError - | RCSRSetupFailed ChatError + = RCSRDiscoveryFailed {chatError :: ChatError} + | RCSRConnectionFailed {chatError :: ChatError} + | RCSRSetupFailed {chatError :: ChatError} | RCSRDisconnected deriving (Show, Exception) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 145ae11e3..892f35fe5 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -526,12 +526,12 @@ getDirectChatPreviews_ db user@User {userId} = do JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id LEFT JOIN connections c ON c.contact_id = ct.contact_id LEFT JOIN ( - SELECT contact_id, MAX(chat_item_id) AS MaxId + SELECT contact_id, chat_item_id, MAX(created_at) FROM chat_items GROUP BY contact_id - ) MaxIds ON MaxIds.contact_id = ct.contact_id - LEFT JOIN chat_items i ON i.contact_id = MaxIds.contact_id - AND i.chat_item_id = MaxIds.MaxId + ) LastItems ON LastItems.contact_id = ct.contact_id + LEFT JOIN chat_items i ON i.contact_id = LastItems.contact_id + AND i.chat_item_id = LastItems.chat_item_id LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN ( SELECT contact_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread @@ -613,12 +613,12 @@ getGroupChatPreviews_ db User {userId, userContactId} = do JOIN group_members mu ON mu.group_id = g.group_id JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) LEFT JOIN ( - SELECT group_id, MAX(chat_item_id) AS MaxId + SELECT group_id, chat_item_id, MAX(item_ts) FROM chat_items GROUP BY group_id - ) MaxIds ON MaxIds.group_id = g.group_id - LEFT JOIN chat_items i ON i.group_id = MaxIds.group_id - AND i.chat_item_id = MaxIds.MaxId + ) LastItems ON LastItems.group_id = g.group_id + LEFT JOIN chat_items i ON i.group_id = LastItems.group_id + AND i.chat_item_id = LastItems.chat_item_id LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN ( SELECT group_id, COUNT(1) AS UnreadCount, MIN(chat_item_id) AS MinUnread @@ -722,7 +722,7 @@ getDirectChatItemsLast db User {userId} contactId count search = ExceptT $ do LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' - ORDER BY i.chat_item_id DESC + ORDER BY i.created_at DESC, i.chat_item_id DESC LIMIT ? |] (userId, contactId, search, count) @@ -752,7 +752,7 @@ getDirectChatAfter_ db User {userId} ct@Contact {contactId} afterChatItemId coun LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' AND i.chat_item_id > ? - ORDER BY i.chat_item_id ASC + ORDER BY i.created_at ASC, i.chat_item_id ASC LIMIT ? |] (userId, contactId, search, afterChatItemId, count) @@ -782,7 +782,7 @@ getDirectChatBefore_ db User {userId} ct@Contact {contactId} beforeChatItemId co LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' AND i.chat_item_id < ? - ORDER BY i.chat_item_id DESC + ORDER BY i.created_at DESC, i.chat_item_id DESC LIMIT ? |] (userId, contactId, search, beforeChatItemId, count)