diff --git a/Tusker/AppDelegate.swift b/Tusker/AppDelegate.swift index a5bb3583..0879b7d8 100644 --- a/Tusker/AppDelegate.swift +++ b/Tusker/AppDelegate.swift @@ -84,7 +84,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { BackgroundManager.shared.registerHandlers() - initializePushManager() + initializePushNotifications() return true } @@ -180,7 +180,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { PushManager.shared.didFailToRegisterForRemoteNotifications(error: error) } - private func initializePushManager() { + private func initializePushNotifications() { + UNUserNotificationCenter.current().delegate = self Task { PushManager.captureError = { SentrySDK.capture(error: $0) } await PushManager.shared.updateIfNecessary(updateSubscription: { @@ -255,3 +256,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif } + +extension AppDelegate: UNUserNotificationCenterDelegate { + func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { + let mainSceneDelegate: MainSceneDelegate + if let delegate = UIApplication.shared.activeScene?.delegate as? MainSceneDelegate { + mainSceneDelegate = delegate + } else if let scene = UIApplication.shared.connectedScenes.first(where: { $0.delegate is MainSceneDelegate }) { + mainSceneDelegate = scene.delegate as! MainSceneDelegate + } else if let accountID = UserAccountsManager.shared.mostRecentAccountID { + let activity = UserActivityManager.mainSceneActivity(accountID: accountID) + activity.addUserInfoEntries(from: ["showNotificationsPreferences": true]) + UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil) + return + } else { + // without an account, we can't do anything + return + } + mainSceneDelegate.showNotificationsPreferences() + } +} diff --git a/Tusker/Scenes/MainSceneDelegate.swift b/Tusker/Scenes/MainSceneDelegate.swift index 63dcd999..f08da5b4 100644 --- a/Tusker/Scenes/MainSceneDelegate.swift +++ b/Tusker/Scenes/MainSceneDelegate.swift @@ -283,6 +283,16 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate } } + func showNotificationsPreferences() { + let preferencesVC: PreferencesNavigationController? + if let presented = rootViewController?.presentedViewController as? PreferencesNavigationController { + preferencesVC = presented + } else { + preferencesVC = rootViewController?.presentPreferences(completion: nil) + } + preferencesVC?.navigationState.showNotificationPreferences = true + } + } extension MainSceneDelegate: OnboardingViewControllerDelegate { diff --git a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift index 59c57e7c..2fde5d39 100644 --- a/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift +++ b/Tusker/Screens/Main/AccountSwitchingContainerViewController.swift @@ -152,9 +152,9 @@ extension AccountSwitchingContainerViewController: TuskerRootViewController { root.performSearch(query: query) } - func presentPreferences(completion: (() -> Void)?) { + func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? { loadViewIfNeeded() - root.presentPreferences(completion: completion) + return root.presentPreferences(completion: completion) } func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult { diff --git a/Tusker/Screens/Main/Duckable+Root.swift b/Tusker/Screens/Main/Duckable+Root.swift index 73e9814b..875a08e8 100644 --- a/Tusker/Screens/Main/Duckable+Root.swift +++ b/Tusker/Screens/Main/Duckable+Root.swift @@ -47,7 +47,7 @@ extension DuckableContainerViewController: AccountSwitchableViewController { (child as? TuskerRootViewController)?.performSearch(query: query) } - func presentPreferences(completion: (() -> Void)?) { + func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? { (child as? TuskerRootViewController)?.presentPreferences(completion: completion) } diff --git a/Tusker/Screens/Main/MainSplitViewController.swift b/Tusker/Screens/Main/MainSplitViewController.swift index 7b021e77..8ac40b2b 100644 --- a/Tusker/Screens/Main/MainSplitViewController.swift +++ b/Tusker/Screens/Main/MainSplitViewController.swift @@ -623,8 +623,10 @@ extension MainSplitViewController: TuskerRootViewController { searchViewController.resultsController.performSearch(query: query) } - func presentPreferences(completion: (() -> Void)?) { - present(PreferencesNavigationController(mastodonController: mastodonController), animated: true, completion: completion) + func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? { + let vc = PreferencesNavigationController(mastodonController: mastodonController) + present(vc, animated: true, completion: completion) + return vc } func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult { diff --git a/Tusker/Screens/Main/MainTabBarViewController.swift b/Tusker/Screens/Main/MainTabBarViewController.swift index b01b2f71..5f447cc2 100644 --- a/Tusker/Screens/Main/MainTabBarViewController.swift +++ b/Tusker/Screens/Main/MainTabBarViewController.swift @@ -342,8 +342,10 @@ extension MainTabBarViewController: TuskerRootViewController { exploreController.resultsController.performSearch(query: query) } - func presentPreferences(completion: (() -> Void)?) { - present(PreferencesNavigationController(mastodonController: mastodonController), animated: true, completion: completion) + func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? { + let vc = PreferencesNavigationController(mastodonController: mastodonController) + present(vc, animated: true, completion: completion) + return vc } func handleStatusBarTapped(xPosition: CGFloat) -> StatusBarTapActionResult { diff --git a/Tusker/Screens/Main/TuskerRootViewController.swift b/Tusker/Screens/Main/TuskerRootViewController.swift index c3f0cbbb..7f12b7cc 100644 --- a/Tusker/Screens/Main/TuskerRootViewController.swift +++ b/Tusker/Screens/Main/TuskerRootViewController.swift @@ -17,7 +17,8 @@ protocol TuskerRootViewController: UIViewController, StateRestorableViewControll func getNavigationDelegate() -> TuskerNavigationDelegate? func getNavigationController() -> NavigationControllerProtocol func performSearch(query: String) - func presentPreferences(completion: (() -> Void)?) + @discardableResult + func presentPreferences(completion: (() -> Void)?) -> PreferencesNavigationController? } //extension TuskerRootViewController { diff --git a/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift b/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift index 995aa383..062df05e 100644 --- a/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift +++ b/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift @@ -70,8 +70,7 @@ struct NotificationsPrefsView: View { private func startRegistration() async -> Bool { let authorized: Bool do { - // TODO: support .providesAppNotificationSettings - authorized = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) + authorized = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .providesAppNotificationSettings]) } catch { self.error = .requestingAuthorization(error) return false diff --git a/Tusker/Screens/Preferences/PreferencesNavigationController.swift b/Tusker/Screens/Preferences/PreferencesNavigationController.swift index ac1c3634..a9870224 100644 --- a/Tusker/Screens/Preferences/PreferencesNavigationController.swift +++ b/Tusker/Screens/Preferences/PreferencesNavigationController.swift @@ -13,16 +13,24 @@ import SafariServices import AuthenticationServices import Pachyderm +// TODO: replace this with NavigationStack and path once we target iOS 16 +class PreferencesNavigationState: ObservableObject { + @Published var showNotificationPreferences = false +} + class PreferencesNavigationController: UINavigationController { private let mastodonController: MastodonController + let navigationState: PreferencesNavigationState private var isSwitchingAccounts = false init(mastodonController: MastodonController) { self.mastodonController = mastodonController + let navigationState = PreferencesNavigationState() + self.navigationState = navigationState - let view = PreferencesView(mastodonController: mastodonController) + let view = PreferencesView(mastodonController: mastodonController, navigationState: navigationState) let hostingController = UIHostingController(rootView: view) super.init(rootViewController: hostingController) hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed)) diff --git a/Tusker/Screens/Preferences/PreferencesView.swift b/Tusker/Screens/Preferences/PreferencesView.swift index b32c87a3..4e5ad84b 100644 --- a/Tusker/Screens/Preferences/PreferencesView.swift +++ b/Tusker/Screens/Preferences/PreferencesView.swift @@ -10,12 +10,14 @@ import UserAccounts struct PreferencesView: View { let mastodonController: MastodonController + @ObservedObject var navigationState: PreferencesNavigationState @ObservedObject private var userAccounts = UserAccountsManager.shared @State private var showingLogoutConfirmation = false - init(mastodonController: MastodonController) { + init(mastodonController: MastodonController, navigationState: PreferencesNavigationState) { self.mastodonController = mastodonController + self.navigationState = navigationState } var body: some View { @@ -92,7 +94,9 @@ struct PreferencesView: View { private var notificationsSection: some View { Section { - NavigationLink(destination: NotificationsPrefsView()) { + NavigationLink(isActive: $navigationState.showNotificationPreferences) { + NotificationsPrefsView() + } label: { Text("Notifications") } }