230 lines
8.7 KiB
Swift
230 lines
8.7 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: NotificationsMode = ChatModel.shared.notificationMode
|
|
@State private var showAlert: NotificationAlert?
|
|
@State private var legacyDatabase = dbContainerGroupDefault.get() == .documents
|
|
|
|
var body: some View {
|
|
List {
|
|
Section {
|
|
NavigationLink {
|
|
List {
|
|
Section {
|
|
SelectionListView(list: NotificationsMode.values, selection: $notificationMode) { mode in
|
|
showAlert = .setMode(mode: mode)
|
|
}
|
|
} footer: {
|
|
VStack(alignment: .leading) {
|
|
Text(ntfModeDescription(notificationMode))
|
|
}
|
|
.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!"))
|
|
}
|
|
}
|
|
} label: {
|
|
HStack {
|
|
Text("Send notifications")
|
|
Spacer()
|
|
Text(m.notificationMode.label)
|
|
}
|
|
}
|
|
|
|
NavigationLink {
|
|
List {
|
|
Section {
|
|
SelectionListView(list: NotificationPreviewMode.values, selection: $m.notificationPreview) { previewMode in
|
|
ntfPreviewModeGroupDefault.set(previewMode)
|
|
m.notificationPreview = previewMode
|
|
}
|
|
} footer: {
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text("You can set lock screen notification preview via settings.")
|
|
Button("Open Settings") {
|
|
DispatchQueue.main.async {
|
|
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Show preview")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
} label: {
|
|
HStack {
|
|
Text("Show preview")
|
|
Spacer()
|
|
Text(m.notificationPreview.label)
|
|
}
|
|
}
|
|
|
|
if let server = m.notificationServer {
|
|
smpServers("Push server", [server])
|
|
}
|
|
} header: {
|
|
Text("Push notifications")
|
|
} footer: {
|
|
if legacyDatabase {
|
|
Text("Please restart the app and migrate the database to enable push notifications.")
|
|
.font(.callout)
|
|
.padding(.top, 1)
|
|
}
|
|
}
|
|
}
|
|
.disabled(legacyDatabase)
|
|
.onAppear {
|
|
(m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken()
|
|
}
|
|
}
|
|
|
|
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(token, mode)
|
|
},
|
|
secondaryButton: .cancel() {
|
|
notificationMode = m.notificationMode
|
|
}
|
|
)
|
|
case let .error(title, error):
|
|
return Alert(title: Text(title), message: Text(error))
|
|
}
|
|
}
|
|
|
|
private func ntfModeAlertTitle(_ mode: NotificationsMode) -> LocalizedStringKey {
|
|
switch mode {
|
|
case .off: return "Use only local notifications?"
|
|
case .periodic: return "Enable periodic notifications?"
|
|
case .instant: return "Enable instant notifications?"
|
|
}
|
|
}
|
|
|
|
private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) {
|
|
Task {
|
|
switch mode {
|
|
case .off:
|
|
do {
|
|
try await apiDeleteToken(token: token)
|
|
await MainActor.run {
|
|
m.tokenStatus = .new
|
|
notificationMode = .off
|
|
m.notificationMode = .off
|
|
m.notificationServer = nil
|
|
}
|
|
} catch let error {
|
|
await MainActor.run {
|
|
let err = responseError(error)
|
|
logger.error("apiDeleteToken error: \(err)")
|
|
showAlert = .error(title: "Error deleting token", error: err)
|
|
}
|
|
}
|
|
default:
|
|
do {
|
|
let _ = try await apiRegisterToken(token: token, notificationMode: mode)
|
|
let (_, tknStatus, ntfMode, ntfServer) = apiGetNtfToken()
|
|
await MainActor.run {
|
|
m.tokenStatus = tknStatus
|
|
notificationMode = ntfMode
|
|
m.notificationMode = ntfMode
|
|
m.notificationServer = ntfServer
|
|
}
|
|
} catch let error {
|
|
await MainActor.run {
|
|
let err = responseError(error)
|
|
logger.error("apiRegisterToken error: \(err)")
|
|
showAlert = .error(title: "Error enabling notifications", error: err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func ntfModeDescription(_ mode: NotificationsMode) -> LocalizedStringKey {
|
|
switch mode {
|
|
case .off: return "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)."
|
|
case .periodic: return "**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have."
|
|
case .instant: return "**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is 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 selection == item { return }
|
|
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: NotificationsMode)
|
|
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()
|
|
}
|
|
}
|