[RN] add support for inviting participants during a call on mobile
* Button conditionally shown based on if the feature is enabled and available * Hooks for launching the invite UI (delegates to the native layer) * Hooks for using the search and dial out checks from the native layer (calls back into JS) * Hooks for handling sending invites and passing any failures back to the native layer * Android and iOS handling for those hooks Author: Ryan Peck <rpeck@atlassian.com> Author: Eric Brynsvold <ebrynsvold@atlassian.com>
This commit is contained in:
committed by
Saúl Ibarra Corretgé
parent
4e36127dc7
commit
f64c13d4b7
@@ -29,6 +29,8 @@
|
||||
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
|
||||
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
|
||||
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
|
||||
412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 412BF89C206AA66F0053B9E5 /* InviteSearch.m */; };
|
||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = 412BF89E206AA82F0053B9E5 /* InviteSearch.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
|
||||
@@ -62,6 +64,8 @@
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
|
||||
75635B0920751D6D00F29C9F /* left.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = left.wav; path = ../../sounds/left.wav; sourceTree = "<group>"; };
|
||||
412BF89C206AA66F0053B9E5 /* InviteSearch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InviteSearch.m; sourceTree = "<group>"; };
|
||||
412BF89E206AA82F0053B9E5 /* InviteSearch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InviteSearch.h; 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>"; };
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
|
||||
@@ -125,6 +129,8 @@
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
412BF89E206AA82F0053B9E5 /* InviteSearch.h */,
|
||||
412BF89C206AA66F0053B9E5 /* InviteSearch.m */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||
@@ -180,6 +186,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
|
||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */,
|
||||
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
@@ -347,6 +354,7 @@
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||
412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */,
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@class InviteSearchController;
|
||||
|
||||
@protocol InviteSearchControllerDelegate
|
||||
|
||||
/**
|
||||
* Called when an InviteSearchController has results for a query that was previously provided.
|
||||
*/
|
||||
- (void)inviteSearchController:(InviteSearchController * _Nonnull)controller
|
||||
didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
/**
|
||||
* Called when all invitations were sent successfully.
|
||||
*/
|
||||
- (void)inviteDidSucceedForSearchController:(InviteSearchController * _Nonnull)searchController;
|
||||
|
||||
/**
|
||||
* Called when one or more invitations fails to send successfully.
|
||||
*/
|
||||
- (void)inviteDidFailForItems:(NSArray<NSDictionary *> * _Nonnull)items
|
||||
fromSearchController:(InviteSearchController * _Nonnull)searchController;
|
||||
|
||||
@end
|
||||
|
||||
@interface InviteSearchController: NSObject
|
||||
|
||||
@property (nonatomic, nullable, weak) id<InviteSearchControllerDelegate> delegate;
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query;
|
||||
- (void)cancelSearch;
|
||||
- (void)submitSelectedItemIds:(NSArray<NSString *> * _Nonnull)ids;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright @ 2018-present Atlassian Pty Ltd
|
||||
*
|
||||
* 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/RCTBridge.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
#import "InviteSearch.h"
|
||||
|
||||
// The events emitted/supported by InviteSearch:
|
||||
static NSString * const InviteSearchPerformQueryAction = @"performQueryAction";
|
||||
static NSString * const InviteSearchPerformSubmitInviteAction = @"performSubmitInviteAction";
|
||||
|
||||
|
||||
@interface InviteSearch : RCTEventEmitter
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface InviteSearchController ()
|
||||
|
||||
@property (nonatomic, readonly) NSString* _Nonnull identifier;
|
||||
@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
|
||||
@property (nonatomic, nullable, weak) InviteSearch* module;
|
||||
|
||||
- (instancetype)initWithSearchModule:(InviteSearch *)module;
|
||||
|
||||
- (void)didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
- (void)inviteDidSucceed;
|
||||
|
||||
- (void)inviteDidFailForItems:(NSArray<NSDictionary *> *)items;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation InviteSearch
|
||||
|
||||
static NSMutableDictionary* searchControllers;
|
||||
|
||||
RCT_EXTERN void RCTRegisterModule(Class);
|
||||
|
||||
+ (void)load {
|
||||
RCTRegisterModule(self);
|
||||
|
||||
searchControllers = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
+ (NSString *)moduleName {
|
||||
return @"InviteSearch";
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[
|
||||
InviteSearchPerformQueryAction,
|
||||
InviteSearchPerformSubmitInviteAction
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the corresponding JitsiMeetView's delegate to request that the native
|
||||
* invite search be presented.
|
||||
*
|
||||
* @param scope
|
||||
*/
|
||||
RCT_EXPORT_METHOD(launchNativeInvite:(NSString *)scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information to
|
||||
// the native 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<JitsiMeetViewDelegate> delegate = view.delegate;
|
||||
|
||||
if (!delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ([delegate respondsToSelector:@selector(launchNativeInviteForSearchController:)]) {
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:scope];
|
||||
if (!searchController) {
|
||||
searchController = [self makeInviteSearchController];
|
||||
}
|
||||
|
||||
[delegate launchNativeInviteForSearchController:searchController];
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(inviteSucceeded:(NSString *)inviteScope) {
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
|
||||
|
||||
[searchController inviteDidSucceed];
|
||||
|
||||
[searchControllers removeObjectForKey:inviteScope];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(inviteFailedForItems:(NSArray<NSDictionary *> *)items inviteScope:(NSString *)inviteScope) {
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
|
||||
|
||||
[searchController inviteDidFailForItems:items];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(receivedResults:(NSArray *)results forQuery:(NSString *)query inviteScope:(NSString *)inviteScope) {
|
||||
|
||||
InviteSearchController* searchController = [searchControllers objectForKey:inviteScope];
|
||||
|
||||
[searchController didReceiveResults:results forQuery:query];
|
||||
}
|
||||
|
||||
- (InviteSearchController *)makeInviteSearchController {
|
||||
InviteSearchController* searchController = [[InviteSearchController alloc] initWithSearchModule:self];
|
||||
|
||||
[searchControllers setObject:searchController forKey:searchController.identifier];
|
||||
|
||||
return searchController;
|
||||
}
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query inviteScope:(NSString * _Nonnull)inviteScope {
|
||||
[self sendEventWithName:InviteSearchPerformQueryAction body:@{ @"query": query, @"inviteScope": inviteScope }];
|
||||
}
|
||||
|
||||
- (void)cancelSearchForInviteScope:(NSString * _Nonnull)inviteScope {
|
||||
[searchControllers removeObjectForKey:inviteScope];
|
||||
}
|
||||
|
||||
- (void)submitSelectedItems:(NSArray<NSDictionary *> * _Nonnull)items inviteScope:(NSString * _Nonnull)inviteScope {
|
||||
[self sendEventWithName:InviteSearchPerformSubmitInviteAction body:@{ @"selectedItems": items, @"inviteScope": inviteScope }];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation InviteSearchController
|
||||
|
||||
- (instancetype)initWithSearchModule:(InviteSearch *)module {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_identifier = [[NSUUID UUID] UUIDString];
|
||||
|
||||
self.items = [[NSMutableDictionary alloc] init];
|
||||
self.module = module;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performQuery:(NSString *)query {
|
||||
[self.module performQuery:query inviteScope:self.identifier];
|
||||
}
|
||||
|
||||
- (void)cancelSearch {
|
||||
[self.module cancelSearchForInviteScope:self.identifier];
|
||||
}
|
||||
|
||||
- (void)submitSelectedItemIds:(NSArray<NSString *> * _Nonnull)ids {
|
||||
NSMutableArray* items = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSString* itemId in ids) {
|
||||
id item = [self.items objectForKey:itemId];
|
||||
|
||||
if (item) {
|
||||
[items addObject:item];
|
||||
}
|
||||
}
|
||||
|
||||
[self.module submitSelectedItems:items inviteScope:self.identifier];
|
||||
}
|
||||
|
||||
- (void)didReceiveResults:(NSArray<NSDictionary *> *)results forQuery:(NSString *)query {
|
||||
for (NSDictionary* item in results) {
|
||||
NSString* itemId = item[@"id"];
|
||||
NSString* itemType = item[@"type"];
|
||||
if (itemId) {
|
||||
[self.items setObject:item forKey:itemId];
|
||||
} else if (itemType != nil && [itemType isEqualToString: @"phone"]) {
|
||||
NSString* number = item[@"number"];
|
||||
if (number) {
|
||||
[self.items setObject:item forKey:number];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self.delegate inviteSearchController:self didReceiveResults:results forQuery:query];
|
||||
}
|
||||
|
||||
- (void)inviteDidSucceed {
|
||||
[self.delegate inviteDidSucceedForSearchController:self];
|
||||
}
|
||||
|
||||
- (void)inviteDidFailForItems:(NSArray<NSDictionary *> *)items {
|
||||
if (!items) {
|
||||
items = @[];
|
||||
}
|
||||
[self.delegate inviteDidFailForItems:items fromSearchController:self];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,3 +16,4 @@
|
||||
|
||||
#import <JitsiMeet/JitsiMeetView.h>
|
||||
#import <JitsiMeet/JitsiMeetViewDelegate.h>
|
||||
#import <JitsiMeet/InviteSearch.h>
|
||||
|
||||
@@ -21,10 +21,14 @@
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
@property (nonatomic) BOOL addPeopleEnabled;
|
||||
|
||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
|
||||
@property (nonatomic) BOOL dialOutEnabled;
|
||||
|
||||
@property (nonatomic) BOOL pictureInPictureEnabled;
|
||||
|
||||
@property (nonatomic) BOOL welcomePageEnabled;
|
||||
|
||||
@@ -268,6 +268,8 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
props[@"defaultURL"] = [self.defaultURL absoluteString];
|
||||
}
|
||||
|
||||
props[@"addPeopleEnabled"] = @(self.addPeopleEnabled);
|
||||
props[@"dialOutEnabled"] = @(self.dialOutEnabled);
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
|
||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@class InviteSearchController;
|
||||
|
||||
@protocol JitsiMeetViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
@@ -55,6 +57,15 @@
|
||||
*/
|
||||
- (void)conferenceWillLeave:(NSDictionary *)data;
|
||||
|
||||
|
||||
/**
|
||||
* Called when the invite button in the conference is tapped.
|
||||
*
|
||||
* The search controller provided can be used to query user search within the
|
||||
* conference.
|
||||
*/
|
||||
- (void)launchNativeInviteForSearchController:(InviteSearchController *)searchController;
|
||||
|
||||
/**
|
||||
* Called when entering Picture-in-Picture is requested by the user. The app
|
||||
* should now activate its Picture-in-Picture implementation (and resize the
|
||||
|
||||
Reference in New Issue
Block a user