fix(ios, pip): update view hierarchy to present the rn view with view controller
This commit is contained in:
parent
6df2e4009c
commit
ddab27e292
|
@ -24,8 +24,14 @@
|
|||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */; };
|
||||
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */; };
|
||||
4EBA6E61286072E300B31882 /* JitsiMeetViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */; };
|
||||
4EBA6E62286072E300B31882 /* JitsiMeetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */; };
|
||||
4EBA6E652860B1E800B31882 /* JitsiMeetRenderingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */; };
|
||||
4EBA6E662860B1E800B31882 /* JitsiMeetRenderingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */; };
|
||||
4ED4FFF32721B9B90074E620 /* JitsiAudioSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4ED4FFF42721B9B90074E620 /* JitsiAudioSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */; };
|
||||
4EEC9630286C73A2008705FA /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */; };
|
||||
4EEC9631286C73A2008705FA /* JitsiMeetView+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */; };
|
||||
6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */; };
|
||||
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
|
@ -79,9 +85,15 @@
|
|||
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScheenshareEventEmiter.h; sourceTree = "<group>"; };
|
||||
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScheenshareEventEmiter.m; sourceTree = "<group>"; };
|
||||
4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewController.h; sourceTree = "<group>"; };
|
||||
4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetViewController.m; sourceTree = "<group>"; };
|
||||
4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetRenderingView.h; sourceTree = "<group>"; };
|
||||
4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetRenderingView.m; sourceTree = "<group>"; };
|
||||
4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiAudioSession.h; sourceTree = "<group>"; };
|
||||
4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiAudioSession.m; sourceTree = "<group>"; };
|
||||
4ED4FFF52721BAE10074E620 /* JitsiAudioSession+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiAudioSession+Private.h"; sourceTree = "<group>"; };
|
||||
4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "JitsiMeetView+Private.m"; sourceTree = "<group>"; };
|
||||
891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDK.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK.release.xcconfig"; sourceTree = "<group>"; };
|
||||
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
|
@ -94,7 +106,6 @@
|
|||
C69EFA0B209A0F660027712B /* JMCallKitListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitListener.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalAPI.h; sourceTree = "<group>"; };
|
||||
C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfoPlistUtil.h; sourceTree = "<group>"; };
|
||||
C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfoPlistUtil.m; sourceTree = "<group>"; };
|
||||
|
@ -194,12 +205,17 @@
|
|||
DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */,
|
||||
DE81A2DD2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */,
|
||||
4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */,
|
||||
4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */,
|
||||
4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */,
|
||||
4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */,
|
||||
DE81A2D72316AC7600AE1940 /* LogBridge.m */,
|
||||
DE65AAC92317FFCD00290BEC /* LogUtils.h */,
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */,
|
||||
DEAFA778229EAD520033A7FA /* RNRootView.m */,
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
|
||||
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */,
|
||||
C6A3426B204F127900E062DD /* picture-in-picture */,
|
||||
|
@ -284,11 +300,14 @@
|
|||
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
|
||||
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */,
|
||||
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
|
||||
4EBA6E652860B1E800B31882 /* JitsiMeetRenderingView.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
4ED4FFF32721B9B90074E620 /* JitsiAudioSession.h in Headers */,
|
||||
4EEC9630286C73A2008705FA /* JitsiMeetView+Private.h in Headers */,
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
||||
DE81A2D42316AC4D00AE1940 /* JitsiMeetLogger.h in Headers */,
|
||||
DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */,
|
||||
4EBA6E61286072E300B31882 /* JitsiMeetViewController.h in Headers */,
|
||||
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */,
|
||||
C81E9AB925AC5AD800B134D9 /* ExternalAPI.h in Headers */,
|
||||
C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */,
|
||||
|
@ -449,6 +468,7 @@
|
|||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
DE81A2DF2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m in Sources */,
|
||||
4EBA6E662860B1E800B31882 /* JitsiMeetRenderingView.m in Sources */,
|
||||
4ED4FFF42721B9B90074E620 /* JitsiAudioSession.m in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
DE81A2D92316AC7600AE1940 /* LogBridge.m in Sources */,
|
||||
|
@ -465,9 +485,11 @@
|
|||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
|
||||
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
|
||||
4EEC9631286C73A2008705FA /* JitsiMeetView+Private.m in Sources */,
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */,
|
||||
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
|
||||
4EBA6E62286072E300B31882 /* JitsiMeetViewController.m in Sources */,
|
||||
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
|
||||
DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
|
||||
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
|
||||
|
||||
- (void)sendHangUp;
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
|
||||
#import "ExternalAPI.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
// Events
|
||||
static NSString * const hangUpAction = @"org.jitsi.meet.HANG_UP";
|
||||
|
@ -91,31 +90,14 @@ RCT_EXPORT_MODULE();
|
|||
RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
data:(NSDictionary *)data
|
||||
scope:(NSString *)scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information to
|
||||
// the native ExternalAPI module so that the latter may match the former
|
||||
// to the native JitsiMeetView which hosts it.
|
||||
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:scope];
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
id delegate = view.delegate;
|
||||
|
||||
if (!delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([name isEqual: @"PARTICIPANTS_INFO_RETRIEVED"]) {
|
||||
[self onParticipantsInfoRetrieved: data];
|
||||
return;
|
||||
}
|
||||
|
||||
SEL sel = NSSelectorFromString([self methodNameFromEventName:name]);
|
||||
|
||||
if (sel && [delegate respondsToSelector:sel]) {
|
||||
[delegate performSelector:sel withObject:data];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:sendEventNotificationName
|
||||
object:nil
|
||||
userInfo:@{@"name": name, @"data": data}];
|
||||
}
|
||||
|
||||
- (void) onParticipantsInfoRetrieved:(NSDictionary *)data {
|
||||
|
@ -127,28 +109,6 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
|||
[participantInfoCompletionHandlers removeObjectForKey:completionHandlerId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a specific event name i.e. redux action type description to a
|
||||
* method name.
|
||||
*
|
||||
* @param eventName The event name to convert to a method name.
|
||||
* @return A method name constructed from the specified `eventName`.
|
||||
*/
|
||||
- (NSString *)methodNameFromEventName:(NSString *)eventName {
|
||||
NSMutableString *methodName
|
||||
= [NSMutableString stringWithCapacity:eventName.length];
|
||||
|
||||
for (NSString *c in [eventName componentsSeparatedByString:@"_"]) {
|
||||
if (c.length) {
|
||||
[methodName appendString:
|
||||
methodName.length ? c.capitalizedString : c.lowercaseString];
|
||||
}
|
||||
}
|
||||
[methodName appendString:@":"];
|
||||
|
||||
return methodName;
|
||||
}
|
||||
|
||||
- (void)sendHangUp {
|
||||
[self sendEventWithName:hangUpAction body:nil];
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
|
||||
#import <Intents/Intents.h>
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
|
||||
#import "Dropbox.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
|
@ -25,9 +27,6 @@
|
|||
#import "RNSplashScreen.h"
|
||||
#import "ScheenshareEventEmiter.h"
|
||||
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
|
||||
@implementation JitsiMeet {
|
||||
RCTBridgeWrapper *_bridgeWrapper;
|
||||
NSDictionary *_launchOptions;
|
||||
|
@ -87,8 +86,12 @@
|
|||
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *))restorationHandler {
|
||||
|
||||
JitsiMeetConferenceOptions *options = [self optionsFromUserActivity:userActivity];
|
||||
if (options) {
|
||||
[JitsiMeetView updateProps:[options asProps]];
|
||||
return true;
|
||||
}
|
||||
|
||||
return options && [JitsiMeetView setPropsInViews:[options asProps]];
|
||||
return false;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
|
@ -112,8 +115,9 @@
|
|||
JitsiMeetConferenceOptions *conferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
|
||||
builder.room = [url absoluteString];
|
||||
}];
|
||||
[JitsiMeetView updateProps:[conferenceOptions asProps]];
|
||||
|
||||
return [JitsiMeetView setPropsInViews:[conferenceOptions asProps]];
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark - Utility methods
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright @ 2022-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 <UIKit/UIKit.h>
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JitsiMeetRenderingView : UIView
|
||||
|
||||
@property (nonatomic, assign) BOOL isPiPEnabled;
|
||||
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright @ 2022-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.
|
||||
*/
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#import "JitsiMeetRenderingView.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNRootView.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
|
||||
/**
|
||||
* Backwards compatibility: turn the boolean prop into a feature flag.
|
||||
*/
|
||||
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
|
||||
@interface JitsiMeetRenderingView ()
|
||||
|
||||
/**
|
||||
* The unique identifier of this `JitsiMeetView` within the process for the
|
||||
* purposes of `ExternalAPI`. The name scope was inspired by postis which we
|
||||
* use on Web for the similar purposes of the iframe-based external API.
|
||||
*/
|
||||
@property (nonatomic, strong) NSString *externalAPIScope;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetRenderingView {
|
||||
/**
|
||||
* React Native view where the entire content will be rendered.
|
||||
*/
|
||||
RNRootView *rootView;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
self.externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the given props to the React Native application. The props which we pass
|
||||
* are a combination of 3 different sources:
|
||||
*
|
||||
* - JitsiMeet.defaultConferenceOptions
|
||||
* - This function's parameters
|
||||
* - Some extras which are added by this function
|
||||
*/
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
||||
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
||||
|
||||
// Set the PiP flag if it wasn't manually set.
|
||||
NSMutableDictionary *featureFlags = props[@"flags"];
|
||||
// TODO: temporary implementation
|
||||
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
|
||||
featureFlags[PiPEnabledFeatureFlag] = @(self.isPiPEnabled);
|
||||
}
|
||||
|
||||
props[@"externalAPIScope"] = self.externalAPIScope;
|
||||
|
||||
// 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 below introduces imperativeness
|
||||
// in React Component props by defining a unique value per invocation.
|
||||
props[@"timestamp"] = @(mach_absolute_time());
|
||||
|
||||
if (rootView) {
|
||||
// Update props with the new URL.
|
||||
rootView.appProperties = props;
|
||||
} else {
|
||||
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
|
||||
rootView = [[RNRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
rootView.backgroundColor = self.backgroundColor;
|
||||
|
||||
// Add rootView as a subview which completely covers this one.
|
||||
[rootView setFrame:[self bounds]];
|
||||
rootView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[self addSubview:rootView];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
* Copyright @ 2022-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.
|
||||
|
@ -15,11 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "JitsiMeetView.h"
|
||||
#import <JitsiMeetSDK/JitsiMeetSDK.h>
|
||||
|
||||
@interface JitsiMeetView ()
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+ (instancetype _Nullable)viewForExternalAPIScope:(NSString *_Nonnull)externalAPIScope;
|
||||
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps;
|
||||
static NSString * const updateViewPropsNotificationName = @"org.jitsi.meet.UpdateViewProps";
|
||||
|
||||
@interface JitsiMeetView (Private)
|
||||
|
||||
+ (void)updateProps:(NSDictionary *_Nonnull)newProps;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright @ 2022-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 "JitsiMeetView+Private.h"
|
||||
|
||||
@implementation JitsiMeetView (Private)
|
||||
|
||||
+ (void)updateProps:(NSDictionary *_Nonnull)newProps {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:updateViewPropsNotificationName object:nil userInfo:@{@"props": newProps}];
|
||||
}
|
||||
|
||||
@end
|
|
@ -20,43 +20,22 @@
|
|||
#import "ExternalAPI.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "JitsiMeetView.h"
|
||||
#import "JitsiMeetViewController.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNRootView.h"
|
||||
|
||||
@interface JitsiMeetView ()
|
||||
|
||||
/**
|
||||
* Backwards compatibility: turn the boolean prop into a feature flag.
|
||||
*/
|
||||
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
@property (nonatomic, strong) JitsiMeetViewController *jitsiMeetViewController;
|
||||
@property (nonatomic, strong) UINavigationController *navController;
|
||||
@property (nonatomic, readonly) BOOL isPiPEnabled;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetView {
|
||||
/**
|
||||
* The unique identifier of this `JitsiMeetView` within the process for the
|
||||
* purposes of `ExternalAPI`. The name scope was inspired by postis which we
|
||||
* use on Web for the similar purposes of the iframe-based external API.
|
||||
*/
|
||||
NSString *externalAPIScope;
|
||||
@implementation JitsiMeetView
|
||||
|
||||
/**
|
||||
* React Native view where the entire content will be rendered.
|
||||
*/
|
||||
RNRootView *rootView;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `JitsiMeetView`s associated with their `ExternalAPI` scopes (i.e. unique
|
||||
* identifiers within the process).
|
||||
*/
|
||||
static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
/**
|
||||
* This gets called automagically when the program starts.
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void initializeViewsMap() {
|
||||
views = [NSMapTable strongToWeakObjectsMapTable];
|
||||
}
|
||||
@dynamic isPiPEnabled;
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
|
@ -87,6 +66,10 @@ static void initializeViewsMap() {
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal initialization:
|
||||
*
|
||||
|
@ -94,70 +77,57 @@ static void initializeViewsMap() {
|
|||
* - initializes the external API scope
|
||||
*/
|
||||
- (void)initWithXXX {
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
[views setObject:self forKey:externalAPIScope];
|
||||
self.jitsiMeetViewController = [[JitsiMeetViewController alloc] init];
|
||||
self.jitsiMeetViewController.view.frame = [self bounds];
|
||||
[self addSubview:self.jitsiMeetViewController.view];
|
||||
|
||||
// Set a background color which is in accord with the JavaScript and Android
|
||||
// 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];
|
||||
[self registerObservers];
|
||||
}
|
||||
|
||||
#pragma mark API
|
||||
|
||||
- (void)join:(JitsiMeetConferenceOptions *)options {
|
||||
[self setProps:options == nil ? @{} : [options asProps]];
|
||||
[self.jitsiMeetViewController join:options withPiP:self.isPiPEnabled];
|
||||
}
|
||||
|
||||
- (void)leave {
|
||||
[self setProps:@{}];
|
||||
[self.jitsiMeetViewController leave];
|
||||
}
|
||||
|
||||
- (void)hangUp {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendHangUp];
|
||||
[self.jitsiMeetViewController hangUp];
|
||||
}
|
||||
|
||||
- (void)setAudioMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
[self.jitsiMeetViewController setAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
[self.jitsiMeetViewController sendEndpointTextMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare:(BOOL)enabled {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI toggleScreenShare:enabled];
|
||||
[self.jitsiMeetViewController toggleScreenShare:enabled];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
[self.jitsiMeetViewController retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
|
||||
- (void)openChat:(NSString*)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI openChat:to];
|
||||
[self.jitsiMeetViewController openChat:to];
|
||||
}
|
||||
|
||||
- (void)closeChat {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI closeChat];
|
||||
[self.jitsiMeetViewController closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
[self.jitsiMeetViewController sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)setVideoMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetVideoMuted:muted];
|
||||
[self.jitsiMeetViewController setVideoMuted:muted];
|
||||
}
|
||||
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled {
|
||||
|
@ -165,79 +135,47 @@ static void initializeViewsMap() {
|
|||
[externalAPI sendSetClosedCaptionsEnabled:enabled];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
#pragma mark Private
|
||||
|
||||
- (BOOL)isPiPEnabled {
|
||||
return self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
|
||||
}
|
||||
|
||||
- (void)registerObservers {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSendEventNotification:) name:sendEventNotificationName object:nil];
|
||||
}
|
||||
|
||||
- (void)handleSendEventNotification:(NSNotification *)notification {
|
||||
NSString *eventName = notification.userInfo[@"name"];
|
||||
NSString *eventData = notification.userInfo[@"data"];
|
||||
|
||||
SEL sel = NSSelectorFromString([self methodNameFromEventName:eventName]);
|
||||
|
||||
if (sel && [self.delegate respondsToSelector:sel]) {
|
||||
[self.delegate performSelector:sel withObject:eventData];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the given props to the React Native application. The props which we pass
|
||||
* are a combination of 3 different sources:
|
||||
* Converts a specific event name i.e. redux action type description to a
|
||||
* method name.
|
||||
*
|
||||
* - JitsiMeet.defaultConferenceOptions
|
||||
* - This function's parameters
|
||||
* - Some extras which are added by this function
|
||||
* @param eventName The event name to convert to a method name.
|
||||
* @return A method name constructed from the specified `eventName`.
|
||||
*/
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
||||
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
||||
- (NSString *)methodNameFromEventName:(NSString *)eventName {
|
||||
NSMutableString *methodName
|
||||
= [NSMutableString stringWithCapacity:eventName.length];
|
||||
|
||||
// Set the PiP flag if it wasn't manually set.
|
||||
NSMutableDictionary *featureFlags = props[@"flags"];
|
||||
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
|
||||
featureFlags[PiPEnabledFeatureFlag]
|
||||
= [NSNumber numberWithBool:
|
||||
self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]];
|
||||
}
|
||||
for (NSString *c in [eventName componentsSeparatedByString:@"_"]) {
|
||||
if (c.length) {
|
||||
[methodName appendString:
|
||||
methodName.length ? c.capitalizedString : c.lowercaseString];
|
||||
}
|
||||
}
|
||||
[methodName appendString:@":"];
|
||||
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
|
||||
// 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 below introduces imperativeness
|
||||
// in React Component props by defining a unique value per invocation.
|
||||
props[@"timestamp"] = @(mach_absolute_time());
|
||||
|
||||
if (rootView) {
|
||||
// Update props with the new URL.
|
||||
rootView.appProperties = props;
|
||||
} else {
|
||||
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
|
||||
rootView
|
||||
= [[RNRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
rootView.backgroundColor = self.backgroundColor;
|
||||
|
||||
// Add rootView as a subview which completely covers this one.
|
||||
[rootView setFrame:[self bounds]];
|
||||
rootView.autoresizingMask
|
||||
= UIViewAutoresizingFlexibleWidth
|
||||
| UIViewAutoresizingFlexibleHeight;
|
||||
[self addSubview:rootView];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps {
|
||||
BOOL handled = NO;
|
||||
|
||||
if (views) {
|
||||
for (NSString *externalAPIScope in views) {
|
||||
JitsiMeetView *view
|
||||
= [self viewForExternalAPIScope:externalAPIScope];
|
||||
|
||||
if (view) {
|
||||
[view setProps:newProps];
|
||||
handled = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
|
||||
return [views objectForKey:externalAPIScope];
|
||||
return methodName;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright @ 2022-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 <UIKit/UIKit.h>
|
||||
#import "JitsiMeetConferenceOptions.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JitsiMeetViewController : UIViewController
|
||||
|
||||
- (void)join:(JitsiMeetConferenceOptions *)options withPiP:(BOOL)enablePiP;
|
||||
- (void)leave;
|
||||
- (void)hangUp;
|
||||
- (void)setAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)toggleScreenShare:(BOOL)enabled;
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright @ 2022-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 "JitsiMeetViewController.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetRenderingView.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
@interface JitsiMeetViewController ()
|
||||
|
||||
@property (strong, nonatomic) JitsiMeetRenderingView *view;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetViewController
|
||||
|
||||
@dynamic view;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self registerObservers];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
[super loadView];
|
||||
|
||||
self.view = [[JitsiMeetRenderingView alloc] init];
|
||||
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// Set a background color which is in accord with the JavaScript and Android
|
||||
// parts of the application and causes less perceived visual flicker than
|
||||
// the default background color.
|
||||
self.view.backgroundColor = [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
|
||||
}
|
||||
|
||||
- (void)join:(JitsiMeetConferenceOptions *)options withPiP:(BOOL)enablePiP {
|
||||
self.view.isPiPEnabled = enablePiP;
|
||||
[self.view setProps:options == nil ? @{} : [options asProps]];
|
||||
}
|
||||
|
||||
- (void)leave {
|
||||
[self.view setProps:@{}];
|
||||
}
|
||||
|
||||
- (void)hangUp {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendHangUp];
|
||||
}
|
||||
|
||||
- (void)setAudioMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare:(BOOL)enabled {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI toggleScreenShare:enabled];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
|
||||
- (void)openChat:(NSString*)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI openChat:to];
|
||||
}
|
||||
|
||||
- (void)closeChat {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)setVideoMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetVideoMuted:muted];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)registerObservers {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUpdateViewPropsNotification:) name:updateViewPropsNotificationName object:nil];
|
||||
}
|
||||
|
||||
- (void)handleUpdateViewPropsNotification:(NSNotification *)notification {
|
||||
NSDictionary *props = [notification.userInfo objectForKey:@"props"];
|
||||
[self.view setProps:props];
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in New Issue