Push configuration on iOS
This commit is contained in:
@ -4,47 +4,120 @@
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
import Transmission
|
||||
|
||||
@MainActor final class PushNotificationSettingsViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var emails = [NewsletterEmail]()
|
||||
@Published var desiredNotificationsEnabled = false
|
||||
@AppStorage(UserDefaultKey.notificationsEnabled.rawValue) var notificationsEnabled = false
|
||||
@MainActor final class PushNotificationSettingsViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var isLoadingRules = true
|
||||
|
||||
func checkPushNotificationsStatus() {
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
DispatchQueue.main.async {
|
||||
self.desiredNotificationsEnabled = settings.alertSetting == UNNotificationSetting.enabled
|
||||
}
|
||||
@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)
|
||||
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)
|
||||
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))
|
||||
}
|
||||
}
|
||||
} 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
|
||||
@ -54,6 +127,12 @@
|
||||
|
||||
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
|
||||
@ -68,7 +147,10 @@
|
||||
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.task { viewModel.checkPushNotificationsStatus() }
|
||||
.task {
|
||||
viewModel.checkPushNotificationsStatus()
|
||||
await viewModel.loadRule(dataService: dataService)
|
||||
}
|
||||
}
|
||||
|
||||
private var notificationsText: some View {
|
||||
@ -84,6 +166,14 @@
|
||||
.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 {
|
||||
@ -96,12 +186,35 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,7 +273,7 @@ struct SubscriptionsView: View {
|
||||
await viewModel.cancelSubscription(dataService: dataService, subscription: subscription)
|
||||
}
|
||||
}
|
||||
)
|
||||
).background(Color.systemBackground)
|
||||
} label: {
|
||||
SubscriptionCell(subscription: subscription)
|
||||
}
|
||||
@ -526,6 +526,25 @@ struct SubscriptionSettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// var notificationRuleRow: some View {
|
||||
// HStack {
|
||||
// Text("Add Labels")
|
||||
// Spacer()
|
||||
// if isLoadingRule || viewModel.rules != nil {
|
||||
// Button(action: { showLabelsSelector = true }, label: {
|
||||
// if let ruleLabels = ruleLabels {
|
||||
// let labelNames = ruleLabels.map(\.unwrappedName)
|
||||
// Text("[\(labelNames.joined(separator: ","))]")
|
||||
// } else {
|
||||
// Text("Create Rule")
|
||||
// }
|
||||
// }).tint(Color.blue)
|
||||
// } else {
|
||||
// ProgressView()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
||||
@ -89,4 +89,51 @@ public extension DataService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createNotificationRule(name: String, filter: String) async throws -> Rule {
|
||||
enum MutationResult {
|
||||
case result(rule: Rule)
|
||||
case error(errorMessage: String)
|
||||
}
|
||||
|
||||
let selection = Selection<MutationResult, Unions.SetRuleResult> {
|
||||
try $0.on(
|
||||
setRuleError: .init { .error(errorMessage: try $0.errorCodes().first?.rawValue ?? "Unknown Error") },
|
||||
setRuleSuccess: .init { .result(rule: try $0.rule(selection: ruleSelection)) }
|
||||
)
|
||||
}
|
||||
|
||||
let mutation = Selection.Mutation {
|
||||
try $0.setRule(
|
||||
input: InputObjects.SetRuleInput(
|
||||
actions: [InputObjects.RuleActionInput(params: [], type: .sendNotification)],
|
||||
enabled: true,
|
||||
eventTypes: [.pageCreated],
|
||||
filter: filter,
|
||||
id: OptionalArgument(nil),
|
||||
name: name
|
||||
),
|
||||
selection: selection
|
||||
)
|
||||
}
|
||||
|
||||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(mutation, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "network error"))
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case let .result(rule: rule):
|
||||
continuation.resume(returning: rule)
|
||||
case let .error(errorMessage: errorMessage):
|
||||
continuation.resume(throwing: BasicError.message(messageText: errorMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
|
||||
|
||||
let userInfo = notification.request.content.userInfo
|
||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||
print(userInfo) // extract data sent along with PN
|
||||
print("push data", userInfo) // extract data sent along with PN
|
||||
completionHandler([[.banner, .sound]])
|
||||
}
|
||||
|
||||
|
||||
@ -70,8 +70,10 @@ const sendNotification = async (obj: RuleActionObj) => {
|
||||
const message = {
|
||||
title: item.author || item.siteName || 'Omnivore',
|
||||
body: item.title,
|
||||
image: item.thumbnail,
|
||||
}
|
||||
const data = {
|
||||
libraryItemId: item.id,
|
||||
thumbnail: item.thumbnail,
|
||||
}
|
||||
|
||||
return sendPushNotifications(obj.userId, message, 'rule')
|
||||
|
||||
Reference in New Issue
Block a user