1
1
import UIKit
2
2
import Flutter
3
+ import UserNotifications
3
4
4
5
@main
5
6
@objc class AppDelegate : FlutterAppDelegate {
@@ -8,6 +9,129 @@ import Flutter
8
9
didFinishLaunchingWithOptions launchOptions: [ UIApplication . LaunchOptionsKey : Any ] ?
9
10
) -> Bool {
10
11
GeneratedPluginRegistrant . register ( with: self )
11
- return super. application ( application, didFinishLaunchingWithOptions: launchOptions)
12
+ UNUserNotificationCenter . current ( ) . delegate = self
13
+
14
+ // Handle launch of application from the notification.
15
+ let controller = window. rootViewController as! FlutterViewController
16
+ if let remoteNotification = launchOptions ? [ . remoteNotification] as? [ AnyHashable : Any ] ,
17
+ let payload = ApnsPayload ( fromJson: remoteNotification) ,
18
+ let routeUrl = internalRouteUrlFromNotification ( payload: payload) {
19
+ // https://api.flutter.dev/flutter/dart-ui/PlatformDispatcher/defaultRouteName.html
20
+ // TODO(?) FlutterViewController.setInitialRoute is deprecated (warning visible in Xcode)
21
+ // but the above doc mentions using it.
22
+ controller. setInitialRoute ( routeUrl. absoluteString)
23
+ }
24
+
25
+ return super. application (
26
+ application,
27
+ didFinishLaunchingWithOptions: launchOptions
28
+ )
29
+ }
30
+
31
+ // Handle notification tap while the app is running.
32
+ override func userNotificationCenter(
33
+ _ center: UNUserNotificationCenter ,
34
+ didReceive response: UNNotificationResponse ,
35
+ withCompletionHandler completionHandler: @escaping ( ) -> Void
36
+ ) {
37
+ if let payload = ApnsPayload ( fromJson: response. notification. request. content. userInfo) ,
38
+ let routeUrl = internalRouteUrlFromNotification ( payload: payload) {
39
+ let controller = window. rootViewController as! FlutterViewController
40
+ controller. pushRoute ( routeUrl. absoluteString)
41
+ completionHandler ( )
42
+ }
43
+ }
44
+ }
45
+
46
+ // https://github.com/zulip/zulip/blob/aa8f47774f08b6fc5d947ae97cafefcf7dfb8bef/zerver/lib/push_notifications.py#L1087
47
+ struct ApnsPayload {
48
+ enum Narrow {
49
+ case topicNarrow( channelId: Int , topic: String )
50
+ case dmNarrow( allRecipientIds: [ Int ] )
51
+ }
52
+
53
+ let realmUrl : String
54
+ let userId : Int
55
+ let narrow : Narrow
56
+
57
+ init ? ( fromJson payload: [ AnyHashable : Any ] ) {
58
+ guard let aps = payload [ " aps " ] as? [ String : Any ] ,
59
+ let customData = aps [ " custom " ] as? [ String : Any ] ,
60
+ let zulipData = customData [ " zulip " ] as? [ String : Any ] ,
61
+ let realmUrl = (
62
+ zulipData [ " realm_url " ] as? String ?? zulipData [ " realm_url " ] as? String
63
+ ) ,
64
+ let userId = zulipData [ " user_id " ] as? Int ,
65
+ let senderId = zulipData [ " sender_id " ] as? Int ,
66
+ let recipientType = zulipData [ " recipient_type " ] as? String
67
+ else {
68
+ return nil
69
+ }
70
+
71
+ var narrow : Narrow
72
+ switch recipientType {
73
+ case " stream " :
74
+ guard let streamId = zulipData [ " stream_id " ] as? Int ,
75
+ let topic = zulipData [ " topic " ] as? String else {
76
+ return nil
77
+ }
78
+
79
+ narrow = Narrow . topicNarrow ( channelId: streamId, topic: topic)
80
+
81
+ case " private " :
82
+ var allRecipientIds = Set < Int > ( )
83
+
84
+ if let pmUsersStr = zulipData [ " pm_users " ] as? String {
85
+ for str in pmUsersStr. split ( separator: " , " ) {
86
+ guard let recipientId = Int ( str, radix: 10 ) else {
87
+ return nil
88
+ }
89
+ allRecipientIds. insert ( recipientId)
90
+ }
91
+ } else {
92
+ allRecipientIds. formUnion ( [ senderId, userId] )
93
+ }
94
+
95
+ narrow = Narrow . dmNarrow ( allRecipientIds: allRecipientIds. sorted ( by: < ) )
96
+
97
+ default :
98
+ return nil
99
+ }
100
+
101
+ self . realmUrl = realmUrl
102
+ self . userId = userId
103
+ self . narrow = narrow
104
+ }
105
+ }
106
+
107
+ func internalRouteUrlFromNotification( payload: ApnsPayload ) -> URL ? {
108
+ var components = URLComponents ( )
109
+ components. scheme = " zulip "
110
+ components. host = " notification "
111
+
112
+ var queryItems = [
113
+ URLQueryItem ( name: " realm_url " , value: payload. realmUrl) ,
114
+ URLQueryItem ( name: " user_id " , value: String ( payload. userId) ) ,
115
+ ]
116
+
117
+ switch payload. narrow {
118
+ case . topicNarrow( channelId: let channelId, topic: let topic) :
119
+ queryItems. append ( contentsOf: [
120
+ URLQueryItem ( name: " narrow_type " , value: " topic " ) ,
121
+ URLQueryItem ( name: " channel_id " , value: String ( channelId) ) ,
122
+ URLQueryItem ( name: " topic " , value: topic) ,
123
+ ] )
124
+
125
+ case . dmNarrow( allRecipientIds: let allRecipientIds) :
126
+ queryItems. append (
127
+ contentsOf: [
128
+ URLQueryItem ( name: " narrow_type " , value: " dm " ) ,
129
+ URLQueryItem (
130
+ name: " all_recipient_ids " ,
131
+ value: allRecipientIds. map { String ( $0) } . joined ( separator: " , " ) ) ,
132
+ ]
133
+ )
12
134
}
135
+ components. queryItems = queryItems
136
+ return components. url
13
137
}
0 commit comments