[RN] add support for inviting participants during a call on mobile (2)
This commit is contained in:
parent
f450756337
commit
effd3728b6
|
@ -19,10 +19,11 @@ package org.jitsi.meet;
|
|||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import org.jitsi.meet.sdk.InviteSearchController;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleController;
|
||||
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||
|
||||
import com.calendarevents.CalendarEventsPackage;
|
||||
|
||||
|
@ -86,16 +87,24 @@ public class MainActivity extends JitsiMeetActivity {
|
|||
on("CONFERENCE_WILL_LEAVE", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchNativeInvite(InviteSearchController inviteSearchController) {
|
||||
on("LAUNCH_NATIVE_INVITE", new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
on("LOAD_CONFIG_ERROR", data);
|
||||
}
|
||||
});
|
||||
|
||||
view.getInviteController().setListener(
|
||||
new InviteControllerListener() {
|
||||
public void beginAddPeople(
|
||||
AddPeopleController addPeopleController) {
|
||||
// Log with the tag "ReactNative" in order to have the
|
||||
// log visible in react-native log-android as well.
|
||||
Log.d(
|
||||
"ReactNative",
|
||||
InviteControllerListener.class.getSimpleName()
|
||||
+ ".beginAddPeople");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller object used by native code to query and submit user selections for the user invitation flow.
|
||||
*/
|
||||
public class InviteSearchController {
|
||||
|
||||
/**
|
||||
* The InviteSearchControllerDelegate for this controller, used to pass query
|
||||
* results back to the native code that initiated the query.
|
||||
*/
|
||||
private InviteSearchControllerDelegate searchControllerDelegate;
|
||||
|
||||
/**
|
||||
* Local cache of search query results. Used to re-hydrate the list
|
||||
* of selected items based on their ids passed to submitSelectedItemIds
|
||||
* in order to pass the full item maps back to the JitsiMeetView during submission.
|
||||
*/
|
||||
private Map<String, ReadableMap> items = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Randomly generated UUID, used for identification in the InviteSearchModule
|
||||
*/
|
||||
private String uuid = UUID.randomUUID().toString();
|
||||
|
||||
private WeakReference<InviteSearchModule> parentModuleRef;
|
||||
|
||||
public InviteSearchController(InviteSearchModule module) {
|
||||
parentModuleRef = new WeakReference<>(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search for entities to invite with the given query.
|
||||
* Results will be returned through the associated InviteSearchControllerDelegate's
|
||||
* onReceiveResults method.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
public void performQuery(String query) {
|
||||
JitsiMeetView.onInviteQuery(query, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send invites to selected users based on their item ids
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
public void submitSelectedItemIds(List<String> ids) {
|
||||
WritableArray selectedItems = new WritableNativeArray();
|
||||
for(int i=0; i<ids.size(); i++) {
|
||||
if(items.containsKey(ids.get(i))) {
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.merge(items.get(ids.get(i)));
|
||||
selectedItems.pushMap(map);
|
||||
} else {
|
||||
// if the id doesn't exist in the map, we can't do anything, so just skip it
|
||||
}
|
||||
}
|
||||
|
||||
JitsiMeetView.submitSelectedItems(selectedItems, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches results received by the search into a local map for use
|
||||
* later when the items are submitted. Submission requires the full
|
||||
* map of information, but only the IDs are returned back to the delegate.
|
||||
* Using this map means we don't have to send the whole map back to the delegate.
|
||||
*
|
||||
* @param results
|
||||
* @param query
|
||||
*/
|
||||
void receivedResultsForQuery(ReadableArray results, String query) {
|
||||
|
||||
List<Map<String, Object>> jvmResults = new ArrayList<>();
|
||||
// cache results for use in submission later
|
||||
// convert to jvm array
|
||||
for(int i=0; i<results.size(); i++) {
|
||||
ReadableMap map = results.getMap(i);
|
||||
if(map.hasKey("id")) {
|
||||
items.put(map.getString("id"), map);
|
||||
} else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
|
||||
items.put(map.getString("number"), map);
|
||||
} else {
|
||||
Log.w("InviteSearchController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
|
||||
}
|
||||
|
||||
jvmResults.add(map.toHashMap());
|
||||
}
|
||||
|
||||
|
||||
searchControllerDelegate.onReceiveResults(this, jvmResults, query);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the InviteSearchControllerDelegate for this controller, used to pass query
|
||||
* results back to the native code that initiated the query.
|
||||
*/
|
||||
public InviteSearchControllerDelegate getSearchControllerDelegate() {
|
||||
return searchControllerDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the InviteSearchControllerDelegate for this controller, used to pass query results
|
||||
* back to the native code that initiated the query.
|
||||
*
|
||||
* @param searchControllerDelegate
|
||||
*/
|
||||
public void setSearchControllerDelegate(InviteSearchControllerDelegate searchControllerDelegate) {
|
||||
this.searchControllerDelegate = searchControllerDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the invitation flow and free memory allocated to the InviteSearchController. After
|
||||
* calling this method, this object is invalid - a new InviteSearchController will be passed
|
||||
* to the caller through launchNativeInvite.
|
||||
*/
|
||||
public void cancelSearch() {
|
||||
InviteSearchModule parentModule = parentModuleRef.get();
|
||||
if(parentModule != null) {
|
||||
parentModule.removeSearchController(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unique identifier for this InviteSearchController
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public interface InviteSearchControllerDelegate {
|
||||
/**
|
||||
* Called when results are received for a query called through InviteSearchController.query()
|
||||
*
|
||||
* @param searchController
|
||||
* @param results a List of Map<String, Object> objects that represent items returned by the query.
|
||||
* The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone".
|
||||
* "user" types have properties at "id", "name", and "avatar"
|
||||
* "videosipgw" types have properties at "id" and "name"
|
||||
* "phone" types have properties at "number", "title", "and "subtitle"
|
||||
* @param query the query that generated the given results
|
||||
*/
|
||||
void onReceiveResults(InviteSearchController searchController, List<Map<String, Object>> results, String query);
|
||||
|
||||
/**
|
||||
* Called when the call to {@link InviteSearchController#submitSelectedItemIds(List)} completes successfully
|
||||
* and invitations are sent to all given IDs.
|
||||
*
|
||||
* @param searchController the active {@link InviteSearchController} for this invite flow. This object will be
|
||||
* cleaned up after the call to inviteSucceeded completes.
|
||||
*/
|
||||
void inviteSucceeded(InviteSearchController searchController);
|
||||
|
||||
/**
|
||||
* Called when the call to {@link InviteSearchController#submitSelectedItemIds(List)} completes, but the
|
||||
* invitation fails for one or more of the selected items.
|
||||
*
|
||||
* @param searchController the active {@link InviteSearchController} for this invite flow. This object
|
||||
* should be cleaned up by calling {@link InviteSearchController#cancelSearch()} if
|
||||
* the user exits the invite flow. Otherwise, it can stay active if the user
|
||||
* will attempt to invite
|
||||
* @param failedInviteItems a {@code List} of {@code Map<String, Object>} dictionaries that represent the
|
||||
* invitations that failed. The data type of the objects is identical to the results
|
||||
* returned in onReceiveResuls.
|
||||
*/
|
||||
void inviteFailed(InviteSearchController searchController, List<Map<String, Object>> failedInviteItems);
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Native module for Invite Search
|
||||
*/
|
||||
class InviteSearchModule extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* Map of InviteSearchController objects passed to connected JitsiMeetView.
|
||||
* A call to launchNativeInvite will create a new InviteSearchController and pass
|
||||
* it back to the caller. On a successful invitation, the controller will be removed automatically.
|
||||
* On a failed invitation, the caller has the option of calling InviteSearchController#cancelSearch()
|
||||
* to remove the controller from this map. The controller should also be removed if the user cancels
|
||||
* the invitation flow.
|
||||
*/
|
||||
private Map<String, InviteSearchController> searchControllers = new HashMap<>();
|
||||
|
||||
public InviteSearchModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the native user invite flow
|
||||
*
|
||||
* @param externalAPIScope a string that represents a connection to a specific JitsiMeetView
|
||||
*/
|
||||
@ReactMethod
|
||||
public void launchNativeInvite(String externalAPIScope) {
|
||||
JitsiMeetView viewToLaunchInvite = JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
|
||||
|
||||
if(viewToLaunchInvite == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(viewToLaunchInvite.getListener() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
InviteSearchController controller = createSearchController();
|
||||
viewToLaunchInvite.getListener().launchNativeInvite(controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for results received from the JavaScript invite search call
|
||||
*
|
||||
* @param results the results in a ReadableArray of ReadableMap objects
|
||||
* @param query the query associated with the search
|
||||
* @param inviteSearchControllerScope a string that represents a connection to a specific InviteSearchController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void receivedResults(ReadableArray results, String query, String inviteSearchControllerScope) {
|
||||
InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
|
||||
|
||||
if(controller == null) {
|
||||
Log.w("InviteSearchModule", "Received results, but unable to find active controller to send results back");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.receivedResultsForQuery(results, query);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for invitation failures
|
||||
*
|
||||
* @param items the items for which the invitation failed
|
||||
* @param inviteSearchControllerScope a string that represents a connection to a specific InviteSearchController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void inviteFailedForItems(ReadableArray items, String inviteSearchControllerScope) {
|
||||
InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
|
||||
|
||||
if(controller == null) {
|
||||
Log.w("InviteSearchModule", "Invite failed, but unable to find active controller to notify");
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<Map<String, Object>> jvmItems = new ArrayList<>();
|
||||
for(int i=0; i<items.size(); i++) {
|
||||
ReadableMap item = items.getMap(i);
|
||||
jvmItems.add(item.toHashMap());
|
||||
}
|
||||
|
||||
controller.getSearchControllerDelegate().inviteFailed(controller, jvmItems);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void inviteSucceeded(String inviteSearchControllerScope) {
|
||||
InviteSearchController controller = searchControllers.get(inviteSearchControllerScope);
|
||||
|
||||
if(controller == null) {
|
||||
Log.w("InviteSearchModule", "Invite succeeded, but unable to find active controller to notify");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.getSearchControllerDelegate().inviteSucceeded(controller);
|
||||
searchControllers.remove(inviteSearchControllerScope);
|
||||
}
|
||||
|
||||
void removeSearchController(String inviteSearchControllerUuid) {
|
||||
searchControllers.remove(inviteSearchControllerUuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "InviteSearch";
|
||||
}
|
||||
|
||||
private InviteSearchController createSearchController() {
|
||||
InviteSearchController searchController = new InviteSearchController(this);
|
||||
searchControllers.put(searchController.getUuid(), searchController);
|
||||
return searchController;
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -229,7 +228,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
|||
if (!super.onKeyUp(keyCode, event)
|
||||
&& BuildConfig.DEBUG
|
||||
&& (reactInstanceManager
|
||||
= JitsiMeetView.getReactInstanceManager())
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager())
|
||||
!= null
|
||||
&& keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
reactInstanceManager.showDevOptionsDialog();
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
@ -29,22 +28,13 @@ import android.widget.FrameLayout;
|
|||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
@ -62,30 +52,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
*/
|
||||
private final static String TAG = JitsiMeetView.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
|
||||
private static final Set<JitsiMeetView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
||||
|
||||
private static List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new InviteSearchModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
);
|
||||
}
|
||||
|
||||
public static JitsiMeetView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
|
@ -99,47 +68,6 @@ public class JitsiMeetView extends FrameLayout {
|
|||
return null;
|
||||
}
|
||||
|
||||
// XXX Strictly internal use only (at the time of this writing)!
|
||||
static ReactInstanceManager getReactInstanceManager() {
|
||||
return reactInstanceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize the React Native instance manager. We
|
||||
* create a single instance in order to load the JavaScript bundle a single
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param application {@code Application} instance which is running.
|
||||
*/
|
||||
private static void initReactInstanceManager(Application application) {
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
|
||||
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
||||
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
||||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
|
||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
|
||||
.addPackage(new ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return JitsiMeetView.createNativeModules(reactContext);
|
||||
}
|
||||
})
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific URL {@code String} in all existing
|
||||
* {@code JitsiMeetView}s.
|
||||
|
@ -174,6 +102,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
* implementation.
|
||||
*/
|
||||
public static boolean onBackPressed() {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
return false;
|
||||
} else {
|
||||
|
@ -190,6 +121,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
* @param activity {@code Activity} being destroyed.
|
||||
*/
|
||||
public static void onHostDestroy(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostDestroy(activity);
|
||||
}
|
||||
|
@ -202,6 +136,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
* @param activity {@code Activity} being paused.
|
||||
*/
|
||||
public static void onHostPause(Activity activity) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostPause(activity);
|
||||
}
|
||||
|
@ -228,6 +165,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
public static void onHostResume(
|
||||
Activity activity,
|
||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||
}
|
||||
|
@ -256,6 +196,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
return;
|
||||
}
|
||||
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
reactInstanceManager.onNewIntent(intent);
|
||||
}
|
||||
|
@ -269,63 +212,9 @@ public class JitsiMeetView extends FrameLayout {
|
|||
* This is currently not mandatory.
|
||||
*/
|
||||
public static void onUserLeaveHint() {
|
||||
sendEvent("onUserLeaveHint", null);
|
||||
ReactInstanceManagerHolder.emitEvent("onUserLeaveHint", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link InviteSearchController.InviteSearchControllerDelegate#onReceiveResults(InviteSearchController, List, String)}
|
||||
* method.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
public static void onInviteQuery(String query, String inviteSearchControllerScope) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
params.putString("query", query);
|
||||
params.putString("inviteScope", inviteSearchControllerScope);
|
||||
sendEvent("performQueryAction", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends JavaScript event to submit invitations to the given item ids
|
||||
*
|
||||
* @param selectedItems a WritableArray of WritableNativeMaps representing selected items.
|
||||
* Each map representing a selected item should match the data passed
|
||||
* back in the return from a query.
|
||||
*/
|
||||
public static void submitSelectedItems(WritableArray selectedItems, String inviteSearchControllerScope) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
params.putArray("selectedItems", selectedItems);
|
||||
params.putString("inviteScope", inviteSearchControllerScope);
|
||||
sendEvent("performSubmitInviteAction", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
* @param eventName {@code String} containing the event name.
|
||||
* @param data {@code Object} optional ancillary data for the event.
|
||||
*/
|
||||
private static void sendEvent(
|
||||
String eventName,
|
||||
@Nullable Object data) {
|
||||
if (reactInstanceManager != null) {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext != null) {
|
||||
reactContext
|
||||
.getJSModule(
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether user invitation is enabled.
|
||||
*/
|
||||
private boolean addPeopleEnabled;
|
||||
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
|
||||
|
@ -333,11 +222,6 @@ public class JitsiMeetView extends FrameLayout {
|
|||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
/**
|
||||
* Whether the ability to add users by phone number is enabled.
|
||||
*/
|
||||
private boolean dialOutEnabled;
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
||||
|
@ -346,6 +230,12 @@ public class JitsiMeetView extends FrameLayout {
|
|||
*/
|
||||
private final String externalAPIScope;
|
||||
|
||||
/**
|
||||
* The entry point into the invite feature of Jitsi Meet. The Java
|
||||
* counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
private final InviteController inviteController;
|
||||
|
||||
/**
|
||||
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
||||
* Jitsi Meet.
|
||||
|
@ -374,15 +264,18 @@ public class JitsiMeetView extends FrameLayout {
|
|||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
if (reactInstanceManager == null) {
|
||||
initReactInstanceManager(((Activity) context).getApplication());
|
||||
}
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||
((Activity) context).getApplication());
|
||||
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
synchronized (views) {
|
||||
views.add(this);
|
||||
}
|
||||
|
||||
// The entry point into the invite feature of Jitsi Meet. The Java
|
||||
// counterpart of the JavaScript InviteButton.
|
||||
inviteController = new InviteController(externalAPIScope);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -413,6 +306,19 @@ public class JitsiMeetView extends FrameLayout {
|
|||
return defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link InviteController} which represents the entry point into
|
||||
* the invite feature of Jitsi Meet and is the Java counterpart of the
|
||||
* JavaScript {@code InviteButton}.
|
||||
*
|
||||
* @return the {@link InviteController} which represents the entry point
|
||||
* into the invite feature of Jitsi Meet and is the Java counterpart of the
|
||||
* JavaScript {@code InviteButton}
|
||||
*/
|
||||
public InviteController getInviteController() {
|
||||
return inviteController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||
*
|
||||
|
@ -483,6 +389,18 @@ public class JitsiMeetView extends FrameLayout {
|
|||
// externalAPIScope
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
if (inviteController != null) {
|
||||
props.putBoolean(
|
||||
"addPeopleEnabled",
|
||||
inviteController.isAddPeopleEnabled());
|
||||
props.putBoolean(
|
||||
"dialOutEnabled",
|
||||
inviteController.isDialOutEnabled());
|
||||
}
|
||||
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
|
@ -496,9 +414,6 @@ public class JitsiMeetView extends FrameLayout {
|
|||
// welcomePageEnabled
|
||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||
|
||||
props.putBoolean("addPeopleEnabled", addPeopleEnabled);
|
||||
props.putBoolean("dialOutEnabled", dialOutEnabled);
|
||||
|
||||
// XXX The method loadURLObject: 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
|
||||
|
@ -513,7 +428,7 @@ public class JitsiMeetView extends FrameLayout {
|
|||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
reactInstanceManager,
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
"App",
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
@ -580,18 +495,6 @@ public class JitsiMeetView extends FrameLayout {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add users to the call is enabled.
|
||||
* If this is enabled, an add user button will appear on the {@link JitsiMeetView}.
|
||||
* If enabled, and the user taps the add user button,
|
||||
* {@link JitsiMeetViewListener#launchNativeInvite(Map)} will be called.
|
||||
*
|
||||
* @param addPeopleEnabled {@code true} to enable the add people button; otherwise, {@code false}
|
||||
*/
|
||||
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
|
||||
this.addPeopleEnabled = addPeopleEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
|
@ -605,18 +508,6 @@ public class JitsiMeetView extends FrameLayout {
|
|||
this.defaultURL = defaultURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add phone numbers to the call is enabled.
|
||||
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to
|
||||
* be effective.
|
||||
*
|
||||
* @param dialOutEnabled {@code true} to enable the ability to add
|
||||
* phone numbers to the call; otherwise, {@code false}
|
||||
*/
|
||||
public void setDialOutEnabled(boolean dialOutEnabled) {
|
||||
this.dialOutEnabled = dialOutEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||
* {@code JitsiMeetView}.
|
||||
|
|
|
@ -46,8 +46,4 @@ public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
|
|||
@Override
|
||||
public void onLoadConfigError(Map<String, Object> data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchNativeInvite(InviteSearchController inviteSearchController) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,16 +59,6 @@ public interface JitsiMeetViewListener {
|
|||
*/
|
||||
void onConferenceWillLeave(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when the add user button is tapped.
|
||||
*
|
||||
* @param inviteSearchController {@code InviteSearchController} scoped
|
||||
* for this user invite flow. The {@code InviteSearchController} is used
|
||||
* to start user queries and accepts an {@code InviteSearchControllerDelegate}
|
||||
* for receiving user query responses.
|
||||
*/
|
||||
void launchNativeInvite(InviteSearchController inviteSearchController);
|
||||
|
||||
/**
|
||||
* Called when loading the main configuration file from the Jitsi Meet
|
||||
* deployment fails.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
|
||||
public class ReactContextUtils {
|
||||
public static boolean emitEvent(
|
||||
ReactContext reactContext,
|
||||
String eventName,
|
||||
@Nullable Object data) {
|
||||
if (reactContext == null) {
|
||||
// XXX If no ReactContext is specified, emit through the
|
||||
// ReactContext of ReactInstanceManager. ReactInstanceManager
|
||||
// cooperates with ReactContextUtils i.e. ReactInstanceManager will
|
||||
// not invoke ReactContextUtils without a ReactContext.
|
||||
return ReactInstanceManagerHolder.emitEvent(eventName, data);
|
||||
}
|
||||
|
||||
reactContext
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(eventName, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class ReactInstanceManagerHolder {
|
||||
/**
|
||||
* React Native bridge. The instance manager allows embedding applications
|
||||
* to create multiple root views off the same JavaScript bundle.
|
||||
*/
|
||||
private static ReactInstanceManager reactInstanceManager;
|
||||
|
||||
private static List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(
|
||||
new AndroidSettingsModule(reactContext),
|
||||
new AppInfoModule(reactContext),
|
||||
new AudioModeModule(reactContext),
|
||||
new ExternalAPIModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to send an event to JavaScript.
|
||||
*
|
||||
* @param eventName {@code String} containing the event name.
|
||||
* @param data {@code Object} optional ancillary data for the event.
|
||||
*/
|
||||
public static boolean emitEvent(
|
||||
String eventName,
|
||||
@Nullable Object data) {
|
||||
ReactInstanceManager reactInstanceManager
|
||||
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||
|
||||
if (reactInstanceManager != null) {
|
||||
ReactContext reactContext
|
||||
= reactInstanceManager.getCurrentReactContext();
|
||||
|
||||
return
|
||||
reactContext != null
|
||||
&& ReactContextUtils.emitEvent(
|
||||
reactContext,
|
||||
eventName,
|
||||
data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static ReactInstanceManager getReactInstanceManager() {
|
||||
return reactInstanceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to initialize the React Native instance manager. We
|
||||
* create a single instance in order to load the JavaScript bundle a single
|
||||
* time. All {@code ReactRootView} instances will be tied to the one and
|
||||
* only {@code ReactInstanceManager}.
|
||||
*
|
||||
* @param application {@code Application} instance which is running.
|
||||
*/
|
||||
static void initReactInstanceManager(Application application) {
|
||||
if (reactInstanceManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
reactInstanceManager
|
||||
= ReactInstanceManager.builder()
|
||||
.setApplication(application)
|
||||
.setBundleAssetName("index.android.bundle")
|
||||
.setJSMainModulePath("index.android")
|
||||
.addPackage(new com.calendarevents.CalendarEventsPackage())
|
||||
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
|
||||
.addPackage(new com.facebook.react.shell.MainReactPackage())
|
||||
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
|
||||
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
|
||||
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
|
||||
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
|
||||
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
|
||||
.addPackage(new com.rnimmersive.RNImmersivePackage())
|
||||
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
|
||||
.addPackage(new ReactPackageAdapter() {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
return
|
||||
ReactInstanceManagerHolder.createNativeModules(
|
||||
reactContext);
|
||||
}
|
||||
})
|
||||
.setUseDeveloperSupport(BuildConfig.DEBUG)
|
||||
.setInitialLifecycleState(LifecycleState.RESUMED)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.invite;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Controller object used by native code to query and submit user selections for the user invitation flow.
|
||||
*/
|
||||
public class AddPeopleController {
|
||||
|
||||
/**
|
||||
* The AddPeopleControllerListener for this controller, used to pass query
|
||||
* results back to the native code that initiated the query.
|
||||
*/
|
||||
private AddPeopleControllerListener listener;
|
||||
|
||||
/**
|
||||
* Local cache of search query results. Used to re-hydrate the list
|
||||
* of selected items based on their ids passed to inviteById
|
||||
* in order to pass the full item maps back to the JitsiMeetView during submission.
|
||||
*/
|
||||
private final Map<String, ReadableMap> items = new HashMap<>();
|
||||
|
||||
private final WeakReference<InviteController> owner;
|
||||
|
||||
private final WeakReference<ReactApplicationContext> reactContext;
|
||||
/**
|
||||
* Randomly generated UUID, used for identification in the InviteModule
|
||||
*/
|
||||
private final String uuid = UUID.randomUUID().toString();
|
||||
|
||||
public AddPeopleController(
|
||||
InviteController owner,
|
||||
ReactApplicationContext reactContext) {
|
||||
this.owner = new WeakReference<>(owner);
|
||||
this.reactContext = new WeakReference<>(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the invitation flow and free memory allocated to the
|
||||
* AddPeopleController. After calling this method, this object is invalid -
|
||||
* a new AddPeopleController will be passed to the caller through
|
||||
* beginAddPeople.
|
||||
*/
|
||||
public void endAddPeople() {
|
||||
InviteController owner = this.owner.get();
|
||||
|
||||
if (owner != null) {
|
||||
owner.endAddPeople(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the AddPeopleControllerListener for this controller, used to pass
|
||||
* query results back to the native code that initiated the query.
|
||||
*/
|
||||
public AddPeopleControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
final ReactApplicationContext getReactApplicationContext() {
|
||||
return reactContext.get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the unique identifier for this AddPeopleController
|
||||
*/
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send invites to selected users based on their item ids
|
||||
*
|
||||
* @param ids
|
||||
*/
|
||||
public void inviteById(List<String> ids) {
|
||||
InviteController owner = this.owner.get();
|
||||
|
||||
if (owner != null) {
|
||||
WritableArray invitees = new WritableNativeArray();
|
||||
|
||||
for(int i = 0, size = ids.size(); i < size; i++) {
|
||||
String id = ids.get(i);
|
||||
|
||||
if(items.containsKey(id)) {
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.merge(items.get(ids));
|
||||
invitees.pushMap(map);
|
||||
} else {
|
||||
// If the id doesn't exist in the map, we can't do anything,
|
||||
// so just skip it.
|
||||
}
|
||||
}
|
||||
|
||||
owner.invite(this, invitees);
|
||||
}
|
||||
}
|
||||
|
||||
void inviteSettled(ReadableArray failedInvitees) {
|
||||
AddPeopleControllerListener listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
ArrayList<Map<String, Object>> jFailedInvitees = new ArrayList<>();
|
||||
|
||||
for (int i = 0, size = failedInvitees.size(); i < size; ++i) {
|
||||
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
|
||||
}
|
||||
|
||||
listener.inviteSettled(this, jFailedInvitees);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search for entities to invite with the given query. Results will
|
||||
* be returned through the associated AddPeopleControllerListener's
|
||||
* onReceiveResults method.
|
||||
*
|
||||
* @param query
|
||||
*/
|
||||
public void performQuery(String query) {
|
||||
InviteController owner = this.owner.get();
|
||||
|
||||
if (owner != null) {
|
||||
owner.performQuery(this, query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches results received by the search into a local map for use
|
||||
* later when the items are submitted. Submission requires the full
|
||||
* map of information, but only the IDs are returned back to the delegate.
|
||||
* Using this map means we don't have to send the whole map back to the delegate.
|
||||
*
|
||||
* @param results
|
||||
* @param query
|
||||
*/
|
||||
void receivedResultsForQuery(ReadableArray results, String query) {
|
||||
AddPeopleControllerListener listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
List<Map<String, Object>> jvmResults = new ArrayList<>();
|
||||
|
||||
// cache results for use in submission later
|
||||
// convert to jvm array
|
||||
for(int i = 0; i < results.size(); i++) {
|
||||
ReadableMap map = results.getMap(i);
|
||||
|
||||
if(map.hasKey("id")) {
|
||||
items.put(map.getString("id"), map);
|
||||
} else if(map.hasKey("type") && map.getString("type").equals("phone") && map.hasKey("number")) {
|
||||
items.put(map.getString("number"), map);
|
||||
} else {
|
||||
Log.w("AddPeopleController", "Received result without id and that was not a phone number, so not adding it to suggestions: " + map);
|
||||
}
|
||||
|
||||
jvmResults.add(map.toHashMap());
|
||||
}
|
||||
|
||||
listener.onReceiveResults(this, jvmResults, query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the AddPeopleControllerListener for this controller, used to pass
|
||||
* query results back to the native code that initiated the query.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void setListener(AddPeopleControllerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.invite;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface AddPeopleControllerListener {
|
||||
/**
|
||||
* Called when results are received for a query called through AddPeopleController.query()
|
||||
*
|
||||
* @param addPeopleController
|
||||
* @param results a List of Map<String, Object> objects that represent items returned by the query.
|
||||
* The object at key "type" describes the type of item: "user", "videosipgw" (conference room), or "phone".
|
||||
* "user" types have properties at "id", "name", and "avatar"
|
||||
* "videosipgw" types have properties at "id" and "name"
|
||||
* "phone" types have properties at "number", "title", "and "subtitle"
|
||||
* @param query the query that generated the given results
|
||||
*/
|
||||
void onReceiveResults(AddPeopleController addPeopleController, List<Map<String, Object>> results, String query);
|
||||
|
||||
/**
|
||||
* Called when the call to {@link AddPeopleController#inviteById(List)} completes, but the
|
||||
* invitation fails for one or more of the selected items.
|
||||
*
|
||||
* @param addPeopleController the active {@link AddPeopleController} for this invite flow. This object
|
||||
* should be cleaned up by calling {@link AddPeopleController#endAddPeople()} if
|
||||
* the user exits the invite flow. Otherwise, it can stay active if the user
|
||||
* will attempt to invite
|
||||
* @param failedInvitees a {@code List} of {@code Map<String, Object>} dictionaries that represent the
|
||||
* invitations that failed. The data type of the objects is identical to the results
|
||||
* returned in onReceiveResuls.
|
||||
*/
|
||||
void inviteSettled(AddPeopleController addPeopleController, List<Map<String, Object>> failedInvitees);
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.invite;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
|
||||
import org.jitsi.meet.sdk.ReactContextUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Represents the entry point into the invite feature of Jitsi Meet and is the
|
||||
* Java counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
public class InviteController {
|
||||
private AddPeopleController addPeopleController;
|
||||
|
||||
/**
|
||||
* Whether adding/inviting people by name (as opposed to phone number) is
|
||||
* enabled.
|
||||
*/
|
||||
private Boolean addPeopleEnabled;
|
||||
|
||||
/**
|
||||
* Whether adding/inviting people by phone number (as opposed to name) is
|
||||
* enabled.
|
||||
*/
|
||||
private Boolean dialOutEnabled;
|
||||
|
||||
private final String externalAPIScope;
|
||||
|
||||
private InviteControllerListener listener;
|
||||
|
||||
public InviteController(String externalAPIScope) {
|
||||
this.externalAPIScope = externalAPIScope;
|
||||
}
|
||||
|
||||
public InviteControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
void beginAddPeople(ReactApplicationContext reactContext) {
|
||||
InviteControllerListener listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
// XXX For the sake of simplicity and in order to reduce the risk of
|
||||
// memory leaks, allow a single AddPeopleController at a time.
|
||||
AddPeopleController addPeopleController = this.addPeopleController;
|
||||
|
||||
if (addPeopleController != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize a new AddPeopleController to represent the click/tap
|
||||
// on the InviteButton and notify the InviteControllerListener
|
||||
// about the event.
|
||||
addPeopleController = new AddPeopleController(this, reactContext);
|
||||
|
||||
boolean success = false;
|
||||
|
||||
this.addPeopleController = addPeopleController;
|
||||
try {
|
||||
listener.beginAddPeople(addPeopleController);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
endAddPeople(addPeopleController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void endAddPeople(AddPeopleController addPeopleController) {
|
||||
if (this.addPeopleController == addPeopleController) {
|
||||
this.addPeopleController = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends JavaScript event to submit invitations to the given item ids
|
||||
*
|
||||
* @param invitees a WritableArray of WritableNativeMaps representing
|
||||
* selected items. Each map representing a selected item should match the
|
||||
* data passed back in the return from a query.
|
||||
*/
|
||||
boolean invite(
|
||||
AddPeopleController addPeopleController,
|
||||
WritableArray invitees) {
|
||||
return
|
||||
invite(
|
||||
addPeopleController.getUuid(),
|
||||
addPeopleController.getReactApplicationContext(),
|
||||
invitees);
|
||||
}
|
||||
|
||||
public Future<List<Map<String, Object>>> invite(
|
||||
final List<Map<String, Object>> invitees) {
|
||||
final boolean inviteBegan
|
||||
= invite(
|
||||
UUID.randomUUID().toString(),
|
||||
/* reactContext */ null,
|
||||
Arguments.makeNativeArray(invitees));
|
||||
FutureTask futureTask
|
||||
= new FutureTask(new Callable() {
|
||||
@Override
|
||||
public List<Map<String, Object>> call() {
|
||||
if (inviteBegan) {
|
||||
// TODO Complete the returned Future when the invite
|
||||
// settles.
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
// The invite failed to even begin so report that all
|
||||
// invitees failed.
|
||||
return invitees;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If the invite failed to even begin, complete the returned Future
|
||||
// already and the Future implementation will report that all invitees
|
||||
// failed.
|
||||
if (!inviteBegan) {
|
||||
futureTask.run();
|
||||
}
|
||||
|
||||
return futureTask;
|
||||
}
|
||||
|
||||
private boolean invite(
|
||||
String addPeopleControllerScope,
|
||||
ReactContext reactContext,
|
||||
WritableArray invitees) {
|
||||
WritableNativeMap data = new WritableNativeMap();
|
||||
|
||||
data.putString("addPeopleControllerScope", addPeopleControllerScope);
|
||||
data.putString("externalAPIScope", externalAPIScope);
|
||||
data.putArray("invitees", invitees);
|
||||
|
||||
return
|
||||
ReactContextUtils.emitEvent(
|
||||
reactContext,
|
||||
"org.jitsi.meet:features/invite#invite",
|
||||
data);
|
||||
}
|
||||
|
||||
void inviteSettled(
|
||||
String addPeopleControllerScope,
|
||||
ReadableArray failedInvitees) {
|
||||
AddPeopleController addPeopleController = this.addPeopleController;
|
||||
|
||||
if (addPeopleController != null
|
||||
&& addPeopleController.getUuid().equals(
|
||||
addPeopleControllerScope)) {
|
||||
try {
|
||||
addPeopleController.inviteSettled(failedInvitees);
|
||||
} finally {
|
||||
if (failedInvitees.size() == 0) {
|
||||
endAddPeople(addPeopleController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAddPeopleEnabled() {
|
||||
Boolean b = this.addPeopleEnabled;
|
||||
|
||||
return
|
||||
(b == null || b.booleanValue()) ? (getListener() != null) : false;
|
||||
}
|
||||
|
||||
public boolean isDialOutEnabled() {
|
||||
Boolean b = this.dialOutEnabled;
|
||||
|
||||
return
|
||||
(b == null || b.booleanValue()) ? (getListener() != null) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a query for users to invite to the conference. Results will be
|
||||
* returned through the {@link AddPeopleControllerListener#onReceiveResults(AddPeopleController, List, String)}
|
||||
* method.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
void performQuery(AddPeopleController addPeopleController, String query) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
|
||||
params.putString("externalAPIScope", externalAPIScope);
|
||||
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
|
||||
params.putString("query", query);
|
||||
ReactContextUtils.emitEvent(
|
||||
addPeopleController.getReactApplicationContext(),
|
||||
"org.jitsi.meet:features/invite#performQuery",
|
||||
params);
|
||||
}
|
||||
|
||||
void receivedResultsForQuery(
|
||||
String addPeopleControllerScope,
|
||||
String query,
|
||||
ReadableArray results) {
|
||||
AddPeopleController addPeopleController = this.addPeopleController;
|
||||
|
||||
if (addPeopleController != null
|
||||
&& addPeopleController.getUuid().equals(
|
||||
addPeopleControllerScope)) {
|
||||
addPeopleController.receivedResultsForQuery(results, query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add users to the call is enabled. If this is
|
||||
* enabled, an add user button will appear on the {@link JitsiMeetView}. If
|
||||
* enabled, and the user taps the add user button,
|
||||
* {@link InviteControllerListener#beginAddPeople(AddPeopleController)}
|
||||
* will be called.
|
||||
*
|
||||
* @param addPeopleEnabled {@code true} to enable the add people button;
|
||||
* otherwise, {@code false}
|
||||
*/
|
||||
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
|
||||
this.addPeopleEnabled = Boolean.valueOf(addPeopleEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the ability to add phone numbers to the call is enabled.
|
||||
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to be
|
||||
* effective.
|
||||
*
|
||||
* @param dialOutEnabled {@code true} to enable the ability to add phone
|
||||
* numbers to the call; otherwise, {@code false}
|
||||
*/
|
||||
public void setDialOutEnabled(boolean dialOutEnabled) {
|
||||
this.dialOutEnabled = Boolean.valueOf(dialOutEnabled);
|
||||
}
|
||||
|
||||
public void setListener(InviteControllerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.invite;
|
||||
|
||||
public interface InviteControllerListener {
|
||||
/**
|
||||
* Called when the add user button is tapped.
|
||||
*
|
||||
* @param addPeopleController {@code AddPeopleController} scoped
|
||||
* for this user invite flow. The {@code AddPeopleController} is used
|
||||
* to start user queries and accepts an {@code AddPeopleControllerListener}
|
||||
* for receiving user query responses.
|
||||
*/
|
||||
void beginAddPeople(AddPeopleController addPeopleController);
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright @ 2017-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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk.invite;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
|
||||
/**
|
||||
* Implements the react-native module of the feature invite.
|
||||
*/
|
||||
public class InviteModule extends ReactContextBaseJavaModule {
|
||||
public InviteModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that a click/tap has been performed on {@code InviteButton} and
|
||||
* that the execution flow for adding/inviting people to the current
|
||||
* conference/meeting is to begin
|
||||
*
|
||||
* @param externalAPIScope the unique identifier of the
|
||||
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void beginAddPeople(String externalAPIScope) {
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
if (inviteController != null) {
|
||||
inviteController.beginAddPeople(getReactApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
private InviteController findInviteControllerByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
JitsiMeetView view
|
||||
= JitsiMeetView.findViewByExternalAPIScope(externalAPIScope);
|
||||
|
||||
return view == null ? null : view.getInviteController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Invite";
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for invitation failures
|
||||
*
|
||||
* @param failedInvitees the items for which the invitation failed
|
||||
* @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void inviteSettled(
|
||||
String externalAPIScope,
|
||||
String addPeopleControllerScope,
|
||||
ReadableArray failedInvitees) {
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
if (inviteController == null) {
|
||||
Log.w(
|
||||
"InviteModule",
|
||||
"Invite settled, but failed to find active controller to notify");
|
||||
} else {
|
||||
inviteController.inviteSettled(
|
||||
addPeopleControllerScope,
|
||||
failedInvitees);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for results received from the JavaScript invite search call
|
||||
*
|
||||
* @param results the results in a ReadableArray of ReadableMap objects
|
||||
* @param query the query associated with the search
|
||||
* @param addPeopleControllerScope a string that represents a connection to a specific AddPeopleController
|
||||
*/
|
||||
@ReactMethod
|
||||
public void receivedResults(
|
||||
String externalAPIScope,
|
||||
String addPeopleControllerScope,
|
||||
String query,
|
||||
ReadableArray results) {
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
if (inviteController == null) {
|
||||
Log.w(
|
||||
"InviteModule",
|
||||
"Received results, but failed to find active controller to send results back");
|
||||
} else {
|
||||
inviteController.receivedResultsForQuery(
|
||||
addPeopleControllerScope,
|
||||
query,
|
||||
results);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,6 @@
|
|||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
@interface ViewController : UIViewController<JitsiMeetViewDelegate>
|
||||
@interface ViewController : UIViewController<JitsiMeetViewDelegate, InviteControllerDelegate>
|
||||
|
||||
@end
|
||||
|
|
|
@ -27,12 +27,25 @@
|
|||
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
view.delegate = self;
|
||||
|
||||
// inviteController
|
||||
InviteController *inviteController = view.inviteController;
|
||||
|
||||
//inviteController.addPeopleEnabled = TRUE;
|
||||
//inviteController.dialOutEnabled = TRUE;
|
||||
inviteController.delegate = self;
|
||||
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
// 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 loadURL:nil];
|
||||
}
|
||||
|
||||
|
@ -68,6 +81,17 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
|
|||
_onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", data);
|
||||
}
|
||||
|
||||
#endif
|
||||
- (void)beginAddPeople:(AddPeopleController *)addPeopleController {
|
||||
NSLog(
|
||||
@"[%s:%d] InviteControllerDelegate %s",
|
||||
__FILE__, __LINE__, __FUNCTION__);
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
|
||||
// is going to be memory-leaked in the associated InviteController and no
|
||||
// subsequent InviteButton clicks/taps will be delivered.
|
||||
[addPeopleController endAddPeople];
|
||||
}
|
||||
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
@end
|
||||
|
|
|
@ -29,8 +29,13 @@
|
|||
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, ); }; };
|
||||
B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
|
||||
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85120981A74000DEF7A /* AddPeopleController.m */; };
|
||||
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85320981A74000DEF7A /* AddPeopleController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85420981A74000DEF7A /* InviteControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85C20981A75000DEF7A /* InviteController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85520981A75000DEF7A /* InviteController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
|
||||
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 */; };
|
||||
|
@ -44,6 +49,9 @@
|
|||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetView.m; sourceTree = "<group>"; };
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewDelegate.h; sourceTree = "<group>"; };
|
||||
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
|
||||
0B6F414F20987DE600FF6789 /* Invite+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Invite+Private.h"; sourceTree = "<group>"; };
|
||||
0B6F41502098840600FF6789 /* InviteController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "InviteController+Private.h"; sourceTree = "<group>"; };
|
||||
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AddPeopleController+Private.h"; sourceTree = "<group>"; };
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
|
||||
0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
|
||||
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
|
||||
|
@ -64,10 +72,15 @@
|
|||
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>"; };
|
||||
B386B85020981A74000DEF7A /* InviteController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
|
||||
B386B85120981A74000DEF7A /* AddPeopleController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddPeopleController.m; sourceTree = "<group>"; };
|
||||
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleControllerDelegate.h; sourceTree = "<group>"; };
|
||||
B386B85320981A74000DEF7A /* AddPeopleController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleController.h; sourceTree = "<group>"; };
|
||||
B386B85420981A74000DEF7A /* InviteControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteControllerDelegate.h; sourceTree = "<group>"; };
|
||||
B386B85520981A75000DEF7A /* InviteController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteController.h; sourceTree = "<group>"; };
|
||||
B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; 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>"; };
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
|
@ -125,12 +138,11 @@
|
|||
0BD906E71EC0C00300C8C18E /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B386B84F20981A11000DEF7A /* invite */,
|
||||
C6A3426B204F127900E062DD /* picture-in-picture */,
|
||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||
412BF89E206AA82F0053B9E5 /* InviteSearch.h */,
|
||||
412BF89C206AA66F0053B9E5 /* InviteSearch.m */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||
|
@ -160,6 +172,23 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B386B84F20981A11000DEF7A /* invite */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B386B85320981A74000DEF7A /* AddPeopleController.h */,
|
||||
B386B85120981A74000DEF7A /* AddPeopleController.m */,
|
||||
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */,
|
||||
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */,
|
||||
B386B85620981A75000DEF7A /* Invite.m */,
|
||||
0B6F414F20987DE600FF6789 /* Invite+Private.h */,
|
||||
B386B85520981A75000DEF7A /* InviteController.h */,
|
||||
B386B85020981A74000DEF7A /* InviteController.m */,
|
||||
0B6F41502098840600FF6789 /* InviteController+Private.h */,
|
||||
B386B85420981A74000DEF7A /* InviteControllerDelegate.h */,
|
||||
);
|
||||
path = invite;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C5E72ADFC30ED96F9B35F076 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -185,11 +214,14 @@
|
|||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B386B85C20981A75000DEF7A /* InviteController.h in Headers */,
|
||||
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */,
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
|
||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */,
|
||||
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
||||
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */,
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */,
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -350,11 +382,13 @@
|
|||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||
B386B85D20981A75000DEF7A /* Invite.m in Sources */,
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
||||
B386B85720981A75000DEF7A /* InviteController.m in Sources */,
|
||||
B386B85820981A75000DEF7A /* AddPeopleController.m 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 */,
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* 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
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* 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
|
|
@ -14,6 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// JitsiMeetView
|
||||
#import <JitsiMeet/JitsiMeetView.h>
|
||||
#import <JitsiMeet/JitsiMeetViewDelegate.h>
|
||||
#import <JitsiMeet/InviteSearch.h>
|
||||
|
||||
// invite/
|
||||
#import <JitsiMeet/AddPeopleController.h>
|
||||
#import <JitsiMeet/AddPeopleControllerDelegate.h>
|
||||
#import <JitsiMeet/InviteController.h>
|
||||
#import <JitsiMeet/InviteControllerDelegate.h>
|
||||
|
|
|
@ -17,17 +17,16 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "InviteController.h"
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (nonatomic) BOOL addPeopleEnabled;
|
||||
|
||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
|
||||
@property (nonatomic) BOOL dialOutEnabled;
|
||||
@property (nonatomic, readonly) InviteController *inviteController;
|
||||
|
||||
@property (nonatomic) BOOL pictureInPictureEnabled;
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#import <React/RCTLinkingManager.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
#import "Invite+Private.h"
|
||||
#import "InviteController+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "RCTBridgeWrapper.h"
|
||||
|
||||
|
@ -268,12 +270,13 @@ 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);
|
||||
|
||||
props[@"addPeopleEnabled"] = @(_inviteController.addPeopleEnabled);
|
||||
props[@"dialOutEnabled"] = @(_inviteController.dialOutEnabled);
|
||||
|
||||
// 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) {
|
||||
|
@ -405,10 +408,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
|||
});
|
||||
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
if (!externalAPIScope) {
|
||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
[views setObject:self forKey:externalAPIScope];
|
||||
}
|
||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
[views setObject:self forKey:externalAPIScope];
|
||||
|
||||
Invite *inviteModule = [bridgeWrapper.bridge moduleForName:@"Invite"];
|
||||
_inviteController
|
||||
= [[InviteController alloc] initWithExternalAPIScope:externalAPIScope
|
||||
andInviteModule:inviteModule];
|
||||
|
||||
// Set a background color which is in accord with the JavaScript and Android
|
||||
// parts of the application and causes less perceived visual flicker than
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@class InviteSearchController;
|
||||
|
||||
@protocol JitsiMeetViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
@ -57,15 +55,6 @@
|
|||
*/
|
||||
- (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
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 "AddPeopleController.h"
|
||||
#import "InviteController.h"
|
||||
|
||||
@interface AddPeopleController ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
|
||||
@property (nonatomic, weak) InviteController *owner;
|
||||
@property (nonatomic, readonly) NSString* _Nonnull uuid;
|
||||
|
||||
- (instancetype)initWithOwner:(InviteController *)owner;
|
||||
|
||||
- (void)inviteSettled:(NSArray<NSDictionary *> *)failedInvitees;
|
||||
|
||||
- (void)receivedResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
@end
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "AddPeopleControllerDelegate.h"
|
||||
|
||||
@interface AddPeopleController: NSObject
|
||||
|
||||
@property (nonatomic, nullable, weak) id<AddPeopleControllerDelegate> delegate;
|
||||
|
||||
- (void)endAddPeople;
|
||||
|
||||
- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids;
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query;
|
||||
|
||||
@end
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 "AddPeopleController+Private.h"
|
||||
#import "InviteController+Private.h"
|
||||
|
||||
@implementation AddPeopleController
|
||||
|
||||
- (instancetype)initWithOwner:(InviteController *)owner {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_uuid = [[NSUUID UUID] UUIDString];
|
||||
_items = [[NSMutableDictionary alloc] init];
|
||||
_owner = owner;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark API
|
||||
|
||||
- (void)endAddPeople {
|
||||
[self.owner endAddPeopleForController:self];
|
||||
}
|
||||
|
||||
- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids {
|
||||
NSMutableArray* invitees = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSString* itemId in ids) {
|
||||
id invitee = [self.items objectForKey:itemId];
|
||||
|
||||
if (invitee) {
|
||||
[invitees addObject:invitee];
|
||||
}
|
||||
}
|
||||
|
||||
[self.owner invite:invitees forController:self];
|
||||
}
|
||||
|
||||
- (void)performQuery:(NSString *)query {
|
||||
[self.owner performQuery:query forController:self];
|
||||
}
|
||||
|
||||
#pragma mark Internal API, used to call the delegate and report to the user
|
||||
|
||||
- (void)receivedResults:(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 addPeopleController:self didReceiveResults:results forQuery:query];
|
||||
}
|
||||
|
||||
- (void)inviteSettled:(NSArray<NSDictionary *> *)failedInvitees {
|
||||
[self.delegate inviteSettled:failedInvitees fromSearchController:self];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "AddPeopleController.h"
|
||||
|
||||
@class AddPeopleController;
|
||||
|
||||
@protocol AddPeopleControllerDelegate
|
||||
|
||||
/**
|
||||
* Called when an AddPeopleController has results for a query that was previously provided.
|
||||
*/
|
||||
- (void)addPeopleController:(AddPeopleController * _Nonnull)controller
|
||||
didReceiveResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
/**
|
||||
* TODO.
|
||||
*/
|
||||
- (void)inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
|
||||
fromSearchController:(AddPeopleController * _Nonnull)addPeopleController;
|
||||
|
||||
@end
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
@interface Invite : RCTEventEmitter <RCTBridgeModule>
|
||||
|
||||
- (void) invite:(NSArray<NSDictionary *> * _Nonnull)invitees
|
||||
externalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
addPeopleControllerScope:(NSString * _Nonnull)addPeopleControllerScope;
|
||||
|
||||
- (void) performQuery:(NSString * _Nonnull)query
|
||||
externalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
addPeopleControllerScope:(NSString * _Nonnull)addPeopleControllerScope;
|
||||
|
||||
@end
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 "Invite+Private.h"
|
||||
#import "InviteController+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
// The events emitted/supported by the Invite react-native module:
|
||||
//
|
||||
// XXX The event names are ridiculous on purpose. Even though iOS makes it look
|
||||
// like it emits within the bounderies of a react-native module ony, it actually
|
||||
// also emits through DeviceEventEmitter. (Of course, Android emits only through
|
||||
// DeviceEventEmitter.)
|
||||
static NSString * const InvitePerformQueryAction
|
||||
= @"org.jitsi.meet:features/invite#performQuery";
|
||||
static NSString * const InvitePerformSubmitInviteAction
|
||||
= @"org.jitsi.meet:features/invite#invite";
|
||||
|
||||
@implementation Invite
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[
|
||||
InvitePerformQueryAction,
|
||||
InvitePerformSubmitInviteAction
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the corresponding JitsiMeetView's delegate to request that the native
|
||||
* invite search be presented.
|
||||
*
|
||||
* @param scope
|
||||
*/
|
||||
RCT_EXPORT_METHOD(beginAddPeople:(NSString *)externalAPIScope) {
|
||||
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
InviteController *controller = view.inviteController;
|
||||
[controller beginAddPeople];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(inviteSettled:(NSString *)externalAPIScope
|
||||
addPeopleControllerScope:(NSString *)addPeopleControllerScope
|
||||
failedInvitees:(NSArray *)failedInvitees) {
|
||||
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
InviteController *controller = view.inviteController;
|
||||
[controller inviteSettled:addPeopleControllerScope failedInvitees:failedInvitees];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(receivedResults:(NSString *)externalAPIScope
|
||||
addPeopleControllerScope:(NSString *)addPeopleControllerScope
|
||||
query:(NSString *)query
|
||||
results:(NSArray *)results) {
|
||||
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
InviteController *controller = view.inviteController;
|
||||
[controller receivedResults:addPeopleControllerScope query:query results:results];
|
||||
}
|
||||
|
||||
- (void) invite:(NSArray<NSDictionary *> * _Nonnull)invitees
|
||||
externalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
|
||||
[self sendEventWithName:InvitePerformSubmitInviteAction
|
||||
body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
|
||||
@"externalAPIScope": externalAPIScope,
|
||||
@"invitees": invitees }];
|
||||
}
|
||||
|
||||
- (void) performQuery:(NSString * _Nonnull)query
|
||||
externalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
|
||||
[self sendEventWithName:InvitePerformQueryAction
|
||||
body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
|
||||
@"externalAPIScope": externalAPIScope,
|
||||
@"query": query }];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 "InviteController.h"
|
||||
|
||||
#import "AddPeopleController.h"
|
||||
#import "Invite+Private.h"
|
||||
|
||||
@interface InviteController ()
|
||||
|
||||
@property (nonatomic, nullable) AddPeopleController *addPeopleController;
|
||||
|
||||
@property (nonatomic) NSString *externalAPIScope;
|
||||
|
||||
@property (nonatomic, nullable, weak) Invite *inviteModule;
|
||||
|
||||
- (instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
andInviteModule:(Invite * _Nonnull)inviteModule;
|
||||
|
||||
- (void)beginAddPeople;
|
||||
|
||||
- (void)endAddPeopleForController:(AddPeopleController *)controller;
|
||||
|
||||
- (void) invite:(NSArray *)invitees
|
||||
forController:(AddPeopleController * _Nonnull)controller;
|
||||
|
||||
- (void)inviteSettled:(NSString * _Nonnull)addPeopleControllerScope
|
||||
failedInvitees:(NSArray *)failedInvitees;
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query
|
||||
forController:(AddPeopleController * _Nonnull)controller;
|
||||
|
||||
- (void)receivedResults:(NSString * _Nonnull)addPeopleControllerScope
|
||||
query:(NSString * _Nonnull)query
|
||||
results:(NSArray *)results;
|
||||
|
||||
@end
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright @ 2017-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 <Foundation/Foundation.h>
|
||||
|
||||
#import "InviteControllerDelegate.h"
|
||||
|
||||
@interface InviteController : NSObject
|
||||
|
||||
@property (nonatomic) BOOL addPeopleEnabled;
|
||||
|
||||
@property (nonatomic) BOOL dialOutEnabled;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<InviteControllerDelegate> delegate;
|
||||
|
||||
- (void) invite:(NSArray *)invitees
|
||||
withCompletion:(void (^)(NSArray<NSDictionary *> *failedInvitees))completion;
|
||||
|
||||
@end
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright @ 2017-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 "InviteController+Private.h"
|
||||
#import "AddPeopleController+Private.h"
|
||||
|
||||
@implementation InviteController
|
||||
|
||||
-(instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
andInviteModule:(Invite * _Nonnull)inviteModule {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.externalAPIScope = externalAPIScope;
|
||||
self.inviteModule = inviteModule;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)beginAddPeople {
|
||||
if (_delegate == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_addPeopleController != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_addPeopleController = [[AddPeopleController alloc] initWithOwner:self];
|
||||
|
||||
@try {
|
||||
if (self.delegate
|
||||
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)]) {
|
||||
[self.delegate beginAddPeople:_addPeopleController];
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
[self endAddPeopleForController:_addPeopleController];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)endAddPeopleForController:(AddPeopleController *)controller {
|
||||
if (self.addPeopleController == controller) {
|
||||
self.addPeopleController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Result handling
|
||||
|
||||
- (void)inviteSettled:(NSString *)addPeopleControllerScope
|
||||
failedInvitees:(NSArray *)failedInvitees {
|
||||
AddPeopleController *controller = self.addPeopleController;
|
||||
|
||||
if (controller != nil
|
||||
&& [controller.uuid isEqualToString:addPeopleControllerScope]) {
|
||||
@try {
|
||||
[controller inviteSettled:failedInvitees];
|
||||
} @finally {
|
||||
if ([failedInvitees count] == 0) {
|
||||
[self endAddPeopleForController:controller];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)receivedResults:(NSString *)addPeopleControllerScope
|
||||
query:(NSString *)query
|
||||
results:(NSArray *)results {
|
||||
AddPeopleController *controller = self.addPeopleController;
|
||||
|
||||
if (controller != nil
|
||||
&& [controller.uuid isEqualToString:addPeopleControllerScope]) {
|
||||
[controller receivedResults:results forQuery:query];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Use the Invite react-native module to emit the search / submission events
|
||||
|
||||
- (void) invite:(NSArray *)invitees
|
||||
forController:(AddPeopleController * _Nonnull)controller {
|
||||
[self invite:invitees
|
||||
forControllerScope:controller.uuid];
|
||||
}
|
||||
|
||||
- (void) invite:(NSArray *)invitees
|
||||
forControllerScope:(NSString * _Nonnull)controllerScope {
|
||||
[self.inviteModule invite:invitees
|
||||
externalAPIScope:self.externalAPIScope
|
||||
addPeopleControllerScope:controllerScope];
|
||||
}
|
||||
|
||||
- (void) invite:(NSArray *)invitees
|
||||
withCompletion:(void (^)(NSArray<NSDictionary *> *failedInvitees))completion {
|
||||
// TODO Execute the specified completion block when the invite settles.
|
||||
[self invite:invitees
|
||||
forControllerScope:[[NSUUID UUID] UUIDString]];
|
||||
}
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query
|
||||
forController:(AddPeopleController * _Nonnull)controller {
|
||||
[self.inviteModule performQuery:query
|
||||
externalAPIScope:self.externalAPIScope
|
||||
addPeopleControllerScope:controller.uuid];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright @ 2017-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 "AddPeopleController.h"
|
||||
|
||||
@protocol InviteControllerDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* 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)beginAddPeople:(AddPeopleController *)addPeopleController;
|
||||
|
||||
@end
|
|
@ -1,3 +1,27 @@
|
|||
/**
|
||||
* The type of the (redux) action which signals that a click/tap has been
|
||||
* performed on {@link InviteButton} and that the execution flow for
|
||||
* adding/inviting people to the current conference/meeting is to begin.
|
||||
*
|
||||
* {
|
||||
* type: BEGIN_ADD_PEOPLE
|
||||
* }
|
||||
*/
|
||||
export const BEGIN_ADD_PEOPLE = Symbol('BEGIN_ADD_PEOPLE');
|
||||
|
||||
/**
|
||||
* The type of redux action to set the {@code EventEmitter} subscriptions
|
||||
* utilized by the feature invite.
|
||||
*
|
||||
* {
|
||||
* type: _SET_EMITTER_SUBSCRIPTIONS,
|
||||
* emitterSubscriptions: Array|undefined
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_EMITTER_SUBSCRIPTIONS = Symbol('_SET_EMITTER_SUBSCRIPTIONS');
|
||||
|
||||
/**
|
||||
* The type of the action which signals an error occurred while requesting dial-
|
||||
* in numbers.
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
BEGIN_ADD_PEOPLE,
|
||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||
} from './actionTypes';
|
||||
import { getDialInConferenceID, getDialInNumbers } from './functions';
|
||||
|
||||
/**
|
||||
* Creates a (redux) action to signal that a click/tap has been performed on
|
||||
* {@link InviteButton} and that the execution flow for adding/inviting people
|
||||
* to the current conference/meeting is to begin.
|
||||
*
|
||||
* @returns {{
|
||||
* type: BEGIN_ADD_PEOPLE
|
||||
* }}
|
||||
*/
|
||||
export function beginAddPeople() {
|
||||
return {
|
||||
type: BEGIN_ADD_PEOPLE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends AJAX requests for dial-in numbers and conference ID.
|
||||
*
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
/**
|
||||
* Created by ystamcheva on 8/6/17.
|
||||
*/
|
|
@ -71,12 +71,12 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
/**
|
||||
* Whether or not to show Add People functionality.
|
||||
*/
|
||||
enableAddPeople: PropTypes.bool,
|
||||
addPeopleEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Whether or not to show Dial Out functionality.
|
||||
*/
|
||||
enableDialOut: PropTypes.bool,
|
||||
dialOutEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* The function closing the dialog.
|
||||
|
@ -187,21 +187,21 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { enableAddPeople, enableDialOut, t } = this.props;
|
||||
const { addPeopleEnabled, dialOutEnabled, t } = this.props;
|
||||
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
|
||||
let placeholder;
|
||||
let loadingMessage;
|
||||
let noMatches;
|
||||
|
||||
if (enableAddPeople && enableDialOut) {
|
||||
if (addPeopleEnabled && dialOutEnabled) {
|
||||
loadingMessage = 'addPeople.loading';
|
||||
noMatches = 'addPeople.noResults';
|
||||
placeholder = 'addPeople.searchPeopleAndNumbers';
|
||||
} else if (enableAddPeople) {
|
||||
} else if (addPeopleEnabled) {
|
||||
loadingMessage = 'addPeople.loadingPeople';
|
||||
noMatches = 'addPeople.noResults';
|
||||
placeholder = 'addPeople.searchPeople';
|
||||
} else if (enableDialOut) {
|
||||
} else if (dialOutEnabled) {
|
||||
loadingMessage = 'addPeople.loadingNumber';
|
||||
noMatches = 'addPeople.noValidNumbers';
|
||||
placeholder = 'addPeople.searchNumbers';
|
||||
|
@ -481,8 +481,8 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
*/
|
||||
_query(query = '') {
|
||||
const {
|
||||
enableAddPeople,
|
||||
enableDialOut,
|
||||
addPeopleEnabled,
|
||||
dialOutEnabled,
|
||||
_dialOutAuthUrl,
|
||||
_jwt,
|
||||
_peopleSearchQueryTypes,
|
||||
|
@ -491,8 +491,8 @@ class AddPeopleDialog extends Component<*, *> {
|
|||
|
||||
const options = {
|
||||
dialOutAuthUrl: _dialOutAuthUrl,
|
||||
enableAddPeople,
|
||||
enableDialOut,
|
||||
addPeopleEnabled,
|
||||
dialOutEnabled,
|
||||
jwt: _jwt,
|
||||
peopleSearchQueryTypes: _peopleSearchQueryTypes,
|
||||
peopleSearchUrl: _peopleSearchUrl
|
||||
|
@ -609,7 +609,11 @@ function _mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, {
|
||||
hideDialog,
|
||||
inviteVideoRooms })(
|
||||
AddPeopleDialog));
|
||||
export default translate(
|
||||
connect(
|
||||
_mapStateToProps,
|
||||
/* mapDispatchToProps */ {
|
||||
hideDialog,
|
||||
inviteVideoRooms
|
||||
})(
|
||||
AddPeopleDialog));
|
||||
|
|
|
@ -3,9 +3,22 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { launchNativeInvite } from '../../mobile/invite-search';
|
||||
import { beginShareRoom } from '../../share-room';
|
||||
import { ToolbarButton } from '../../toolbox';
|
||||
|
||||
import { beginAddPeople } from '../actions';
|
||||
import { isAddPeopleEnabled, isDialOutEnabled } from '../functions';
|
||||
|
||||
/**
|
||||
* The indicator which determines (at bundle time) whether there should be a
|
||||
* {@code ToolbarButton} in {@code Toolbox} to expose the functionality of the
|
||||
* feature share-room in the user interface of the app.
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
const _SHARE_ROOM_TOOLBAR_BUTTON = true;
|
||||
|
||||
/**
|
||||
* The type of {@link EnterPictureInPictureToobarButton}'s React
|
||||
* {@code Component} props.
|
||||
|
@ -13,21 +26,28 @@ import { ToolbarButton } from '../../toolbox';
|
|||
type Props = {
|
||||
|
||||
/**
|
||||
* Indicates if the "Add to call" feature is available.
|
||||
* Whether or not the feature to directly invite people into the
|
||||
* conference is available.
|
||||
*/
|
||||
enableAddPeople: boolean,
|
||||
_addPeopleEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Indicates if the "Dial out" feature is available.
|
||||
* Whether or not the feature to dial out to number to join the
|
||||
* conference is available.
|
||||
*/
|
||||
enableDialOut: boolean,
|
||||
_dialOutEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Launches native invite dialog.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
onLaunchNativeInvite: Function,
|
||||
_onAddPeople: Function,
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*/
|
||||
_onShareRoom: Function
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -43,22 +63,32 @@ class InviteButton extends Component<Props> {
|
|||
*/
|
||||
render() {
|
||||
const {
|
||||
enableAddPeople,
|
||||
enableDialOut,
|
||||
onLaunchNativeInvite,
|
||||
_addPeopleEnabled,
|
||||
_dialOutEnabled,
|
||||
_onAddPeople,
|
||||
_onShareRoom,
|
||||
...props
|
||||
} = this.props;
|
||||
|
||||
if (!enableAddPeople && !enableDialOut) {
|
||||
return null;
|
||||
if (_SHARE_ROOM_TOOLBAR_BUTTON) {
|
||||
return (
|
||||
<ToolbarButton
|
||||
iconName = 'link'
|
||||
onClick = { _onShareRoom }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarButton
|
||||
iconName = { 'add' }
|
||||
onClick = { onLaunchNativeInvite }
|
||||
{ ...props } />
|
||||
);
|
||||
if (_addPeopleEnabled || _dialOutEnabled) {
|
||||
return (
|
||||
<ToolbarButton
|
||||
iconName = { 'link' }
|
||||
onClick = { _onAddPeople }
|
||||
{ ...props } />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,13 +98,13 @@ class InviteButton extends Component<Props> {
|
|||
*
|
||||
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||
* @returns {{
|
||||
* onLaunchNativeInvite
|
||||
* _onAddPeople,
|
||||
* _onShareRoom
|
||||
* }}
|
||||
* @private
|
||||
*/
|
||||
function _mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
|
||||
/**
|
||||
* Launches native invite dialog.
|
||||
*
|
||||
|
@ -82,10 +112,50 @@ function _mapDispatchToProps(dispatch) {
|
|||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
onLaunchNativeInvite() {
|
||||
dispatch(launchNativeInvite());
|
||||
_onAddPeople() {
|
||||
dispatch(beginAddPeople());
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onShareRoom() {
|
||||
dispatch(beginShareRoom());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(undefined, _mapDispatchToProps)(InviteButton);
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Toolbox}'s React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
/**
|
||||
* Whether or not the feature to directly invite people into the
|
||||
* conference is available.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
_addPeopleEnabled: isAddPeopleEnabled(state),
|
||||
|
||||
/**
|
||||
* Whether or not the feature to dial out to number to join the
|
||||
* conference is available.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
_dialOutEnabled: isDialOutEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(InviteButton);
|
||||
|
|
|
@ -8,125 +8,6 @@ declare var interfaceConfig: Object;
|
|||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Sends a GET request to obtain the conference ID necessary for identifying
|
||||
* which conference to join after diaing the dial-in service.
|
||||
*
|
||||
* @param {string} baseUrl - The url for obtaining the conference ID (pin) for
|
||||
* dialing into a conference.
|
||||
* @param {string} roomName - The conference name to find the associated
|
||||
* conference ID.
|
||||
* @param {string} mucURL - In which MUC the conference exists.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function getDialInConferenceID(
|
||||
baseUrl: string,
|
||||
roomName: string,
|
||||
mucURL: string): Promise<Object> {
|
||||
const conferenceIDURL = `${baseUrl}?conference=${roomName}@${mucURL}`;
|
||||
|
||||
return doGetJSON(conferenceIDURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request for phone numbers used to dial into a conference.
|
||||
*
|
||||
* @param {string} url - The service that returns confernce dial-in numbers.
|
||||
* @returns {Promise} - The promise created by the request. The returned numbers
|
||||
* may be an array of numbers or an object with countries as keys and arrays of
|
||||
* phone number strings.
|
||||
*/
|
||||
export function getDialInNumbers(url: string): Promise<*> {
|
||||
return doGetJSON(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a post request to an invite service.
|
||||
*
|
||||
* @param {string} inviteServiceUrl - The invite service that generates the
|
||||
* invitation.
|
||||
* @param {string} inviteUrl - The url to the conference.
|
||||
* @param {string} jwt - The jwt token to pass to the search service.
|
||||
* @param {Immutable.List} inviteItems - The list of the "user" or "room"
|
||||
* type items to invite.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
inviteServiceUrl: string,
|
||||
inviteUrl: string,
|
||||
jwt: string,
|
||||
inviteItems: Array<Object>): Promise<void> {
|
||||
if (!inviteItems || inviteItems.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
$.post(
|
||||
`${inviteServiceUrl}?token=${jwt}`,
|
||||
JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
'url': inviteUrl
|
||||
}),
|
||||
resolve,
|
||||
'json')
|
||||
.fail((jqxhr, textStatus, error) => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ajax request to a directory service.
|
||||
*
|
||||
* @param {string} serviceUrl - The service to query.
|
||||
* @param {string} jwt - The jwt token to pass to the search service.
|
||||
* @param {string} text - Text to search.
|
||||
* @param {Array<string>} queryTypes - Array with the query types that will be
|
||||
* executed - "conferenceRooms" | "user" | "room".
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function searchDirectory( // eslint-disable-line max-params
|
||||
serviceUrl: string,
|
||||
jwt: string,
|
||||
text: string,
|
||||
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
|
||||
): Promise<Array<Object>> {
|
||||
const query = encodeURIComponent(text);
|
||||
const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
|
||||
|
||||
return fetch(`${serviceUrl}?query=${query}&queryTypes=${
|
||||
queryTypesString}&jwt=${jwt}`)
|
||||
.then(response => {
|
||||
const jsonify = response.json();
|
||||
|
||||
if (response.ok) {
|
||||
return jsonify;
|
||||
}
|
||||
|
||||
return jsonify
|
||||
.then(result => Promise.reject(result));
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
'Error searching directory:', error);
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* RegExp to use to determine if some text might be a phone number.
|
||||
*
|
||||
* @returns {RegExp}
|
||||
*/
|
||||
function isPhoneNumberRegex(): RegExp {
|
||||
let regexString = '^[0-9+()-\\s]*$';
|
||||
|
||||
if (typeof interfaceConfig !== 'undefined') {
|
||||
regexString = interfaceConfig.PHONE_NUMBER_REGEX || regexString;
|
||||
}
|
||||
|
||||
return new RegExp(regexString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ajax request to check if the phone number can be called.
|
||||
*
|
||||
|
@ -135,7 +16,10 @@ function isPhoneNumberRegex(): RegExp {
|
|||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function checkDialNumber(
|
||||
dialNumber: string, dialOutAuthUrl: string): Promise<Object> {
|
||||
dialNumber: string,
|
||||
dialOutAuthUrl: string
|
||||
): Promise<Object> {
|
||||
|
||||
if (!dialOutAuthUrl) {
|
||||
// no auth url, let's say it is valid
|
||||
const response = {
|
||||
|
@ -155,6 +39,40 @@ export function checkDialNumber(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request to obtain the conference ID necessary for identifying
|
||||
* which conference to join after diaing the dial-in service.
|
||||
*
|
||||
* @param {string} baseUrl - The url for obtaining the conference ID (pin) for
|
||||
* dialing into a conference.
|
||||
* @param {string} roomName - The conference name to find the associated
|
||||
* conference ID.
|
||||
* @param {string} mucURL - In which MUC the conference exists.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function getDialInConferenceID(
|
||||
baseUrl: string,
|
||||
roomName: string,
|
||||
mucURL: string
|
||||
): Promise<Object> {
|
||||
|
||||
const conferenceIDURL = `${baseUrl}?conference=${roomName}@${mucURL}`;
|
||||
|
||||
return doGetJSON(conferenceIDURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request for phone numbers used to dial into a conference.
|
||||
*
|
||||
* @param {string} url - The service that returns confernce dial-in numbers.
|
||||
* @returns {Promise} - The promise created by the request. The returned numbers
|
||||
* may be an array of numbers or an object with countries as keys and arrays of
|
||||
* phone number strings.
|
||||
*/
|
||||
export function getDialInNumbers(url: string): Promise<*> {
|
||||
return doGetJSON(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all non-numeric characters from a string.
|
||||
*
|
||||
|
@ -180,12 +98,12 @@ export type GetInviteResultsOptions = {
|
|||
/**
|
||||
* Whether or not to search for people.
|
||||
*/
|
||||
enableAddPeople: boolean,
|
||||
addPeopleEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not to check phone numbers.
|
||||
*/
|
||||
enableDialOut: boolean,
|
||||
dialOutEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Array with the query types that will be executed -
|
||||
|
@ -214,13 +132,15 @@ export type GetInviteResultsOptions = {
|
|||
*/
|
||||
export function getInviteResultsForQuery(
|
||||
query: string,
|
||||
options: GetInviteResultsOptions): Promise<*> {
|
||||
options: GetInviteResultsOptions
|
||||
): Promise<*> {
|
||||
|
||||
const text = query.trim();
|
||||
|
||||
const {
|
||||
dialOutAuthUrl,
|
||||
enableAddPeople,
|
||||
enableDialOut,
|
||||
addPeopleEnabled,
|
||||
dialOutEnabled,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl,
|
||||
jwt
|
||||
|
@ -228,7 +148,7 @@ export function getInviteResultsForQuery(
|
|||
|
||||
let peopleSearchPromise;
|
||||
|
||||
if (enableAddPeople && text) {
|
||||
if (addPeopleEnabled && text) {
|
||||
peopleSearchPromise = searchDirectory(
|
||||
peopleSearchUrl,
|
||||
jwt,
|
||||
|
@ -242,7 +162,7 @@ export function getInviteResultsForQuery(
|
|||
const hasCountryCode = text.startsWith('+');
|
||||
let phoneNumberPromise;
|
||||
|
||||
if (enableDialOut && isMaybeAPhoneNumber(text)) {
|
||||
if (dialOutEnabled && isMaybeAPhoneNumber(text)) {
|
||||
let numberToVerify = text;
|
||||
|
||||
// When the number to verify does not start with a +, we assume no
|
||||
|
@ -296,6 +216,82 @@ export function getInviteResultsForQuery(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a post request to an invite service.
|
||||
*
|
||||
* @param {string} inviteServiceUrl - The invite service that generates the
|
||||
* invitation.
|
||||
* @param {string} inviteUrl - The url to the conference.
|
||||
* @param {string} jwt - The jwt token to pass to the search service.
|
||||
* @param {Immutable.List} inviteItems - The list of the "user" or "room"
|
||||
* type items to invite.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
inviteServiceUrl: string,
|
||||
inviteUrl: string,
|
||||
jwt: string,
|
||||
inviteItems: Array<Object>
|
||||
): Promise<void> {
|
||||
|
||||
if (!inviteItems || inviteItems.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
$.post(
|
||||
`${inviteServiceUrl}?token=${jwt}`,
|
||||
JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
'url': inviteUrl
|
||||
}),
|
||||
resolve,
|
||||
'json')
|
||||
.fail((jqxhr, textStatus, error) => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if adding people is currently enabled.
|
||||
*
|
||||
* @param {boolean} state - Current state.
|
||||
* @returns {boolean} Indication of whether adding people is currently enabled.
|
||||
*/
|
||||
export function isAddPeopleEnabled(state: Object): boolean {
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
|
||||
if (!isGuest) {
|
||||
// XXX The mobile/react-native app is capable of disabling the
|
||||
// adding/inviting of people in the current conference. Anyway, the
|
||||
// Web/React app does not have that capability so default appropriately.
|
||||
const { app } = state['features/app'];
|
||||
const addPeopleEnabled = app && app.props.addPeopleEnabled;
|
||||
|
||||
return (
|
||||
(typeof addPeopleEnabled === 'undefined')
|
||||
|| Boolean(addPeopleEnabled));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if dial out is currently enabled or not.
|
||||
*
|
||||
* @param {boolean} state - Current state.
|
||||
* @returns {boolean} Indication of whether dial out is currently enabled.
|
||||
*/
|
||||
export function isDialOutEnabled(state: Object): boolean {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
const { enableUserRolesBasedOnToken } = state['features/base/config'];
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
return participant && participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& conference && conference.isSIPCallingSupported()
|
||||
&& (!enableUserRolesBasedOnToken || !isGuest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a string looks like it could be for a phone number.
|
||||
*
|
||||
|
@ -315,6 +311,61 @@ function isMaybeAPhoneNumber(text: string): boolean {
|
|||
return Boolean(digits.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* RegExp to use to determine if some text might be a phone number.
|
||||
*
|
||||
* @returns {RegExp}
|
||||
*/
|
||||
function isPhoneNumberRegex(): RegExp {
|
||||
let regexString = '^[0-9+()-\\s]*$';
|
||||
|
||||
if (typeof interfaceConfig !== 'undefined') {
|
||||
regexString = interfaceConfig.PHONE_NUMBER_REGEX || regexString;
|
||||
}
|
||||
|
||||
return new RegExp(regexString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ajax request to a directory service.
|
||||
*
|
||||
* @param {string} serviceUrl - The service to query.
|
||||
* @param {string} jwt - The jwt token to pass to the search service.
|
||||
* @param {string} text - Text to search.
|
||||
* @param {Array<string>} queryTypes - Array with the query types that will be
|
||||
* executed - "conferenceRooms" | "user" | "room".
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function searchDirectory( // eslint-disable-line max-params
|
||||
serviceUrl: string,
|
||||
jwt: string,
|
||||
text: string,
|
||||
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
|
||||
): Promise<Array<Object>> {
|
||||
|
||||
const query = encodeURIComponent(text);
|
||||
const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
|
||||
|
||||
return fetch(`${serviceUrl}?query=${query}&queryTypes=${
|
||||
queryTypesString}&jwt=${jwt}`)
|
||||
.then(response => {
|
||||
const jsonify = response.json();
|
||||
|
||||
if (response.ok) {
|
||||
return jsonify;
|
||||
}
|
||||
|
||||
return jsonify
|
||||
.then(result => Promise.reject(result));
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(
|
||||
'Error searching directory:', error);
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the options to use when sending invites.
|
||||
*/
|
||||
|
@ -436,33 +487,3 @@ export function sendInvitesForItems(
|
|||
return Promise.all(allInvitePromises)
|
||||
.then(() => invitesLeftToSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if adding people is currently enabled.
|
||||
*
|
||||
* @param {boolean} state - Current state.
|
||||
* @returns {boolean} Indication of whether adding people is currently enabled.
|
||||
*/
|
||||
export function isAddPeopleEnabled(state: Object): boolean {
|
||||
const { app } = state['features/app'];
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
|
||||
return !isGuest && Boolean(app && app.props.addPeopleEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if dial out is currently enabled or not.
|
||||
*
|
||||
* @param {boolean} state - Current state.
|
||||
* @returns {boolean} Indication of whether dial out is currently enabled.
|
||||
*/
|
||||
export function isDialOutEnabled(state: Object): boolean {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
const { enableUserRolesBasedOnToken } = state['features/base/config'];
|
||||
const participant = getLocalParticipant(state);
|
||||
|
||||
return participant && participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& conference && conference.isSIPCallingSupported()
|
||||
&& (!enableUserRolesBasedOnToken || !isGuest);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
|
||||
|
@ -5,9 +7,10 @@ import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
|
|||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Middleware that catches actions fetching dial-in numbers.
|
||||
* The middleware of the feature invite common to mobile/react-native and
|
||||
* Web/React.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
@ -15,7 +18,6 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
||||
logger.error(
|
||||
'Error encountered while fetching dial-in numbers:',
|
|
@ -0,0 +1,208 @@
|
|||
// @flow
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
|
||||
import { getInviteURL } from '../base/connection';
|
||||
import { inviteVideoRooms } from '../videosipgw';
|
||||
|
||||
import {
|
||||
BEGIN_ADD_PEOPLE,
|
||||
_SET_EMITTER_SUBSCRIPTIONS
|
||||
} from './actionTypes';
|
||||
import {
|
||||
getInviteResultsForQuery,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled,
|
||||
sendInvitesForItems
|
||||
} from './functions';
|
||||
import './middleware.any';
|
||||
|
||||
/**
|
||||
* The react-native module of the feature invite.
|
||||
*/
|
||||
const { Invite } = NativeModules;
|
||||
|
||||
/**
|
||||
* The middleware of the feature invite specific to mobile/react-native.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
Invite && MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
return _appWillMount(store, next, action);
|
||||
|
||||
case APP_WILL_UNMOUNT: {
|
||||
const result = next(action);
|
||||
|
||||
store.dispatch({
|
||||
type: _SET_EMITTER_SUBSCRIPTIONS,
|
||||
emitterSubscriptions: undefined
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case BEGIN_ADD_PEOPLE:
|
||||
return _beginAddPeople(store, next, action);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _appWillMount({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const emitter = new NativeEventEmitter(Invite);
|
||||
const context = {
|
||||
dispatch,
|
||||
getState
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: _SET_EMITTER_SUBSCRIPTIONS,
|
||||
emitterSubscriptions: [
|
||||
emitter.addListener(
|
||||
'org.jitsi.meet:features/invite#performQuery',
|
||||
_onPerformQuery,
|
||||
context),
|
||||
emitter.addListener(
|
||||
'org.jitsi.meet:features/invite#invite',
|
||||
_onInvite,
|
||||
context)
|
||||
]
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature invite that the action {@link BEGIN_ADD_PEOPLE} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code BEGIN_ADD_PEOPLE} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _beginAddPeople({ getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
// The JavaScript App needs to provide uniquely identifying information to
|
||||
// the native Invite module so that the latter may match the former to the
|
||||
// native JitsiMeetView which hosts it.
|
||||
const { app } = getState()['features/app'];
|
||||
|
||||
if (app) {
|
||||
const { externalAPIScope } = app.props;
|
||||
|
||||
if (externalAPIScope) {
|
||||
Invite.beginAddPeople(externalAPIScope);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the {@code invite} event of the feature invite i.e. invites specific
|
||||
* invitees to the current, ongoing conference.
|
||||
*
|
||||
* @param {Object} event - The details of the event.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onInvite(
|
||||
{ addPeopleControllerScope, externalAPIScope, invitees }) {
|
||||
const { getState } = this; // eslint-disable-line no-invalid-this
|
||||
const state = getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { inviteServiceUrl } = state['features/base/config'];
|
||||
const options = {
|
||||
conference,
|
||||
inviteServiceUrl,
|
||||
inviteUrl: getInviteURL(state),
|
||||
inviteVideoRooms,
|
||||
jwt: state['features/base/jwt'].jwt
|
||||
};
|
||||
|
||||
sendInvitesForItems(invitees, options)
|
||||
.then(failedInvitees =>
|
||||
Invite.inviteSettled(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
failedInvitees));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the {@code performQuery} event of the feature invite i.e. queries for
|
||||
* invitees who may subsequently be invited to the current, ongoing conference.
|
||||
*
|
||||
* @param {Object} event - The details of the event.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onPerformQuery(
|
||||
{ addPeopleControllerScope, externalAPIScope, query }) {
|
||||
const { getState } = this; // eslint-disable-line no-invalid-this
|
||||
|
||||
const state = getState();
|
||||
const {
|
||||
dialOutAuthUrl,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
} = state['features/base/config'];
|
||||
const options = {
|
||||
dialOutAuthUrl,
|
||||
addPeopleEnabled: isAddPeopleEnabled(state),
|
||||
dialOutEnabled: isDialOutEnabled(state),
|
||||
jwt: state['features/base/jwt'].jwt,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
};
|
||||
|
||||
getInviteResultsForQuery(query, options)
|
||||
.catch(() => [])
|
||||
.then(results => {
|
||||
const translatedResults = results.map(result => {
|
||||
if (result.type === 'phone') {
|
||||
result.title = i18next.t('addPeople.telephone', {
|
||||
number: result.number
|
||||
});
|
||||
|
||||
if (result.showCountryCodeReminder) {
|
||||
result.subtitle = i18next.t(
|
||||
'addPeople.countryReminder'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}).filter(result => result.type !== 'phone' || result.allowed);
|
||||
|
||||
Invite.receivedResults(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
query,
|
||||
translatedResults);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { BEGIN_ADD_PEOPLE } from './actionTypes';
|
||||
import { AddPeopleDialog } from './components';
|
||||
import { isAddPeopleEnabled, isDialOutEnabled } from './functions';
|
||||
import './middleware.any';
|
||||
|
||||
/**
|
||||
* The middleware of the feature invite specific to Web/React.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case BEGIN_ADD_PEOPLE:
|
||||
return _beginAddPeople(store, next, action);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature invite that the action {@link BEGIN_ADD_PEOPLE} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code BEGIN_ADD_PEOPLE} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _beginAddPeople({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const state = getState();
|
||||
|
||||
dispatch(openDialog(AddPeopleDialog, {
|
||||
addPeopleEnabled: isAddPeopleEnabled(state),
|
||||
dialOutEnabled: isDialOutEnabled(state)
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import { ReducerRegistry } from '../base/redux';
|
||||
// @flow
|
||||
|
||||
import { assign, ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
_SET_EMITTER_SUBSCRIPTIONS,
|
||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||
} from './actionTypes';
|
||||
|
@ -11,6 +14,10 @@ const DEFAULT_STATE = {
|
|||
|
||||
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_EMITTER_SUBSCRIPTIONS:
|
||||
return (
|
||||
assign(state, 'emitterSubscriptions', action.emitterSubscriptions));
|
||||
|
||||
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -159,9 +159,9 @@ function _sendEvent(
|
|||
{ getState }: { getState: Function },
|
||||
name: string,
|
||||
data: Object) {
|
||||
// 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.
|
||||
// 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.
|
||||
const { app } = getState()['features/app'];
|
||||
|
||||
if (app) {
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* The type of redux action to set InviteSearch's event subscriptions.
|
||||
*
|
||||
* {
|
||||
* type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
* subscriptions: Array|undefined
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_INVITE_SEARCH_SUBSCRIPTIONS
|
||||
= Symbol('_SET_INVITE_SEARCH_SUBSCRIPTIONS');
|
||||
|
||||
|
||||
/**
|
||||
* The type of the action which signals a request to launch the native invite
|
||||
* dialog.
|
||||
*
|
||||
* {
|
||||
* type: LAUNCH_NATIVE_INVITE
|
||||
* }
|
||||
*/
|
||||
export const LAUNCH_NATIVE_INVITE = Symbol('LAUNCH_NATIVE_INVITE');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that native invites were sent
|
||||
* successfully.
|
||||
*
|
||||
* {
|
||||
* type: SEND_INVITE_SUCCESS,
|
||||
* inviteScope: string
|
||||
* }
|
||||
*/
|
||||
export const SEND_INVITE_SUCCESS = Symbol('SEND_INVITE_SUCCESS');
|
||||
|
||||
/**
|
||||
* The type of the action which signals that native invites failed to send
|
||||
* successfully.
|
||||
*
|
||||
* {
|
||||
* type: SEND_INVITE_FAILURE,
|
||||
* items: Array<*>,
|
||||
* inviteScope: string
|
||||
* }
|
||||
*/
|
||||
export const SEND_INVITE_FAILURE = Symbol('SEND_INVITE_FAILURE');
|
|
@ -1,50 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
LAUNCH_NATIVE_INVITE,
|
||||
SEND_INVITE_SUCCESS,
|
||||
SEND_INVITE_FAILURE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Launches the native invite dialog.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LAUNCH_NATIVE_INVITE
|
||||
* }}
|
||||
*/
|
||||
export function launchNativeInvite() {
|
||||
return {
|
||||
type: LAUNCH_NATIVE_INVITE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that all native invites were sent successfully.
|
||||
*
|
||||
* @param {string} inviteScope - Scope identifier for the invite success. This
|
||||
* is used to look up relevant information on the native side.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendInviteSuccess(inviteScope: string) {
|
||||
return {
|
||||
type: SEND_INVITE_SUCCESS,
|
||||
inviteScope
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that some native invites failed to send successfully.
|
||||
*
|
||||
* @param {Array<*>} items - Invite items that failed to send.
|
||||
* @param {string} inviteScope - Scope identifier for the invite failure. This
|
||||
* is used to look up relevant information on the native side.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendInviteFailure(items: Array<*>, inviteScope: string) {
|
||||
return {
|
||||
type: SEND_INVITE_FAILURE,
|
||||
items,
|
||||
inviteScope
|
||||
};
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
|
||||
import './reducer';
|
||||
import './middleware';
|
|
@ -1,233 +0,0 @@
|
|||
/* @flow */
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||
|
||||
import { MiddlewareRegistry } from '../../base/redux';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
|
||||
import { getInviteURL } from '../../base/connection';
|
||||
import {
|
||||
getInviteResultsForQuery,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled,
|
||||
sendInvitesForItems
|
||||
} from '../../invite';
|
||||
import { inviteVideoRooms } from '../../videosipgw';
|
||||
|
||||
import { sendInviteSuccess, sendInviteFailure } from './actions';
|
||||
import {
|
||||
_SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
LAUNCH_NATIVE_INVITE,
|
||||
SEND_INVITE_SUCCESS,
|
||||
SEND_INVITE_FAILURE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Middleware that captures Redux actions and uses the InviteSearch module to
|
||||
* turn them into native events so the application knows about them.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case APP_WILL_MOUNT:
|
||||
return _appWillMount(store, next, action);
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
store.dispatch({
|
||||
type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
subscriptions: undefined
|
||||
});
|
||||
break;
|
||||
|
||||
case LAUNCH_NATIVE_INVITE:
|
||||
launchNativeInvite(store);
|
||||
break;
|
||||
|
||||
case SEND_INVITE_SUCCESS:
|
||||
onSendInviteSuccess(action);
|
||||
break;
|
||||
|
||||
case SEND_INVITE_FAILURE:
|
||||
onSendInviteFailure(action);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
|
||||
* dispatched within a specific redux {@code store}.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code APP_WILL_MOUNT} which is
|
||||
* being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*}
|
||||
*/
|
||||
function _appWillMount({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const emitter = new NativeEventEmitter(NativeModules.InviteSearch);
|
||||
|
||||
const context = {
|
||||
dispatch,
|
||||
getState
|
||||
};
|
||||
const subscriptions = [
|
||||
emitter.addListener(
|
||||
'performQueryAction',
|
||||
_onPerformQueryAction,
|
||||
context),
|
||||
emitter.addListener(
|
||||
'performSubmitInviteAction',
|
||||
_onPerformSubmitInviteAction,
|
||||
context)
|
||||
];
|
||||
|
||||
dispatch({
|
||||
type: _SET_INVITE_SEARCH_SUBSCRIPTIONS,
|
||||
subscriptions
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the native counterpart of InviteSearch to launch a native.
|
||||
* invite search.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function launchNativeInvite(store: { getState: Function }) {
|
||||
// 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.
|
||||
const { app } = store.getState()['features/app'];
|
||||
|
||||
if (app) {
|
||||
const { externalAPIScope } = app.props;
|
||||
|
||||
if (externalAPIScope) {
|
||||
NativeModules.InviteSearch.launchNativeInvite(externalAPIScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification to the native counterpart of InviteSearch that all
|
||||
* invites were sent successfully.
|
||||
*
|
||||
* @param {Object} action - The redux action {@code SEND_INVITE_SUCCESS} which
|
||||
* is being dispatched.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onSendInviteSuccess({ inviteScope }) {
|
||||
NativeModules.InviteSearch.inviteSucceeded(inviteScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification to the native counterpart of InviteSearch that some
|
||||
* invite items failed to send successfully.
|
||||
*
|
||||
* @param {Object} action - The redux action {@code SEND_INVITE_FAILURE} which
|
||||
* is being dispatched.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onSendInviteFailure({ items, inviteScope }) {
|
||||
NativeModules.InviteSearch.inviteFailedForItems(items, inviteScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles InviteSearch's event {@code performQueryAction}.
|
||||
*
|
||||
* @param {Object} event - The details of the InviteSearch event
|
||||
* {@code performQueryAction}.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onPerformQueryAction({ query, inviteScope }) {
|
||||
const { getState } = this; // eslint-disable-line no-invalid-this
|
||||
|
||||
const state = getState();
|
||||
|
||||
const {
|
||||
dialOutAuthUrl,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
} = state['features/base/config'];
|
||||
|
||||
const options = {
|
||||
dialOutAuthUrl,
|
||||
enableAddPeople: isAddPeopleEnabled(state),
|
||||
enableDialOut: isDialOutEnabled(state),
|
||||
jwt: state['features/base/jwt'].jwt,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
};
|
||||
|
||||
getInviteResultsForQuery(query, options)
|
||||
.catch(() => [])
|
||||
.then(results => {
|
||||
const translatedResults = results.map(result => {
|
||||
if (result.type === 'phone') {
|
||||
result.title = i18next.t('addPeople.telephone', {
|
||||
number: result.number
|
||||
});
|
||||
|
||||
if (result.showCountryCodeReminder) {
|
||||
result.subtitle = i18next.t(
|
||||
'addPeople.countryReminder'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}).filter(result => result.type !== 'phone' || result.allowed);
|
||||
|
||||
NativeModules.InviteSearch.receivedResults(
|
||||
translatedResults,
|
||||
query,
|
||||
inviteScope);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles InviteSearch's event {@code performSubmitInviteAction}.
|
||||
*
|
||||
* @param {Object} event - The details of the InviteSearch event.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onPerformSubmitInviteAction({ selectedItems, inviteScope }) {
|
||||
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
|
||||
const state = getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
const {
|
||||
inviteServiceUrl
|
||||
} = state['features/base/config'];
|
||||
const options = {
|
||||
conference,
|
||||
inviteServiceUrl,
|
||||
inviteUrl: getInviteURL(state),
|
||||
inviteVideoRooms,
|
||||
jwt: state['features/base/jwt'].jwt
|
||||
};
|
||||
|
||||
sendInvitesForItems(selectedItems, options)
|
||||
.then(invitesLeftToSend => {
|
||||
if (invitesLeftToSend.length) {
|
||||
dispatch(sendInviteFailure(invitesLeftToSend, inviteScope));
|
||||
} else {
|
||||
dispatch(sendInviteSuccess(inviteScope));
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { assign, ReducerRegistry } from '../../base/redux';
|
||||
|
||||
import { _SET_INVITE_SEARCH_SUBSCRIPTIONS } from './actionTypes';
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/invite-search',
|
||||
(state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_INVITE_SEARCH_SUBSCRIPTIONS:
|
||||
return assign(state, 'subscriptions', action.subscriptions);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
|
@ -14,16 +14,11 @@ import {
|
|||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import {
|
||||
InviteButton,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled
|
||||
} from '../../invite';
|
||||
import { InviteButton } from '../../invite';
|
||||
import {
|
||||
EnterPictureInPictureToolbarButton
|
||||
} from '../../mobile/picture-in-picture';
|
||||
import { beginRoomLockRequest } from '../../room-lock';
|
||||
import { beginShareRoom } from '../../share-room';
|
||||
|
||||
import {
|
||||
abstractMapDispatchToProps,
|
||||
|
@ -51,18 +46,6 @@ type Props = {
|
|||
*/
|
||||
_audioOnly: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the feature to directly invite people into the
|
||||
* conference is available.
|
||||
*/
|
||||
_enableAddPeople: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the feature to dial out to number to join the
|
||||
* conference is available.
|
||||
*/
|
||||
_enableDialOut: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the toolbox is enabled.
|
||||
*/
|
||||
|
@ -83,11 +66,6 @@ type Props = {
|
|||
*/
|
||||
_onRoomLock: Function,
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*/
|
||||
_onShareRoom: Function,
|
||||
|
||||
/**
|
||||
* Toggles the audio-only flag of the conference.
|
||||
*/
|
||||
|
@ -112,7 +90,6 @@ type Props = {
|
|||
dispatch: Function
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements the conference toolbox on React Native.
|
||||
*/
|
||||
|
@ -219,13 +196,9 @@ class Toolbox extends Component<Props> {
|
|||
const underlayColor = 'transparent';
|
||||
const {
|
||||
_audioOnly: audioOnly,
|
||||
_enableAddPeople: enableAddPeople,
|
||||
_enableDialOut: enableDialOut,
|
||||
_videoMuted: videoMuted
|
||||
} = this.props;
|
||||
|
||||
const showInviteButton = enableAddPeople || enableDialOut;
|
||||
|
||||
/* eslint-disable react/jsx-curly-spacing,react/jsx-handler-names */
|
||||
|
||||
return (
|
||||
|
@ -262,24 +235,10 @@ class Toolbox extends Component<Props> {
|
|||
onClick = { this.props._onRoomLock }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
{
|
||||
!showInviteButton
|
||||
&& <ToolbarButton
|
||||
iconName = 'link'
|
||||
iconStyle = { iconStyle }
|
||||
onClick = { this.props._onShareRoom }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
}
|
||||
{
|
||||
showInviteButton
|
||||
&& <InviteButton
|
||||
enableAddPeople = { enableAddPeople }
|
||||
enableDialOut = { enableDialOut }
|
||||
iconStyle = { iconStyle }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
}
|
||||
<InviteButton
|
||||
iconStyle = { iconStyle }
|
||||
style = { style }
|
||||
underlayColor = { underlayColor } />
|
||||
<EnterPictureInPictureToolbarButton
|
||||
iconStyle = { iconStyle }
|
||||
style = { style }
|
||||
|
@ -344,17 +303,6 @@ function _mapDispatchToProps(dispatch) {
|
|||
dispatch(beginRoomLockRequest());
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins the UI procedure to share the conference/room URL.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onShareRoom() {
|
||||
dispatch(beginShareRoom());
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the audio-only flag of the conference.
|
||||
*
|
||||
|
@ -408,22 +356,6 @@ function _mapStateToProps(state) {
|
|||
*/
|
||||
_audioOnly: Boolean(conference.audioOnly),
|
||||
|
||||
/**
|
||||
* Whether or not the feature to directly invite people into the
|
||||
* conference is available.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
_enableAddPeople: isAddPeopleEnabled(state),
|
||||
|
||||
/**
|
||||
* Whether or not the feature to dial out to number to join the
|
||||
* conference is available.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
_enableDialOut: isDialOutEnabled(state),
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the toolbox is enabled.
|
||||
*
|
||||
|
|
|
@ -21,7 +21,12 @@ import { ChatCounter } from '../../chat';
|
|||
import { openDeviceSelectionDialog } from '../../device-selection';
|
||||
import { toggleDocument } from '../../etherpad';
|
||||
import { openFeedbackDialog } from '../../feedback';
|
||||
import { AddPeopleDialog, InfoDialogButton } from '../../invite';
|
||||
import {
|
||||
beginAddPeople,
|
||||
InfoDialogButton,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled
|
||||
} from '../../invite';
|
||||
import { openKeyboardShortcutsDialog } from '../../keyboard-shortcuts';
|
||||
import { RECORDING_TYPES, toggleRecording } from '../../recording';
|
||||
import { toggleSharedVideo } from '../../shared-video';
|
||||
|
@ -43,12 +48,6 @@ import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
|
|||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether or not the feature for adding people directly into the call
|
||||
* is enabled.
|
||||
*/
|
||||
_addPeopleAvailable: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the chat feature is currently displayed.
|
||||
*/
|
||||
|
@ -69,12 +68,6 @@ type Props = {
|
|||
*/
|
||||
_desktopSharingEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the feature for telephony to dial out to a number is
|
||||
* enabled.
|
||||
*/
|
||||
_dialOutAvailable: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not a dialog is displayed.
|
||||
*/
|
||||
|
@ -399,23 +392,6 @@ class Toolbox extends Component<Props, State> {
|
|||
this.props.dispatch(openFeedbackDialog(_conference));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog for inviting people directly into the conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_doOpenInvite() {
|
||||
const { _addPeopleAvailable, _dialOutAvailable, dispatch } = this.props;
|
||||
|
||||
if (_addPeopleAvailable || _dialOutAvailable) {
|
||||
dispatch(openDialog(AddPeopleDialog, {
|
||||
enableAddPeople: _addPeopleAvailable,
|
||||
enableDialOut: _dialOutAvailable
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to display {@code KeyboardShortcuts}.
|
||||
*
|
||||
|
@ -692,8 +668,7 @@ class Toolbox extends Component<Props, State> {
|
|||
*/
|
||||
_onToolbarOpenInvite() {
|
||||
sendAnalytics(createToolbarEvent('invite'));
|
||||
|
||||
this._doOpenInvite();
|
||||
this.props.dispatch(beginAddPeople());
|
||||
}
|
||||
|
||||
_onToolbarOpenKeyboardShortcuts: () => void;
|
||||
|
@ -1118,10 +1093,8 @@ function _mapStateToProps(state) {
|
|||
callStatsID,
|
||||
disableDesktopSharing,
|
||||
enableRecording,
|
||||
enableUserRolesBasedOnToken,
|
||||
iAmRecorder
|
||||
} = state['features/base/config'];
|
||||
const { isGuest } = state['features/base/jwt'];
|
||||
const { isRecording, recordingType } = state['features/recording'];
|
||||
const sharedVideoStatus = state['features/shared-video'].status;
|
||||
const { current } = state['features/side-panel'];
|
||||
|
@ -1134,25 +1107,20 @@ function _mapStateToProps(state) {
|
|||
const localParticipant = getLocalParticipant(state);
|
||||
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
const isAddPeopleAvailable = !isGuest;
|
||||
const isDialOutAvailable
|
||||
= isModerator
|
||||
&& conference && conference.isSIPCallingSupported()
|
||||
&& (!enableUserRolesBasedOnToken || !isGuest);
|
||||
const addPeopleEnabled = isAddPeopleEnabled(state);
|
||||
const dialOutEnabled = isDialOutEnabled(state);
|
||||
|
||||
return {
|
||||
_addPeopleAvailable: isAddPeopleAvailable,
|
||||
_chatOpen: current === 'chat_container',
|
||||
_conference: conference,
|
||||
_desktopSharingEnabled: desktopSharingEnabled,
|
||||
_desktopSharingDisabledByConfig: disableDesktopSharing,
|
||||
_dialOutAvailable: isDialOutAvailable,
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
_editingDocument: Boolean(state['features/etherpad'].editing),
|
||||
_etherpadInitialized: Boolean(state['features/etherpad'].initialized),
|
||||
_feedbackConfigured: Boolean(callStatsID),
|
||||
_hideInviteButton: iAmRecorder
|
||||
|| (!isAddPeopleAvailable && !isDialOutAvailable),
|
||||
_hideInviteButton:
|
||||
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
|
||||
_isRecording: isRecording,
|
||||
_fullScreen: fullScreen,
|
||||
_localParticipantID: localParticipant.id,
|
||||
|
|
Loading…
Reference in New Issue