feat: add unread notification shortcut handler and updater

This commit is contained in:
CMK 2022-07-27 17:39:27 +08:00
parent b61e7518b5
commit 112fa56ee6
5 changed files with 67 additions and 22 deletions

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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,

View File

@ -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