ios: introduce JitsiMeetConferenceOptions

This commit is contained in:
Saúl Ibarra Corretgé 2019-02-08 10:40:37 +01:00 committed by Saúl Ibarra Corretgé
parent b97cb3509a
commit aedcfba263
14 changed files with 413 additions and 100 deletions

View File

@ -37,11 +37,18 @@
[Fabric with:@[[Crashlytics class]]];
}
[JitsiMeet sharedInstance].conferenceActivityType = JitsiMeetConferenceActivityType;
[JitsiMeet sharedInstance].customUrlScheme = @"org.jitsi.meet";
[JitsiMeet sharedInstance].universalLinkDomains = @[@"meet.jit.si", @"beta.meet.jit.si"];
JitsiMeet *jitsiMeet = [JitsiMeet sharedInstance];
[[JitsiMeet sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
jitsiMeet.conferenceActivityType = JitsiMeetConferenceActivityType;
jitsiMeet.customUrlScheme = @"org.jitsi.meet";
jitsiMeet.universalLinkDomains = @[@"meet.jit.si", @"beta.meet.jit.si"];
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
builder.welcomePageEnabled = YES;
}];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}

View File

@ -21,6 +21,8 @@
@import MobileCoreServices;
@import Intents; // Needed for NSUserActivity suggestedInvocationPhrase
@import JitsiMeet;
#import "Types.h"
#import "ViewController.h"
@ -33,13 +35,7 @@
JitsiMeetView *view = (JitsiMeetView *) self.view;
view.delegate = self;
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do want
// the Welcome page to be enabled. It defaults to disabled in the SDK at the
// time of this writing but it is clearer to be explicit about what we want
// anyway.
view.welcomePageEnabled = YES;
[view join:[[JitsiMeet sharedInstance] getInitialURL]];
[view join:[[JitsiMeet sharedInstance] getInitialConferenceOptions]];
}
// JitsiMeetViewDelegate

View File

@ -42,7 +42,8 @@
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; };
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */; };
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */; };
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535321FB1BF800011A3A /* JitsiMeet.m */; };
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535521FB2E8300011A3A /* ReactUtils.m */; };
@ -92,6 +93,9 @@
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetConferenceOptions.h; sourceTree = "<group>"; };
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetConferenceOptions.m; sourceTree = "<group>"; };
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetConferenceOptions+Private.h"; sourceTree = "<group>"; };
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocaleDetector.m; sourceTree = "<group>"; };
DEFE535321FB1BF800011A3A /* JitsiMeet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeet.m; sourceTree = "<group>"; };
DEFE535521FB2E8300011A3A /* ReactUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactUtils.m; sourceTree = "<group>"; };
@ -164,6 +168,9 @@
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */,
DEFE535321FB1BF800011A3A /* JitsiMeet.m */,
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */,
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */,
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */,
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
@ -247,11 +254,11 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -451,6 +458,7 @@
files = (
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */,
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,

View File

@ -18,6 +18,7 @@
@interface JitsiMeet ()
- (NSDictionary *)getDefaultProps;
- (RCTBridge *)getReactBridge;
@end

View File

@ -17,6 +17,7 @@
#import <JitsiMeet/JitsiMeetView.h>
#import <JitsiMeet/JitsiMeetViewDelegate.h>
#import <JitsiMeet/JitsiMeetConferenceOptions.h>
@interface JitsiMeet : NSObject
@ -25,6 +26,8 @@
@property (copy, nonatomic, nullable) NSString *customUrlScheme;
@property (copy, nonatomic, nullable) NSArray<NSString *> *universalLinkDomains;
@property (nonatomic, nullable) JitsiMeetConferenceOptions *defaultConferenceOptions;
#pragma mak - This class is a singleton
+ (instancetype)sharedInstance;
@ -44,6 +47,6 @@
#pragma mark - Utility methods
- (NSDictionary *)getInitialURL;
- (JitsiMeetConferenceOptions *)getInitialConferenceOptions;
@end

View File

@ -18,6 +18,7 @@
#import "Dropbox.h"
#import "JitsiMeet+Private.h"
#import "JitsiMeetConferenceOptions+Private.h"
#import "JitsiMeetView+Private.h"
#import "RCTBridgeWrapper.h"
#import "ReactUtils.h"
@ -71,9 +72,9 @@
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
id url = [self urlFromUserActivity:userActivity];
JitsiMeetConferenceOptions *options = [self optionsFromUserActivity:userActivity];
return url && [JitsiMeetView loadURLInViews:url];
return options && [JitsiMeetView setPropsInViews:[options asProps]];
}
- (BOOL)application:(UIApplication *)app
@ -95,36 +96,44 @@
return NO;
}
return [JitsiMeetView loadURLInViews:@{ @"url" : url.absoluteString }];
JitsiMeetConferenceOptions *conferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.room = [url absoluteString];
}];
return [JitsiMeetView setPropsInViews:[conferenceOptions asProps]];
}
#pragma mark - Utility methods
- (NSDictionary *)getInitialURL {
- (JitsiMeetConferenceOptions *)getInitialConferenceOptions {
if (_launchOptions[UIApplicationLaunchOptionsURLKey]) {
NSURL *url = _launchOptions[UIApplicationLaunchOptionsURLKey];
return @{ @"url" : url.absoluteString };
return [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.room = [url absoluteString];
}];
} else {
NSDictionary *userActivityDictionary
= _launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
NSUserActivity *userActivity
= [userActivityDictionary objectForKey:@"UIApplicationLaunchOptionsUserActivityKey"];
if (userActivity != nil) {
return [self urlFromUserActivity:userActivity];
return [self optionsFromUserActivity:userActivity];
}
}
return nil;
}
- (NSDictionary *)urlFromUserActivity:(NSUserActivity *)userActivity {
- (JitsiMeetConferenceOptions *)optionsFromUserActivity:(NSUserActivity *)userActivity {
NSString *activityType = userActivity.activityType;
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
// App was started by opening a URL in the browser
NSURL *url = userActivity.webpageURL;
if ([_universalLinkDomains containsObject:url.host]) {
return @{ @"url" : url.absoluteString };
return [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.room = [url absoluteString];
}];
}
} else if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
@ -132,27 +141,29 @@
INIntent *intent = userActivity.interaction.intent;
NSArray<INPerson *> *contacts;
NSString *url;
BOOL startAudioOnly = NO;
BOOL audioOnly = NO;
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
contacts = ((INStartAudioCallIntent *) intent).contacts;
startAudioOnly = YES;
audioOnly = YES;
} else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
contacts = ((INStartVideoCallIntent *) intent).contacts;
}
if (contacts && (url = contacts.firstObject.personHandle.value)) {
return @{
@"config": @{@"startAudioOnly":@(startAudioOnly)},
@"url": url
};
return [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.audioOnly = audioOnly;
builder.room = url;
}];
}
} else if (self.conferenceActivityType && [activityType isEqualToString:self.conferenceActivityType]) {
// App was started by continuing a registered NSUserActivity (SiriKit, Handoff, ...)
NSString *url;
if ((url = userActivity.userInfo[@"url"])) {
return @{ @"url" : url };
return [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
builder.room = url;
}];
}
}
@ -171,6 +182,10 @@
#pragma mark - Private API methods
- (NSDictionary *)getDefaultProps {
return _defaultConferenceOptions == nil ? @{} : [_defaultConferenceOptions asProps];
}
- (RCTBridge *)getReactBridge {
return _bridgeWrapper.bridge;
}

View File

@ -0,0 +1,23 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "JitsiMeetConferenceOptions.h"
@interface JitsiMeetConferenceOptions ()
- (NSMutableDictionary *)asProps;
@end

View File

@ -0,0 +1,52 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
@interface JitsiMeetConferenceOptionsBuilder : NSObject
@property (nonatomic, copy, nullable) NSURL *serverURL;
@property (nonatomic, copy, nullable) NSString *room;
@property (nonatomic, copy, nullable) NSString *token;
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
@property (nonatomic) BOOL audioOnly;
@property (nonatomic) BOOL audioMuted;
@property (nonatomic) BOOL videoMuted;
@property (nonatomic) BOOL welcomePageEnabled;
@end
@interface JitsiMeetConferenceOptions : NSObject
@property (nonatomic, copy, nullable, readonly) NSURL *serverURL;
@property (nonatomic, copy, nullable, readonly) NSString *room;
@property (nonatomic, copy, nullable, readonly) NSString *token;
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
@property (nonatomic, readonly) BOOL audioOnly;
@property (nonatomic, readonly) BOOL audioMuted;
@property (nonatomic, readonly) BOOL videoMuted;
@property (nonatomic, readonly) BOOL welcomePageEnabled;
+ (instancetype)fromBuilder:(void (^)(JitsiMeetConferenceOptionsBuilder *))initBlock;
- (instancetype)init NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,212 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <React/RCTUtils.h>
#import "JitsiMeetConferenceOptions+Private.h"
@implementation JitsiMeetConferenceOptionsBuilder {
NSNumber *_audioOnly;
NSNumber *_audioMuted;
NSNumber *_videoMuted;
NSNumber *_welcomePageEnabled;
}
@dynamic audioOnly;
@dynamic audioMuted;
@dynamic videoMuted;
@dynamic welcomePageEnabled;
- (instancetype)init {
if (self = [super init]) {
_serverURL = nil;
_room = nil;
_token = nil;
_colorScheme = nil;
_audioOnly = nil;
_audioMuted = nil;
_videoMuted = nil;
_welcomePageEnabled = nil;
}
return self;
}
#pragma mark - Dynamic properties
- (void)setAudioOnly:(BOOL)audioOnly {
_audioOnly = [NSNumber numberWithBool:audioOnly];
}
- (BOOL)audioOnly {
return _audioOnly && [_audioOnly boolValue];
}
- (void)setAudioMuted:(BOOL)audioMuted {
_audioMuted = [NSNumber numberWithBool:audioMuted];
}
- (BOOL)audioMuted {
return _audioMuted && [_audioMuted boolValue];
}
- (void)setVideoMuted:(BOOL)videoMuted {
_videoMuted = [NSNumber numberWithBool:videoMuted];
}
- (BOOL)videoMuted {
return _videoMuted && [_videoMuted boolValue];
}
- (void)setWelcomePageEnabled:(BOOL)welcomePageEnabled {
_welcomePageEnabled = [NSNumber numberWithBool:welcomePageEnabled];
}
- (BOOL)welcomePageEnabled {
return _welcomePageEnabled && [_welcomePageEnabled boolValue];
}
#pragma mark - Private API
- (NSNumber *)getAudioOnly {
return _audioOnly;
}
- (NSNumber *)getAudioMuted {
return _audioMuted;
}
- (NSNumber *)getVideoMuted {
return _videoMuted;
}
- (NSNumber *)getWelcomePageEnabled {
return _welcomePageEnabled;
}
@end
@implementation JitsiMeetConferenceOptions {
NSNumber *_audioOnly;
NSNumber *_audioMuted;
NSNumber *_videoMuted;
NSNumber *_welcomePageEnabled;
}
@dynamic audioOnly;
@dynamic audioMuted;
@dynamic videoMuted;
@dynamic welcomePageEnabled;
#pragma mark - Dynamic properties
- (BOOL)audioOnly {
return _audioOnly && [_audioOnly boolValue];
}
- (BOOL)audioMuted {
return _audioMuted && [_audioMuted boolValue];
}
- (BOOL)videoMuted {
return _videoMuted && [_videoMuted boolValue];
}
- (BOOL)welcomePageEnabled {
return _welcomePageEnabled && [_welcomePageEnabled boolValue];
}
#pragma mark - Internal initializer
- (instancetype)initWithBuilder:(JitsiMeetConferenceOptionsBuilder *)builder {
if (self = [super init]) {
_serverURL = builder.serverURL;
_room = builder.room;
_token = builder.token;
_colorScheme = builder.colorScheme;
_audioOnly = [builder getAudioOnly];
_audioMuted = [builder getAudioMuted];
_videoMuted = [builder getVideoMuted];
_welcomePageEnabled = [builder getWelcomePageEnabled];
}
return self;
}
#pragma mark - API
+ (instancetype)fromBuilder:(void (^)(JitsiMeetConferenceOptionsBuilder *))initBlock {
JitsiMeetConferenceOptionsBuilder *builder = [[JitsiMeetConferenceOptionsBuilder alloc] init];
initBlock(builder);
return [[JitsiMeetConferenceOptions alloc] initWithBuilder:builder];
}
#pragma mark - Private API
- (NSDictionary *)asProps {
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
if (_colorScheme != nil) {
props[@"colorScheme"] = self.colorScheme;
}
if (_welcomePageEnabled != nil) {
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
}
NSMutableDictionary *config = [[NSMutableDictionary alloc] init];
if (_audioOnly != nil) {
config[@"startAudioOnly"] = @(self.audioOnly);
}
if (_audioMuted != nil) {
config[@"startWithAudioMuted"] = @(self.audioMuted);
}
if (_videoMuted != nil) {
config[@"startWithVideoMuted"] = @(self.videoMuted);
}
NSMutableDictionary *urlProps = [[NSMutableDictionary alloc] init];
// The room is fully qualified.
if (_room != nil && [_room containsString:@"://"]) {
urlProps[@"url"] = _room;
} else {
if (_serverURL != nil) {
urlProps[@"serverURL"] = [_serverURL absoluteString];
}
if (_room != nil) {
urlProps[@"room"] = _room;
}
}
if (_token != nil) {
urlProps[@"jwt"] = _token;
}
urlProps[@"config"] = config;
props[@"url"] = urlProps;
return props;
}
@end

View File

@ -20,6 +20,6 @@
@interface JitsiMeetView ()
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope;
+ (BOOL)loadURLInViews:(NSDictionary *)urlObject;
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps;
@end

View File

@ -18,23 +18,14 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "JitsiMeetConferenceOptions.h"
#import "JitsiMeetViewDelegate.h"
@interface JitsiMeetView : UIView
@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
@property (nonatomic) NSDictionary *colorScheme;
@property (copy, nonatomic, nullable) NSURL *defaultURL;
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
@property (nonatomic) BOOL pictureInPictureEnabled;
@property (nonatomic) BOOL welcomePageEnabled;
- (void)join:(NSDictionary * _Nullable)url;
- (void)join:(JitsiMeetConferenceOptions *)options;
- (void)leave;
@end

View File

@ -20,7 +20,9 @@
#import <React/RCTRootView.h>
#import "JitsiMeet+Private.h"
#import "JitsiMeetConferenceOptions+Private.h"
#import "JitsiMeetView+Private.h"
#import "ReactUtils.h"
@implementation JitsiMeetView {
@ -32,12 +34,8 @@
NSString *externalAPIScope;
RCTRootView *rootView;
NSNumber *_pictureInPictureEnabled;
}
@dynamic pictureInPictureEnabled;
/**
* The `JitsiMeetView`s associated with their `ExternalAPI` scopes (i.e. unique
* identifiers within the process).
@ -96,80 +94,46 @@ static void initializeViewsMap() {
// parts of the application and causes less perceived visual flicker than
// the default background color.
self.backgroundColor
= [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
= [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
}
#pragma mark API
- (void)join:(NSDictionary *_Nullable)url {
[self loadURL:url];
- (void)join:(JitsiMeetConferenceOptions *)options {
[self setProps:options == nil ? @{} : [options asProps]];
}
- (void)leave {
[self loadURL:nil];
}
#pragma pictureInPictureEnabled getter / setter
- (void) setPictureInPictureEnabled:(BOOL)pictureInPictureEnabled {
_pictureInPictureEnabled
= [NSNumber numberWithBool:pictureInPictureEnabled];
}
- (BOOL) pictureInPictureEnabled {
if (_pictureInPictureEnabled) {
return [_pictureInPictureEnabled boolValue];
}
// The SDK/JitsiMeetView client/consumer did not explicitly enable/disable
// Picture-in-Picture. However, we may automatically deduce their
// intentions: we need the support of the client in order to implement
// Picture-in-Picture on iOS (in contrast to Android) so if the client
// appears to have provided the support then we can assume that they did it
// with the intention to have Picture-in-Picture enabled.
return self.delegate
&& [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
[self setProps:@{}];
}
#pragma mark Private methods
/**
* Loads a specific URL which may identify a conference to join. The URL is
* specified in the form of an `NSDictionary` of properties which (1)
* internally are sufficient to construct a URL `NSString` while (2) abstracting
* the specifics of constructing the URL away from API clients/consumers. If the
* specified URL is `nil` and the Welcome page is enabled, the Welcome page is
* displayed instead.
* Passes the given props to the React Native application. The props which we pass
* are a combination of 3 different sources:
*
* @param urlObject The URL to load which may identify a conference to join.
* - JitsiMeet.defaultConferenceOptions
* - This function's parameters
* - Some extras which are added by this function
*/
- (void)loadURL:(NSDictionary *_Nullable)urlObject {
NSMutableDictionary *props = [[NSMutableDictionary alloc] init];
- (void)setProps:(NSDictionary *_Nonnull)newProps {
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
if (self.defaultURL) {
props[@"defaultURL"] = [self.defaultURL absoluteString];
}
props[@"colorScheme"] = self.colorScheme;
props[@"externalAPIScope"] = externalAPIScope;
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
// XXX If urlObject is nil, then it must appear as undefined in the
// JavaScript source code so that we check the launchOptions there.
if (urlObject) {
props[@"url"] = urlObject;
}
// TODO: put this in some 'flags' field
props[@"pictureInPictureEnabled"]
= @(self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]);
// XXX The method loadURL: is supposed to be imperative i.e. a second
// This method is supposed to be imperative i.e. a second
// invocation with one and the same URL is expected to join the respective
// conference again if the first invocation was followed by leaving the
// conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one and
// the same URL will not trigger an automatic re-render in the JavaScript
// source code. The workaround implemented bellow introduces imperativeness
// in React Component props by defining a unique value per loadURL:
// invocation.
// in React Component props by defining a unique value per invocation.
props[@"timestamp"] = @(mach_absolute_time());
if (rootView) {
@ -192,7 +156,7 @@ static void initializeViewsMap() {
}
}
+ (BOOL)loadURLInViews:(NSDictionary *)urlObject {
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps {
BOOL handled = NO;
if (views) {
@ -201,7 +165,7 @@ static void initializeViewsMap() {
= [self viewForExternalAPIScope:externalAPIScope];
if (view) {
[view loadURL:urlObject];
[view setProps:newProps];
handled = YES;
}
}

View File

@ -14,9 +14,10 @@
* limitations under the License.
*/
#ifndef ReactUtils_h
#define ReactUtils_h
#ifndef JM_REACTUTILS_H
#define JM_REACTUTILS_H
NSMutableDictionary* mergeProps(NSDictionary *a, NSDictionary *b);
void registerReactFatalErrorHandler(void);
#endif /* ReactUtils_h */
#endif /* JM_REACTUTILS_H */

View File

@ -18,6 +18,46 @@
#import "ReactUtils.h"
#pragma mark - Utility functions
/**
* Merges 2 sets of props into a single one.
*/
NSMutableDictionary* mergeProps(NSDictionary *a, NSDictionary *b) {
if (a == nil) {
return [NSMutableDictionary dictionaryWithDictionary:b == nil ? @{} : b];
}
if (b == nil) {
return [NSMutableDictionary dictionaryWithDictionary:a];
}
// Both have values, let's merge them, the strategy is to take the value from a first,
// then override it with the one from b. If the value is a dictionary, merge them
// recursively. Same goes for arrays.
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:a];
for (NSString *key in b) {
id value = b[key];
id aValue = result[key];
if (aValue == nil) {
result[key] = value;
continue;
}
if ([value isKindOfClass:NSArray.class]) {
result[key] = [aValue arrayByAddingObjectsFromArray:value];
} else if ([value isKindOfClass:NSDictionary.class]) {
result[key] = mergeProps(aValue, value);
} else {
result[key] = value;
}
}
return result;
}
/**
* A `RCTFatalHandler` implementation which swallows JavaScript errors. In the
* Release configuration, React Native will (intentionally) raise an unhandled