[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.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.jitsi.meet.sdk.InviteSearchController;
|
|
||||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||||
|
import org.jitsi.meet.sdk.invite.AddPeopleController;
|
||||||
|
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||||
|
|
||||||
import com.calendarevents.CalendarEventsPackage;
|
import com.calendarevents.CalendarEventsPackage;
|
||||||
|
|
||||||
|
@ -86,16 +87,24 @@ public class MainActivity extends JitsiMeetActivity {
|
||||||
on("CONFERENCE_WILL_LEAVE", data);
|
on("CONFERENCE_WILL_LEAVE", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void launchNativeInvite(InviteSearchController inviteSearchController) {
|
|
||||||
on("LAUNCH_NATIVE_INVITE", new HashMap<String, Object>());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadConfigError(Map<String, Object> data) {
|
public void onLoadConfigError(Map<String, Object> data) {
|
||||||
on("LOAD_CONFIG_ERROR", 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;
|
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;
|
package org.jitsi.meet.sdk;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -229,7 +228,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
|
||||||
if (!super.onKeyUp(keyCode, event)
|
if (!super.onKeyUp(keyCode, event)
|
||||||
&& BuildConfig.DEBUG
|
&& BuildConfig.DEBUG
|
||||||
&& (reactInstanceManager
|
&& (reactInstanceManager
|
||||||
= JitsiMeetView.getReactInstanceManager())
|
= ReactInstanceManagerHolder.getReactInstanceManager())
|
||||||
!= null
|
!= null
|
||||||
&& keyCode == KeyEvent.KEYCODE_MENU) {
|
&& keyCode == KeyEvent.KEYCODE_MENU) {
|
||||||
reactInstanceManager.showDevOptionsDialog();
|
reactInstanceManager.showDevOptionsDialog();
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.jitsi.meet.sdk;
|
package org.jitsi.meet.sdk;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Application;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -29,22 +28,13 @@ import android.widget.FrameLayout;
|
||||||
|
|
||||||
import com.facebook.react.ReactInstanceManager;
|
import com.facebook.react.ReactInstanceManager;
|
||||||
import com.facebook.react.ReactRootView;
|
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.DefaultHardwareBackBtnHandler;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
||||||
import com.rnimmersive.RNImmersiveModule;
|
import com.rnimmersive.RNImmersiveModule;
|
||||||
|
|
||||||
|
import org.jitsi.meet.sdk.invite.InviteController;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
@ -62,30 +52,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
*/
|
*/
|
||||||
private final static String TAG = JitsiMeetView.class.getSimpleName();
|
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
|
private static final Set<JitsiMeetView> views
|
||||||
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
|
= 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(
|
public static JitsiMeetView findViewByExternalAPIScope(
|
||||||
String externalAPIScope) {
|
String externalAPIScope) {
|
||||||
synchronized (views) {
|
synchronized (views) {
|
||||||
|
@ -99,47 +68,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
return null;
|
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
|
* Loads a specific URL {@code String} in all existing
|
||||||
* {@code JitsiMeetView}s.
|
* {@code JitsiMeetView}s.
|
||||||
|
@ -174,6 +102,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
* implementation.
|
* implementation.
|
||||||
*/
|
*/
|
||||||
public static boolean onBackPressed() {
|
public static boolean onBackPressed() {
|
||||||
|
ReactInstanceManager reactInstanceManager
|
||||||
|
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||||
|
|
||||||
if (reactInstanceManager == null) {
|
if (reactInstanceManager == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -190,6 +121,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
* @param activity {@code Activity} being destroyed.
|
* @param activity {@code Activity} being destroyed.
|
||||||
*/
|
*/
|
||||||
public static void onHostDestroy(Activity activity) {
|
public static void onHostDestroy(Activity activity) {
|
||||||
|
ReactInstanceManager reactInstanceManager
|
||||||
|
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||||
|
|
||||||
if (reactInstanceManager != null) {
|
if (reactInstanceManager != null) {
|
||||||
reactInstanceManager.onHostDestroy(activity);
|
reactInstanceManager.onHostDestroy(activity);
|
||||||
}
|
}
|
||||||
|
@ -202,6 +136,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
* @param activity {@code Activity} being paused.
|
* @param activity {@code Activity} being paused.
|
||||||
*/
|
*/
|
||||||
public static void onHostPause(Activity activity) {
|
public static void onHostPause(Activity activity) {
|
||||||
|
ReactInstanceManager reactInstanceManager
|
||||||
|
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||||
|
|
||||||
if (reactInstanceManager != null) {
|
if (reactInstanceManager != null) {
|
||||||
reactInstanceManager.onHostPause(activity);
|
reactInstanceManager.onHostPause(activity);
|
||||||
}
|
}
|
||||||
|
@ -228,6 +165,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
public static void onHostResume(
|
public static void onHostResume(
|
||||||
Activity activity,
|
Activity activity,
|
||||||
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
|
||||||
|
ReactInstanceManager reactInstanceManager
|
||||||
|
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||||
|
|
||||||
if (reactInstanceManager != null) {
|
if (reactInstanceManager != null) {
|
||||||
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
|
||||||
}
|
}
|
||||||
|
@ -256,6 +196,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReactInstanceManager reactInstanceManager
|
||||||
|
= ReactInstanceManagerHolder.getReactInstanceManager();
|
||||||
|
|
||||||
if (reactInstanceManager != null) {
|
if (reactInstanceManager != null) {
|
||||||
reactInstanceManager.onNewIntent(intent);
|
reactInstanceManager.onNewIntent(intent);
|
||||||
}
|
}
|
||||||
|
@ -269,63 +212,9 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
* This is currently not mandatory.
|
* This is currently not mandatory.
|
||||||
*/
|
*/
|
||||||
public static void onUserLeaveHint() {
|
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
|
* 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
|
* (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;
|
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
|
* The unique identifier of this {@code JitsiMeetView} within the process
|
||||||
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
|
* 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;
|
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
|
* {@link JitsiMeetViewListener} instance for reporting events occurring in
|
||||||
* Jitsi Meet.
|
* Jitsi Meet.
|
||||||
|
@ -374,15 +264,18 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
|
|
||||||
setBackgroundColor(BACKGROUND_COLOR);
|
setBackgroundColor(BACKGROUND_COLOR);
|
||||||
|
|
||||||
if (reactInstanceManager == null) {
|
ReactInstanceManagerHolder.initReactInstanceManager(
|
||||||
initReactInstanceManager(((Activity) context).getApplication());
|
((Activity) context).getApplication());
|
||||||
}
|
|
||||||
|
|
||||||
// Hook this JitsiMeetView into ExternalAPI.
|
// Hook this JitsiMeetView into ExternalAPI.
|
||||||
externalAPIScope = UUID.randomUUID().toString();
|
externalAPIScope = UUID.randomUUID().toString();
|
||||||
synchronized (views) {
|
synchronized (views) {
|
||||||
views.add(this);
|
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;
|
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}.
|
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
|
||||||
*
|
*
|
||||||
|
@ -483,6 +389,18 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
// externalAPIScope
|
// externalAPIScope
|
||||||
props.putString("externalAPIScope", externalAPIScope);
|
props.putString("externalAPIScope", externalAPIScope);
|
||||||
|
|
||||||
|
// inviteController
|
||||||
|
InviteController inviteController = getInviteController();
|
||||||
|
|
||||||
|
if (inviteController != null) {
|
||||||
|
props.putBoolean(
|
||||||
|
"addPeopleEnabled",
|
||||||
|
inviteController.isAddPeopleEnabled());
|
||||||
|
props.putBoolean(
|
||||||
|
"dialOutEnabled",
|
||||||
|
inviteController.isDialOutEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
// pictureInPictureEnabled
|
// pictureInPictureEnabled
|
||||||
props.putBoolean(
|
props.putBoolean(
|
||||||
"pictureInPictureEnabled",
|
"pictureInPictureEnabled",
|
||||||
|
@ -496,9 +414,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
// welcomePageEnabled
|
// welcomePageEnabled
|
||||||
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
|
||||||
|
|
||||||
props.putBoolean("addPeopleEnabled", addPeopleEnabled);
|
|
||||||
props.putBoolean("dialOutEnabled", dialOutEnabled);
|
|
||||||
|
|
||||||
// XXX The method loadURLObject: is supposed to be imperative i.e.
|
// XXX The method loadURLObject: is supposed to be imperative i.e.
|
||||||
// a second invocation with one and the same URL is expected to join
|
// a second invocation with one and the same URL is expected to join
|
||||||
// the respective conference again if the first invocation was followed
|
// the respective conference again if the first invocation was followed
|
||||||
|
@ -513,7 +428,7 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
if (reactRootView == null) {
|
if (reactRootView == null) {
|
||||||
reactRootView = new ReactRootView(getContext());
|
reactRootView = new ReactRootView(getContext());
|
||||||
reactRootView.startReactApplication(
|
reactRootView.startReactApplication(
|
||||||
reactInstanceManager,
|
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||||
"App",
|
"App",
|
||||||
props);
|
props);
|
||||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
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
|
* Sets the default base {@code URL} used to join a conference when a
|
||||||
* partial URL (e.g. a room name only) is specified to
|
* partial URL (e.g. a room name only) is specified to
|
||||||
|
@ -605,18 +508,6 @@ public class JitsiMeetView extends FrameLayout {
|
||||||
this.defaultURL = defaultURL;
|
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
|
* Sets a specific {@link JitsiMeetViewListener} on this
|
||||||
* {@code JitsiMeetView}.
|
* {@code JitsiMeetView}.
|
||||||
|
|
|
@ -46,8 +46,4 @@ public abstract class JitsiMeetViewAdapter implements JitsiMeetViewListener {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadConfigError(Map<String, Object> data) {
|
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);
|
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
|
* Called when loading the main configuration file from the Jitsi Meet
|
||||||
* deployment fails.
|
* 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>
|
#import <JitsiMeet/JitsiMeet.h>
|
||||||
|
|
||||||
@interface ViewController : UIViewController<JitsiMeetViewDelegate>
|
@interface ViewController : UIViewController<JitsiMeetViewDelegate, InviteControllerDelegate>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -27,12 +27,25 @@
|
||||||
|
|
||||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
view.delegate = self;
|
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
|
// 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
|
// 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
|
// time of this writing but it is clearer to be explicit about what we want
|
||||||
// anyway.
|
// anyway.
|
||||||
view.welcomePageEnabled = YES;
|
view.welcomePageEnabled = YES;
|
||||||
|
|
||||||
[view loadURL:nil];
|
[view loadURL:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +81,17 @@ void _onJitsiMeetViewDelegateEvent(NSString *name, NSDictionary *data) {
|
||||||
_onJitsiMeetViewDelegateEvent(@"LOAD_CONFIG_ERROR", 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
|
@end
|
||||||
|
|
|
@ -29,8 +29,13 @@
|
||||||
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
|
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 03F2ADC957FF109849B7FCA1 /* libPods-JitsiMeet.a */; };
|
||||||
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
|
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
|
||||||
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
|
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
|
||||||
412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 412BF89C206AA66F0053B9E5 /* InviteSearch.m */; };
|
B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
|
||||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = 412BF89E206AA82F0053B9E5 /* InviteSearch.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -125,12 +138,11 @@
|
||||||
0BD906E71EC0C00300C8C18E /* src */ = {
|
0BD906E71EC0C00300C8C18E /* src */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
B386B84F20981A11000DEF7A /* invite */,
|
||||||
C6A3426B204F127900E062DD /* picture-in-picture */,
|
C6A3426B204F127900E062DD /* picture-in-picture */,
|
||||||
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
|
||||||
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
|
||||||
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
0BB9AD7A1F5EC8F4001C08DB /* CallKit.m */,
|
||||||
412BF89E206AA82F0053B9E5 /* InviteSearch.h */,
|
|
||||||
412BF89C206AA66F0053B9E5 /* InviteSearch.m */,
|
|
||||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */,
|
||||||
|
@ -160,6 +172,23 @@
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
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 */ = {
|
C5E72ADFC30ED96F9B35F076 /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -185,11 +214,14 @@
|
||||||
isa = PBXHeadersBuildPhase;
|
isa = PBXHeadersBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
B386B85C20981A75000DEF7A /* InviteController.h in Headers */,
|
||||||
|
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */,
|
||||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
|
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
|
||||||
412BF89F206ABAE40053B9E5 /* InviteSearch.h in Headers */,
|
|
||||||
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
||||||
|
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */,
|
||||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||||
|
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */,
|
||||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -350,11 +382,13 @@
|
||||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||||
|
B386B85D20981A75000DEF7A /* Invite.m in Sources */,
|
||||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
||||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
||||||
|
B386B85720981A75000DEF7A /* InviteController.m in Sources */,
|
||||||
|
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */,
|
||||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||||
412BF89D206AA66F0053B9E5 /* InviteSearch.m in Sources */,
|
|
||||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||||
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m 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.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// JitsiMeetView
|
||||||
#import <JitsiMeet/JitsiMeetView.h>
|
#import <JitsiMeet/JitsiMeetView.h>
|
||||||
#import <JitsiMeet/JitsiMeetViewDelegate.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 <Foundation/Foundation.h>
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import "InviteController.h"
|
||||||
#import "JitsiMeetViewDelegate.h"
|
#import "JitsiMeetViewDelegate.h"
|
||||||
|
|
||||||
@interface JitsiMeetView : UIView
|
@interface JitsiMeetView : UIView
|
||||||
|
|
||||||
@property (nonatomic) BOOL addPeopleEnabled;
|
|
||||||
|
|
||||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||||
|
|
||||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||||
|
|
||||||
@property (nonatomic) BOOL dialOutEnabled;
|
@property (nonatomic, readonly) InviteController *inviteController;
|
||||||
|
|
||||||
@property (nonatomic) BOOL pictureInPictureEnabled;
|
@property (nonatomic) BOOL pictureInPictureEnabled;
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#import <React/RCTLinkingManager.h>
|
#import <React/RCTLinkingManager.h>
|
||||||
#import <React/RCTRootView.h>
|
#import <React/RCTRootView.h>
|
||||||
|
|
||||||
|
#import "Invite+Private.h"
|
||||||
|
#import "InviteController+Private.h"
|
||||||
#import "JitsiMeetView+Private.h"
|
#import "JitsiMeetView+Private.h"
|
||||||
#import "RCTBridgeWrapper.h"
|
#import "RCTBridgeWrapper.h"
|
||||||
|
|
||||||
|
@ -268,12 +270,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||||
props[@"defaultURL"] = [self.defaultURL absoluteString];
|
props[@"defaultURL"] = [self.defaultURL absoluteString];
|
||||||
}
|
}
|
||||||
|
|
||||||
props[@"addPeopleEnabled"] = @(self.addPeopleEnabled);
|
|
||||||
props[@"dialOutEnabled"] = @(self.dialOutEnabled);
|
|
||||||
props[@"externalAPIScope"] = externalAPIScope;
|
props[@"externalAPIScope"] = externalAPIScope;
|
||||||
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
|
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
|
||||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
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
|
// XXX If urlObject is nil, then it must appear as undefined in the
|
||||||
// JavaScript source code so that we check the launchOptions there.
|
// JavaScript source code so that we check the launchOptions there.
|
||||||
if (urlObject) {
|
if (urlObject) {
|
||||||
|
@ -405,10 +408,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hook this JitsiMeetView into ExternalAPI.
|
// Hook this JitsiMeetView into ExternalAPI.
|
||||||
if (!externalAPIScope) {
|
|
||||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||||
[views setObject:self forKey:externalAPIScope];
|
[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
|
// Set a background color which is in accord with the JavaScript and Android
|
||||||
// parts of the application and causes less perceived visual flicker than
|
// parts of the application and causes less perceived visual flicker than
|
||||||
|
|
|
@ -14,8 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@class InviteSearchController;
|
|
||||||
|
|
||||||
@protocol JitsiMeetViewDelegate <NSObject>
|
@protocol JitsiMeetViewDelegate <NSObject>
|
||||||
|
|
||||||
@optional
|
@optional
|
||||||
|
@ -57,15 +55,6 @@
|
||||||
*/
|
*/
|
||||||
- (void)conferenceWillLeave:(NSDictionary *)data;
|
- (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
|
* Called when entering Picture-in-Picture is requested by the user. The app
|
||||||
* should now activate its Picture-in-Picture implementation (and resize the
|
* 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-
|
* The type of the action which signals an error occurred while requesting dial-
|
||||||
* in numbers.
|
* in numbers.
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BEGIN_ADD_PEOPLE,
|
||||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { getDialInConferenceID, getDialInNumbers } from './functions';
|
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.
|
* 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.
|
* Whether or not to show Add People functionality.
|
||||||
*/
|
*/
|
||||||
enableAddPeople: PropTypes.bool,
|
addPeopleEnabled: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to show Dial Out functionality.
|
* Whether or not to show Dial Out functionality.
|
||||||
*/
|
*/
|
||||||
enableDialOut: PropTypes.bool,
|
dialOutEnabled: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function closing the dialog.
|
* The function closing the dialog.
|
||||||
|
@ -187,21 +187,21 @@ class AddPeopleDialog extends Component<*, *> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { enableAddPeople, enableDialOut, t } = this.props;
|
const { addPeopleEnabled, dialOutEnabled, t } = this.props;
|
||||||
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
|
let isMultiSelectDisabled = this.state.addToCallInProgress || false;
|
||||||
let placeholder;
|
let placeholder;
|
||||||
let loadingMessage;
|
let loadingMessage;
|
||||||
let noMatches;
|
let noMatches;
|
||||||
|
|
||||||
if (enableAddPeople && enableDialOut) {
|
if (addPeopleEnabled && dialOutEnabled) {
|
||||||
loadingMessage = 'addPeople.loading';
|
loadingMessage = 'addPeople.loading';
|
||||||
noMatches = 'addPeople.noResults';
|
noMatches = 'addPeople.noResults';
|
||||||
placeholder = 'addPeople.searchPeopleAndNumbers';
|
placeholder = 'addPeople.searchPeopleAndNumbers';
|
||||||
} else if (enableAddPeople) {
|
} else if (addPeopleEnabled) {
|
||||||
loadingMessage = 'addPeople.loadingPeople';
|
loadingMessage = 'addPeople.loadingPeople';
|
||||||
noMatches = 'addPeople.noResults';
|
noMatches = 'addPeople.noResults';
|
||||||
placeholder = 'addPeople.searchPeople';
|
placeholder = 'addPeople.searchPeople';
|
||||||
} else if (enableDialOut) {
|
} else if (dialOutEnabled) {
|
||||||
loadingMessage = 'addPeople.loadingNumber';
|
loadingMessage = 'addPeople.loadingNumber';
|
||||||
noMatches = 'addPeople.noValidNumbers';
|
noMatches = 'addPeople.noValidNumbers';
|
||||||
placeholder = 'addPeople.searchNumbers';
|
placeholder = 'addPeople.searchNumbers';
|
||||||
|
@ -481,8 +481,8 @@ class AddPeopleDialog extends Component<*, *> {
|
||||||
*/
|
*/
|
||||||
_query(query = '') {
|
_query(query = '') {
|
||||||
const {
|
const {
|
||||||
enableAddPeople,
|
addPeopleEnabled,
|
||||||
enableDialOut,
|
dialOutEnabled,
|
||||||
_dialOutAuthUrl,
|
_dialOutAuthUrl,
|
||||||
_jwt,
|
_jwt,
|
||||||
_peopleSearchQueryTypes,
|
_peopleSearchQueryTypes,
|
||||||
|
@ -491,8 +491,8 @@ class AddPeopleDialog extends Component<*, *> {
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
dialOutAuthUrl: _dialOutAuthUrl,
|
dialOutAuthUrl: _dialOutAuthUrl,
|
||||||
enableAddPeople,
|
addPeopleEnabled,
|
||||||
enableDialOut,
|
dialOutEnabled,
|
||||||
jwt: _jwt,
|
jwt: _jwt,
|
||||||
peopleSearchQueryTypes: _peopleSearchQueryTypes,
|
peopleSearchQueryTypes: _peopleSearchQueryTypes,
|
||||||
peopleSearchUrl: _peopleSearchUrl
|
peopleSearchUrl: _peopleSearchUrl
|
||||||
|
@ -609,7 +609,11 @@ function _mapStateToProps(state) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(connect(_mapStateToProps, {
|
export default translate(
|
||||||
|
connect(
|
||||||
|
_mapStateToProps,
|
||||||
|
/* mapDispatchToProps */ {
|
||||||
hideDialog,
|
hideDialog,
|
||||||
inviteVideoRooms })(
|
inviteVideoRooms
|
||||||
|
})(
|
||||||
AddPeopleDialog));
|
AddPeopleDialog));
|
||||||
|
|
|
@ -3,9 +3,22 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { launchNativeInvite } from '../../mobile/invite-search';
|
import { beginShareRoom } from '../../share-room';
|
||||||
import { ToolbarButton } from '../../toolbox';
|
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
|
* The type of {@link EnterPictureInPictureToobarButton}'s React
|
||||||
* {@code Component} props.
|
* {@code Component} props.
|
||||||
|
@ -13,21 +26,28 @@ import { ToolbarButton } from '../../toolbox';
|
||||||
type Props = {
|
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.
|
* Launches native invite dialog.
|
||||||
*
|
*
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
onLaunchNativeInvite: Function,
|
_onAddPeople: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins the UI procedure to share the conference/room URL.
|
||||||
|
*/
|
||||||
|
_onShareRoom: Function
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,23 +63,33 @@ class InviteButton extends Component<Props> {
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
enableAddPeople,
|
_addPeopleEnabled,
|
||||||
enableDialOut,
|
_dialOutEnabled,
|
||||||
onLaunchNativeInvite,
|
_onAddPeople,
|
||||||
|
_onShareRoom,
|
||||||
...props
|
...props
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!enableAddPeople && !enableDialOut) {
|
if (_SHARE_ROOM_TOOLBAR_BUTTON) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
iconName = { 'add' }
|
iconName = 'link'
|
||||||
onClick = { onLaunchNativeInvite }
|
onClick = { _onShareRoom }
|
||||||
{ ...props } />
|
{ ...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.
|
* @param {Function} dispatch - The redux action {@code dispatch} function.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* onLaunchNativeInvite
|
* _onAddPeople,
|
||||||
|
* _onShareRoom
|
||||||
* }}
|
* }}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _mapDispatchToProps(dispatch) {
|
function _mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches native invite dialog.
|
* Launches native invite dialog.
|
||||||
*
|
*
|
||||||
|
@ -82,10 +112,50 @@ function _mapDispatchToProps(dispatch) {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
*/
|
*/
|
||||||
onLaunchNativeInvite() {
|
_onAddPeople() {
|
||||||
dispatch(launchNativeInvite());
|
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);
|
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.
|
* 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.
|
* @returns {Promise} - The promise created by the request.
|
||||||
*/
|
*/
|
||||||
export function checkDialNumber(
|
export function checkDialNumber(
|
||||||
dialNumber: string, dialOutAuthUrl: string): Promise<Object> {
|
dialNumber: string,
|
||||||
|
dialOutAuthUrl: string
|
||||||
|
): Promise<Object> {
|
||||||
|
|
||||||
if (!dialOutAuthUrl) {
|
if (!dialOutAuthUrl) {
|
||||||
// no auth url, let's say it is valid
|
// no auth url, let's say it is valid
|
||||||
const response = {
|
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.
|
* Removes all non-numeric characters from a string.
|
||||||
*
|
*
|
||||||
|
@ -180,12 +98,12 @@ export type GetInviteResultsOptions = {
|
||||||
/**
|
/**
|
||||||
* Whether or not to search for people.
|
* Whether or not to search for people.
|
||||||
*/
|
*/
|
||||||
enableAddPeople: boolean,
|
addPeopleEnabled: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to check phone numbers.
|
* Whether or not to check phone numbers.
|
||||||
*/
|
*/
|
||||||
enableDialOut: boolean,
|
dialOutEnabled: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array with the query types that will be executed -
|
* Array with the query types that will be executed -
|
||||||
|
@ -214,13 +132,15 @@ export type GetInviteResultsOptions = {
|
||||||
*/
|
*/
|
||||||
export function getInviteResultsForQuery(
|
export function getInviteResultsForQuery(
|
||||||
query: string,
|
query: string,
|
||||||
options: GetInviteResultsOptions): Promise<*> {
|
options: GetInviteResultsOptions
|
||||||
|
): Promise<*> {
|
||||||
|
|
||||||
const text = query.trim();
|
const text = query.trim();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dialOutAuthUrl,
|
dialOutAuthUrl,
|
||||||
enableAddPeople,
|
addPeopleEnabled,
|
||||||
enableDialOut,
|
dialOutEnabled,
|
||||||
peopleSearchQueryTypes,
|
peopleSearchQueryTypes,
|
||||||
peopleSearchUrl,
|
peopleSearchUrl,
|
||||||
jwt
|
jwt
|
||||||
|
@ -228,7 +148,7 @@ export function getInviteResultsForQuery(
|
||||||
|
|
||||||
let peopleSearchPromise;
|
let peopleSearchPromise;
|
||||||
|
|
||||||
if (enableAddPeople && text) {
|
if (addPeopleEnabled && text) {
|
||||||
peopleSearchPromise = searchDirectory(
|
peopleSearchPromise = searchDirectory(
|
||||||
peopleSearchUrl,
|
peopleSearchUrl,
|
||||||
jwt,
|
jwt,
|
||||||
|
@ -242,7 +162,7 @@ export function getInviteResultsForQuery(
|
||||||
const hasCountryCode = text.startsWith('+');
|
const hasCountryCode = text.startsWith('+');
|
||||||
let phoneNumberPromise;
|
let phoneNumberPromise;
|
||||||
|
|
||||||
if (enableDialOut && isMaybeAPhoneNumber(text)) {
|
if (dialOutEnabled && isMaybeAPhoneNumber(text)) {
|
||||||
let numberToVerify = text;
|
let numberToVerify = text;
|
||||||
|
|
||||||
// When the number to verify does not start with a +, we assume no
|
// 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.
|
* 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);
|
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.
|
* Type of the options to use when sending invites.
|
||||||
*/
|
*/
|
||||||
|
@ -436,33 +487,3 @@ export function sendInvitesForItems(
|
||||||
return Promise.all(allInvitePromises)
|
return Promise.all(allInvitePromises)
|
||||||
.then(() => invitesLeftToSend);
|
.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 { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
import { UPDATE_DIAL_IN_NUMBERS_FAILED } from './actionTypes';
|
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);
|
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}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
@ -15,7 +18,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
||||||
logger.error(
|
logger.error(
|
||||||
'Error encountered while fetching dial-in numbers:',
|
'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 {
|
import {
|
||||||
|
_SET_EMITTER_SUBSCRIPTIONS,
|
||||||
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
UPDATE_DIAL_IN_NUMBERS_FAILED,
|
||||||
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
UPDATE_DIAL_IN_NUMBERS_SUCCESS
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
@ -11,6 +14,10 @@ const DEFAULT_STATE = {
|
||||||
|
|
||||||
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case _SET_EMITTER_SUBSCRIPTIONS:
|
||||||
|
return (
|
||||||
|
assign(state, 'emitterSubscriptions', action.emitterSubscriptions));
|
||||||
|
|
||||||
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
case UPDATE_DIAL_IN_NUMBERS_FAILED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -159,9 +159,9 @@ function _sendEvent(
|
||||||
{ getState }: { getState: Function },
|
{ getState }: { getState: Function },
|
||||||
name: string,
|
name: string,
|
||||||
data: Object) {
|
data: Object) {
|
||||||
// The JavaScript App needs to provide uniquely identifying information
|
// The JavaScript App needs to provide uniquely identifying information to
|
||||||
// to the native ExternalAPI module so that the latter may match the former
|
// the native ExternalAPI module so that the latter may match the former to
|
||||||
// to the native JitsiMeetView which hosts it.
|
// the native JitsiMeetView which hosts it.
|
||||||
const { app } = getState()['features/app'];
|
const { app } = getState()['features/app'];
|
||||||
|
|
||||||
if (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,
|
isNarrowAspectRatio,
|
||||||
makeAspectRatioAware
|
makeAspectRatioAware
|
||||||
} from '../../base/responsive-ui';
|
} from '../../base/responsive-ui';
|
||||||
import {
|
import { InviteButton } from '../../invite';
|
||||||
InviteButton,
|
|
||||||
isAddPeopleEnabled,
|
|
||||||
isDialOutEnabled
|
|
||||||
} from '../../invite';
|
|
||||||
import {
|
import {
|
||||||
EnterPictureInPictureToolbarButton
|
EnterPictureInPictureToolbarButton
|
||||||
} from '../../mobile/picture-in-picture';
|
} from '../../mobile/picture-in-picture';
|
||||||
import { beginRoomLockRequest } from '../../room-lock';
|
import { beginRoomLockRequest } from '../../room-lock';
|
||||||
import { beginShareRoom } from '../../share-room';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
abstractMapDispatchToProps,
|
abstractMapDispatchToProps,
|
||||||
|
@ -51,18 +46,6 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_audioOnly: boolean,
|
_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.
|
* The indicator which determines whether the toolbox is enabled.
|
||||||
*/
|
*/
|
||||||
|
@ -83,11 +66,6 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_onRoomLock: Function,
|
_onRoomLock: Function,
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins the UI procedure to share the conference/room URL.
|
|
||||||
*/
|
|
||||||
_onShareRoom: Function,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the audio-only flag of the conference.
|
* Toggles the audio-only flag of the conference.
|
||||||
*/
|
*/
|
||||||
|
@ -112,7 +90,6 @@ type Props = {
|
||||||
dispatch: Function
|
dispatch: Function
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the conference toolbox on React Native.
|
* Implements the conference toolbox on React Native.
|
||||||
*/
|
*/
|
||||||
|
@ -219,13 +196,9 @@ class Toolbox extends Component<Props> {
|
||||||
const underlayColor = 'transparent';
|
const underlayColor = 'transparent';
|
||||||
const {
|
const {
|
||||||
_audioOnly: audioOnly,
|
_audioOnly: audioOnly,
|
||||||
_enableAddPeople: enableAddPeople,
|
|
||||||
_enableDialOut: enableDialOut,
|
|
||||||
_videoMuted: videoMuted
|
_videoMuted: videoMuted
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const showInviteButton = enableAddPeople || enableDialOut;
|
|
||||||
|
|
||||||
/* eslint-disable react/jsx-curly-spacing,react/jsx-handler-names */
|
/* eslint-disable react/jsx-curly-spacing,react/jsx-handler-names */
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -262,24 +235,10 @@ class Toolbox extends Component<Props> {
|
||||||
onClick = { this.props._onRoomLock }
|
onClick = { this.props._onRoomLock }
|
||||||
style = { style }
|
style = { style }
|
||||||
underlayColor = { underlayColor } />
|
underlayColor = { underlayColor } />
|
||||||
{
|
<InviteButton
|
||||||
!showInviteButton
|
|
||||||
&& <ToolbarButton
|
|
||||||
iconName = 'link'
|
|
||||||
iconStyle = { iconStyle }
|
|
||||||
onClick = { this.props._onShareRoom }
|
|
||||||
style = { style }
|
|
||||||
underlayColor = { underlayColor } />
|
|
||||||
}
|
|
||||||
{
|
|
||||||
showInviteButton
|
|
||||||
&& <InviteButton
|
|
||||||
enableAddPeople = { enableAddPeople }
|
|
||||||
enableDialOut = { enableDialOut }
|
|
||||||
iconStyle = { iconStyle }
|
iconStyle = { iconStyle }
|
||||||
style = { style }
|
style = { style }
|
||||||
underlayColor = { underlayColor } />
|
underlayColor = { underlayColor } />
|
||||||
}
|
|
||||||
<EnterPictureInPictureToolbarButton
|
<EnterPictureInPictureToolbarButton
|
||||||
iconStyle = { iconStyle }
|
iconStyle = { iconStyle }
|
||||||
style = { style }
|
style = { style }
|
||||||
|
@ -344,17 +303,6 @@ function _mapDispatchToProps(dispatch) {
|
||||||
dispatch(beginRoomLockRequest());
|
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.
|
* Toggles the audio-only flag of the conference.
|
||||||
*
|
*
|
||||||
|
@ -408,22 +356,6 @@ function _mapStateToProps(state) {
|
||||||
*/
|
*/
|
||||||
_audioOnly: Boolean(conference.audioOnly),
|
_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.
|
* The indicator which determines whether the toolbox is enabled.
|
||||||
*
|
*
|
||||||
|
|
|
@ -21,7 +21,12 @@ import { ChatCounter } from '../../chat';
|
||||||
import { openDeviceSelectionDialog } from '../../device-selection';
|
import { openDeviceSelectionDialog } from '../../device-selection';
|
||||||
import { toggleDocument } from '../../etherpad';
|
import { toggleDocument } from '../../etherpad';
|
||||||
import { openFeedbackDialog } from '../../feedback';
|
import { openFeedbackDialog } from '../../feedback';
|
||||||
import { AddPeopleDialog, InfoDialogButton } from '../../invite';
|
import {
|
||||||
|
beginAddPeople,
|
||||||
|
InfoDialogButton,
|
||||||
|
isAddPeopleEnabled,
|
||||||
|
isDialOutEnabled
|
||||||
|
} from '../../invite';
|
||||||
import { openKeyboardShortcutsDialog } from '../../keyboard-shortcuts';
|
import { openKeyboardShortcutsDialog } from '../../keyboard-shortcuts';
|
||||||
import { RECORDING_TYPES, toggleRecording } from '../../recording';
|
import { RECORDING_TYPES, toggleRecording } from '../../recording';
|
||||||
import { toggleSharedVideo } from '../../shared-video';
|
import { toggleSharedVideo } from '../../shared-video';
|
||||||
|
@ -43,12 +48,6 @@ import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
|
||||||
|
|
||||||
type Props = {
|
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.
|
* Whether or not the chat feature is currently displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -69,12 +68,6 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_desktopSharingEnabled: boolean,
|
_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.
|
* Whether or not a dialog is displayed.
|
||||||
*/
|
*/
|
||||||
|
@ -399,23 +392,6 @@ class Toolbox extends Component<Props, State> {
|
||||||
this.props.dispatch(openFeedbackDialog(_conference));
|
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}.
|
* Dispatches an action to display {@code KeyboardShortcuts}.
|
||||||
*
|
*
|
||||||
|
@ -692,8 +668,7 @@ class Toolbox extends Component<Props, State> {
|
||||||
*/
|
*/
|
||||||
_onToolbarOpenInvite() {
|
_onToolbarOpenInvite() {
|
||||||
sendAnalytics(createToolbarEvent('invite'));
|
sendAnalytics(createToolbarEvent('invite'));
|
||||||
|
this.props.dispatch(beginAddPeople());
|
||||||
this._doOpenInvite();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onToolbarOpenKeyboardShortcuts: () => void;
|
_onToolbarOpenKeyboardShortcuts: () => void;
|
||||||
|
@ -1118,10 +1093,8 @@ function _mapStateToProps(state) {
|
||||||
callStatsID,
|
callStatsID,
|
||||||
disableDesktopSharing,
|
disableDesktopSharing,
|
||||||
enableRecording,
|
enableRecording,
|
||||||
enableUserRolesBasedOnToken,
|
|
||||||
iAmRecorder
|
iAmRecorder
|
||||||
} = state['features/base/config'];
|
} = state['features/base/config'];
|
||||||
const { isGuest } = state['features/base/jwt'];
|
|
||||||
const { isRecording, recordingType } = state['features/recording'];
|
const { isRecording, recordingType } = state['features/recording'];
|
||||||
const sharedVideoStatus = state['features/shared-video'].status;
|
const sharedVideoStatus = state['features/shared-video'].status;
|
||||||
const { current } = state['features/side-panel'];
|
const { current } = state['features/side-panel'];
|
||||||
|
@ -1134,25 +1107,20 @@ function _mapStateToProps(state) {
|
||||||
const localParticipant = getLocalParticipant(state);
|
const localParticipant = getLocalParticipant(state);
|
||||||
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
|
const localVideo = getLocalVideoTrack(state['features/base/tracks']);
|
||||||
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
const isModerator = localParticipant.role === PARTICIPANT_ROLE.MODERATOR;
|
||||||
const isAddPeopleAvailable = !isGuest;
|
const addPeopleEnabled = isAddPeopleEnabled(state);
|
||||||
const isDialOutAvailable
|
const dialOutEnabled = isDialOutEnabled(state);
|
||||||
= isModerator
|
|
||||||
&& conference && conference.isSIPCallingSupported()
|
|
||||||
&& (!enableUserRolesBasedOnToken || !isGuest);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_addPeopleAvailable: isAddPeopleAvailable,
|
|
||||||
_chatOpen: current === 'chat_container',
|
_chatOpen: current === 'chat_container',
|
||||||
_conference: conference,
|
_conference: conference,
|
||||||
_desktopSharingEnabled: desktopSharingEnabled,
|
_desktopSharingEnabled: desktopSharingEnabled,
|
||||||
_desktopSharingDisabledByConfig: disableDesktopSharing,
|
_desktopSharingDisabledByConfig: disableDesktopSharing,
|
||||||
_dialOutAvailable: isDialOutAvailable,
|
|
||||||
_dialog: Boolean(state['features/base/dialog'].component),
|
_dialog: Boolean(state['features/base/dialog'].component),
|
||||||
_editingDocument: Boolean(state['features/etherpad'].editing),
|
_editingDocument: Boolean(state['features/etherpad'].editing),
|
||||||
_etherpadInitialized: Boolean(state['features/etherpad'].initialized),
|
_etherpadInitialized: Boolean(state['features/etherpad'].initialized),
|
||||||
_feedbackConfigured: Boolean(callStatsID),
|
_feedbackConfigured: Boolean(callStatsID),
|
||||||
_hideInviteButton: iAmRecorder
|
_hideInviteButton:
|
||||||
|| (!isAddPeopleAvailable && !isDialOutAvailable),
|
iAmRecorder || (!addPeopleEnabled && !dialOutEnabled),
|
||||||
_isRecording: isRecording,
|
_isRecording: isRecording,
|
||||||
_fullScreen: fullScreen,
|
_fullScreen: fullScreen,
|
||||||
_localParticipantID: localParticipant.id,
|
_localParticipantID: localParticipant.id,
|
||||||
|
|
Loading…
Reference in New Issue