222 lines
7.1 KiB
Swift
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
|