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:
Evgeny Poberezkin
2022-06-28 19:03:39 +01:00
committed by GitHub
parent e8c9f6d5ab
commit 7226e5d37a
2 changed files with 79 additions and 110 deletions

View File

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

View File

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