mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-03 17:55:52 +00:00
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>
This commit is contained in:
committed by
GitHub
parent
e8c9f6d5ab
commit
7226e5d37a
@@ -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<Item: SelectableItem>: 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()
|
||||
|
||||
@@ -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<Content : View>(_ icon: String, color: Color = .secondary, content: @escaping () -> Content) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user