diff --git a/Packages/PushNotifications/.gitignore b/Packages/PushNotifications/.gitignore new file mode 100644 index 00000000..0023a534 --- /dev/null +++ b/Packages/PushNotifications/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/PushNotifications/.swiftpm/xcode/xcshareddata/xcschemes/PushNotifications.xcscheme b/Packages/PushNotifications/.swiftpm/xcode/xcshareddata/xcschemes/PushNotifications.xcscheme new file mode 100644 index 00000000..dc00e721 --- /dev/null +++ b/Packages/PushNotifications/.swiftpm/xcode/xcshareddata/xcschemes/PushNotifications.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Packages/PushNotifications/Package.swift b/Packages/PushNotifications/Package.swift new file mode 100644 index 00000000..aa1efd40 --- /dev/null +++ b/Packages/PushNotifications/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version: 5.10 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "PushNotifications", + platforms: [ + .iOS(.v15), + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "PushNotifications", + targets: ["PushNotifications"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "PushNotifications"), + .testTarget( + name: "PushNotificationsTests", + dependencies: ["PushNotifications"]), + ] +) diff --git a/Packages/PushNotifications/Sources/PushNotifications/DisabledPushManager.swift b/Packages/PushNotifications/Sources/PushNotifications/DisabledPushManager.swift new file mode 100644 index 00000000..6a715a43 --- /dev/null +++ b/Packages/PushNotifications/Sources/PushNotifications/DisabledPushManager.swift @@ -0,0 +1,45 @@ +// +// DisabledPushManager.swift +// PushNotifications +// +// Created by Shadowfacts on 4/7/24. +// + +import Foundation +import UserAccounts + +class DisabledPushManager: _PushManager { + var enabled: Bool { + false + } + + var pushProxyRegistration: PushProxyRegistration? { + nil + } + + func pushSubscription(account: UserAccountInfo) -> PushSubscription? { + nil + } + + func register(transactionID: UInt64) async throws -> PushProxyRegistration { + throw Disabled() + } + + func unregister() async throws { + throw Disabled() + } + + func updateIfNecessary() async { + } + + func didRegisterForRemoteNotifications(deviceToken: Data) { + } + func didFailToRegisterForRemoteNotifications(error: any Error) { + } + + struct Disabled: LocalizedError { + var errorDescription: String? { + "Push notifications disabled" + } + } +} diff --git a/Packages/PushNotifications/Sources/PushNotifications/PushManager.swift b/Packages/PushNotifications/Sources/PushNotifications/PushManager.swift new file mode 100644 index 00000000..cd449e6d --- /dev/null +++ b/Packages/PushNotifications/Sources/PushNotifications/PushManager.swift @@ -0,0 +1,56 @@ +// +// PushManager.swift +// PushNotifications +// +// Created by Shadowfacts on 4/7/24. +// + +import Foundation +import OSLog +#if canImport(Sentry) +import Sentry +#endif +import Pachyderm +import UserAccounts + +public struct PushManager { + @MainActor + public static let shared = createPushManager() + + static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PushManager") + + private init() {} + + @MainActor + private static func createPushManager() -> any _PushManager { + guard let info = Bundle.main.object(forInfoDictionaryKey: "TuskerInfo") as? [String: Any], + let scheme = info["PushProxyScheme"] as? String, + let host = info["PushProxyHost"] as? String, + !scheme.isEmpty, + !host.isEmpty else { + logger.debug("Missing proxy info, push disabled") + return DisabledPushManager() + } + var endpoint = URLComponents() + endpoint.scheme = scheme + endpoint.host = host + let url = endpoint.url! + logger.debug("Push notifications enabled with proxy \(url.absoluteString, privacy: .public)") + return PushManagerImpl(endpoint: url) + } +} + +@MainActor +public protocol _PushManager { + var enabled: Bool { get } + var pushProxyRegistration: PushProxyRegistration? { get } + + func pushSubscription(account: UserAccountInfo) -> PushSubscription? + + func register(transactionID: UInt64) async throws -> PushProxyRegistration + func unregister() async throws + func updateIfNecessary() async + + func didRegisterForRemoteNotifications(deviceToken: Data) + func didFailToRegisterForRemoteNotifications(error: any Error) +} diff --git a/Tusker/Push/PushManager.swift b/Packages/PushNotifications/Sources/PushNotifications/PushManagerImpl.swift similarity index 61% rename from Tusker/Push/PushManager.swift rename to Packages/PushNotifications/Sources/PushNotifications/PushManagerImpl.swift index ac6267fa..ed7bb81f 100644 --- a/Tusker/Push/PushManager.swift +++ b/Packages/PushNotifications/Sources/PushNotifications/PushManagerImpl.swift @@ -1,97 +1,17 @@ // -// PushManager.swift -// Tusker +// PushManagerImpl.swift +// PushNotifications // -// Created by Shadowfacts on 4/6/24. -// Copyright © 2024 Shadowfacts. All rights reserved. +// Created by Shadowfacts on 4/7/24. // -import Foundation -import OSLog +import UIKit +import UserAccounts #if canImport(Sentry) import Sentry #endif -import Pachyderm -import UserAccounts -private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PushManager") - -@MainActor -struct PushManager { - static let shared = createPushManager() - - private init() {} - - private static func createPushManager() -> any _PushManager { - guard let info = Bundle.main.object(forInfoDictionaryKey: "TuskerInfo") as? [String: Any], - let scheme = info["PushProxyScheme"] as? String, - let host = info["PushProxyHost"] as? String, - !scheme.isEmpty, - !host.isEmpty else { - logger.debug("Missing proxy info, push disabled") - return DisabledPushManager() - } - var endpoint = URLComponents() - endpoint.scheme = scheme - endpoint.host = host - let url = endpoint.url! - logger.debug("Push notifications enabled with proxy \(url.absoluteString, privacy: .public)") - return PushManagerImpl(endpoint: url) - } -} - -@MainActor -protocol _PushManager { - var enabled: Bool { get } - var pushProxyRegistration: PushProxyRegistration? { get } - - func pushSubscription(account: UserAccountInfo) -> PushSubscription? - - func register(transactionID: UInt64) async throws -> PushProxyRegistration - func unregister() async throws - func updateIfNecessary() async - - func didRegisterForRemoteNotifications(deviceToken: Data) - func didFailToRegisterForRemoteNotifications(error: any Error) -} - -private class DisabledPushManager: _PushManager { - var enabled: Bool { - false - } - - var pushProxyRegistration: PushProxyRegistration? { - nil - } - - func pushSubscription(account: UserAccountInfo) -> PushSubscription? { - nil - } - - func register(transactionID: UInt64) async throws -> PushProxyRegistration { - throw Disabled() - } - - func unregister() async throws { - throw Disabled() - } - - func updateIfNecessary() async { - } - - func didRegisterForRemoteNotifications(deviceToken: Data) { - } - func didFailToRegisterForRemoteNotifications(error: any Error) { - } - - struct Disabled: LocalizedError { - var errorDescription: String? { - "Push notifications disabled" - } - } -} - -private class PushManagerImpl: _PushManager { +class PushManagerImpl: _PushManager { private let endpoint: URL var enabled: Bool { @@ -148,13 +68,13 @@ private class PushManagerImpl: _PushManager { throw PushRegistrationError.alreadyRegistering } let deviceToken = try await getDeviceToken().hexEncodedString() - logger.debug("Got device token: \(deviceToken)") + PushManager.logger.debug("Got device token: \(deviceToken)") let registration: PushProxyRegistration do { registration = try await register(deviceToken: deviceToken) - logger.debug("Got endpoint: \(registration.endpoint)") + PushManager.logger.debug("Got endpoint: \(registration.endpoint)") } catch { - logger.error("Proxy registration failed: \(String(describing: error))") + PushManager.logger.error("Proxy registration failed: \(String(describing: error))") throw PushRegistrationError.registeringWithProxy(error) } pushProxyRegistration = registration @@ -173,9 +93,9 @@ private class PushManagerImpl: _PushManager { let status = (resp as! HTTPURLResponse).statusCode if (200...299).contains(status) { self.pushProxyRegistration = nil - logger.debug("Unregistered from proxy") + PushManager.logger.debug("Unregistered from proxy") } else { - logger.error("Unregistering: unexpected status \(status)") + PushManager.logger.error("Unregistering: unexpected status \(status)") let error = (try? JSONDecoder().decode(ProxyRegistrationError.self, from: data)) ?? ProxyRegistrationError(error: "Unknown error", fields: nil) throw PushRegistrationError.unregistering(error) } @@ -185,7 +105,7 @@ private class PushManagerImpl: _PushManager { guard let pushProxyRegistration else { return } - logger.debug("Push proxy registration: \(pushProxyRegistration.id, privacy: .public)") + PushManager.logger.debug("Push proxy registration: \(pushProxyRegistration.id, privacy: .public)") do { let token = try await getDeviceToken().hexEncodedString() guard token != pushProxyRegistration.deviceToken else { @@ -197,7 +117,7 @@ private class PushManagerImpl: _PushManager { // TODO: update subscriptions if the endpoint's changed } } catch { - logger.error("Failed to update push registration: \(String(describing: error), privacy: .public)") + PushManager.logger.error("Failed to update push registration: \(String(describing: error), privacy: .public)") #if canImport(Sentry) SentrySDK.capture(error: error) #endif @@ -232,7 +152,7 @@ private class PushManagerImpl: _PushManager { let (data, resp) = try await URLSession.shared.data(for: request) let status = (resp as! HTTPURLResponse).statusCode guard (200...299).contains(status) else { - logger.error("Registering: unexpected status \(status)") + PushManager.logger.error("Registering: unexpected status \(status)") let error = (try? JSONDecoder().decode(ProxyRegistrationError.self, from: data)) ?? ProxyRegistrationError(error: "Unknown error", fields: []) throw error } @@ -249,7 +169,7 @@ private class PushManagerImpl: _PushManager { let (data, resp) = try await URLSession.shared.data(for: request) let status = (resp as! HTTPURLResponse).statusCode guard (200...299).contains(status) else { - logger.error("Updating: unexpected status \(status)") + PushManager.logger.error("Updating: unexpected status \(status)") let error = (try? JSONDecoder().decode(ProxyRegistrationError.self, from: data)) ?? ProxyRegistrationError(error: "Unknown error", fields: []) throw error } @@ -322,37 +242,6 @@ private struct PushUpdateParams: Encodable { } } -struct PushProxyRegistration: Decodable { - let id: String - let endpoint: URL - let deviceToken: String - - fileprivate var defaultsDict: [String: String] { - [ - "id": id, - "endpoint": endpoint.absoluteString, - "deviceToken": deviceToken, - ] - } - - fileprivate init?(defaultsDict: [String: String]) { - guard let id = defaultsDict["id"], - let endpoint = defaultsDict["endpoint"].flatMap(URL.init(string:)), - let deviceToken = defaultsDict["deviceToken"] else { - return nil - } - self.id = id - self.endpoint = endpoint - self.deviceToken = deviceToken - } - - private enum CodingKeys: String, CodingKey { - case id - case endpoint - case deviceToken = "device_token" - } -} - private extension Data { func hexEncodedString() -> String { String(unsafeUninitializedCapacity: count * 2) { buffer in @@ -366,49 +255,3 @@ private extension Data { } } } - -struct PushSubscription { - let accountID: String - let endpoint: URL - let alerts: Alerts - let policy: Policy - - fileprivate var defaultsDict: [String: Any] { - [ - "accountID": accountID, - "endpoint": endpoint.absoluteString, - "alerts": alerts.rawValue, - "policy": policy.rawValue - ] - } - - init?(defaultsDict: [String: Any]) { - guard let accountID = defaultsDict["accountID"] as? String, - let endpoint = (defaultsDict["endpoint"] as? String).flatMap(URL.init(string:)), - let alerts = defaultsDict["alerts"] as? Int, - let policy = (defaultsDict["policy"] as? String).flatMap(Policy.init(rawValue:)) else { - return nil - } - self.accountID = accountID - self.endpoint = endpoint - self.alerts = Alerts(rawValue: alerts) - self.policy = policy - } - - enum Policy: String { - case all, followed, followers - } - - struct Alerts: OptionSet { - static let mention = Alerts(rawValue: 1 << 0) - static let status = Alerts(rawValue: 1 << 1) - static let reblog = Alerts(rawValue: 1 << 2) - static let follow = Alerts(rawValue: 1 << 3) - static let followRequest = Alerts(rawValue: 1 << 4) - static let favorite = Alerts(rawValue: 1 << 5) - static let poll = Alerts(rawValue: 1 << 6) - static let update = Alerts(rawValue: 1 << 7) - - let rawValue: Int - } -} diff --git a/Packages/PushNotifications/Sources/PushNotifications/PushProxyRegistration.swift b/Packages/PushNotifications/Sources/PushNotifications/PushProxyRegistration.swift new file mode 100644 index 00000000..395cda90 --- /dev/null +++ b/Packages/PushNotifications/Sources/PushNotifications/PushProxyRegistration.swift @@ -0,0 +1,39 @@ +// +// PushProxyRegistration.swift +// PushNotifications +// +// Created by Shadowfacts on 4/7/24. +// + +import Foundation + +public struct PushProxyRegistration: Decodable { + let id: String + let endpoint: URL + let deviceToken: String + + var defaultsDict: [String: String] { + [ + "id": id, + "endpoint": endpoint.absoluteString, + "deviceToken": deviceToken, + ] + } + + init?(defaultsDict: [String: String]) { + guard let id = defaultsDict["id"], + let endpoint = defaultsDict["endpoint"].flatMap(URL.init(string:)), + let deviceToken = defaultsDict["deviceToken"] else { + return nil + } + self.id = id + self.endpoint = endpoint + self.deviceToken = deviceToken + } + + private enum CodingKeys: String, CodingKey { + case id + case endpoint + case deviceToken = "device_token" + } +} diff --git a/Packages/PushNotifications/Sources/PushNotifications/PushSubscription.swift b/Packages/PushNotifications/Sources/PushNotifications/PushSubscription.swift new file mode 100644 index 00000000..4003a4eb --- /dev/null +++ b/Packages/PushNotifications/Sources/PushNotifications/PushSubscription.swift @@ -0,0 +1,54 @@ +// +// PushSubscription.swift +// PushNotifications +// +// Created by Shadowfacts on 4/7/24. +// + +import Foundation + +public struct PushSubscription { + let accountID: String + let endpoint: URL + let alerts: Alerts + let policy: Policy + + var defaultsDict: [String: Any] { + [ + "accountID": accountID, + "endpoint": endpoint.absoluteString, + "alerts": alerts.rawValue, + "policy": policy.rawValue + ] + } + + init?(defaultsDict: [String: Any]) { + guard let accountID = defaultsDict["accountID"] as? String, + let endpoint = (defaultsDict["endpoint"] as? String).flatMap(URL.init(string:)), + let alerts = defaultsDict["alerts"] as? Int, + let policy = (defaultsDict["policy"] as? String).flatMap(Policy.init(rawValue:)) else { + return nil + } + self.accountID = accountID + self.endpoint = endpoint + self.alerts = Alerts(rawValue: alerts) + self.policy = policy + } + + enum Policy: String { + case all, followed, followers + } + + struct Alerts: OptionSet { + static let mention = Alerts(rawValue: 1 << 0) + static let status = Alerts(rawValue: 1 << 1) + static let reblog = Alerts(rawValue: 1 << 2) + static let follow = Alerts(rawValue: 1 << 3) + static let followRequest = Alerts(rawValue: 1 << 4) + static let favorite = Alerts(rawValue: 1 << 5) + static let poll = Alerts(rawValue: 1 << 6) + static let update = Alerts(rawValue: 1 << 7) + + let rawValue: Int + } +} diff --git a/Packages/PushNotifications/Tests/PushNotificationsTests/PushNotificationsTests.swift b/Packages/PushNotifications/Tests/PushNotificationsTests/PushNotificationsTests.swift new file mode 100644 index 00000000..d36eb331 --- /dev/null +++ b/Packages/PushNotifications/Tests/PushNotificationsTests/PushNotificationsTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import PushNotifications + +final class PushNotificationsTests: XCTestCase { + func testExample() throws { + // XCTest Documentation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index ad9973fb..f2e6a02a 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ D62E9985279CA23900C26176 /* URLSession+Development.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9984279CA23900C26176 /* URLSession+Development.swift */; }; D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */; }; D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */; }; + D630C3C82BC43AFD00208903 /* PushNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = D630C3C72BC43AFD00208903 /* PushNotifications */; }; D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; }; D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; }; D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; }; @@ -122,7 +123,6 @@ D64AAE9526C88C5000FC57FB /* ToastableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */; }; D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */; }; D64B967C2BC19C28002C8990 /* NotificationsPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64B967B2BC19C28002C8990 /* NotificationsPrefsView.swift */; }; - D64B967F2BC1D447002C8990 /* PushManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64B967E2BC1D447002C8990 /* PushManager.swift */; }; D64B96812BC3279D002C8990 /* PrefsAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64B96802BC3279D002C8990 /* PrefsAccountView.swift */; }; D64B96842BC3893C002C8990 /* PushSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64B96832BC3893C002C8990 /* PushSubscriptionView.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; @@ -528,7 +528,6 @@ D64AAE9426C88C5000FC57FB /* ToastableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastableViewController.swift; sourceTree = ""; }; D64AAE9626C88DC400FC57FB /* ToastConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastConfiguration.swift; sourceTree = ""; }; D64B967B2BC19C28002C8990 /* NotificationsPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPrefsView.swift; sourceTree = ""; }; - D64B967E2BC1D447002C8990 /* PushManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushManager.swift; sourceTree = ""; }; D64B96802BC3279D002C8990 /* PrefsAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsAccountView.swift; sourceTree = ""; }; D64B96832BC3893C002C8990 /* PushSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSubscriptionView.swift; sourceTree = ""; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; @@ -539,6 +538,7 @@ D659F36129541065002D944A /* TTTView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTTView.swift; sourceTree = ""; }; D65A261A2BC3928A005EB5D8 /* TriStateToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriStateToggle.swift; sourceTree = ""; }; D65A261C2BC39399005EB5D8 /* PushInstanceSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushInstanceSettingsView.swift; sourceTree = ""; }; + D65A26242BC39A02005EB5D8 /* PushNotifications */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PushNotifications; sourceTree = ""; }; D65B4B532971F71D00DABDFB /* EditedReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditedReport.swift; sourceTree = ""; }; D65B4B552971F98300DABDFB /* ReportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = ""; }; D65B4B57297203A700DABDFB /* ReportSelectRulesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportSelectRulesView.swift; sourceTree = ""; }; @@ -793,6 +793,7 @@ buildActionMask = 2147483647; files = ( D6BD395929B64426005FFD2B /* ComposeUI in Frameworks */, + D630C3C82BC43AFD00208903 /* PushNotifications in Frameworks */, D6FA94E129B52898006AAC51 /* InstanceFeatures in Frameworks */, D635237129B78A7D009ED5E7 /* TuskerComponents in Frameworks */, D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */, @@ -1181,6 +1182,7 @@ D6CA6ED029EF6060003EC5DF /* TuskerPreferences */, D6A9E04F29F8917500BEDC7E /* MatchedGeometryPresentation */, D642E83D2BA7AD0F004BFD6A /* GalleryVC */, + D65A26242BC39A02005EB5D8 /* PushNotifications */, ); path = Packages; sourceTree = ""; @@ -1195,14 +1197,6 @@ path = Toast; sourceTree = ""; }; - D64B967D2BC1D43A002C8990 /* Push */ = { - isa = PBXGroup; - children = ( - D64B967E2BC1D447002C8990 /* PushManager.swift */, - ); - path = Push; - sourceTree = ""; - }; D64B96822BC3892B002C8990 /* Notifications */ = { isa = PBXGroup; children = ( @@ -1551,7 +1545,6 @@ D6E57FA525C26FAB00341037 /* Localizable.stringsdict */, D61959D2241E846D00A37B8E /* Models */, D663626021360A9600C9CBA2 /* Preferences */, - D64B967D2BC1D43A002C8990 /* Push */, D63CC70A2910AAC6000E19DE /* Scenes */, D641C780213DD7C4004B4513 /* Screens */, D62D241E217AA46B005076CC /* Shortcuts */, @@ -1739,6 +1732,7 @@ D6CA6ED129EF6091003EC5DF /* TuskerPreferences */, D60BB3932B30076F00DAEA65 /* HTMLStreamer */, D6934F2B2BA7AD32002B1C8D /* GalleryVC */, + D630C3C72BC43AFD00208903 /* PushNotifications */, ); productName = Tusker; productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */; @@ -2161,7 +2155,6 @@ D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */, D65A261B2BC3928A005EB5D8 /* TriStateToggle.swift in Sources */, D620483423D3801D008A63EF /* LinkTextView.swift in Sources */, - D64B967F2BC1D447002C8990 /* PushManager.swift in Sources */, D61F75882932DB6000C0B37F /* StatusSwipeActions.swift in Sources */, D68A76EA295285D0001DA1B3 /* AddHashtagPinnedTimelineView.swift in Sources */, D6F253CF2AC9F86300806D83 /* SearchTokenSuggestionCollectionViewCell.swift in Sources */, @@ -3044,6 +3037,10 @@ isa = XCSwiftPackageProductDependency; productName = Pachyderm; }; + D630C3C72BC43AFD00208903 /* PushNotifications */ = { + isa = XCSwiftPackageProductDependency; + productName = PushNotifications; + }; D635237029B78A7D009ED5E7 /* TuskerComponents */ = { isa = XCSwiftPackageProductDependency; productName = TuskerComponents; diff --git a/Tusker/AppDelegate.swift b/Tusker/AppDelegate.swift index d53d231f..6a7cc075 100644 --- a/Tusker/AppDelegate.swift +++ b/Tusker/AppDelegate.swift @@ -15,6 +15,7 @@ import Sentry import UserAccounts import ComposeUI import TuskerPreferences +import PushNotifications typealias Preferences = TuskerPreferences.Preferences diff --git a/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift b/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift index 94676603..1717ce5d 100644 --- a/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift +++ b/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift @@ -9,6 +9,7 @@ import SwiftUI import UserNotifications import UserAccounts +import PushNotifications struct NotificationsPrefsView: View { @State private var error: NotificationsSetupError? @@ -19,8 +20,9 @@ struct NotificationsPrefsView: View { var body: some View { List { enableSection - if isSetup == .on { - accountsSection + if isSetup == .on, + let pushProxyRegistration { + accountsSection(pushProxyRegistration: pushProxyRegistration) } } .listStyle(.insetGrouped) @@ -53,10 +55,10 @@ struct NotificationsPrefsView: View { } } - private var accountsSection: some View { + private func accountsSection(pushProxyRegistration: PushProxyRegistration) -> some View { Section { ForEach(userAccounts.accounts) { account in - PushInstanceSettingsView(account: account) + PushInstanceSettingsView(account: account, pushProxyRegistration: pushProxyRegistration) } } .appGroupedListRowBackground() diff --git a/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift b/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift index 9206f102..fac58441 100644 --- a/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift +++ b/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift @@ -9,11 +9,12 @@ import SwiftUI import UserAccounts import Pachyderm +import PushNotifications struct PushInstanceSettingsView: View { let account: UserAccountInfo let pushProxyRegistration: PushProxyRegistration - @State private var mode: TriStateToggle.Mode + @State private var mode: TriStateToggle.Mode = .off @State private var error: Error? var body: some View { @@ -49,13 +50,13 @@ struct PushInstanceSettingsView: View { } private func enableNotifications() async throws { - let req = Pachyderm.PushSubscription.create( - endpoint: pushProxyRegistration.endpoint, - publicKey: <#T##Data#>, - authSecret: <#T##Data#>, - alerts: <#T##PushSubscription.Alerts#>, - policy: <#T##PushSubscription.Policy#> - ) +// let req = Pachyderm.PushSubscription.create( +// endpoint: pushProxyRegistration.endpoint, +// publicKey: <#T##Data#>, +// authSecret: <#T##Data#>, +// alerts: <#T##PushSubscription.Alerts#>, +// policy: <#T##PushSubscription.Policy#> +// ) } private func disableNotifications() async throws { diff --git a/Tusker/Screens/Preferences/Notifications/PushSubscriptionView.swift b/Tusker/Screens/Preferences/Notifications/PushSubscriptionView.swift index eac3dbbf..02c6604b 100644 --- a/Tusker/Screens/Preferences/Notifications/PushSubscriptionView.swift +++ b/Tusker/Screens/Preferences/Notifications/PushSubscriptionView.swift @@ -8,6 +8,7 @@ import SwiftUI import UserAccounts +import PushNotifications struct PushSubscriptionView: View { let account: UserAccountInfo