// // PushInstanceSettingsView.swift // Tusker // // Created by Shadowfacts on 4/7/24. // Copyright © 2024 Shadowfacts. All rights reserved. // import SwiftUI import UserAccounts import Pachyderm import PushNotifications import TuskerComponents struct PushInstanceSettingsView: View { let account: UserAccountInfo let pushProxyRegistration: PushProxyRegistration @State private var mode: AsyncToggle.Mode @State private var error: Error? @State private var subscription: PushNotifications.PushSubscription? @State private var showReLoginRequiredAlert = false @MainActor init(account: UserAccountInfo, pushProxyRegistration: PushProxyRegistration) { self.account = account self.pushProxyRegistration = pushProxyRegistration let subscription = PushManager.shared.pushSubscription(account: account) self.subscription = subscription self.mode = subscription == nil ? .off : .on } var body: some View { VStack(alignment: .prefsAvatar) { HStack { PrefsAccountView(account: account) Spacer() AsyncToggle("\(account.instanceURL.host!) notifications enabled", labelHidden: true, mode: $mode, onChange: updateNotificationsEnabled(enabled:)) .labelsHidden() } PushSubscriptionView(account: account, subscription: subscription, updateSubscription: updateSubscription) } .alertWithData("An Error Occurred", data: $error) { data in Button("OK") {} } message: { data in Text(data.localizedDescription) } .alert("Re-Login Required", isPresented: $showReLoginRequiredAlert) { Button("Cancel", role: .cancel) {} Button("Login") { NotificationCenter.default.post(name: .reLogInRequired, object: account) } } message: { Text("You must grant permission on \(account.instanceURL.host!) to turn on push notifications.") } } private func updateNotificationsEnabled(enabled: Bool) async -> Bool { if enabled { do { return try await enableNotifications() } catch { PushManager.logger.error("Error creating instance subscription: \(String(describing: error))") self.error = .enabling(error) return false } } else { do { try await disableNotifications() } catch { PushManager.logger.error("Error removing instance subscription: \(String(describing: error))") self.error = .disabling(error) return false } } return true } private func enableNotifications() async throws -> Bool { guard account.scopes?.contains(Scope.push.rawValue) == true else { showReLoginRequiredAlert = true return false } let subscription = try await PushManager.shared.createSubscription(account: account) let mastodonController = await MastodonController.getForAccount(account) do { let result = try await mastodonController.createPushSubscription(subscription: subscription) PushManager.logger.debug("Push subscription \(result.id) created on \(account.instanceURL)") self.subscription = subscription return true } catch { // if creation failed, remove the subscription locally as well await PushManager.shared.removeSubscription(account: account) throw error } } private func disableNotifications() async throws { let mastodonController = await MastodonController.getForAccount(account) try await mastodonController.deletePushSubscription() await PushManager.shared.removeSubscription(account: account) subscription = nil PushManager.logger.debug("Push subscription removed on \(account.instanceURL)") } private func updateSubscription(alerts: PushNotifications.PushSubscription.Alerts, policy: PushNotifications.PushSubscription.Policy) async -> Bool { let mastodonController = await MastodonController.getForAccount(account) do { let result = try await mastodonController.updatePushSubscription(alerts: alerts, policy: policy) PushManager.logger.debug("Push subscription \(result.id) updated on \(account.instanceURL)") subscription?.alerts = alerts subscription?.policy = policy return true } catch { PushManager.logger.error("Error updating subscription: \(String(describing: error))") self.error = .updating(error) return false } } } private enum Error: LocalizedError { case enabling(any Swift.Error) case disabling(any Swift.Error) case updating(any Swift.Error) var errorDescription: String? { switch self { case .enabling(let error): "Enabling push: \(error.localizedDescription)" case .disabling(let error): "Disabling push: \(error.localizedDescription)" case .updating(let error): "Updating settings: \(error.localizedDescription)" } } } //#Preview { // PushInstanceSettingsView() //}