From 34a74da0b91f734f534453bab8d8e8d28641820a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 21 Oct 2022 12:32:11 +0100 Subject: [PATCH] ios: mark chat unread (#1237) * ios: mark unread (wip) * mark unread works --- apps/ios/Shared/Model/ChatModel.swift | 23 +++++++++-- apps/ios/Shared/Model/SimpleXAPI.swift | 29 +++++++++++--- apps/ios/Shared/Views/Chat/ChatView.swift | 7 ++++ .../Views/ChatList/ChatListNavLink.swift | 30 ++++++++------ .../Views/ChatList/ChatPreviewView.swift | 12 +++--- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++---------- apps/ios/SimpleXChat/APITypes.swift | 3 ++ apps/ios/SimpleXChat/ChatTypes.swift | 4 +- 8 files changed, 101 insertions(+), 47 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 93c60868f3..3048309075 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -113,6 +113,17 @@ final class ChatModel: ObservableObject { } } + private func _updateChat(_ id: ChatId, _ update: @escaping (Chat) -> Void) { + if let i = getChatIndex(id) { + // we need to separately update the chat object, as it is ObservedObject, + // and chat in the list so the list view is updated... + // simply updating chats[i] replaces the object without updating the current object in the list + let chat = chats[i] + update(chat) + chats[i] = chat + } + } + func updateNetworkStatus(_ id: ChatId, _ status: Chat.NetworkStatus) { if let i = getChatIndex(id) { chats[i].serverInfo.networkStatus = status @@ -257,7 +268,7 @@ final class ChatModel: ObservableObject { func markChatItemsRead(_ cInfo: ChatInfo) { // update preview - if let chat = getChat(cInfo.id) { + _updateChat(cInfo.id) { chat in NtfManager.shared.decNtfBadgeCount(by: chat.chatStats.unreadCount) chat.chatStats = ChatStats() } @@ -282,11 +293,11 @@ final class ChatModel: ObservableObject { if let cItem = aboveItem { if chatId == cInfo.id, let i = reversedChatItems.firstIndex(where: { $0.id == cItem.id }) { markCurrentChatRead(fromIndex: i) - if let chat = getChat(cInfo.id) { + _updateChat(cInfo.id) { chat in var unreadBelow = 0 var j = i - 1 while j >= 0 { - if case .rcvNew = reversedChatItems[j].meta.itemStatus { + if case .rcvNew = self.reversedChatItems[j].meta.itemStatus { unreadBelow += 1 } j -= 1 @@ -303,6 +314,12 @@ final class ChatModel: ObservableObject { markChatItemsRead(cInfo) } } + + func markChatUnread(_ cInfo: ChatInfo, unreadChat: Bool = true) { + _updateChat(cInfo.id) { chat in + chat.chatStats.unreadChat = unreadChat + } + } func clearChat(_ cInfo: ChatInfo) { // clear preview diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index de4cdf332d..39fa1763fb 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -529,6 +529,10 @@ func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async thr try await sendCommandOkResp(.apiChatRead(type: type, id: id, itemRange: itemRange)) } +func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { + try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat)) +} + func receiveFile(fileId: Int64) async { if let chatItem = await apiReceiveFile(fileId: fileId) { DispatchQueue.main.async { chatItemSimpleUpdate(chatItem) } @@ -634,16 +638,31 @@ func apiCallStatus(_ contact: Contact, _ status: String) async throws { func markChatRead(_ chat: Chat, aboveItem: ChatItem? = nil) async { do { - let minItemId = chat.chatStats.minUnreadItemId - let itemRange = (minItemId, aboveItem?.id ?? chat.chatItems.last?.id ?? minItemId) - let cInfo = chat.chatInfo - try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange) - DispatchQueue.main.async { ChatModel.shared.markChatItemsRead(cInfo, aboveItem: aboveItem) } + if chat.chatStats.unreadCount > 0 { + let minItemId = chat.chatStats.minUnreadItemId + let itemRange = (minItemId, aboveItem?.id ?? chat.chatItems.last?.id ?? minItemId) + let cInfo = chat.chatInfo + try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange) + await MainActor.run { ChatModel.shared.markChatItemsRead(cInfo, aboveItem: aboveItem) } + } + if chat.chatStats.unreadChat { + await markChatUnread(chat, unreadChat: false) + } } catch { logger.error("markChatRead apiChatRead error: \(responseError(error))") } } +func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async { + do { + let cInfo = chat.chatInfo + try await apiChatUnread(type: cInfo.chatType, id: cInfo.apiId, unreadChat: unreadChat) + await MainActor.run { ChatModel.shared.markChatUnread(cInfo, unreadChat: unreadChat) } + } catch { + logger.error("markChatUnread apiChatUnread error: \(responseError(error))") + } +} + func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { do { logger.debug("apiMarkChatItemRead: \(cItem.id)") diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index bbe39d7f89..443d87fcc2 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -62,6 +62,13 @@ struct ChatView: View { .navigationTitle(cInfo.chatViewName) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) + .onAppear { + if chat.chatStats.unreadChat { + Task { + await markChatUnread(chat, unreadChat: false) + } + } + } .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 672a279cc6..9b544d5299 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -53,9 +53,7 @@ struct ChatListNavLink: View { disabled: !contact.ready ) .swipeActions(edge: .leading, allowsFullSwipe: true) { - if chat.chatStats.unreadCount > 0 { - markReadButton() - } + markReadButton() } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if !chat.chatItems.isEmpty { @@ -116,9 +114,7 @@ struct ChatListNavLink: View { ) .frame(height: rowHeights[dynamicTypeSize]) .swipeActions(edge: .leading, allowsFullSwipe: true) { - if chat.chatStats.unreadCount > 0 { - markReadButton() - } + markReadButton() } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if !chat.chatItems.isEmpty { @@ -150,13 +146,23 @@ struct ChatListNavLink: View { .tint(chat.chatInfo.incognito ? .indigo : .accentColor) } - private func markReadButton() -> some View { - Button { - Task { await markChatRead(chat) } - } label: { - Label("Read", systemImage: "checkmark") + @ViewBuilder private func markReadButton() -> some View { + if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat { + Button { + Task { await markChatRead(chat) } + } label: { + Label("Read", systemImage: "checkmark") + } + .tint(Color.accentColor) + } else { + Button { + Task { await markChatUnread(chat) } + } label: { + Label("Unread", systemImage: "circlebadge.fill") + } + .tint(Color.accentColor) } - .tint(Color.accentColor) + } private func clearChatButton() -> some View { diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index c2720ea6f7..e56780c38f 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -17,7 +17,6 @@ struct ChatPreviewView: View { var body: some View { let cItem = chat.chatItems.last - let unread = chat.chatStats.unreadCount return HStack(spacing: 8) { ZStack(alignment: .bottomTrailing) { ChatInfoImage(chat: chat) @@ -41,7 +40,7 @@ struct ChatPreviewView: View { .padding(.horizontal, 8) ZStack(alignment: .topTrailing) { - chatPreviewText(cItem, unread) + chatPreviewText(cItem) if case .direct = chat.chatInfo { chatStatusImage() .padding(.top, 24) @@ -100,7 +99,7 @@ struct ChatPreviewView: View { } } - @ViewBuilder private func chatPreviewText(_ cItem: ChatItem?, _ unread: Int) -> some View { + @ViewBuilder private func chatPreviewText(_ cItem: ChatItem?) -> some View { if let cItem = cItem { ZStack(alignment: .topTrailing) { (itemStatusMark(cItem) + messageText(cItem.text, cItem.formattedText, cItem.memberDisplayName, preview: true)) @@ -109,8 +108,9 @@ struct ChatPreviewView: View { .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.leading, 8) .padding(.trailing, 36) - if unread > 0 { - unreadCountText(unread) + let s = chat.chatStats + if s.unreadCount > 0 || s.unreadChat { + unreadCountText(s.unreadCount) .font(.caption) .foregroundColor(.white) .padding(.horizontal, 4) @@ -185,7 +185,7 @@ struct ChatPreviewView: View { } func unreadCountText(_ n: Int) -> Text { - Text(n > 999 ? "\(n / 1000)k" : "\(n)") + Text(n > 999 ? "\(n / 1000)k" : n > 0 ? "\(n)" : "") } struct ChatPreviewView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 6dc1bb6dca..b966c0e9dc 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -89,6 +89,11 @@ 5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; }; 5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; }; 5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; }; + 5CCA7DED2901E32900C8FEBA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DE82901E32800C8FEBA /* libgmp.a */; }; + 5CCA7DEE2901E32900C8FEBA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DE92901E32800C8FEBA /* libffi.a */; }; + 5CCA7DEF2901E32900C8FEBA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */; }; + 5CCA7DF02901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */; }; + 5CCA7DF12901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */; }; 5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; }; 5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; }; 5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; }; @@ -123,11 +128,6 @@ 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */; }; 6442E0BE2880182D00CEC0F9 /* GroupChatInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */; }; - 6448BBB028F9C5FF000D2AB9 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6448BBAB28F9C5FF000D2AB9 /* libffi.a */; }; - 6448BBB128F9C5FF000D2AB9 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6448BBAC28F9C5FF000D2AB9 /* libgmp.a */; }; - 6448BBB228F9C5FF000D2AB9 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6448BBAD28F9C5FF000D2AB9 /* libgmpxx.a */; }; - 6448BBB328F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6448BBAE28F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC-ghc8.10.7.a */; }; - 6448BBB428F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6448BBAF28F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC.a */; }; 6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */; }; 6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; }; 646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; }; @@ -289,6 +289,11 @@ 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = ""; }; 5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; + 5CCA7DE82901E32800C8FEBA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5CCA7DE92901E32800C8FEBA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a"; sourceTree = ""; }; + 5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a"; sourceTree = ""; }; 5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = ""; }; 5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -321,11 +326,6 @@ 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; 6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = ""; }; 6442E0BD2880182D00CEC0F9 /* GroupChatInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatInfoView.swift; sourceTree = ""; }; - 6448BBAB28F9C5FF000D2AB9 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 6448BBAC28F9C5FF000D2AB9 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 6448BBAD28F9C5FF000D2AB9 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 6448BBAE28F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC-ghc8.10.7.a"; sourceTree = ""; }; - 6448BBAF28F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC.a"; sourceTree = ""; }; 6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupLinkView.swift; sourceTree = ""; }; 6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = ""; }; 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/System/Library/Frameworks/LocalAuthentication.framework; sourceTree = DEVELOPER_DIR; }; @@ -373,12 +373,12 @@ buildActionMask = 2147483647; files = ( 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 6448BBB428F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC.a in Frameworks */, - 6448BBB228F9C5FF000D2AB9 /* libgmpxx.a in Frameworks */, - 6448BBB028F9C5FF000D2AB9 /* libffi.a in Frameworks */, - 6448BBB328F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC-ghc8.10.7.a in Frameworks */, - 6448BBB128F9C5FF000D2AB9 /* libgmp.a in Frameworks */, + 5CCA7DF02901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a in Frameworks */, + 5CCA7DEF2901E32900C8FEBA /* libgmpxx.a in Frameworks */, + 5CCA7DED2901E32900C8FEBA /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, + 5CCA7DF12901E32900C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a in Frameworks */, + 5CCA7DEE2901E32900C8FEBA /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -433,11 +433,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 6448BBAB28F9C5FF000D2AB9 /* libffi.a */, - 6448BBAC28F9C5FF000D2AB9 /* libgmp.a */, - 6448BBAD28F9C5FF000D2AB9 /* libgmpxx.a */, - 6448BBAE28F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC-ghc8.10.7.a */, - 6448BBAF28F9C5FF000D2AB9 /* libHSsimplex-chat-4.1.0-KX468bxbJE45DCoSvMEVhC.a */, + 5CCA7DE92901E32800C8FEBA /* libffi.a */, + 5CCA7DE82901E32800C8FEBA /* libgmp.a */, + 5CCA7DEA2901E32800C8FEBA /* libgmpxx.a */, + 5CCA7DEB2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX-ghc8.10.7.a */, + 5CCA7DEC2901E32800C8FEBA /* libHSsimplex-chat-4.1.1-YMBxN0i6bGIdLfYB8ndX.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f835424a8a..102a15088d 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -78,6 +78,7 @@ public enum ChatCommand { case apiGetCallInvitations case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus) case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) + case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) case receiveFile(fileId: Int64) case string(String) @@ -151,6 +152,7 @@ public enum ChatCommand { case .apiGetCallInvitations: return "/_call get" case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)" case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)" + case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))" case let .receiveFile(fileId): return "/freceive \(fileId)" case let .string(str): return str } @@ -224,6 +226,7 @@ public enum ChatCommand { case .apiGetCallInvitations: return "apiGetCallInvitations" case .apiCallStatus: return "apiCallStatus" case .apiChatRead: return "apiChatRead" + case .apiChatUnread: return "apiChatUnread" case .receiveFile: return "receiveFile" case .string: return "console command" } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 40a1b69672..38edb60337 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -303,13 +303,15 @@ public struct ChatData: Decodable, Identifiable { } public struct ChatStats: Decodable { - public init(unreadCount: Int = 0, minUnreadItemId: Int64 = 0) { + public init(unreadCount: Int = 0, minUnreadItemId: Int64 = 0, unreadChat: Bool = false) { self.unreadCount = unreadCount self.minUnreadItemId = minUnreadItemId + self.unreadChat = unreadChat } public var unreadCount: Int = 0 public var minUnreadItemId: Int64 = 0 + public var unreadChat: Bool = false } public struct Contact: Identifiable, Decodable, NamedChat {