Browse 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.
master
Saúl Ibarra Corretgé 6 years ago
parent
commit
889644f7bd

+ 2
- 0
ios/app/app.entitlements View File

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

+ 5
- 0
ios/app/app.xcodeproj/project.pbxproj View File

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

17
 
17
 
18
 #import "AppDelegate.h"
18
 #import "AppDelegate.h"
19
 #import "FIRUtilities.h"
19
 #import "FIRUtilities.h"
20
+#import "Types.h"
20
 
21
 
21
 #import <JitsiMeet/JitsiMeet.h>
22
 #import <JitsiMeet/JitsiMeet.h>
22
 
23
 
37
         [Fabric with:@[[Crashlytics class]]];
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
     return [JitsiMeetView application:application
45
     return [JitsiMeetView application:application
41
         didFinishLaunchingWithOptions:launchOptions];
46
         didFinishLaunchingWithOptions:launchOptions];
42
 }
47
 }

+ 4
- 0
ios/app/src/Info.plist View File

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

+ 7
- 0
ios/app/src/Types.h View File

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 View File

14
  * limitations under the License.
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
 #import "ViewController.h"
22
 #import "ViewController.h"
18
 
23
 
24
+// Needed for NSUserActivity suggestedInvocationPhrase
25
+@import Intents;
26
+
19
 /**
27
 /**
20
  * The query to perform through JMAddPeopleController when the InviteButton is
28
  * The query to perform through JMAddPeopleController when the InviteButton is
21
  * tapped in order to exercise the public API of the feature invite. If nil, the
29
  * tapped in order to exercise the public API of the feature invite. If nil, the
33
     [super viewDidLoad];
41
     [super viewDidLoad];
34
 
42
 
35
     JitsiMeetView *view = (JitsiMeetView *) self.view;
43
     JitsiMeetView *view = (JitsiMeetView *) self.view;
44
+    view.delegate = self;
36
 
45
 
37
 #ifdef DEBUG
46
 #ifdef DEBUG
38
 
47
 
39
-    view.delegate = self;
40
-
41
     // inviteController
48
     // inviteController
42
     JMInviteController *inviteController = view.inviteController;
49
     JMInviteController *inviteController = view.inviteController;
43
     inviteController.delegate = self;
50
     inviteController.delegate = self;
56
     [view loadURL:nil];
63
     [view loadURL:nil];
57
 }
64
 }
58
 
65
 
59
-#if DEBUG
66
+
60
 
67
 
61
 // JitsiMeetViewDelegate
68
 // JitsiMeetViewDelegate
62
 
69
 
63
 - (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
70
 - (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
64
                              withData:(NSDictionary *)data {
71
                              withData:(NSDictionary *)data {
72
+#if DEBUG
65
     NSLog(
73
     NSLog(
66
         @"[%s:%d] JitsiMeetViewDelegate %@ %@",
74
         @"[%s:%d] JitsiMeetViewDelegate %@ %@",
67
         __FILE__, __LINE__, name, data);
75
         __FILE__, __LINE__, name, data);
70
         [NSThread isMainThread],
78
         [NSThread isMainThread],
71
         @"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
79
         @"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
72
         name);
80
         name);
81
+#endif
73
 }
82
 }
74
 
83
 
75
 - (void)conferenceFailed:(NSDictionary *)data {
84
 - (void)conferenceFailed:(NSDictionary *)data {
78
 
87
 
79
 - (void)conferenceJoined:(NSDictionary *)data {
88
 - (void)conferenceJoined:(NSDictionary *)data {
80
     [self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
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
 - (void)conferenceLeft:(NSDictionary *)data {
122
 - (void)conferenceLeft:(NSDictionary *)data {
96
     [self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
135
     [self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
97
 }
136
 }
98
 
137
 
138
+#if DEBUG
139
+
99
 // JMInviteControllerDelegate
140
 // JMInviteControllerDelegate
100
 
141
 
101
 - (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
142
 - (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {

+ 1
- 0
ios/sdk/src/JitsiMeetView+Private.h View File

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

+ 2
- 0
ios/sdk/src/JitsiMeetView.h View File

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

+ 19
- 0
ios/sdk/src/JitsiMeetView.m View File

88
 
88
 
89
 @dynamic pictureInPictureEnabled;
89
 @dynamic pictureInPictureEnabled;
90
 
90
 
91
+static NSString *_conferenceActivityType;
92
+
91
 static RCTBridgeWrapper *bridgeWrapper;
93
 static RCTBridgeWrapper *bridgeWrapper;
92
 
94
 
93
 /**
95
 /**
277
     [self loadURLObject:urlString ? @{ @"url": urlString } : nil];
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
 #pragma pictureInPictureEnabled getter / setter
292
 #pragma pictureInPictureEnabled getter / setter
281
 
293
 
282
 - (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
294
 - (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
358
                 @"url": url
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
     return nil;
382
     return nil;

Loading…
Cancel
Save