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>
This commit is contained in:
Evgeny Poberezkin
2022-08-20 12:47:48 +01:00
committed by GitHub
parent e6233722db
commit 307db450d8
8 changed files with 85 additions and 33 deletions

View File

@@ -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) {

View File

@@ -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 }

View File

@@ -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()

View File

@@ -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))

View File

@@ -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 {

View File

@@ -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 = "<group>"; };
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = "<group>"; };
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 = "<group>"; };
5C00164828ACEA380094D739 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C00164928ACEA380094D739 /* libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-iRSBBAuo4W6e6BG2FodFz.a"; sourceTree = "<group>"; };
5C00164A28ACEA380094D739 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C00164B28ACEA380094D739 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C00165128B02AF30094D739 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
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 = "<group>"; };
5C00165328B02AF30094D739 /* libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.1.0-KA4pfwpgEHbFrTKfOobU7J.a"; sourceTree = "<group>"; };
5C00165428B02AF30094D739 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C00165528B02AF30094D739 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = "<group>"; };
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
@@ -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 = "<group>";

View File

@@ -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)
}

View File

@@ -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
)