Parcourir la source

[iOS] Add support for Siri shortcuts

This is mostly implemented in the app, with the needed support in the SDK. Since
the app needs to donate intents and deal with creating NSUserActivity objects it
doesn't feel right to do this in a library. Instead, we donate the intents from
the app, but the SDK is ready to extract conference URLs from any intent which
was registered as a conference activity.

This also opens the door for eventually adding Handoff support.
j8
Saúl Ibarra Corretgé il y a 6 ans
Parent
révision
889644f7bd

+ 2
- 0
ios/app/app.entitlements Voir le fichier

@@ -7,5 +7,7 @@
7 7
 		<string>applinks:beta.meet.jit.si</string>
8 8
 		<string>applinks:meet.jit.si</string>
9 9
 	</array>
10
+	<key>com.apple.developer.siri</key>
11
+	<true/>
10 12
 </dict>
11 13
 </plist>

+ 5
- 0
ios/app/app.xcodeproj/project.pbxproj Voir le fichier

@@ -43,6 +43,7 @@
43 43
 		0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
44 44
 		0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
45 45
 		0B412F201EDEE95300B1A0A6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
46
+		0BBD021F212EB69D00CCB19F /* Types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Types.h; sourceTree = "<group>"; };
46 47
 		0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
47 48
 		13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
48 49
 		13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@@ -96,6 +97,7 @@
96 97
 				13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
97 98
 				13B07FB71A68108700A75B9A /* main.m */,
98 99
 				0B412F201EDEE95300B1A0A6 /* Main.storyboard */,
100
+				0BBD021F212EB69D00CCB19F /* Types.h */,
99 101
 				0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */,
100 102
 				0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */,
101 103
 			);
@@ -173,6 +175,9 @@
173 175
 							com.apple.SafariKeychain = {
174 176
 								enabled = 1;
175 177
 							};
178
+							com.apple.Siri = {
179
+								enabled = 1;
180
+							};
176 181
 						};
177 182
 					};
178 183
 				};

+ 5
- 0
ios/app/src/AppDelegate.m Voir le fichier

@@ -17,6 +17,7 @@
17 17
 
18 18
 #import "AppDelegate.h"
19 19
 #import "FIRUtilities.h"
20
+#import "Types.h"
20 21
 
21 22
 #import <JitsiMeet/JitsiMeet.h>
22 23
 
@@ -37,6 +38,10 @@
37 38
         [Fabric with:@[[Crashlytics class]]];
38 39
     }
39 40
 
41
+    // Set the conference activity type defined in this application.
42
+    // This cannot be defined by the SDK.
43
+    JitsiMeetView.conferenceActivityType = JitsiMeetConferenceActivityType;
44
+
40 45
     return [JitsiMeetView application:application
41 46
         didFinishLaunchingWithOptions:launchOptions];
42 47
 }

+ 4
- 0
ios/app/src/Info.plist Voir le fichier

@@ -97,5 +97,9 @@
97 97
 	<false/>
98 98
 	<key>firebase_crashlytics_collection_enabled</key>
99 99
 	<string>false</string>
100
+	<key>NSUserActivityTypes</key>
101
+	<array>
102
+		<string>org.jitsi.JitsiMeet.ios.conference</string>
103
+	</array>
100 104
 </dict>
101 105
 </plist>

+ 7
- 0
ios/app/src/Types.h Voir le fichier

@@ -0,0 +1,7 @@
1
+
2
+#import <Foundation/Foundation.h>
3
+
4
+// This must match what's defined in the NSUserActivityTypes array in the
5
+// Info.plist file.
6
+static NSString *const JitsiMeetConferenceActivityType
7
+  = @"org.jitsi.JitsiMeet.ios.conference";

+ 44
- 3
ios/app/src/ViewController.m Voir le fichier

@@ -14,8 +14,16 @@
14 14
  * limitations under the License.
15 15
  */
16 16
 
17
+#import <Availability.h>
18
+#import <CoreSpotlight/CoreSpotlight.h>
19
+#import <MobileCoreServices/MobileCoreServices.h>
20
+
21
+#import "Types.h"
17 22
 #import "ViewController.h"
18 23
 
24
+// Needed for NSUserActivity suggestedInvocationPhrase
25
+@import Intents;
26
+
19 27
 /**
20 28
  * The query to perform through JMAddPeopleController when the InviteButton is
21 29
  * tapped in order to exercise the public API of the feature invite. If nil, the
@@ -33,11 +41,10 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
33 41
     [super viewDidLoad];
34 42
 
35 43
     JitsiMeetView *view = (JitsiMeetView *) self.view;
44
+    view.delegate = self;
36 45
 
37 46
 #ifdef DEBUG
38 47
 
39
-    view.delegate = self;
40
-
41 48
     // inviteController
42 49
     JMInviteController *inviteController = view.inviteController;
43 50
     inviteController.delegate = self;
@@ -56,12 +63,13 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
56 63
     [view loadURL:nil];
57 64
 }
58 65
 
59
-#if DEBUG
66
+
60 67
 
61 68
 // JitsiMeetViewDelegate
62 69
 
63 70
 - (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
64 71
                              withData:(NSDictionary *)data {
72
+#if DEBUG
65 73
     NSLog(
66 74
         @"[%s:%d] JitsiMeetViewDelegate %@ %@",
67 75
         __FILE__, __LINE__, name, data);
@@ -70,6 +78,7 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
70 78
         [NSThread isMainThread],
71 79
         @"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
72 80
         name);
81
+#endif
73 82
 }
74 83
 
75 84
 - (void)conferenceFailed:(NSDictionary *)data {
@@ -78,6 +87,36 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
78 87
 
79 88
 - (void)conferenceJoined:(NSDictionary *)data {
80 89
     [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
90
+
91
+    // Register a NSUserActivity for this conference so it can be invoked as a
92
+    // Siri shortcut. This is only supported in iOS >= 12.
93
+#ifdef __IPHONE_12_0
94
+    if (@available(iOS 12.0, *)) {
95
+      NSUserActivity *userActivity
96
+        = [[NSUserActivity alloc] initWithActivityType:JitsiMeetConferenceActivityType];
97
+
98
+      NSString *urlStr = data[@"url"];
99
+      NSURL *url = [NSURL URLWithString:urlStr];
100
+      NSString *conference = [url.pathComponents lastObject];
101
+
102
+      userActivity.title = [NSString stringWithFormat:@"Join %@", conference];
103
+      userActivity.suggestedInvocationPhrase = @"Join my Jitsi meeting";
104
+      userActivity.userInfo = @{@"url": urlStr};
105
+      [userActivity setEligibleForSearch:YES];
106
+      [userActivity setEligibleForPrediction:YES];
107
+      [userActivity setPersistentIdentifier:urlStr];
108
+
109
+      // Subtitle
110
+      CSSearchableItemAttributeSet *attributes
111
+        = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeItem];
112
+      attributes.contentDescription = urlStr;
113
+      userActivity.contentAttributeSet = attributes;
114
+
115
+      self.userActivity = userActivity;
116
+      [userActivity becomeCurrent];
117
+    }
118
+#endif
119
+
81 120
 }
82 121
 
83 122
 - (void)conferenceLeft:(NSDictionary *)data {
@@ -96,6 +135,8 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
96 135
     [self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
97 136
 }
98 137
 
138
+#if DEBUG
139
+
99 140
 // JMInviteControllerDelegate
100 141
 
101 142
 - (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {

+ 1
- 0
ios/sdk/src/JitsiMeetView+Private.h Voir le fichier

@@ -18,6 +18,7 @@
18 18
 
19 19
 @interface JitsiMeetView ()
20 20
 
21
++ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity;
21 22
 + (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
22 23
 
23 24
 @end

+ 2
- 0
ios/sdk/src/JitsiMeetView.h Voir le fichier

@@ -22,6 +22,8 @@
22 22
 
23 23
 @interface JitsiMeetView : UIView
24 24
 
25
+@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
26
+
25 27
 @property (copy, nonatomic, nullable) NSURL *defaultURL;
26 28
 
27 29
 @property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;

+ 19
- 0
ios/sdk/src/JitsiMeetView.m Voir le fichier

@@ -88,6 +88,8 @@ void registerFatalErrorHandler() {
88 88
 
89 89
 @dynamic pictureInPictureEnabled;
90 90
 
91
+static NSString *_conferenceActivityType;
92
+
91 93
 static RCTBridgeWrapper *bridgeWrapper;
92 94
 
93 95
 /**
@@ -277,6 +279,16 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
277 279
     [self loadURLObject:urlString ? @{ @"url": urlString } : nil];
278 280
 }
279 281
 
282
+#pragma conferenceActivityType getter / setter
283
+
284
++ (NSString *)conferenceActivityType {
285
+    return _conferenceActivityType;
286
+}
287
+
288
++ (void) setConferenceActivityType:(NSString *)conferenceActivityType {
289
+    _conferenceActivityType = conferenceActivityType;
290
+}
291
+
280 292
 #pragma pictureInPictureEnabled getter / setter
281 293
 
282 294
 - (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
@@ -358,6 +370,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
358 370
                 @"url": url
359 371
                 };
360 372
         }
373
+    } else if (_conferenceActivityType && [activityType isEqualToString:_conferenceActivityType]) {
374
+        // App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
375
+        NSString *url;
376
+
377
+        if ((url = userActivity.userInfo[@"url"])) {
378
+            return @{ @"url" : url };
379
+        }
361 380
     }
362 381
 
363 382
     return nil;

Chargement…
Annuler
Enregistrer