Files
damus/damus/Views/Settings/NotificationSettingsView.swift
T

261 lines
12 KiB
Swift

//
// NotificationSettings.swift
// damus
//
// Created by William Casarin on 2023-04-05.
//
import SwiftUI
let MINIMUM_PUSH_NOTIFICATION_SYNC_DELAY_IN_SECONDS = 0.25
struct NotificationSettingsView: View {
let damus_state: DamusState
@ObservedObject var settings: UserSettingsStore
@State var notification_mode_setting_error: String? = nil
@State var notification_preferences_sync_state: PreferencesSyncState = .undefined
@Environment(\.dismiss) var dismiss
func indicator_binding(_ val: NewEventsBits) -> Binding<Bool> {
return Binding.init(get: {
(settings.notification_indicators & val.rawValue) > 0
}, set: { v in
if v {
settings.notification_indicators |= val.rawValue
} else {
settings.notification_indicators &= ~val.rawValue
}
})
}
var hellthread_notification_max_pubkeys_binding: Binding<Double> {
Binding<Double>(get: {
return Double(settings.hellthread_notification_max_pubkeys)
}, set: {
settings.hellthread_notification_max_pubkeys = Int($0)
})
}
func try_to_set_notifications_mode(new_value: UserSettingsStore.NotificationsMode) {
notification_mode_setting_error = nil
if new_value == .push {
Task {
do {
try await damus_state.push_notification_client.send_token()
await self.sync_up_remote_notification_settings()
settings.notification_mode = new_value
}
catch {
notification_mode_setting_error = String(format: NSLocalizedString("Error configuring push notifications with the server: %@", comment: "Error label shown when user tries to enable push notifications but something fails"), error.localizedDescription)
}
}
}
else {
Task {
do {
try await damus_state.push_notification_client.revoke_token()
settings.notification_mode = new_value
notification_preferences_sync_state = .not_applicable
}
catch {
notification_mode_setting_error = String(format: NSLocalizedString("Error disabling push notifications with the server: %@", comment: "Error label shown when user tries to disable push notifications but something fails"), error.localizedDescription)
}
}
}
}
// MARK: - Push notification preference sync management
func notification_preference_binding<T>(_ raw_binding: Binding<T>) -> Binding<T> {
return Binding(
get: {
return raw_binding.wrappedValue
},
set: { new_value in
let old_value = raw_binding.wrappedValue
raw_binding.wrappedValue = new_value
if self.settings.notification_mode == .push {
Task {
await self.send_push_notification_preferences(on_failure: {
raw_binding.wrappedValue = old_value
})
}
}
}
)
}
func sync_up_remote_notification_settings() async {
do {
notification_preferences_sync_state = .syncing
let remote_settings = try await damus_state.push_notification_client.get_settings()
let local_settings = PushNotificationClient.NotificationSettings.from(settings: settings)
if remote_settings != local_settings {
await self.send_push_notification_preferences(local_settings)
}
else {
notification_preferences_sync_state = .success
}
}
catch {
notification_preferences_sync_state = .failure(error: String(format: NSLocalizedString("Failed to get push notification preferences from the server", comment: "Error label indicating about a failure in fetching notification preferences"), error.localizedDescription))
}
}
func send_push_notification_preferences(_ new_settings: PushNotificationClient.NotificationSettings? = nil, on_failure: (() -> Void)? = nil) async {
do {
notification_preferences_sync_state = .syncing
try await damus_state.push_notification_client.set_settings(new_settings)
// Make sync appear to take at least a few milliseconds or so to avoid issues with labor perception bias (https://growth.design/case-studies/labor-perception-bias)
DispatchQueue.main.asyncAfter(deadline: .now() + MINIMUM_PUSH_NOTIFICATION_SYNC_DELAY_IN_SECONDS) {
notification_preferences_sync_state = .success
}
}
catch {
notification_preferences_sync_state = .failure(error: String(format: NSLocalizedString("Error syncing up push notifications preferences with the server: %@", comment: "Error label shown when system tries to sync up notification preferences to the push notification server but something fails"), error.localizedDescription))
on_failure?()
}
}
// MARK: - View layout
func hellthread_notification_settings_text() -> String {
if !settings.hellthread_notifications_disabled {
return NSLocalizedString("Hide notifications that tag many profiles", comment: "Label for notification settings toggle that hides notifications that tag many people.")
}
return pluralizedString(key: "hellthread_notifications_disabled", count: $settings.hellthread_notification_max_pubkeys.wrappedValue)
}
var hellthread_notifications_max_pubkeys_view: some View {
VStack(alignment: .leading) {
Slider(
value: self.notification_preference_binding(hellthread_notification_max_pubkeys_binding),
in: Double(HELLTHREAD_MIN_PUBKEYS)...Double(HELLTHREAD_MAX_PUBKEYS),
step: 1
)
}
}
var body: some View {
Form {
if settings.enable_push_notifications {
Section(
header: Text("General", comment: "Section header for general damus notifications user configuration"),
footer: VStack {
if let notification_mode_setting_error {
Text(notification_mode_setting_error)
.foregroundStyle(.damusDangerPrimary)
}
}
) {
Picker(NSLocalizedString("Notifications mode", comment: "Prompt selection of the notification mode (Feature to switch between local notifications (generated from user's own phone) or push notifications (generated by Damus server)."),
selection: Binding(
get: { settings.notification_mode },
set: { newValue in
self.try_to_set_notifications_mode(new_value: newValue)
}
)
) {
ForEach(UserSettingsStore.NotificationsMode.allCases, id: \.self) { notification_mode in
Text(notification_mode.text_description())
.tag(notification_mode.rawValue)
}
}
}
}
Section(
header: Text("Notification Preferences", comment: "Section header for Notification Preferences"),
footer: VStack {
switch notification_preferences_sync_state {
case .undefined, .not_applicable:
EmptyView()
case .success:
HStack {
Image("check-circle.fill")
.foregroundStyle(.damusGreen)
Text("Successfully synced", comment: "Label indicating success in syncing notification preferences")
}
case .syncing:
HStack(spacing: 10) {
ProgressView()
Text("Syncing", comment: "Label indicating success in syncing notification preferences")
}
case .failure(let error):
Text(error)
.foregroundStyle(.damusDangerPrimary)
}
}
) {
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: self.notification_preference_binding($settings.zap_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: self.notification_preference_binding($settings.mention_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Reposts", comment: "Setting to enable Repost Local Notification"), isOn: self.notification_preference_binding($settings.repost_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Likes", comment: "Setting to enable Like Local Notification"), isOn: self.notification_preference_binding($settings.like_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("DMs", comment: "Setting to enable DM Local Notification"), isOn: self.notification_preference_binding($settings.dm_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Show only from users you follow", comment: "Setting to Show notifications only associated to users your follow"), isOn: self.notification_preference_binding($settings.notification_only_from_following))
.toggleStyle(.switch)
VStack {
Toggle(hellthread_notification_settings_text(), isOn: self.notification_preference_binding($settings.hellthread_notifications_disabled))
.toggleStyle(.switch)
if settings.hellthread_notifications_disabled {
hellthread_notifications_max_pubkeys_view
}
}
}
Section(
header: Text("Notification Dots", comment: "Section header for notification indicator dot settings"),
footer: Text("").padding(.bottom, tabHeight + getSafeAreaBottom())
) {
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: indicator_binding(.zaps))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Reposts", comment: "Setting to enable Repost Local Notification"), isOn: indicator_binding(.reposts))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Likes", comment: "Setting to enable Like Local Notification"), isOn: indicator_binding(.likes))
.toggleStyle(.switch)
}
}
.navigationTitle("Notifications")
.onReceive(handle_notify(.switched_timeline)) { _ in
dismiss()
}
.onAppear(perform: {
Task {
if self.settings.notification_mode == .push {
await self.sync_up_remote_notification_settings()
}
}
})
}
}
extension NotificationSettingsView {
enum PreferencesSyncState {
/// State is unknown
case undefined
/// State is not applicable (e.g. Notifications are set to local)
case not_applicable
/// Preferences are successfully synced
case success
/// Preferences are being synced
case syncing
/// There was a failure during syncing
case failure(error: String)
}
}
struct NotificationSettings_Previews: PreviewProvider {
static var previews: some View {
NotificationSettingsView(damus_state: test_damus_state, settings: UserSettingsStore())
}
}