Files
omnivore/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettingsView.swift
2024-03-08 17:15:59 +08:00

222 lines
7.1 KiB
Swift

#if os(iOS)
import Models
import Services
import SwiftUI
import Utils
import Views
import Transmission
@MainActor final class PushNotificationSettingsViewModel: ObservableObject {
@Published var isLoading = false
@Published var isLoadingRules = true
@Published var emails = [NewsletterEmail]()
@Published var desiredNotificationsEnabled = false
@Published var allSubscriptionsNotificationRule: Rule?
@Published var hasSubscriptionsNotifyRule = false
@Published var showOperationToast = false
@Published var operationStatus: OperationStatus = .none
@Published var operationMessage: String?
let subscriptionRuleName = "system.autoNotify.subscriptions"
@AppStorage(UserDefaultKey.notificationsEnabled.rawValue) var notificationsEnabled = false
func checkPushNotificationsStatus() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
DispatchQueue.main.async {
self.desiredNotificationsEnabled = settings.alertSetting == UNNotificationSetting.enabled
}
}
}
func tryUpdateToDesired(dataService: DataService) {
UserDefaults.standard.set(desiredNotificationsEnabled, forKey: UserDefaultKey.notificationsEnabled.rawValue)
if desiredNotificationsEnabled {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in
DispatchQueue.main.async {
self.desiredNotificationsEnabled = granted
Task {
if let savedToken = UserDefaults.standard.string(forKey: UserDefaultKey.firebasePushToken.rawValue) {
_ = try? await dataService.syncDeviceToken(
deviceTokenOperation: DeviceTokenOperation.addToken(token: savedToken))
}
NotificationCenter.default.post(name: Notification.Name("ReconfigurePushNotifications"), object: nil)
}
}
}
} else {
if let tokenID = UserDefaults.standard.string(forKey: UserDefaultKey.deviceTokenID.rawValue) {
Task {
try? await Services().dataService.syncDeviceToken(deviceTokenOperation: .deleteToken(tokenID: tokenID))
}
}
}
}
func loadRule(dataService: DataService) async {
do {
let rule = try await dataService.rules().filter { $0.name == subscriptionRuleName }.first
setAllSubscriptionRule(rule: rule)
} catch {
print("error fetching", error)
setAllSubscriptionRule(rule: nil)
}
}
func setAllSubscriptionRule(rule: Rule?) {
allSubscriptionsNotificationRule = rule
hasSubscriptionsNotifyRule = allSubscriptionsNotificationRule != nil
isLoadingRules = false
}
func createSubscriptionNotificationRule(dataService: DataService) {
if allSubscriptionsNotificationRule != nil {
return
}
operationMessage = "Creating notification rule..."
operationStatus = .isPerforming
showOperationToast = true
Task {
do {
let rule = try await dataService.createNotificationRule(
name: subscriptionRuleName,
filter: "in:all has:subscription"
)
setAllSubscriptionRule(rule: rule)
operationMessage = "Rule created"
operationStatus = .success
} catch {
print("error creating notification rule: ", error)
operationMessage = "Failed to create notification rule"
operationStatus = .failure
}
}
}
func deleteSubscriptionNotificationRule(dataService: DataService) {
operationMessage = "Creating label rule..."
operationStatus = .isPerforming
showOperationToast = true
Task {
do {
if let allSubscriptionsNotificationRule = allSubscriptionsNotificationRule {
_ = try await dataService.deleteRule(ruleID: allSubscriptionsNotificationRule.id)
setAllSubscriptionRule(rule: nil)
operationMessage = "Notification rule deleted"
operationStatus = .success
}
} catch {
operationMessage = "Failed to create label rule"
operationStatus = .failure
}
}
}
}
struct PushNotificationSettingsView: View {
@EnvironmentObject var dataService: DataService
@Environment(\.dismiss) private var dismiss
@StateObject var viewModel = PushNotificationSettingsViewModel()
@State var desiredNotificationsEnabled: Bool = false
var body: some View {
Group {
WindowLink(level: .alert, transition: .move(edge: .bottom), isPresented: $viewModel.showOperationToast) {
OperationToast(operationMessage: $viewModel.operationMessage, showOperationToast: $viewModel.showOperationToast, operationStatus: $viewModel.operationStatus)
} label: {
EmptyView()
}.buttonStyle(.plain)
#if os(iOS)
Form {
innerBody
}
#elseif os(macOS)
List {
innerBody
}
.listStyle(InsetListStyle())
#endif
}
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in
dismiss()
}
.task {
viewModel.checkPushNotificationsStatus()
await viewModel.loadRule(dataService: dataService)
}
}
private var notificationsText: some View {
let markdown = "\(LocalText.notificationsExplainer)\n\n\(LocalText.notificationsTriggerExplainer)"
if let notificationsText = try? AttributedString(
markdown: markdown,
options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
) {
return Text(notificationsText)
.accentColor(.blue)
}
return Text(markdown)
.accentColor(.blue)
}
private var rulesSection: some View {
if viewModel.isLoadingRules {
AnyView(EmptyView())
} else {
AnyView(Toggle("Notify me when new items arrive from my subscriptions", isOn: $viewModel.hasSubscriptionsNotifyRule))
}
}
private var innerBody: some View {
Group {
Section {
Toggle(isOn: $viewModel.desiredNotificationsEnabled, label: { Text(LocalText.notificationsEnabled) })
}.onChange(of: viewModel.desiredNotificationsEnabled) { _ in
viewModel.tryUpdateToDesired(dataService: dataService)
}
Section {
notificationsText
}
Section {
rulesSection
}
Section {
NavigationLink("Devices") {
PushNotificationDevicesView()
}
}
}
.onChange(of: viewModel.hasSubscriptionsNotifyRule) { newValue in
print("has notification rule: \(newValue)")
if viewModel.isLoadingRules {
return
}
if newValue {
viewModel.createSubscriptionNotificationRule(dataService: dataService)
} else {
viewModel.deleteSubscriptionNotificationRule(dataService: dataService)
}
}
.onChange(of: viewModel.operationStatus) { newValue in
if newValue == .success || newValue == .failure {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2000)) {
viewModel.showOperationToast = false
}
}
}
.navigationTitle(LocalText.pushNotificationsGeneric)
}
}
#endif