mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-04 13:26:09 +00:00
* 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>
206 lines
7.5 KiB
Swift
206 lines
7.5 KiB
Swift
//
|
|
// NotificationsView.swift
|
|
// SimpleX (iOS)
|
|
//
|
|
// Created by Evgeny on 26/06/2022.
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SimpleXChat
|
|
|
|
struct NotificationsView: View {
|
|
@EnvironmentObject var m: ChatModel
|
|
@State private var notificationMode: NotificationMode?
|
|
@State private var showAlert: NotificationAlert?
|
|
@AppStorage(DEFAULT_USE_NOTIFICATIONS) private var useNotifications = false
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
NavigationLink {
|
|
List {
|
|
Section {
|
|
SelectionListView(list: NotificationMode.values, selection: $notificationMode) { mode in
|
|
showAlert = .setMode(mode: mode)
|
|
}
|
|
} footer: {
|
|
VStack(alignment: .leading) {
|
|
if let mode = notificationMode {
|
|
Text(ntfModeDescription(mode))
|
|
}
|
|
}
|
|
.font(.callout)
|
|
.padding(.top, 1)
|
|
}
|
|
}
|
|
.navigationTitle("Send notifications")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.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 {
|
|
Text("Send notifications")
|
|
Spacer()
|
|
Text(m.notificationMode.label)
|
|
}
|
|
}
|
|
|
|
NavigationLink {
|
|
List {
|
|
Section {
|
|
SelectionListView(list: NotificationPreviewMode.values, selection: $m.notificationPreview)
|
|
} footer: {
|
|
|
|
}
|
|
}
|
|
.navigationTitle("Show preview")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
} label: {
|
|
HStack {
|
|
Text("Show preview")
|
|
Spacer()
|
|
Text(m.notificationPreview?.label ?? "")
|
|
}
|
|
}
|
|
} header: {
|
|
Text("Message notifications")
|
|
}
|
|
}
|
|
}
|
|
|
|
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 "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
|
|
}
|
|
catch let error {
|
|
DispatchQueue.main.async {
|
|
let err = responseError(error)
|
|
logger.error("apiDeleteToken error: \(err)")
|
|
showAlert = .error(title: "Error deleting token", error: err)
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func ntfModeDescription(_ mode: NotificationMode) -> LocalizedStringKey {
|
|
switch mode {
|
|
case .off: return "**Maximum privacy**: push notifications are off.\nNo meta-data is shared with SimpleX Chat notification server."
|
|
case .periodic: return "**High privacy**: new messages are checked every 20 minutes.\nYour device token is shared with SimpleX Chat notification server, but it cannot see how many connections you have or how many messages you receive."
|
|
case .instant: return "**Medium privacy** (recommended): notifications are sent instantly.\nYour device token and notifications are sent to SimpleX Chat notification server, but it cannot access the message content, size or who is it from."
|
|
}
|
|
}
|
|
|
|
struct SelectionListView<Item: SelectableItem>: View {
|
|
var list: [Item]
|
|
@Binding var selection: Item?
|
|
var onSelection: ((Item) -> Void)?
|
|
@State private var tapped: Item? = nil
|
|
|
|
var body: some View {
|
|
ForEach(list) { item in
|
|
HStack {
|
|
Text(item.label)
|
|
Spacer()
|
|
if selection == item {
|
|
Image(systemName: "checkmark")
|
|
.resizable().scaledToFit().frame(width: 16)
|
|
.foregroundColor(.accentColor)
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
.listRowBackground(Color(uiColor: tapped == item ? .secondarySystemFill : .systemBackground))
|
|
.onTapGesture {
|
|
if let f = onSelection {
|
|
f(item)
|
|
} else {
|
|
selection = item
|
|
}
|
|
}
|
|
._onButtonGesture { down in
|
|
if down {
|
|
tapped = item
|
|
} else {
|
|
tapped = nil
|
|
}
|
|
} perform: {}
|
|
}
|
|
.environment(\.editMode, .constant(.active))
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|