[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.
This commit is contained in:
parent
4898f81596
commit
889644f7bd
|
@ -7,5 +7,7 @@
|
|||
<string>applinks:beta.meet.jit.si</string>
|
||||
<string>applinks:meet.jit.si</string>
|
||||
</array>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
|
||||
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
|
||||
0B412F201EDEE95300B1A0A6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
0BBD021F212EB69D00CCB19F /* Types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Types.h; sourceTree = "<group>"; };
|
||||
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
|
@ -96,6 +97,7 @@
|
|||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
0B412F201EDEE95300B1A0A6 /* Main.storyboard */,
|
||||
0BBD021F212EB69D00CCB19F /* Types.h */,
|
||||
0B412F1D1EDEE6E800B1A0A6 /* ViewController.h */,
|
||||
0B412F1E1EDEE6E800B1A0A6 /* ViewController.m */,
|
||||
);
|
||||
|
@ -173,6 +175,9 @@
|
|||
com.apple.SafariKeychain = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.Siri = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#import "AppDelegate.h"
|
||||
#import "FIRUtilities.h"
|
||||
#import "Types.h"
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
|
@ -37,6 +38,10 @@
|
|||
[Fabric with:@[[Crashlytics class]]];
|
||||
}
|
||||
|
||||
// Set the conference activity type defined in this application.
|
||||
// This cannot be defined by the SDK.
|
||||
JitsiMeetView.conferenceActivityType = JitsiMeetConferenceActivityType;
|
||||
|
||||
return [JitsiMeetView application:application
|
||||
didFinishLaunchingWithOptions:launchOptions];
|
||||
}
|
||||
|
|
|
@ -97,5 +97,9 @@
|
|||
<false/>
|
||||
<key>firebase_crashlytics_collection_enabled</key>
|
||||
<string>false</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>org.jitsi.JitsiMeet.ios.conference</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// This must match what's defined in the NSUserActivityTypes array in the
|
||||
// Info.plist file.
|
||||
static NSString *const JitsiMeetConferenceActivityType
|
||||
= @"org.jitsi.JitsiMeet.ios.conference";
|
|
@ -14,8 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Availability.h>
|
||||
#import <CoreSpotlight/CoreSpotlight.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
#import "Types.h"
|
||||
#import "ViewController.h"
|
||||
|
||||
// Needed for NSUserActivity suggestedInvocationPhrase
|
||||
@import Intents;
|
||||
|
||||
/**
|
||||
* The query to perform through JMAddPeopleController when the InviteButton is
|
||||
* 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;
|
|||
[super viewDidLoad];
|
||||
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
view.delegate = self;
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
view.delegate = self;
|
||||
|
||||
// inviteController
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
inviteController.delegate = self;
|
||||
|
@ -56,12 +63,13 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
|||
[view loadURL:nil];
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
|
||||
// JitsiMeetViewDelegate
|
||||
|
||||
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
|
||||
withData:(NSDictionary *)data {
|
||||
#if DEBUG
|
||||
NSLog(
|
||||
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
|
||||
__FILE__, __LINE__, name, data);
|
||||
|
@ -70,6 +78,7 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
|||
[NSThread isMainThread],
|
||||
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
|
||||
name);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)conferenceFailed:(NSDictionary *)data {
|
||||
|
@ -78,6 +87,36 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
|||
|
||||
- (void)conferenceJoined:(NSDictionary *)data {
|
||||
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_JOINED" withData:data];
|
||||
|
||||
// Register a NSUserActivity for this conference so it can be invoked as a
|
||||
// Siri shortcut. This is only supported in iOS >= 12.
|
||||
#ifdef __IPHONE_12_0
|
||||
if (@available(iOS 12.0, *)) {
|
||||
NSUserActivity *userActivity
|
||||
= [[NSUserActivity alloc] initWithActivityType:JitsiMeetConferenceActivityType];
|
||||
|
||||
NSString *urlStr = data[@"url"];
|
||||
NSURL *url = [NSURL URLWithString:urlStr];
|
||||
NSString *conference = [url.pathComponents lastObject];
|
||||
|
||||
userActivity.title = [NSString stringWithFormat:@"Join %@", conference];
|
||||
userActivity.suggestedInvocationPhrase = @"Join my Jitsi meeting";
|
||||
userActivity.userInfo = @{@"url": urlStr};
|
||||
[userActivity setEligibleForSearch:YES];
|
||||
[userActivity setEligibleForPrediction:YES];
|
||||
[userActivity setPersistentIdentifier:urlStr];
|
||||
|
||||
// Subtitle
|
||||
CSSearchableItemAttributeSet *attributes
|
||||
= [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString *)kUTTypeItem];
|
||||
attributes.contentDescription = urlStr;
|
||||
userActivity.contentAttributeSet = attributes;
|
||||
|
||||
self.userActivity = userActivity;
|
||||
[userActivity becomeCurrent];
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (void)conferenceLeft:(NSDictionary *)data {
|
||||
|
@ -96,6 +135,8 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
|||
[self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// JMInviteControllerDelegate
|
||||
|
||||
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
@interface JitsiMeetView ()
|
||||
|
||||
+ (NSDictionary *)conferenceURLFromUserActivity:(NSUserActivity *)userActivity;
|
||||
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
|
||||
|
||||
@end
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
|
||||
|
||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
|
|
|
@ -88,6 +88,8 @@ void registerFatalErrorHandler() {
|
|||
|
||||
@dynamic pictureInPictureEnabled;
|
||||
|
||||
static NSString *_conferenceActivityType;
|
||||
|
||||
static RCTBridgeWrapper *bridgeWrapper;
|
||||
|
||||
/**
|
||||
|
@ -277,6 +279,16 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
[self loadURLObject:urlString ? @{ @"url": urlString } : nil];
|
||||
}
|
||||
|
||||
#pragma conferenceActivityType getter / setter
|
||||
|
||||
+ (NSString *)conferenceActivityType {
|
||||
return _conferenceActivityType;
|
||||
}
|
||||
|
||||
+ (void) setConferenceActivityType:(NSString *)conferenceActivityType {
|
||||
_conferenceActivityType = conferenceActivityType;
|
||||
}
|
||||
|
||||
#pragma pictureInPictureEnabled getter / setter
|
||||
|
||||
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
|
||||
|
@ -358,6 +370,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
@"url": url
|
||||
};
|
||||
}
|
||||
} else if (_conferenceActivityType && [activityType isEqualToString:_conferenceActivityType]) {
|
||||
// App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
|
||||
NSString *url;
|
||||
|
||||
if ((url = userActivity.userInfo[@"url"])) {
|
||||
return @{ @"url" : url };
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
|
Loading…
Reference in New Issue