feat: add unread notification shortcut handler and updater
This commit is contained in:
parent
b61e7518b5
commit
112fa56ee6
|
@ -114,7 +114,7 @@
|
||||||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>31</integer>
|
<integer>20</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -129,12 +129,12 @@
|
||||||
<key>NotificationService.xcscheme_^#shared#^_</key>
|
<key>NotificationService.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>30</integer>
|
<integer>21</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>32</integer>
|
<integer>22</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -19,7 +19,7 @@ final public class SceneCoordinator {
|
||||||
|
|
||||||
private weak var scene: UIScene!
|
private weak var scene: UIScene!
|
||||||
private weak var sceneDelegate: SceneDelegate!
|
private weak var sceneDelegate: SceneDelegate!
|
||||||
private weak var appContext: AppContext!
|
private(set) weak var appContext: AppContext!
|
||||||
|
|
||||||
let id = UUID().uuidString
|
let id = UUID().uuidString
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,6 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionDomains</key>
|
|
||||||
<dict>
|
|
||||||
<key>onion</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSIncludesSubdomains</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
@ -59,6 +46,19 @@
|
||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExceptionDomains</key>
|
||||||
|
<dict>
|
||||||
|
<key>onion</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSIncludesSubdomains</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>NSUserActivityTypes</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>SendPostIntent</string>
|
<string>SendPostIntent</string>
|
||||||
|
@ -103,6 +103,10 @@
|
||||||
</array>
|
</array>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>remote-notification</string>
|
||||||
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
|
|
|
@ -106,6 +106,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
|
||||||
completionHandler([.sound])
|
completionHandler([.sound])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// notification present in the background (or resume from background)
|
||||||
|
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
|
||||||
|
let shortcutItems = try? await appContext.notificationService.unreadApplicationShortcutItems()
|
||||||
|
UIApplication.shared.shortcutItems = shortcutItems
|
||||||
|
return .noData
|
||||||
|
}
|
||||||
|
|
||||||
// response to user action for notification (e.g. redirect to post)
|
// response to user action for notification (e.g. redirect to post)
|
||||||
func userNotificationCenter(
|
func userNotificationCenter(
|
||||||
_ center: UNUserNotificationCenter,
|
_ center: UNUserNotificationCenter,
|
||||||
|
|
|
@ -110,7 +110,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
AppContext.shared.statusFilterService.filterUpdatePublisher.send()
|
AppContext.shared.statusFilterService.filterUpdatePublisher.send()
|
||||||
|
|
||||||
if let shortcutItem = savedShortCutItem {
|
if let shortcutItem = savedShortCutItem {
|
||||||
_ = handler(shortcutItem: shortcutItem)
|
Task {
|
||||||
|
_ = await handler(shortcutItem: shortcutItem)
|
||||||
|
}
|
||||||
savedShortCutItem = nil
|
savedShortCutItem = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,16 +136,45 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SceneDelegate {
|
extension SceneDelegate {
|
||||||
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
|
||||||
completionHandler(handler(shortcutItem: shortcutItem))
|
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem) async -> Bool {
|
||||||
|
return await handler(shortcutItem: shortcutItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handler(shortcutItem: UIApplicationShortcutItem) -> Bool {
|
@MainActor
|
||||||
|
private func handler(shortcutItem: UIApplicationShortcutItem) async -> Bool {
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(shortcutItem.type)")
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): \(shortcutItem.type)")
|
||||||
|
|
||||||
switch shortcutItem.type {
|
switch shortcutItem.type {
|
||||||
case NotificationService.unreadShortcutItemIdentifier:
|
case NotificationService.unreadShortcutItemIdentifier:
|
||||||
coordinator?.switchToTabBar(tab: .notification)
|
guard let coordinator = self.coordinator else { return false }
|
||||||
|
|
||||||
|
guard let accessToken = shortcutItem.userInfo?["accessToken"] as? String else {
|
||||||
|
assertionFailure()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let request = MastodonAuthentication.sortedFetchRequest
|
||||||
|
request.predicate = MastodonAuthentication.predicate(userAccessToken: accessToken)
|
||||||
|
request.fetchLimit = 1
|
||||||
|
|
||||||
|
guard let authentication = try? coordinator.appContext.managedObjectContext.fetch(request).first else {
|
||||||
|
assertionFailure()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let _isActive = try? await coordinator.appContext.authenticationService.activeMastodonUser(
|
||||||
|
domain: authentication.domain,
|
||||||
|
userID: authentication.userID
|
||||||
|
)
|
||||||
|
.singleOutput()
|
||||||
|
.get()
|
||||||
|
|
||||||
|
guard _isActive == true else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
coordinator.switchToTabBar(tab: .notification)
|
||||||
|
|
||||||
case "org.joinmastodon.app.new-post":
|
case "org.joinmastodon.app.new-post":
|
||||||
if coordinator?.tabBarController.topMost is ComposeViewController {
|
if coordinator?.tabBarController.topMost is ComposeViewController {
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): composing…")
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): composing…")
|
||||||
|
@ -160,6 +191,7 @@ extension SceneDelegate {
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): not authenticated")
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): not authenticated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "org.joinmastodon.app.search":
|
case "org.joinmastodon.app.search":
|
||||||
coordinator?.switchToTabBar(tab: .search)
|
coordinator?.switchToTabBar(tab: .search)
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select search tab")
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): select search tab")
|
||||||
|
@ -168,6 +200,7 @@ extension SceneDelegate {
|
||||||
searchViewController.searchBarTapPublisher.send()
|
searchViewController.searchBarTapPublisher.send()
|
||||||
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): trigger search")
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): trigger search")
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
break
|
break
|
||||||
|
|
Loading…
Reference in New Issue