From 307db450d8c73ec42d1d3275b8ef519aa775bcc5 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 20 Aug 2022 12:47:48 +0100 Subject: [PATCH] ios: mute notifications per chat (#950) * mute notifications per chat * toggle notifications * update settings api * move model changes to main thread * add mute indication, remove swipe buttons * icon Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> --- apps/ios/Shared/Model/NtfManager.swift | 4 +- apps/ios/Shared/Model/SimpleXAPI.swift | 4 ++ apps/ios/Shared/Views/Chat/ChatView.swift | 36 +++++++++++++++++ .../Views/ChatList/ChatListNavLink.swift | 10 +---- .../Views/ChatList/ChatPreviewView.swift | 6 ++- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++---------- apps/ios/SimpleXChat/APITypes.swift | 4 ++ apps/ios/SimpleXChat/ChatTypes.swift | 14 ++++++- 8 files changed, 85 insertions(+), 33 deletions(-) diff --git a/apps/ios/Shared/Model/NtfManager.swift b/apps/ios/Shared/Model/NtfManager.swift index 114baa36b2..0c36e58d0c 100644 --- a/apps/ios/Shared/Model/NtfManager.swift +++ b/apps/ios/Shared/Model/NtfManager.swift @@ -200,7 +200,9 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject { func notifyMessageReceived(_ cInfo: ChatInfo, _ cItem: ChatItem) { logger.debug("NtfManager.notifyMessageReceived") - addNotification(createMessageReceivedNtf(cInfo, cItem)) + if cInfo.ntfsEnabled { + addNotification(createMessageReceivedNtf(cInfo, cItem)) + } } func notifyCallInvitation(_ invitation: RcvCallInvitation) { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index bdd037e769..84f8699074 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -308,6 +308,10 @@ func setNetworkConfig(_ cfg: NetCfg) throws { throw r } +func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) async throws { + try await sendCommandOkResp(.apiSetChatSettings(type: type, id: id, chatSettings: chatSettings)) +} + func apiContactInfo(contactId: Int64) async throws -> ConnectionStats? { let r = await chatSendCmd(.apiContactInfo(contactId: contactId)) if case let .contactInfo(_, connStats) = r { return connStats } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index ffce1b86fb..5bfd2d98b6 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -118,6 +118,7 @@ struct ChatView: View { Label("Video call", systemImage: "video") } searchButton() + toggleNtfsButton(chat) } label: { Image(systemName: "ellipsis") } @@ -132,6 +133,7 @@ struct ChatView: View { } Menu { searchButton() + toggleNtfsButton(chat) } label: { Image(systemName: "ellipsis") } @@ -502,6 +504,40 @@ struct ChatView: View { } } +@ViewBuilder func toggleNtfsButton(_ chat: Chat) -> some View { + Button { + toggleNotifications(chat, enableNtfs: !chat.chatInfo.ntfsEnabled) + } label: { + if chat.chatInfo.ntfsEnabled { + Label("Mute", systemImage: "speaker.slash") + } else { + Label("Unmute", systemImage: "speaker.wave.2") + } + } +} + +func toggleNotifications(_ chat: Chat, enableNtfs: Bool) { + Task { + do { + let chatSettings = ChatSettings(enableNtfs: enableNtfs) + try await apiSetChatSettings(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, chatSettings: chatSettings) + await MainActor.run { + switch chat.chatInfo { + case var .direct(contact): + contact.chatSettings = chatSettings + ChatModel.shared.updateContact(contact) + case var .group(groupInfo): + groupInfo.chatSettings = chatSettings + ChatModel.shared.updateGroup(groupInfo) + default: () + } + } + } catch let error { + logger.error("apiSetChatSettings error \(responseError(error))") + } + } +} + struct ChatView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index ecf978e67c..74756bfabd 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -41,15 +41,13 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat) }, disabled: !contact.ready ) - .swipeActions(edge: .leading) { + .swipeActions(edge: .leading, allowsFullSwipe: true) { if chat.chatStats.unreadCount > 0 { markReadButton() } } .swipeActions(edge: .trailing, allowsFullSwipe: true) { clearChatButton() - } - .swipeActions(edge: .trailing) { Button(role: .destructive) { AlertManager.shared.showAlert( contact.ready @@ -78,8 +76,6 @@ struct ChatListNavLink: View { .frame(height: 80) .swipeActions(edge: .trailing, allowsFullSwipe: true) { joinGroupButton() - } - .swipeActions(edge: .trailing) { if groupInfo.canDelete { deleteGroupChatButton(groupInfo) } @@ -104,15 +100,13 @@ struct ChatListNavLink: View { disabled: !groupInfo.ready ) .frame(height: 80) - .swipeActions(edge: .leading) { + .swipeActions(edge: .leading, allowsFullSwipe: true) { if chat.chatStats.unreadCount > 0 { markReadButton() } } .swipeActions(edge: .trailing, allowsFullSwipe: true) { clearChatButton() - } - .swipeActions(edge: .trailing) { if (groupInfo.membership.memberCurrent) { Button { AlertManager.shared.showAlert(leaveGroupAlert(groupInfo)) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 84819a92b6..aca5e9e44a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -36,7 +36,6 @@ struct ChatPreviewView: View { .frame(minWidth: 60, alignment: .trailing) .foregroundColor(.secondary) .padding(.top, 4) - } .padding(.top, 4) .padding(.horizontal, 8) @@ -113,8 +112,11 @@ struct ChatPreviewView: View { .foregroundColor(.white) .padding(.horizontal, 4) .frame(minWidth: 18, minHeight: 18) - .background(Color.accentColor) + .background(chat.chatInfo.ntfsEnabled ? Color.accentColor : Color.secondary) .cornerRadius(10) + } else if !chat.chatInfo.ntfsEnabled { + Image(systemName: "speaker.slash.fill") + .foregroundColor(.secondary) } } } else { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 77a5450424..5f435b04a4 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -13,11 +13,11 @@ 3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; }; 3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; }; 5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; }; - 5C00164C28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164728ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a */; }; - 5C00164D28ACEA380094D739 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164828ACEA380094D739 /* libgmp.a */; }; - 5C00164E28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */; }; - 5C00164F28ACEA380094D739 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164A28ACEA380094D739 /* libffi.a */; }; - 5C00165028ACEA380094D739 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00164B28ACEA380094D739 /* libgmpxx.a */; }; + 5C00165628B02AF40094D739 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00165128B02AF30094D739 /* libffi.a */; }; + 5C00165728B02AF40094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00165228B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J-ghc8.10.7.a */; }; + 5C00165828B02AF40094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00165328B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a */; }; + 5C00165928B02AF40094D739 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00165428B02AF30094D739 /* libgmp.a */; }; + 5C00165A28B02AF40094D739 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00165528B02AF30094D739 /* libgmpxx.a */; }; 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA72837DBB3004A9677 /* CICallItemView.swift */; }; 5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; }; 5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; }; @@ -196,11 +196,11 @@ 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = ""; }; 3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = ""; }; 5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; - 5C00164728ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a"; sourceTree = ""; }; - 5C00164828ACEA380094D739 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a"; sourceTree = ""; }; - 5C00164A28ACEA380094D739 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5C00164B28ACEA380094D739 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C00165128B02AF30094D739 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C00165228B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J-ghc8.10.7.a"; sourceTree = ""; }; + 5C00165328B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a"; sourceTree = ""; }; + 5C00165428B02AF30094D739 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C00165528B02AF30094D739 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = ""; }; 5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = ""; }; 5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = ""; }; @@ -350,12 +350,12 @@ buildActionMask = 2147483647; files = ( 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 5C00164F28ACEA380094D739 /* libffi.a in Frameworks */, + 5C00165728B02AF40094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J-ghc8.10.7.a in Frameworks */, + 5C00165828B02AF40094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a in Frameworks */, + 5C00165628B02AF40094D739 /* libffi.a in Frameworks */, + 5C00165928B02AF40094D739 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5C00164E28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a in Frameworks */, - 5C00164D28ACEA380094D739 /* libgmp.a in Frameworks */, - 5C00164C28ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a in Frameworks */, - 5C00165028ACEA380094D739 /* libgmpxx.a in Frameworks */, + 5C00165A28B02AF40094D739 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -410,11 +410,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C00164A28ACEA380094D739 /* libffi.a */, - 5C00164828ACEA380094D739 /* libgmp.a */, - 5C00164B28ACEA380094D739 /* libgmpxx.a */, - 5C00164728ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz-ghc8.10.7.a */, - 5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */, + 5C00165128B02AF30094D739 /* libffi.a */, + 5C00165428B02AF30094D739 /* libgmp.a */, + 5C00165528B02AF30094D739 /* libgmpxx.a */, + 5C00165228B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J-ghc8.10.7.a */, + 5C00165328B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 64ee7b6970..486139e169 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -597,6 +597,10 @@ public struct KeepAliveOpts: Codable, Equatable { public struct ChatSettings: Codable { public var enableNtfs: Bool + public init(enableNtfs: Bool) { + self.enableNtfs = enableNtfs + } + public static let defaults: ChatSettings = ChatSettings(enableNtfs: true) } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index fd82605e65..267d27d03a 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -189,6 +189,14 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat { } } + public var ntfsEnabled: Bool { + switch self { + case let .direct(contact): return contact.chatSettings.enableNtfs + case let .group(groupInfo): return groupInfo.chatSettings.enableNtfs + default: return false + } + } + var createdAt: Date { switch self { case let .direct(contact): return contact.createdAt @@ -244,7 +252,7 @@ public struct Contact: Identifiable, Decodable, NamedChat { public var profile: Profile public var activeConn: Connection public var viaGroup: Int64? -// public var chatSettings: ChatSettings + public var chatSettings: ChatSettings var createdAt: Date var updatedAt: Date @@ -265,6 +273,7 @@ public struct Contact: Identifiable, Decodable, NamedChat { localDisplayName: "alice", profile: Profile.sampleData, activeConn: Connection.sampleData, + chatSettings: ChatSettings.defaults, createdAt: .now, updatedAt: .now ) @@ -434,7 +443,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat { var localDisplayName: GroupName public var groupProfile: GroupProfile public var membership: GroupMember -// public var chatSettings: ChatSettings + public var chatSettings: ChatSettings var createdAt: Date var updatedAt: Date @@ -463,6 +472,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat { localDisplayName: "team", groupProfile: GroupProfile.sampleData, membership: GroupMember.sampleData, + chatSettings: ChatSettings.defaults, createdAt: .now, updatedAt: .now )