From 7226e5d37af0280a12ce83c8b6344ea00994cb73 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:03:39 +0100 Subject: [PATCH] ios: notifications UI (#758) * ios: notifications UI * Apply suggestions from code review Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> --- .../UserSettings/NotificationsView.swift | 116 ++++++++++++------ .../Views/UserSettings/SettingsView.swift | 73 ----------- 2 files changed, 79 insertions(+), 110 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift index ce1eefcbfe..3cf7d39198 100644 --- a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift +++ b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift @@ -12,7 +12,8 @@ import SimpleXChat struct NotificationsView: View { @EnvironmentObject var m: ChatModel @State private var notificationMode: NotificationMode? - @State private var alert: NotificationMode? + @State private var showAlert: NotificationAlert? + @AppStorage(DEFAULT_USE_NOTIFICATIONS) private var useNotifications = false var body: some View { List { @@ -21,7 +22,7 @@ struct NotificationsView: View { List { Section { SelectionListView(list: NotificationMode.values, selection: $notificationMode) { mode in - alert = mode + showAlert = .setMode(mode: mode) } } footer: { VStack(alignment: .leading) { @@ -35,7 +36,13 @@ struct NotificationsView: View { } .navigationTitle("Send notifications") .navigationBarTitleDisplayMode(.inline) - .alert(item: $alert) { notificationAlert($0) } + .alert(item: $showAlert) { alert in + if let token = m.deviceToken { + return notificationAlert(alert, token) + } else { + return Alert(title: Text("No device token!")) + } + } .onAppear { notificationMode = m.notificationMode } } label: { HStack { @@ -68,44 +75,67 @@ struct NotificationsView: View { } } - private func notificationAlert(_ mode: NotificationMode) -> Alert { + private func notificationAlert(_ alert: NotificationAlert, _ token: DeviceToken) -> Alert { + switch alert { + case let .setMode(mode): + return Alert( + title: Text(ntfModeAlertTitle(mode)), + message: Text(ntfModeDescription(mode)), + primaryButton: .default(Text(mode == .off ? "Turn off" : "Enable")) { + setNotificationsMode(mode, token) + }, + secondaryButton: .cancel() { + notificationMode = m.notificationMode + } + ) + case let .error(title, error): + return Alert(title: Text(title), message: Text(error)) + } + } + + private func ntfModeAlertTitle(_ mode: NotificationMode) -> LocalizedStringKey { switch mode { - case .off: - return Alert( - title: Text("Turn off notifications?"), - message: Text(ntfModeDescription(mode)), - primaryButton: .default(Text("Turn off")) { - notificationMode = mode - m.notificationMode = mode - }, - secondaryButton: .cancel() { - notificationMode = m.notificationMode + case .off: return "Turn off notifications?" + case .periodic: return "Enable periodic notifications?" + case .instant: return "Enable instant notifications?" + } + } + + private func setNotificationsMode(_ mode: NotificationMode, _ token: DeviceToken) { + Task { + switch mode { + case .off: + do { + try await apiDeleteToken(token: token) + useNotifications = false + m.tokenStatus = .new + notificationMode = .off + m.notificationMode = .off } - ) - case .periodic: - return Alert( - title: Text("Enable periodic notifcations?"), - message: Text(ntfModeDescription(mode)), - primaryButton: .default(Text("Enable")) { - notificationMode = mode - m.notificationMode = mode - }, - secondaryButton: .cancel() { - notificationMode = m.notificationMode + catch let error { + DispatchQueue.main.async { + let err = responseError(error) + logger.error("apiDeleteToken error: \(err)") + showAlert = .error(title: "Error deleting token", error: err) + } } - ) - case .instant: - return Alert( - title: Text("Enable instant notifications?"), - message: Text(ntfModeDescription(mode)), - primaryButton: .default(Text("Enable")) { - notificationMode = mode - m.notificationMode = mode - }, - secondaryButton: .cancel() { - notificationMode = m.notificationMode + default: + do { + do { + m.tokenStatus = try await apiRegisterToken(token: token, notificationMode: mode) + useNotifications = true + notificationMode = mode + m.notificationMode = mode + } catch let error { + DispatchQueue.main.async { + useNotifications = notificationMode != .off + let err = responseError(error) + logger.error("apiRegisterToken error: \(err)") + showAlert = .error(title: "Error enabling notifications", error: err) + } + } } - ) + } } } } @@ -156,6 +186,18 @@ struct SelectionListView: View { } } +enum NotificationAlert: Identifiable { + case setMode(mode: NotificationMode) + case error(title: LocalizedStringKey, error: String) + + var id: String { + switch self { + case let .setMode(mode): return "enable \(mode.rawValue)" + case let .error(title, error): return "error \(title): \(error)" + } + } +} + struct NotificationsView_Previews: PreviewProvider { static var previews: some View { NotificationsView() diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index cc48759c4e..97e0ba5c0f 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -49,11 +49,8 @@ struct SettingsView: View { @Environment(\.colorScheme) var colorScheme @EnvironmentObject var chatModel: ChatModel @Binding var showSettings: Bool - @AppStorage(DEFAULT_USE_NOTIFICATIONS) private var useNotifications = false @AppStorage(DEFAULT_PENDING_CONNECTIONS) private var pendingConnections = true @AppStorage(DEFAULT_EXPERIMENTAL_CALLS) private var enableCalls = false - @State var showNotificationsAlert: Bool = false - @State var whichNotificationsAlert = NotificationAlert.enable var body: some View { let user: User = chatModel.currentUser! @@ -187,13 +184,6 @@ struct SettingsView: View { } label: { settingsRow("gauge") { Text("Experimental features") } } - if let token = chatModel.deviceToken { - HStack { - notificationsIcon() - notificationsToggle(token) - } - .disabled(chatModel.chatRunning != true) - } Text("v\(appVersion ?? "?") (\(appBuild ?? "?"))") } } @@ -233,69 +223,6 @@ struct SettingsView: View { .padding(.trailing, 9) .foregroundColor(color) } - - private func notificationsToggle(_ token: DeviceToken) -> some View { - Toggle("Check messages", isOn: $useNotifications) - .onChange(of: useNotifications) { enable in - if enable { - showNotificationsAlert = true - whichNotificationsAlert = .enable - } else { - Task { - do { - try await apiDeleteToken(token: token) - chatModel.tokenStatus = .new - } - catch { - DispatchQueue.main.async { - if let cr = error as? ChatResponse { - let err = String(describing: cr) - logger.error("apiDeleteToken error: \(err)") - showNotificationsAlert = true - whichNotificationsAlert = .error("Error deleting token", err) - } else { - logger.error("apiDeleteToken unknown error: \(error.localizedDescription)") - } - } - } - } - } - } - .alert(isPresented: $showNotificationsAlert) { - switch (whichNotificationsAlert) { - case .enable: return enableNotificationsAlert(token) - case let .error(title, err): return Alert(title: Text(title), message: Text(err)) - } - } - } - - private func enableNotificationsAlert(_ token: DeviceToken) -> Alert { - Alert( - title: Text("Enable notifications? (BETA)"), - message: Text("The app can receive background notifications every 20 minutes to check the new messages.\n*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server."), - primaryButton: .destructive(Text("Confirm")) { - Task { - do { - chatModel.tokenStatus = try await apiRegisterToken(token: token, notificationMode: .instant) - } catch { - DispatchQueue.main.async { - useNotifications = false - if let cr = error as? ChatResponse { - let err = String(describing: cr) - logger.error("apiRegisterToken error: \(err)") - showNotificationsAlert = true - whichNotificationsAlert = .error("Error registering token", err) - } else { - logger.error("apiRegisterToken unknown error: \(error.localizedDescription)") - } - } - } - } - }, secondaryButton: .cancel() { - withAnimation() { useNotifications = false } - } - ) - } } func settingsRow(_ icon: String, color: Color = .secondary, content: @escaping () -> Content) -> some View {