// // PushSubscriptionView.swift // Tusker // // Created by Shadowfacts on 4/7/24. // Copyright © 2024 Shadowfacts. All rights reserved. // import SwiftUI import UserAccounts import PushNotifications import TuskerComponents struct PushSubscriptionView: View { let account: UserAccountInfo let mastodonController: MastodonController let subscription: PushSubscription? let updateSubscription: (PushSubscription.Alerts, PushSubscription.Policy) async -> Bool var body: some View { if let subscription { PushSubscriptionSettingsView(account: account, mastodonController: mastodonController, subscription: subscription, updateSubscription: updateSubscription) } else { Text("No notifications") .font(.callout) .foregroundStyle(.secondary) } } } private struct PushSubscriptionSettingsView: View { let account: UserAccountInfo let mastodonController: MastodonController let subscription: PushSubscription let updateSubscription: (PushSubscription.Alerts, PushSubscription.Policy) async -> Bool @State private var isLoading: [PushSubscription.Alerts: Bool] = [:] var body: some View { VStack { alertsToggles if mastodonController.instanceFeatures.pushNotificationPolicy { AsyncPicker("From", alignment: .trailing, value: .constant(subscription.policy)) { newPolicy in await updateSubscription(subscription.alerts, newPolicy) } content: { ForEach(PushSubscription.Policy.allCases) { Text($0.displayName).tag($0) } } .pickerStyle(.menu) } } // this is the default value of the alignment guide, but this modifier is loading bearing .alignmentGuide(.prefsAvatar, computeValue: { dimension in dimension[.leading] }) // otherwise the flexible view makes the containing stack extend under the edge of the list row .padding(.leading, 38) } private var alertsToggles: some View { GroupBox("Get notifications for") { VStack { toggle("All", alert: allSupportedAlertTypes) toggle("Mentions", alert: .mention) toggle("Favorites", alert: .favorite) toggle("Reblogs", alert: .reblog) if mastodonController.instanceFeatures.pushNotificationTypeFollowRequest { toggle("Follows", alert: [.follow, .followRequest]) } else { toggle("Follows", alert: .follow) } toggle("Polls finishing", alert: .poll) if mastodonController.instanceFeatures.pushNotificationTypeUpdate { toggle("Edits", alert: .update) } // status notifications not supported until we can enable/disable them in the app } } .groupBoxStyle(AppBackgroundGroupBoxStyle()) } private var allSupportedAlertTypes: PushSubscription.Alerts { var alerts: PushSubscription.Alerts = [.mention, .favorite, .reblog, .follow, .poll] if mastodonController.instanceFeatures.pushNotificationTypeFollowRequest { alerts.insert(.followRequest) } if mastodonController.instanceFeatures.pushNotificationTypeUpdate { alerts.insert(.update) } return alerts } private func toggle(_ titleKey: LocalizedStringKey, alert: PushSubscription.Alerts) -> some View { let binding: Binding = Binding { isLoading[alert] == true ? .loading : subscription.alerts.contains(alert) ? .on : .off } set: { newValue in isLoading[alert] = newValue == .loading } return AsyncToggle(titleKey, mode: binding) { return await updateAlert(alert, isOn: $0) } } private func updateAlert(_ alert: PushSubscription.Alerts, isOn: Bool) async -> Bool { var newAlerts = subscription.alerts if isOn { newAlerts.insert(alert) } else { newAlerts.remove(alert) } return await updateSubscription(newAlerts, subscription.policy) } } private extension PushSubscription.Policy { var displayName: String { switch self { case .all: "Anyone" case .followed: "Accounts you follow" case .followers: "Your followers" } } } private struct AppBackgroundGroupBoxStyle: GroupBoxStyle { func makeBody(configuration: Configuration) -> some View { VStack(alignment: .leading, spacing: 16) { configuration.label .font(.headline) configuration.content } .padding() .background(Color.appGroupedBackground, in: RoundedRectangle(cornerRadius: 8)) } } //#Preview { // PushSubscriptionView() //}