mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-27 03:07:48 +00:00
Compare commits
14 Commits
3207
...
invitation
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5e2763c1 | ||
|
|
76638f524d | ||
|
|
8ea693616d | ||
|
|
2442ef80b0 | ||
|
|
87f171caa4 | ||
|
|
e094b6516a | ||
|
|
2941f5dde4 | ||
|
|
eec7a1b628 | ||
|
|
5f7a515610 | ||
|
|
b7133f5717 | ||
|
|
f77e1dc591 | ||
|
|
4d817fc6c2 | ||
|
|
b8a7037959 | ||
|
|
1cfd6164f5 |
@@ -24,10 +24,6 @@ import android.util.Log;
|
||||
import org.jitsi.meet.sdk.JitsiMeetActivity;
|
||||
import org.jitsi.meet.sdk.JitsiMeetView;
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleController;
|
||||
import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
import org.jitsi.meet.sdk.invite.InviteControllerListener;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
@@ -36,8 +32,6 @@ import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -53,13 +47,6 @@ import java.util.Map;
|
||||
* {@code react-native run-android}.
|
||||
*/
|
||||
public class MainActivity extends JitsiMeetActivity {
|
||||
/**
|
||||
* The query to perform through {@link AddPeopleController} when the
|
||||
* {@code InviteButton} is tapped in order to exercise the public API of the
|
||||
* feature invite. If {@code null}, the {@code InviteButton} will not be
|
||||
* rendered.
|
||||
*/
|
||||
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
|
||||
|
||||
@Override
|
||||
protected JitsiMeetView initializeView() {
|
||||
@@ -113,72 +100,11 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// inviteController
|
||||
final InviteController inviteController
|
||||
= view.getInviteController();
|
||||
|
||||
inviteController.setListener(new InviteControllerListener() {
|
||||
public void beginAddPeople(
|
||||
AddPeopleController addPeopleController) {
|
||||
onInviteControllerBeginAddPeople(
|
||||
inviteController,
|
||||
addPeopleController);
|
||||
}
|
||||
});
|
||||
inviteController.setAddPeopleEnabled(
|
||||
ADD_PEOPLE_CONTROLLER_QUERY != null);
|
||||
inviteController.setDialOutEnabled(
|
||||
inviteController.isAddPeopleEnabled());
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// 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.
|
||||
// Technically, endAddPeople will automatically be invoked if there are
|
||||
// no failedInviteees i.e. the invite succeeeded for all specified
|
||||
// invitees.
|
||||
addPeopleController.endAddPeople();
|
||||
}
|
||||
|
||||
private void onAddPeopleControllerReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
int size = results.size();
|
||||
|
||||
if (size > 0) {
|
||||
// Exercise AddPeopleController's inviteById implementation.
|
||||
List<String> ids = new ArrayList<>(size);
|
||||
|
||||
for (Map<String, Object> result : results) {
|
||||
Object id = result.get("id");
|
||||
|
||||
if (id != null) {
|
||||
ids.add(id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
addPeopleController.inviteById(ids);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
|
||||
@@ -213,47 +139,4 @@ public class MainActivity extends JitsiMeetActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void onInviteControllerBeginAddPeople(
|
||||
InviteController inviteController,
|
||||
AddPeopleController addPeopleController) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
// 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");
|
||||
|
||||
String query = ADD_PEOPLE_CONTROLLER_QUERY;
|
||||
|
||||
if (query != null
|
||||
&& (inviteController.isAddPeopleEnabled()
|
||||
|| inviteController.isDialOutEnabled())) {
|
||||
addPeopleController.setListener(new AddPeopleControllerListener() {
|
||||
public void onInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees) {
|
||||
onAddPeopleControllerInviteSettled(
|
||||
addPeopleController,
|
||||
failedInvitees);
|
||||
}
|
||||
|
||||
public void onReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query) {
|
||||
onAddPeopleControllerReceivedResults(
|
||||
addPeopleController,
|
||||
results, query);
|
||||
}
|
||||
});
|
||||
addPeopleController.performQuery(query);
|
||||
} else {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.modules.core.PermissionListener;
|
||||
|
||||
import java.net.URL;
|
||||
@@ -52,6 +53,11 @@ public class JitsiMeetActivity
|
||||
private static final int OVERLAY_PERMISSION_REQUEST_CODE
|
||||
= (int) (Math.random() * Short.MAX_VALUE);
|
||||
|
||||
/**
|
||||
* A color scheme object to override the default color is the SDK.
|
||||
*/
|
||||
private WritableMap colorScheme;
|
||||
|
||||
/**
|
||||
* The default base {@code URL} used to join a conference when a partial URL
|
||||
* (e.g. a room name only) is specified. The value is used only while
|
||||
@@ -120,6 +126,7 @@ public class JitsiMeetActivity
|
||||
|
||||
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
|
||||
// is documented to need such an order in order to take effect:
|
||||
view.setColorScheme(colorScheme);
|
||||
view.setDefaultURL(defaultURL);
|
||||
if (pictureInPictureEnabled != null) {
|
||||
view.setPictureInPictureEnabled(
|
||||
@@ -286,6 +293,17 @@ public class JitsiMeetActivity
|
||||
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see JitsiMeetView#setColorScheme(WritableMap)
|
||||
*/
|
||||
public void setColorScheme(WritableMap colorScheme) {
|
||||
if (view == null) {
|
||||
this.colorScheme = colorScheme;
|
||||
} else {
|
||||
view.setColorScheme(colorScheme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see JitsiMeetView#setDefaultURL(URL)
|
||||
|
||||
@@ -22,9 +22,9 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import org.jitsi.meet.sdk.invite.InviteController;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
@@ -71,6 +71,11 @@ public class JitsiMeetView
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* A color scheme object to override the default color is the SDK.
|
||||
*/
|
||||
private WritableMap colorScheme;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -78,12 +83,6 @@ public class JitsiMeetView
|
||||
*/
|
||||
private URL defaultURL;
|
||||
|
||||
/**
|
||||
* The entry point into the invite feature of Jitsi Meet. The Java
|
||||
* counterpart of the JavaScript {@code InviteButton}.
|
||||
*/
|
||||
private final InviteController inviteController;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
|
||||
* {@code true} iff the Android platform supports Picture-in-Picture
|
||||
@@ -106,10 +105,6 @@ public class JitsiMeetView
|
||||
public JitsiMeetView(@NonNull Context context) {
|
||||
super(context);
|
||||
|
||||
// The entry point into the invite feature of Jitsi Meet. The Java
|
||||
// counterpart of the JavaScript InviteButton.
|
||||
inviteController = new InviteController(externalAPIScope);
|
||||
|
||||
// Check if the parent Activity implements JitsiMeetActivityInterface,
|
||||
// otherwise things may go wrong.
|
||||
if (!(context instanceof JitsiMeetActivityInterface)) {
|
||||
@@ -142,6 +137,15 @@ public class JitsiMeetView
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color scheme used in the SDK.
|
||||
*
|
||||
* @return The color scheme map.
|
||||
*/
|
||||
public WritableMap getColorScheme() {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
@@ -155,19 +159,6 @@ public class JitsiMeetView
|
||||
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 URL of the current conference.
|
||||
*
|
||||
@@ -233,23 +224,16 @@ public class JitsiMeetView
|
||||
public void loadURLObject(@Nullable Bundle urlObject) {
|
||||
Bundle props = new Bundle();
|
||||
|
||||
// color scheme
|
||||
if (colorScheme != null) {
|
||||
props.putBundle("colorScheme", Arguments.toBundle(colorScheme));
|
||||
}
|
||||
|
||||
// defaultURL
|
||||
if (defaultURL != null) {
|
||||
props.putString("defaultURL", defaultURL.toString());
|
||||
}
|
||||
|
||||
// inviteController
|
||||
InviteController inviteController = getInviteController();
|
||||
|
||||
if (inviteController != null) {
|
||||
props.putBoolean(
|
||||
"addPeopleEnabled",
|
||||
inviteController.isAddPeopleEnabled());
|
||||
props.putBoolean(
|
||||
"dialOutEnabled",
|
||||
inviteController.isDialOutEnabled());
|
||||
}
|
||||
|
||||
// pictureInPictureEnabled
|
||||
props.putBoolean(
|
||||
"pictureInPictureEnabled",
|
||||
@@ -342,6 +326,15 @@ public class JitsiMeetView
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color scheme to override the default colors of the SDK.
|
||||
*
|
||||
* @param colorScheme The color scheme map.
|
||||
*/
|
||||
public void setColorScheme(WritableMap colorScheme) {
|
||||
this.colorScheme = colorScheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default base {@code URL} used to join a conference when a
|
||||
* partial URL (e.g. a room name only) is specified to
|
||||
|
||||
@@ -54,7 +54,6 @@ class ReactInstanceManagerHolder {
|
||||
new ProximityModule(reactContext),
|
||||
new WiFiStatsModule(reactContext),
|
||||
new org.jitsi.meet.sdk.dropbox.Dropbox(reactContext),
|
||||
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* 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(id));
|
||||
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.onInviteSettled(this, jFailedInvitees);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a search for entities to invite with the given query. Results will
|
||||
* be returned through the associated AddPeopleControllerListener's
|
||||
* onReceivedResults 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.onReceivedResults(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;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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 the call to {@link AddPeopleController#inviteById(List)}
|
||||
* completes.
|
||||
*
|
||||
* @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 onReceivedResuls.
|
||||
*/
|
||||
void onInviteSettled(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> failedInvitees);
|
||||
|
||||
/**
|
||||
* 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 onReceivedResults(
|
||||
AddPeopleController addPeopleController,
|
||||
List<Map<String, Object>> results,
|
||||
String query);
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public InviteControllerListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
|
||||
*
|
||||
* @param query {@code String} to use for the query
|
||||
*/
|
||||
void performQuery(AddPeopleController addPeopleController, String query) {
|
||||
WritableNativeMap params = new WritableNativeMap();
|
||||
|
||||
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
|
||||
params.putString("externalAPIScope", externalAPIScope);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* 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 com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import org.jitsi.meet.sdk.BaseReactView;
|
||||
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(final String externalAPIScope) {
|
||||
// Make sure InviteControllerListener (like all other listeners of the
|
||||
// SDK) is invoked on the UI thread. It was requested by SDK consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
beginAddPeople(externalAPIScope);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
InviteController inviteController
|
||||
= findInviteControllerByExternalAPIScope(externalAPIScope);
|
||||
|
||||
if (inviteController != null) {
|
||||
inviteController.beginAddPeople(getReactApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
private InviteController findInviteControllerByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
JitsiMeetView view
|
||||
= (JitsiMeetView)
|
||||
BaseReactView.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(
|
||||
final String externalAPIScope,
|
||||
final String addPeopleControllerScope,
|
||||
final ReadableArray failedInvitees) {
|
||||
// Make sure AddPeopleControllerListener (like all other listeners of
|
||||
// the SDK) is invoked on the UI thread. It was requested by SDK
|
||||
// consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
inviteSettled(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
failedInvitees);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
final String externalAPIScope,
|
||||
final String addPeopleControllerScope,
|
||||
final String query,
|
||||
final ReadableArray results) {
|
||||
// Make sure AddPeopleControllerListener (like all other listeners of
|
||||
// the SDK) is invoked on the UI thread. It was requested by SDK
|
||||
// consumers.
|
||||
if (!UiThreadUtil.isOnUiThread()) {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
receivedResults(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
query,
|
||||
results);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1237,6 +1237,7 @@ export default {
|
||||
= connection.initJitsiConference(
|
||||
APP.conference.roomName,
|
||||
this._getConferenceOptions());
|
||||
|
||||
APP.store.dispatch(conferenceWillJoin(room));
|
||||
this._setLocalAudioVideoStreams(localTracks);
|
||||
this._room = room; // FIXME do not use this
|
||||
|
||||
@@ -146,7 +146,7 @@ var config = {
|
||||
desktopSharingChromeExtId: null,
|
||||
|
||||
// Whether desktop sharing should be disabled on Chrome.
|
||||
desktopSharingChromeDisabled: true,
|
||||
// desktopSharingChromeDisabled: false,
|
||||
|
||||
// The media sources to use when using screen sharing with the Chrome
|
||||
// extension.
|
||||
@@ -156,7 +156,7 @@ var config = {
|
||||
desktopSharingChromeMinExtVersion: '0.1',
|
||||
|
||||
// Whether desktop sharing should be disabled on Firefox.
|
||||
desktopSharingFirefoxDisabled: false,
|
||||
// desktopSharingFirefoxDisabled: false,
|
||||
|
||||
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
|
||||
// desktopSharingFrameRate: {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
background: #0074E0;
|
||||
border-radius: 4px;
|
||||
color: #FFFFFF;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
|
||||
@@ -496,18 +496,22 @@
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatarContainer,
|
||||
#dominantSpeakerAvatar,
|
||||
.dynamic-shadow {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatar {
|
||||
#dominantSpeakerAvatarContainer {
|
||||
top: 50px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
overflow: hidden;
|
||||
visibility: inherit;
|
||||
}
|
||||
#dominantSpeakerAvatar {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
|
||||
#import <JitsiMeet/JitsiMeet.h>
|
||||
|
||||
@interface ViewController
|
||||
: UIViewController<
|
||||
JitsiMeetViewDelegate,
|
||||
JMAddPeopleControllerDelegate,
|
||||
JMInviteControllerDelegate>
|
||||
@interface ViewController : UIViewController<JitsiMeetViewDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,15 +24,8 @@
|
||||
// Needed for NSUserActivity suggestedInvocationPhrase
|
||||
@import Intents;
|
||||
|
||||
/**
|
||||
* The query to perform through JMAddPeopleController when the InviteButton is
|
||||
* tapped in order to exercise the public API of the feature invite. If nil, the
|
||||
* InviteButton will not be rendered.
|
||||
*/
|
||||
static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
||||
|
||||
@interface ViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
@@ -43,17 +36,6 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
view.delegate = self;
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
// inviteController
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
inviteController.delegate = self;
|
||||
inviteController.addPeopleEnabled
|
||||
= inviteController.dialOutEnabled
|
||||
= ADD_PEOPLE_CONTROLLER_QUERY != nil;
|
||||
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do want
|
||||
// the Welcome page to be enabled. It defaults to disabled in the SDK at the
|
||||
// time of this writing but it is clearer to be explicit about what we want
|
||||
@@ -135,89 +117,4 @@ static NSString * const ADD_PEOPLE_CONTROLLER_QUERY = nil;
|
||||
[self _onJitsiMeetViewDelegateEvent:@"LOAD_CONFIG_ERROR" withData:data];
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// JMInviteControllerDelegate
|
||||
|
||||
- (void)beginAddPeople:(JMAddPeopleController *)addPeopleController {
|
||||
NSLog(
|
||||
@"[%s:%d] JMInviteControllerDelegate %s",
|
||||
__FILE__, __LINE__, __FUNCTION__);
|
||||
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JMInviteControllerDelegate beginAddPeople: invoked on a non-main thread");
|
||||
|
||||
NSString *query = ADD_PEOPLE_CONTROLLER_QUERY;
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
|
||||
if (query
|
||||
&& (inviteController.addPeopleEnabled
|
||||
|| inviteController.dialOutEnabled)) {
|
||||
addPeopleController.delegate = self;
|
||||
[addPeopleController performQuery:query];
|
||||
} else {
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
|
||||
// it is going to be memory-leaked in the associated JMInviteController
|
||||
// and no subsequent InviteButton clicks/taps will be delivered.
|
||||
[addPeopleController endAddPeople];
|
||||
}
|
||||
}
|
||||
|
||||
// JMAddPeopleControllerDelegate
|
||||
|
||||
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
|
||||
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query {
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JMAddPeopleControllerDelegate addPeopleController:didReceiveResults:forQuery: invoked on a non-main thread");
|
||||
|
||||
NSUInteger count = results.count;
|
||||
|
||||
if (count) {
|
||||
// Exercise JMAddPeopleController's inviteById: implementation.
|
||||
NSMutableArray *ids = [NSMutableArray arrayWithCapacity:count];
|
||||
|
||||
for (NSUInteger i = 0; i < count; ++i) {
|
||||
ids[i] = results[i][@"id"];
|
||||
}
|
||||
|
||||
[controller inviteById:ids];
|
||||
|
||||
// Exercise JMInviteController's invite:withCompletion: implementation.
|
||||
//
|
||||
// XXX Technically, only at most one of the two exercises will result in
|
||||
// an actual invitation eventually.
|
||||
JitsiMeetView *view = (JitsiMeetView *) self.view;
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
|
||||
[inviteController invite:results withCompletion:nil];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
|
||||
// is going to be memory-leaked in the associated JMInviteController and no
|
||||
// subsequent InviteButton clicks/taps will be delivered.
|
||||
[controller endAddPeople];
|
||||
}
|
||||
|
||||
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
|
||||
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController {
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JMAddPeopleControllerDelegate inviteSettled:fromSearchController: invoked on a non-main thread");
|
||||
|
||||
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise, it
|
||||
// is going to be memory-leaked in the associated JMInviteController and no
|
||||
// subsequent InviteButton clicks/taps will be delivered. Technically,
|
||||
// endAddPeople will automatically be invoked if there are no
|
||||
// failedInviteees i.e. the invite succeeeded for all specified invitees.
|
||||
[addPeopleController endAddPeople];
|
||||
}
|
||||
|
||||
#endif // #ifdef DEBUG
|
||||
|
||||
@end
|
||||
|
||||
@@ -35,13 +35,6 @@
|
||||
87FE6F3321E52437004A5DC7 /* incomingMessage.wav in Resources */ = {isa = PBXBuildFile; fileRef = 87FE6F3221E52437004A5DC7 /* incomingMessage.wav */; };
|
||||
A4414AE020B37F1A003546E6 /* rejected.wav in Resources */ = {isa = PBXBuildFile; fileRef = A4414ADF20B37F1A003546E6 /* rejected.wav */; };
|
||||
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
|
||||
B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
|
||||
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85120981A74000DEF7A /* AddPeopleController.m */; };
|
||||
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85320981A74000DEF7A /* AddPeopleController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85420981A74000DEF7A /* InviteControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85C20981A75000DEF7A /* InviteController.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85520981A75000DEF7A /* InviteController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
|
||||
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */; };
|
||||
@@ -61,9 +54,6 @@
|
||||
0B44A0181F902126009D1D64 /* MPVolumeViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPVolumeViewManager.m; sourceTree = "<group>"; };
|
||||
0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingStart.wav; path = ../../sounds/outgoingStart.wav; sourceTree = "<group>"; };
|
||||
0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = outgoingRinging.wav; path = ../../sounds/outgoingRinging.wav; sourceTree = "<group>"; };
|
||||
0B6F414F20987DE600FF6789 /* Invite+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Invite+Private.h"; sourceTree = "<group>"; };
|
||||
0B6F41502098840600FF6789 /* InviteController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "InviteController+Private.h"; sourceTree = "<group>"; };
|
||||
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AddPeopleController+Private.h"; sourceTree = "<group>"; };
|
||||
0B7C2CFC200F51D60060D076 /* LaunchOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchOptions.m; sourceTree = "<group>"; };
|
||||
0B93EF7A1EC608550030D24D /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
|
||||
0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeWrapper.h; sourceTree = "<group>"; };
|
||||
@@ -92,13 +82,6 @@
|
||||
A4414ADF20B37F1A003546E6 /* rejected.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = rejected.wav; path = ../../sounds/rejected.wav; sourceTree = "<group>"; };
|
||||
A4A934E8212F3ADB001E9388 /* Dropbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Dropbox.m; sourceTree = "<group>"; };
|
||||
A4A934EB21349A06001E9388 /* Dropbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Dropbox.h; sourceTree = "<group>"; };
|
||||
B386B85020981A74000DEF7A /* InviteController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
|
||||
B386B85120981A74000DEF7A /* AddPeopleController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddPeopleController.m; sourceTree = "<group>"; };
|
||||
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleControllerDelegate.h; sourceTree = "<group>"; };
|
||||
B386B85320981A74000DEF7A /* AddPeopleController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleController.h; sourceTree = "<group>"; };
|
||||
B386B85420981A74000DEF7A /* InviteControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteControllerDelegate.h; sourceTree = "<group>"; };
|
||||
B386B85520981A75000DEF7A /* InviteController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InviteController.h; sourceTree = "<group>"; };
|
||||
B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; };
|
||||
C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
|
||||
C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
|
||||
C69EFA09209A0F650027712B /* JMCallKitEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitEmitter.swift; sourceTree = "<group>"; };
|
||||
@@ -171,7 +154,6 @@
|
||||
A4A934E7212F3AB8001E9388 /* dropbox */,
|
||||
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */,
|
||||
B386B84F20981A11000DEF7A /* invite */,
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
@@ -210,23 +192,6 @@
|
||||
path = dropbox;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B386B84F20981A11000DEF7A /* invite */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B386B85320981A74000DEF7A /* AddPeopleController.h */,
|
||||
B386B85120981A74000DEF7A /* AddPeopleController.m */,
|
||||
0B6F4151209884E500FF6789 /* AddPeopleController+Private.h */,
|
||||
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */,
|
||||
B386B85620981A75000DEF7A /* Invite.m */,
|
||||
0B6F414F20987DE600FF6789 /* Invite+Private.h */,
|
||||
B386B85520981A75000DEF7A /* InviteController.h */,
|
||||
B386B85020981A74000DEF7A /* InviteController.m */,
|
||||
0B6F41502098840600FF6789 /* InviteController+Private.h */,
|
||||
B386B85420981A74000DEF7A /* InviteControllerDelegate.h */,
|
||||
);
|
||||
path = invite;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C5E72ADFC30ED96F9B35F076 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -263,14 +228,10 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B386B85C20981A75000DEF7A /* InviteController.h in Headers */,
|
||||
B386B85B20981A75000DEF7A /* InviteControllerDelegate.h in Headers */,
|
||||
C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */,
|
||||
0B412F181EDEC65D00B1A0A6 /* JitsiMeetView.h in Headers */,
|
||||
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */,
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
B386B85A20981A75000DEF7A /* AddPeopleController.h in Headers */,
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -466,12 +427,9 @@
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */,
|
||||
B386B85D20981A75000DEF7A /* Invite.m in Sources */,
|
||||
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */,
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */,
|
||||
B386B85720981A75000DEF7A /* InviteController.m in Sources */,
|
||||
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */,
|
||||
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */,
|
||||
0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
|
||||
0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
|
||||
@@ -17,9 +17,3 @@
|
||||
// JitsiMeetView
|
||||
#import <JitsiMeet/JitsiMeetView.h>
|
||||
#import <JitsiMeet/JitsiMeetViewDelegate.h>
|
||||
|
||||
// invite/
|
||||
#import <JitsiMeet/AddPeopleController.h>
|
||||
#import <JitsiMeet/AddPeopleControllerDelegate.h>
|
||||
#import <JitsiMeet/InviteController.h>
|
||||
#import <JitsiMeet/InviteControllerDelegate.h>
|
||||
|
||||
@@ -17,19 +17,18 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "InviteController.h"
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (class, copy, nonatomic, nullable) NSString *conferenceActivityType;
|
||||
|
||||
@property (nonatomic) NSDictionary *colorScheme;
|
||||
|
||||
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
|
||||
@property (nonatomic, readonly, nonnull) JMInviteController *inviteController;
|
||||
|
||||
@property (nonatomic) BOOL pictureInPictureEnabled;
|
||||
|
||||
@property (nonatomic) BOOL welcomePageEnabled;
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
|
||||
#import "Dropbox.h"
|
||||
#import "Invite+Private.h"
|
||||
#import "InviteController+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "RCTBridgeWrapper.h"
|
||||
|
||||
@@ -233,13 +231,11 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
props[@"defaultURL"] = [self.defaultURL absoluteString];
|
||||
}
|
||||
|
||||
props[@"colorScheme"] = self.colorScheme;
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
props[@"pictureInPictureEnabled"] = @(self.pictureInPictureEnabled);
|
||||
props[@"welcomePageEnabled"] = @(self.welcomePageEnabled);
|
||||
|
||||
props[@"addPeopleEnabled"] = @(_inviteController.addPeopleEnabled);
|
||||
props[@"dialOutEnabled"] = @(_inviteController.dialOutEnabled);
|
||||
|
||||
// XXX If urlObject is nil, then it must appear as undefined in the
|
||||
// JavaScript source code so that we check the launchOptions there.
|
||||
if (urlObject) {
|
||||
@@ -420,10 +416,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
[views setObject:self forKey:externalAPIScope];
|
||||
|
||||
_inviteController
|
||||
= [[JMInviteController alloc] initWithExternalAPIScope:externalAPIScope
|
||||
bridgeWrapper:bridgeWrapper];
|
||||
|
||||
// Set a background color which is in accord with the JavaScript and Android
|
||||
// parts of the application and causes less perceived visual flicker than
|
||||
// the default background color.
|
||||
|
||||
@@ -1,33 +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 "AddPeopleController.h"
|
||||
#import "InviteController.h"
|
||||
|
||||
@interface JMAddPeopleController ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary* _Nonnull items;
|
||||
@property (nonatomic, weak, nullable) JMInviteController *owner;
|
||||
@property (nonatomic, readonly) NSString* _Nonnull uuid;
|
||||
|
||||
- (instancetype _Nonnull)initWithOwner:(JMInviteController * _Nonnull)owner;
|
||||
|
||||
- (void)inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees;
|
||||
|
||||
- (void)receivedResults:(NSArray<NSDictionary*> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
@end
|
||||
@@ -1,31 +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 <Foundation/Foundation.h>
|
||||
|
||||
#import "AddPeopleControllerDelegate.h"
|
||||
|
||||
@interface JMAddPeopleController: NSObject
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JMAddPeopleControllerDelegate> delegate;
|
||||
|
||||
- (void)endAddPeople;
|
||||
|
||||
- (void)inviteById:(NSArray<NSString *> * _Nonnull)ids;
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query;
|
||||
|
||||
@end
|
||||
@@ -1,82 +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 "AddPeopleController+Private.h"
|
||||
#import "InviteController+Private.h"
|
||||
|
||||
@implementation JMAddPeopleController
|
||||
|
||||
- (instancetype)initWithOwner:(JMInviteController *)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
|
||||
@@ -1,41 +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 <Foundation/Foundation.h>
|
||||
|
||||
#import "AddPeopleController.h"
|
||||
|
||||
@class JMAddPeopleController;
|
||||
|
||||
@protocol JMAddPeopleControllerDelegate
|
||||
|
||||
/**
|
||||
* Called when a JMAddPeopleController has results for a query that was
|
||||
* previously provided.
|
||||
*/
|
||||
- (void)addPeopleController:(JMAddPeopleController * _Nonnull)controller
|
||||
didReceiveResults:(NSArray<NSDictionary *> * _Nonnull)results
|
||||
forQuery:(NSString * _Nonnull)query;
|
||||
|
||||
/**
|
||||
* Called when a JMAddPeopleController has finished the inviting process, either
|
||||
* succesfully or not. In case of failure the failedInvitees array will contain
|
||||
* the items for which invitations failed.
|
||||
*/
|
||||
- (void) inviteSettled:(NSArray<NSDictionary *> * _Nonnull)failedInvitees
|
||||
fromSearchController:(JMAddPeopleController * _Nonnull)addPeopleController;
|
||||
|
||||
@end
|
||||
@@ -1,30 +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>
|
||||
|
||||
@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
|
||||
@@ -1,127 +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 "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 InviteEmitterEvent
|
||||
= @"org.jitsi.meet:features/invite#invite";
|
||||
static NSString * const PerformQueryEmitterEvent
|
||||
= @"org.jitsi.meet:features/invite#performQuery";
|
||||
|
||||
@implementation Invite
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
/**
|
||||
* Make sure all methods in this module are invoked on the main/UI thread.
|
||||
*/
|
||||
- (dispatch_queue_t)methodQueue {
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)supportedEvents {
|
||||
return @[
|
||||
InviteEmitterEvent,
|
||||
PerformQueryEmitterEvent
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the process to add people. This involves calling a delegate method
|
||||
* in the JMInviteControllerDelegate so the native host application can start
|
||||
* the query process.
|
||||
*
|
||||
* @param externalAPIScope - Scope identifying the JitsiMeetView where the
|
||||
* calling JS code is being executed.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(beginAddPeople:(NSString *)externalAPIScope) {
|
||||
JitsiMeetView *view
|
||||
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
[inviteController beginAddPeople];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the the invite process has settled / finished.
|
||||
*
|
||||
* @param externalAPIScope - Scope identifying the JitsiMeetView where the
|
||||
* calling JS code is being executed.
|
||||
* @param addPeopleControllerScope - Scope identifying the JMAddPeopleController
|
||||
* wich was settled.
|
||||
* @param failedInvitees - Array with the invitees which were not invited due
|
||||
* to a failure.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(inviteSettled:(NSString *)externalAPIScope
|
||||
addPeopleControllerScope:(NSString *)addPeopleControllerScope
|
||||
failedInvitees:(NSArray *)failedInvitees) {
|
||||
JitsiMeetView *view
|
||||
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
[inviteController inviteSettled:addPeopleControllerScope
|
||||
failedInvitees:failedInvitees];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process results received for the given query. This involves calling a
|
||||
* delegate method in JMAddPeopleControllerDelegate so the native host
|
||||
* application is made aware of the query results.
|
||||
*
|
||||
* @param externalAPIScope - Scope identifying the JitsiMeetView where the
|
||||
* calling JS code is being executed.
|
||||
* @param addPeopleControllerScope - Scope identifying the JMAddPeopleController
|
||||
* for which the results were received.
|
||||
* @param query - The actual query for which the results were received.
|
||||
* @param results - The query results.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(receivedResults:(NSString *)externalAPIScope
|
||||
addPeopleControllerScope:(NSString *)addPeopleControllerScope
|
||||
query:(NSString *)query
|
||||
results:(NSArray *)results) {
|
||||
JitsiMeetView *view
|
||||
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
||||
JMInviteController *inviteController = view.inviteController;
|
||||
[inviteController receivedResults:addPeopleControllerScope
|
||||
query:query
|
||||
results:results];
|
||||
}
|
||||
|
||||
- (void) invite:(NSArray<NSDictionary *> * _Nonnull)invitees
|
||||
externalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
|
||||
[self sendEventWithName:InviteEmitterEvent
|
||||
body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
|
||||
@"externalAPIScope": externalAPIScope,
|
||||
@"invitees": invitees }];
|
||||
}
|
||||
|
||||
- (void) performQuery:(NSString * _Nonnull)query
|
||||
externalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
addPeopleControllerScope:(NSString * _Nonnull) addPeopleControllerScope {
|
||||
[self sendEventWithName:PerformQueryEmitterEvent
|
||||
body:@{ @"addPeopleControllerScope": addPeopleControllerScope,
|
||||
@"externalAPIScope": externalAPIScope,
|
||||
@"query": query }];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,53 +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 "InviteController.h"
|
||||
|
||||
#import "AddPeopleController.h"
|
||||
#import "Invite+Private.h"
|
||||
#import "RCTBridgeWrapper.h"
|
||||
|
||||
@interface JMInviteController ()
|
||||
|
||||
@property (nonatomic, nullable) JMAddPeopleController *addPeopleController;
|
||||
|
||||
@property (nonatomic) NSString * _Nonnull externalAPIScope;
|
||||
|
||||
@property (nonatomic, nullable, weak) RCTBridgeWrapper *bridgeWrapper;
|
||||
|
||||
@property (nonatomic, readonly) Invite * _Nullable inviteModule;
|
||||
|
||||
- (instancetype _Nonnull)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
bridgeWrapper:(RCTBridgeWrapper * _Nullable)bridgeWrapper;
|
||||
|
||||
- (void)beginAddPeople;
|
||||
|
||||
- (void)endAddPeopleForController:(JMAddPeopleController * _Nonnull)controller;
|
||||
|
||||
- (void) invite:(NSArray * _Nonnull)invitees
|
||||
forController:(JMAddPeopleController * _Nonnull)controller;
|
||||
|
||||
- (void)inviteSettled:(NSString * _Nonnull)addPeopleControllerScope
|
||||
failedInvitees:(NSArray * _Nonnull)failedInvitees;
|
||||
|
||||
- (void)performQuery:(NSString * _Nonnull)query
|
||||
forController:(JMAddPeopleController * _Nonnull)controller;
|
||||
|
||||
- (void)receivedResults:(NSString * _Nonnull)addPeopleControllerScope
|
||||
query:(NSString * _Nonnull)query
|
||||
results:(NSArray * _Nonnull)results;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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 JMInviteController : NSObject
|
||||
|
||||
@property (nonatomic) BOOL addPeopleEnabled;
|
||||
|
||||
@property (nonatomic) BOOL dialOutEnabled;
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JMInviteControllerDelegate> delegate;
|
||||
|
||||
- (void) invite:(NSArray * _Nonnull)invitees
|
||||
withCompletion:(void (^ _Nullable)(NSArray<NSDictionary *> * _Nonnull failedInvitees))completion;
|
||||
|
||||
@end
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* 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 JMInviteController {
|
||||
NSNumber *_addPeopleEnabled;
|
||||
NSNumber *_dialOutEnabled;
|
||||
}
|
||||
|
||||
@dynamic addPeopleEnabled;
|
||||
@dynamic dialOutEnabled;
|
||||
|
||||
#pragma mark Constructor
|
||||
|
||||
-(instancetype)initWithExternalAPIScope:(NSString * _Nonnull)externalAPIScope
|
||||
bridgeWrapper:(RCTBridgeWrapper * _Nullable)bridgeWrapper {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.externalAPIScope = externalAPIScope;
|
||||
self.bridgeWrapper = bridgeWrapper;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Public API
|
||||
|
||||
-(Invite * _Nullable)inviteModule {
|
||||
return [self.bridgeWrapper.bridge moduleForName:@"Invite"];
|
||||
}
|
||||
|
||||
-(void)beginAddPeople {
|
||||
if (_delegate == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_addPeopleController != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_addPeopleController = [[JMAddPeopleController alloc] initWithOwner:self];
|
||||
|
||||
@try {
|
||||
if (self.delegate
|
||||
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)]) {
|
||||
[self.delegate beginAddPeople:_addPeopleController];
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
[self endAddPeopleForController:_addPeopleController];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)endAddPeopleForController:(JMAddPeopleController *)controller {
|
||||
if (self.addPeopleController == controller) {
|
||||
self.addPeopleController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Property getters / setters
|
||||
|
||||
- (void) setAddPeopleEnabled:(BOOL)addPeopleEnabled {
|
||||
_addPeopleEnabled = [NSNumber numberWithBool:addPeopleEnabled];
|
||||
}
|
||||
|
||||
- (BOOL) addPeopleEnabled {
|
||||
if (_addPeopleEnabled == nil || [_addPeopleEnabled boolValue]) {
|
||||
return self.delegate
|
||||
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) setDialOutEnabled:(BOOL)dialOutEnabled {
|
||||
_dialOutEnabled = [NSNumber numberWithBool:dialOutEnabled];
|
||||
}
|
||||
|
||||
- (BOOL) dialOutEnabled {
|
||||
if (_dialOutEnabled == nil || [_dialOutEnabled boolValue]) {
|
||||
return self.delegate
|
||||
&& [self.delegate respondsToSelector:@selector(beginAddPeople:)];
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark Result handling
|
||||
|
||||
- (void)inviteSettled:(NSString *)addPeopleControllerScope
|
||||
failedInvitees:(NSArray *)failedInvitees {
|
||||
JMAddPeopleController *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 {
|
||||
JMAddPeopleController *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:(JMAddPeopleController * _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:(JMAddPeopleController * _Nonnull)controller {
|
||||
[self.inviteModule performQuery:query
|
||||
externalAPIScope:self.externalAPIScope
|
||||
addPeopleControllerScope:controller.uuid];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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 JMInviteControllerDelegate <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:(JMAddPeopleController *)addPeopleController;
|
||||
|
||||
@end
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"en": "English",
|
||||
"af": "Afrikaans",
|
||||
"az": "Azerbaijani",
|
||||
"bg": "Bulgarian",
|
||||
"cs": "Czech",
|
||||
|
||||
@@ -87,7 +87,8 @@ const AudioLevels = {
|
||||
|
||||
// External circle audio level.
|
||||
const ext = {
|
||||
level: ((int.level * scale * level) + int.level).toFixed(0),
|
||||
level: parseFloat(
|
||||
((int.level * scale * level) + int.level).toFixed(0)),
|
||||
color: interfaceConfig.AUDIO_LEVEL_SECONDARY_COLOR
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import {
|
||||
Avatar,
|
||||
getAvatarURLByParticipantId
|
||||
} from '../../../react/features/base/participants';
|
||||
import { PresenceLabel } from '../../../react/features/presence-status';
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
@@ -14,9 +18,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
import {
|
||||
JitsiParticipantConnectionStatus
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
getAvatarURLByParticipantId
|
||||
} from '../../../react/features/base/participants';
|
||||
import {
|
||||
updateKnownLargeVideoResolution
|
||||
} from '../../../react/features/large-video';
|
||||
@@ -95,6 +96,9 @@ export default class LargeVideoManager {
|
||||
= this._onVideoResolutionUpdate.bind(this);
|
||||
|
||||
this.videoContainer.addResizeListener(this._onVideoResolutionUpdate);
|
||||
|
||||
this._dominantSpeakerAvatarContainer
|
||||
= document.getElementById('dominantSpeakerAvatarContainer');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,6 +113,8 @@ export default class LargeVideoManager {
|
||||
|
||||
this.removePresenceLabel();
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this._dominantSpeakerAvatarContainer);
|
||||
|
||||
this.$container.css({ display: 'none' });
|
||||
}
|
||||
|
||||
@@ -394,7 +400,17 @@ export default class LargeVideoManager {
|
||||
* Updates the src of the dominant speaker avatar
|
||||
*/
|
||||
updateAvatar(avatarUrl) {
|
||||
$('#dominantSpeakerAvatar').attr('src', avatarUrl);
|
||||
if (avatarUrl) {
|
||||
ReactDOM.render(
|
||||
<Avatar
|
||||
id = "dominantSpeakerAvatar"
|
||||
uri = { avatarUrl } />,
|
||||
this._dominantSpeakerAvatarContainer
|
||||
);
|
||||
} else {
|
||||
ReactDOM.unmountComponentAtNode(
|
||||
this._dominantSpeakerAvatarContainer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -265,7 +265,7 @@ export class VideoContainer extends LargeContainer {
|
||||
*/
|
||||
this.$wrapperParent = this.$wrapper.parent();
|
||||
|
||||
this.avatarHeight = $('#dominantSpeakerAvatar').height();
|
||||
this.avatarHeight = $('#dominantSpeakerAvatarContainer').height();
|
||||
|
||||
const onPlayingCallback = function(event) {
|
||||
if (typeof resizeContainer === 'function') {
|
||||
@@ -408,7 +408,7 @@ export class VideoContainer extends LargeContainer {
|
||||
*/
|
||||
_positionParticipantStatus($element) {
|
||||
if (this.avatarDisplayed) {
|
||||
const $avatarImage = $('#dominantSpeakerAvatar');
|
||||
const $avatarImage = $('#dominantSpeakerAvatarContainer');
|
||||
|
||||
$element.css(
|
||||
'top',
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||
import {
|
||||
getLocalParticipant as getLocalParticipantFromStore,
|
||||
getPinnedParticipant,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
@@ -75,6 +76,16 @@ function getAllThumbnails() {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper to get the redux representation of the local participant.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getLocalParticipant() {
|
||||
return getLocalParticipantFromStore(APP.store.getState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user ID of the remote participant that is current the dominant
|
||||
* speaker.
|
||||
@@ -181,7 +192,7 @@ const VideoLayout = {
|
||||
},
|
||||
|
||||
changeLocalVideo(stream) {
|
||||
const localId = APP.conference.getMyUserId();
|
||||
const localId = getLocalParticipant().id;
|
||||
|
||||
this.onVideoTypeChanged(localId, stream.videoType);
|
||||
|
||||
@@ -198,7 +209,7 @@ const VideoLayout = {
|
||||
*/
|
||||
mucJoined() {
|
||||
if (largeVideo && !largeVideo.id) {
|
||||
this.updateLargeVideo(APP.conference.getMyUserId(), true);
|
||||
this.updateLargeVideo(getLocalParticipant().id, true);
|
||||
}
|
||||
|
||||
// FIXME: replace this call with a generic update call once SmallVideo
|
||||
@@ -302,7 +313,7 @@ const VideoLayout = {
|
||||
// Go with local video
|
||||
logger.info('Fallback to local video...');
|
||||
|
||||
const id = APP.conference.getMyUserId();
|
||||
const { id } = getLocalParticipant();
|
||||
|
||||
logger.info(`electLastVisibleVideo: ${id}`);
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -8431,8 +8431,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#96dfda58411d8168d5edd061374120943f4fff74",
|
||||
"from": "github:jitsi/lib-jitsi-meet#96dfda58411d8168d5edd061374120943f4fff74",
|
||||
"version": "github:jitsi/lib-jitsi-meet#2e1436e20d4d8fb6020497a87b2714dff38a6c86",
|
||||
"from": "github:jitsi/lib-jitsi-meet#2e1436e20d4d8fb6020497a87b2714dff38a6c86",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.13",
|
||||
"@jitsi/sdp-simulcast": "0.2.1",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"jsc-android": "224109.1.0",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#96dfda58411d8168d5edd061374120943f4fff74",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#2e1436e20d4d8fb6020497a87b2714dff38a6c86",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.11",
|
||||
"moment": "2.19.4",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Linking } from 'react-native';
|
||||
|
||||
import '../../analytics';
|
||||
import '../../authentication';
|
||||
import { setColorScheme } from '../../base/color-scheme';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/jwt';
|
||||
import { Platform } from '../../base/react';
|
||||
@@ -36,14 +37,9 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
type Props = AbstractAppProps & {
|
||||
|
||||
/**
|
||||
* Whether the add people feature is enabled.
|
||||
* An object of colors that override the default colors of the app/sdk.
|
||||
*/
|
||||
addPeopleEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether the dial-out feature is enabled.
|
||||
*/
|
||||
dialOutEnabled: boolean,
|
||||
colorScheme: Object,
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled. If {@code true}, a toolbar button
|
||||
@@ -66,6 +62,8 @@ type Props = AbstractAppProps & {
|
||||
* @extends AbstractApp
|
||||
*/
|
||||
export class App extends AbstractApp {
|
||||
_init: Promise<*>;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code App} instance.
|
||||
*
|
||||
@@ -96,6 +94,12 @@ export class App extends AbstractApp {
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
this._init.then(() => {
|
||||
// We set the color scheme early enough so then we avoid any
|
||||
// unnecessary re-renders.
|
||||
this.state.store.dispatch(setColorScheme(this.props.colorScheme));
|
||||
});
|
||||
|
||||
Linking.addEventListener('url', this._onLinkingURL);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
CustomSubmitDialog,
|
||||
FIELD_UNDERLINE,
|
||||
PLACEHOLDER_COLOR,
|
||||
_abstractMapStateToProps,
|
||||
inputDialog as inputDialogStyle
|
||||
} from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import { StyleType } from '../../base/styles';
|
||||
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
|
||||
import styles from './styles';
|
||||
@@ -38,6 +40,11 @@ type Props = {
|
||||
*/
|
||||
_connecting: boolean,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the base/dialog feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* The error which occurred during login/authentication.
|
||||
*/
|
||||
@@ -134,6 +141,7 @@ class LoginDialog extends Component<Props, State> {
|
||||
render() {
|
||||
const {
|
||||
_connecting: connecting,
|
||||
_dialogStyles,
|
||||
_error: error,
|
||||
_progress: progress,
|
||||
t
|
||||
@@ -186,7 +194,7 @@ class LoginDialog extends Component<Props, State> {
|
||||
onChangeText = { this._onUsernameChange }
|
||||
placeholder = { 'user@domain.com' }
|
||||
placeholderTextColor = { PLACEHOLDER_COLOR }
|
||||
style = { inputDialogStyle.field }
|
||||
style = { _dialogStyles.field }
|
||||
underlineColorAndroid = { FIELD_UNDERLINE }
|
||||
value = { this.state.username } />
|
||||
<TextInput
|
||||
@@ -195,7 +203,7 @@ class LoginDialog extends Component<Props, State> {
|
||||
placeholderTextColor = { PLACEHOLDER_COLOR }
|
||||
secureTextEntry = { true }
|
||||
style = { [
|
||||
inputDialogStyle.field,
|
||||
_dialogStyles.field,
|
||||
inputDialogStyle.bottomField
|
||||
] }
|
||||
underlineColorAndroid = { FIELD_UNDERLINE }
|
||||
@@ -289,6 +297,7 @@ class LoginDialog extends Component<Props, State> {
|
||||
* _conference: JitsiConference,
|
||||
* _configHosts: Object,
|
||||
* _connecting: boolean,
|
||||
* _dialogStyles: StyleType,
|
||||
* _error: Object,
|
||||
* _progress: number
|
||||
* }}
|
||||
@@ -307,6 +316,7 @@ function _mapStateToProps(state) {
|
||||
} = state['features/base/connection'];
|
||||
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_conference: authRequired,
|
||||
_configHosts: configHosts,
|
||||
_connecting: Boolean(connecting) || Boolean(thenableWithCancel),
|
||||
|
||||
160
react/features/base/color-scheme/ColorSchemeRegistry.js
Normal file
160
react/features/base/color-scheme/ColorSchemeRegistry.js
Normal file
@@ -0,0 +1,160 @@
|
||||
// @flow
|
||||
|
||||
import { toState } from '../redux';
|
||||
import { StyleType } from '../styles';
|
||||
|
||||
import defaultScheme from './defaultScheme';
|
||||
|
||||
/**
|
||||
* A registry class to register styles that need to be color-schemed.
|
||||
*
|
||||
* This class uses lazy initialization for scheme-ified style definitions on
|
||||
* request.
|
||||
*/
|
||||
class ColorSchemeRegistry {
|
||||
/**
|
||||
* A map of already scheme-ified style definitions.
|
||||
*/
|
||||
_schemedStyles = new Map();
|
||||
|
||||
/**
|
||||
* A map of registered style templates.
|
||||
*/
|
||||
_styleTemplates = new Map();
|
||||
|
||||
/**
|
||||
* Clears the already scheme-ified style definitions. This is useful when
|
||||
* the {@code SET_COLOR_SCHEME} action is dispatched (again).
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
this._schemedStyles.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives the color-scheme applied style definition of a component.
|
||||
*
|
||||
* @param {Object | Function} stateful - An object or function that can be
|
||||
* resolved to Redux state using the {@code toState} function.
|
||||
* @param {string} componentName - The name of the component whose style we
|
||||
* want to retreive.
|
||||
* @returns {StyleType}
|
||||
*/
|
||||
get(stateful: Object | Function, componentName: string): StyleType {
|
||||
let schemedStyle = this._schemedStyles.get(componentName);
|
||||
|
||||
if (!schemedStyle) {
|
||||
schemedStyle
|
||||
= this._applyColorScheme(
|
||||
stateful,
|
||||
componentName,
|
||||
this._styleTemplates.get(componentName));
|
||||
this._schemedStyles.set(componentName, schemedStyle);
|
||||
}
|
||||
|
||||
return schemedStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a style definition to the registry for color-scheming.
|
||||
*
|
||||
* NOTE: It's suggested to only use this registry on styles where color
|
||||
* scheming is needed, otherwise just use a static style object as before.
|
||||
*
|
||||
* @param {string} componentName - The name of the component to register the
|
||||
* style to (e.g. {@code 'Toolbox'}).
|
||||
* @param {StyleType} style - The style definition to register.
|
||||
* @returns {void}
|
||||
*/
|
||||
register(componentName: string, style: StyleType): void {
|
||||
this._styleTemplates.set(componentName, style);
|
||||
|
||||
// If this is a style overwrite, we need to delete the processed version
|
||||
// of the style from the other map
|
||||
this._schemedStyles.delete(componentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a color schemed style object applying the color scheme to every
|
||||
* colors in the style object prepared in a special way.
|
||||
*
|
||||
* @param {Object | Function} stateful - An object or function that can be
|
||||
* resolved to Redux state using the {@code toState} function.
|
||||
* @param {string} componentName - The name of the component to apply the
|
||||
* color scheme to.
|
||||
* @param {StyleType} style - The style definition to apply the color scheme
|
||||
* to.
|
||||
* @returns {StyleType}
|
||||
*/
|
||||
_applyColorScheme(
|
||||
stateful: Object | Function,
|
||||
componentName: string,
|
||||
style: StyleType): StyleType {
|
||||
let schemedStyle;
|
||||
|
||||
if (Array.isArray(style)) {
|
||||
// The style is an array of styles, we apply the same transformation
|
||||
// to each, recursively.
|
||||
schemedStyle = [];
|
||||
|
||||
for (const entry of style) {
|
||||
schemedStyle.push(this._applyColorScheme(
|
||||
stateful, componentName, entry));
|
||||
}
|
||||
} else {
|
||||
// The style is an object, we create a copy of it to avoid in-place
|
||||
// modification.
|
||||
schemedStyle = {
|
||||
...style
|
||||
};
|
||||
|
||||
for (const [
|
||||
styleName,
|
||||
styleValue
|
||||
] of Object.entries(schemedStyle)) {
|
||||
if (typeof styleValue === 'object') {
|
||||
// The value is another style object, we apply the same
|
||||
// transformation recusively.
|
||||
schemedStyle[styleName]
|
||||
= this._applyColorScheme(
|
||||
stateful, componentName, styleValue);
|
||||
} else if (typeof styleValue === 'function') {
|
||||
// The value is a function, which indicates that it's a
|
||||
// dynamic, schemed color we need to resolve.
|
||||
schemedStyle[styleName]
|
||||
= this._getColor(stateful, componentName, styleValue());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return schemedStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get the color value for the provided identifier.
|
||||
*
|
||||
* @param {Object | Function} stateful - An object or function that can be
|
||||
* resolved to Redux state using the {@code toState} function.
|
||||
* @param {string} componentName - The name of the component to get the
|
||||
* color value for.
|
||||
* @param {string} colorDefinition - The string identifier of the color,
|
||||
* e.g. {@code appBackground}.
|
||||
* @returns {string}
|
||||
*/
|
||||
_getColor(
|
||||
stateful: Object | Function,
|
||||
componentName: string,
|
||||
colorDefinition: string): string {
|
||||
const colorScheme = toState(stateful)['features/base/color-scheme'];
|
||||
|
||||
return {
|
||||
...defaultScheme[componentName],
|
||||
...colorScheme[componentName]
|
||||
}[colorDefinition];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ColorSchemeRegistry();
|
||||
11
react/features/base/color-scheme/actionTypes.js
Normal file
11
react/features/base/color-scheme/actionTypes.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* Redux action to signal a color scheme change in the app/sdk.
|
||||
*
|
||||
* {
|
||||
* type: SET_COLOR_SCHEME
|
||||
* colorScheme: Object
|
||||
* }
|
||||
*/
|
||||
export const SET_COLOR_SCHEME = Symbol('SET_COLOR_SCHEME');
|
||||
19
react/features/base/color-scheme/actions.js
Normal file
19
react/features/base/color-scheme/actions.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// @flow
|
||||
|
||||
import { SET_COLOR_SCHEME } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Dispatches a Redux action to set the color scheme of the app/sdk.
|
||||
*
|
||||
* @param {Object} colorScheme - The color scheme to set.
|
||||
* @returns {{
|
||||
* type: SET_COLOR_SCHEME,
|
||||
* colorScheme: Object
|
||||
* }}
|
||||
*/
|
||||
export function setColorScheme(colorScheme: Object): Object {
|
||||
return {
|
||||
type: SET_COLOR_SCHEME,
|
||||
colorScheme
|
||||
};
|
||||
}
|
||||
35
react/features/base/color-scheme/defaultScheme.js
Normal file
35
react/features/base/color-scheme/defaultScheme.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// @flow
|
||||
|
||||
import { ColorPalette, getRGBAFormat } from '../styles';
|
||||
|
||||
/**
|
||||
* The default color scheme of the application.
|
||||
*/
|
||||
export default {
|
||||
'BottomSheet': {
|
||||
background: ColorPalette.blackBlue,
|
||||
icon: ColorPalette.white,
|
||||
label: ColorPalette.white
|
||||
},
|
||||
'Dialog': {
|
||||
background: ColorPalette.blackBlue,
|
||||
border: getRGBAFormat(ColorPalette.white, 0.2),
|
||||
icon: ColorPalette.white,
|
||||
text: ColorPalette.white
|
||||
},
|
||||
'LargeVideo': {
|
||||
background: ColorPalette.black
|
||||
},
|
||||
'Thumbnail': {
|
||||
activeParticipantHighlight: ColorPalette.blue,
|
||||
activeParticipantTint: ColorPalette.black,
|
||||
background: ColorPalette.black
|
||||
},
|
||||
'Toolbox': {
|
||||
button: getRGBAFormat(ColorPalette.white, 0.7),
|
||||
buttonToggled: getRGBAFormat(ColorPalette.buttonUnderlay, 0.7),
|
||||
buttonToggledBorder:
|
||||
getRGBAFormat(ColorPalette.buttonUnderlay, 0.7),
|
||||
hangup: ColorPalette.red
|
||||
}
|
||||
};
|
||||
13
react/features/base/color-scheme/functions.js
Normal file
13
react/features/base/color-scheme/functions.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* A special function to be used in the {@code createColorSchemedStyle} call,
|
||||
* that denotes that the color is a dynamic color.
|
||||
*
|
||||
* @param {string} colorDefinition - The definition of the color to mark to be
|
||||
* resolved.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function schemeColor(colorDefinition: string): Function {
|
||||
return () => colorDefinition;
|
||||
}
|
||||
8
react/features/base/color-scheme/index.js
Normal file
8
react/features/base/color-scheme/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './functions';
|
||||
export { default as ColorSchemeRegistry } from './ColorSchemeRegistry';
|
||||
|
||||
import './reducer';
|
||||
20
react/features/base/color-scheme/middleware.js
Normal file
20
react/features/base/color-scheme/middleware.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// @flow
|
||||
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
import { SET_COLOR_SCHEME } from './actionTypes';
|
||||
import ColorSchemeRegistry from './ColorSchemeRegistry';
|
||||
|
||||
/**
|
||||
* The middleware of the feature {@code base/color-scheme}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register((/* store */) => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_COLOR_SCHEME:
|
||||
return ColorSchemeRegistry.clear();
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
21
react/features/base/color-scheme/reducer.js
Normal file
21
react/features/base/color-scheme/reducer.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { ReducerRegistry } from '../redux';
|
||||
|
||||
import { SET_COLOR_SCHEME } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The reducer of the feature {@code base/color-scheme}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
ReducerRegistry.register('features/base/color-scheme', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case SET_COLOR_SCHEME:
|
||||
return _.cloneDeep(action.colorScheme) || state;
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
import { Icon } from '../../../font-icons';
|
||||
import { StyleType } from '../../../styles';
|
||||
|
||||
import AbstractDialog, {
|
||||
type Props as AbstractProps,
|
||||
@@ -18,6 +19,11 @@ import { brandedDialog as styles } from './styles';
|
||||
export type Props = {
|
||||
...AbstractProps,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
t: Function
|
||||
}
|
||||
|
||||
@@ -43,7 +49,7 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { style } = this.props;
|
||||
const { _dialogStyles, style } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -55,7 +61,7 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.dialog,
|
||||
_dialogStyles.dialog,
|
||||
this.props.style
|
||||
] }>
|
||||
<TouchableOpacity
|
||||
@@ -63,7 +69,7 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
|
||||
style = { styles.closeWrapper }>
|
||||
<Icon
|
||||
name = 'close'
|
||||
style = { styles.closeStyle } />
|
||||
style = { _dialogStyles.closeStyle } />
|
||||
</TouchableOpacity>
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import React from 'react';
|
||||
import { View, Text, TouchableOpacity } from 'react-native';
|
||||
|
||||
import { StyleType } from '../../../styles';
|
||||
|
||||
import BaseDialog, { type Props as BaseProps } from './BaseDialog';
|
||||
import {
|
||||
brandedDialog
|
||||
@@ -11,6 +13,11 @@ import {
|
||||
type Props = {
|
||||
...BaseProps,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
t: Function
|
||||
}
|
||||
|
||||
@@ -46,7 +53,7 @@ class BaseSubmitDialog<P: Props, S: *> extends BaseDialog<P, S> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderContent() {
|
||||
const { t } = this.props;
|
||||
const { _dialogStyles, t } = this.props;
|
||||
const additionalButtons = this._renderAdditionalButtons();
|
||||
|
||||
return (
|
||||
@@ -65,7 +72,7 @@ class BaseSubmitDialog<P: Props, S: *> extends BaseDialog<P, S> {
|
||||
? null : brandedDialog.buttonFarLeft,
|
||||
brandedDialog.buttonFarRight
|
||||
] }>
|
||||
<Text style = { brandedDialog.text }>
|
||||
<Text style = { _dialogStyles.text }>
|
||||
{ t(this._getSubmitButtonKey()) }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
import React, { Component, type Node } from 'react';
|
||||
import { TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../color-scheme';
|
||||
import { Modal } from '../../../react';
|
||||
import { StyleType } from '../../../styles';
|
||||
|
||||
import { bottomSheetStyles as styles } from './styles';
|
||||
|
||||
@@ -12,6 +15,11 @@ import { bottomSheetStyles as styles } from './styles';
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* The children to be displayed within this component.
|
||||
*/
|
||||
@@ -28,7 +36,7 @@ type Props = {
|
||||
* A component emulating Android's BottomSheet. For all intents and purposes,
|
||||
* this component has been designed to work and behave as a {@code Dialog}.
|
||||
*/
|
||||
export default class BottomSheet extends Component<Props> {
|
||||
class BottomSheet extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code BottomSheet} instance.
|
||||
*
|
||||
@@ -47,6 +55,8 @@ export default class BottomSheet extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return [
|
||||
<View
|
||||
key = 'overlay'
|
||||
@@ -60,7 +70,7 @@ export default class BottomSheet extends Component<Props> {
|
||||
onPress = { this._onCancel } >
|
||||
<View style = { styles.backdrop } />
|
||||
</TouchableWithoutFeedback>
|
||||
<View style = { styles.sheet }>
|
||||
<View style = { _styles.sheet }>
|
||||
{ this.props.children }
|
||||
</View>
|
||||
</View>
|
||||
@@ -82,3 +92,19 @@ export default class BottomSheet extends Component<Props> {
|
||||
onCancel && onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {{
|
||||
* _styles: StyleType
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'BottomSheet')
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(BottomSheet);
|
||||
|
||||
@@ -5,6 +5,9 @@ import { Text, TouchableOpacity } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { StyleType } from '../../../styles';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../functions';
|
||||
|
||||
import { type Props as BaseProps } from './BaseDialog';
|
||||
import BaseSubmitDialog from './BaseSubmitDialog';
|
||||
@@ -13,6 +16,11 @@ import { brandedDialog } from './styles';
|
||||
type Props = {
|
||||
...BaseProps,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* Untranslated i18n key of the content to be displayed.
|
||||
*
|
||||
@@ -49,7 +57,7 @@ class ConfirmDialog extends BaseSubmitDialog<Props, *> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderAdditionalButtons() {
|
||||
const { t } = this.props;
|
||||
const { _dialogStyles, t } = this.props;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -57,9 +65,9 @@ class ConfirmDialog extends BaseSubmitDialog<Props, *> {
|
||||
style = { [
|
||||
brandedDialog.button,
|
||||
brandedDialog.buttonFarLeft,
|
||||
brandedDialog.buttonSeparator
|
||||
_dialogStyles.buttonSeparator
|
||||
] }>
|
||||
<Text style = { brandedDialog.text }>
|
||||
<Text style = { _dialogStyles.text }>
|
||||
{ t('dialog.confirmNo') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -72,14 +80,14 @@ class ConfirmDialog extends BaseSubmitDialog<Props, *> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderSubmittable() {
|
||||
const { contentKey, t } = this.props;
|
||||
const { _dialogStyles, contentKey, t } = this.props;
|
||||
const content
|
||||
= typeof contentKey === 'string'
|
||||
? t(contentKey)
|
||||
: this._renderHTML(t(contentKey.key, contentKey.params));
|
||||
|
||||
return (
|
||||
<Text style = { brandedDialog.text }>
|
||||
<Text style = { _dialogStyles.text }>
|
||||
{ content }
|
||||
</Text>
|
||||
);
|
||||
@@ -88,4 +96,4 @@ class ConfirmDialog extends BaseSubmitDialog<Props, *> {
|
||||
_renderHTML: string => Object | string
|
||||
}
|
||||
|
||||
export default translate(connect()(ConfirmDialog));
|
||||
export default translate(connect(_abstractMapStateToProps)(ConfirmDialog));
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../functions';
|
||||
|
||||
import BaseDialog, { type Props } from './BaseDialog';
|
||||
|
||||
/**
|
||||
@@ -19,4 +21,4 @@ class CustomDialog extends BaseDialog<Props, *> {
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(CustomDialog);
|
||||
export default connect(_abstractMapStateToProps)(CustomDialog);
|
||||
|
||||
@@ -4,6 +4,8 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../functions';
|
||||
|
||||
import { type Props as BaseProps } from './BaseDialog';
|
||||
import BaseSubmitDialog from './BaseSubmitDialog';
|
||||
|
||||
@@ -27,4 +29,5 @@ class CustomSubmitDialog extends BaseSubmitDialog<Props, *> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(CustomSubmitDialog));
|
||||
export default translate(
|
||||
connect(_abstractMapStateToProps)(CustomSubmitDialog));
|
||||
|
||||
@@ -5,6 +5,9 @@ import { View, Text, TextInput, TouchableOpacity } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { StyleType } from '../../../styles';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../functions';
|
||||
|
||||
import { type State as AbstractState } from '../AbstractDialog';
|
||||
|
||||
@@ -18,6 +21,11 @@ import {
|
||||
type Props = {
|
||||
...BaseProps,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* The untranslated i18n key for the field label on the dialog.
|
||||
*/
|
||||
@@ -63,7 +71,7 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderContent() {
|
||||
const { okDisabled, t } = this.props;
|
||||
const { _dialogStyles, okDisabled, t } = this.props;
|
||||
|
||||
return (
|
||||
<View>
|
||||
@@ -72,12 +80,12 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
brandedDialog.mainWrapper,
|
||||
styles.fieldWrapper
|
||||
] }>
|
||||
<Text style = { styles.fieldLabel }>
|
||||
<Text style = { _dialogStyles.fieldLabel }>
|
||||
{ t(this.props.contentKey) }
|
||||
</Text>
|
||||
<TextInput
|
||||
onChangeText = { this._onChangeText }
|
||||
style = { styles.field }
|
||||
style = { _dialogStyles.field }
|
||||
underlineColorAndroid = { FIELD_UNDERLINE }
|
||||
value = { this.state.fieldValue }
|
||||
{ ...this.props.textInputProps } />
|
||||
@@ -91,7 +99,7 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
brandedDialog.buttonFarLeft,
|
||||
brandedDialog.buttonFarRight
|
||||
] }>
|
||||
<Text style = { brandedDialog.text }>
|
||||
<Text style = { _dialogStyles.text }>
|
||||
{ t('dialog.Ok') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -130,4 +138,4 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(InputDialog));
|
||||
export default translate(connect(_abstractMapStateToProps)(InputDialog));
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../color-scheme';
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../../styles';
|
||||
|
||||
import { PREFERRED_DIALOG_SIZE } from '../../constants';
|
||||
|
||||
const BORDER_RADIUS = 5;
|
||||
const DIALOG_BORDER_COLOR = 'rgba(255, 255, 255, 0.2)';
|
||||
|
||||
export const FIELD_UNDERLINE = ColorPalette.transparent;
|
||||
|
||||
@@ -22,47 +22,6 @@ export const MD_ITEM_MARGIN_PADDING = 16;
|
||||
|
||||
export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
|
||||
|
||||
/**
|
||||
* Default styles for the items of a {@code BottomSheet}-based menu.
|
||||
*
|
||||
* These have been implemented as per the Material Design guidelines:
|
||||
* {@link https://material.io/guidelines/components/bottom-sheets.html}.
|
||||
*/
|
||||
const bottomSheetItemStyles = createStyleSheet({
|
||||
/**
|
||||
* Container style for a generic item rendered in the menu.
|
||||
*/
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: MD_ITEM_HEIGHT
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the {@code Icon} element in a generic item of the menu.
|
||||
*/
|
||||
iconStyle: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 24
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the label in a generic item rendered in the menu.
|
||||
*/
|
||||
labelStyle: {
|
||||
color: ColorPalette.white,
|
||||
flexShrink: 1,
|
||||
fontSize: MD_FONT_SIZE,
|
||||
marginLeft: 32,
|
||||
opacity: 0.90
|
||||
}
|
||||
});
|
||||
|
||||
export const bottomSheetItemStylesCombined = {
|
||||
...bottomSheetItemStyles,
|
||||
underlayColor: ColorPalette.overflowMenuItemUnderlay
|
||||
};
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of {@code BottomSheet}. These have
|
||||
* been implemented as per the Material Design guidelines:
|
||||
@@ -94,16 +53,6 @@ export const bottomSheetStyles = createStyleSheet({
|
||||
overlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: 'rgba(127, 127, 127, 0.6)'
|
||||
},
|
||||
|
||||
/**
|
||||
* Bottom sheet's base style.
|
||||
*/
|
||||
sheet: {
|
||||
backgroundColor: 'rgb(0, 3, 6)',
|
||||
flex: 1,
|
||||
paddingHorizontal: MD_ITEM_MARGIN_PADDING,
|
||||
paddingVertical: 8
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,38 +80,17 @@ export const brandedDialog = createStyleSheet({
|
||||
borderBottomRightRadius: BORDER_RADIUS
|
||||
},
|
||||
|
||||
buttonSeparator: {
|
||||
borderRightColor: DIALOG_BORDER_COLOR,
|
||||
borderRightWidth: 1
|
||||
},
|
||||
|
||||
buttonWrapper: {
|
||||
alignItems: 'stretch',
|
||||
borderRadius: BORDER_RADIUS,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
closeStyle: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: MD_FONT_SIZE
|
||||
},
|
||||
|
||||
closeWrapper: {
|
||||
alignSelf: 'flex-end',
|
||||
padding: BoxModel.padding
|
||||
},
|
||||
|
||||
dialog: {
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: 'rgb(0, 3, 6)',
|
||||
borderColor: DIALOG_BORDER_COLOR,
|
||||
borderRadius: BORDER_RADIUS,
|
||||
borderWidth: 1,
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
maxWidth: PREFERRED_DIALOG_SIZE
|
||||
},
|
||||
|
||||
mainWrapper: {
|
||||
alignSelf: 'stretch',
|
||||
padding: BoxModel.padding * 2,
|
||||
@@ -179,56 +107,135 @@ export const brandedDialog = createStyleSheet({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
padding: 30
|
||||
},
|
||||
|
||||
text: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: MD_FONT_SIZE,
|
||||
textAlign: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of {@code Dialog}.
|
||||
* Reusable (colored) style for text in any branded dialogs.
|
||||
*/
|
||||
export const dialog = createStyleSheet({
|
||||
/**
|
||||
* The style of the {@code Text} in a {@code Dialog} button.
|
||||
*/
|
||||
buttonText: {
|
||||
color: ColorPalette.blue
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the {@code Text} in a {@code Dialog} button which is
|
||||
* disabled.
|
||||
*/
|
||||
disabledButtonText: {
|
||||
color: ColorPalette.darkGrey
|
||||
}
|
||||
});
|
||||
const brandedDialogText = {
|
||||
color: schemeColor('text'),
|
||||
fontSize: MD_FONT_SIZE,
|
||||
textAlign: 'center'
|
||||
};
|
||||
|
||||
export const inputDialog = createStyleSheet({
|
||||
bottomField: {
|
||||
marginBottom: 0
|
||||
},
|
||||
|
||||
field: {
|
||||
...brandedDialog.text,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: DIALOG_BORDER_COLOR,
|
||||
margin: BoxModel.margin,
|
||||
textAlign: 'left'
|
||||
},
|
||||
|
||||
fieldLabel: {
|
||||
...brandedDialog.text,
|
||||
margin: BoxModel.margin,
|
||||
textAlign: 'left'
|
||||
},
|
||||
|
||||
fieldWrapper: {
|
||||
...brandedDialog.mainWrapper,
|
||||
paddingBottom: BoxModel.padding * 2
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Default styles for the items of a {@code BottomSheet}-based menu.
|
||||
*
|
||||
* These have been implemented as per the Material Design guidelines:
|
||||
* {@link https://material.io/guidelines/components/bottom-sheets.html}.
|
||||
*/
|
||||
ColorSchemeRegistry.register('BottomSheet', {
|
||||
/**
|
||||
* Style for the {@code Icon} element in a generic item of the menu.
|
||||
*/
|
||||
iconStyle: {
|
||||
color: schemeColor('icon'),
|
||||
fontSize: 24
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the label in a generic item rendered in the menu.
|
||||
*/
|
||||
labelStyle: {
|
||||
color: schemeColor('label'),
|
||||
flexShrink: 1,
|
||||
fontSize: MD_FONT_SIZE,
|
||||
marginLeft: 32,
|
||||
opacity: 0.90
|
||||
},
|
||||
|
||||
/**
|
||||
* Bottom sheet's base style.
|
||||
*/
|
||||
sheet: {
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1,
|
||||
paddingHorizontal: MD_ITEM_MARGIN_PADDING,
|
||||
paddingVertical: 8
|
||||
},
|
||||
|
||||
/**
|
||||
* Container style for a generic item rendered in the menu.
|
||||
*/
|
||||
style: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
height: MD_ITEM_HEIGHT
|
||||
},
|
||||
|
||||
/**
|
||||
* Additional style that is not directly used as a style object.
|
||||
*/
|
||||
underlayColor: ColorPalette.overflowMenuItemUnderlay
|
||||
});
|
||||
|
||||
/**
|
||||
* Color schemed styles for all the component based on the abstract dialog.
|
||||
*/
|
||||
ColorSchemeRegistry.register('Dialog', {
|
||||
/**
|
||||
* Separator line for the buttons in a dialog.
|
||||
*/
|
||||
buttonSeparator: {
|
||||
borderRightColor: schemeColor('border'),
|
||||
borderRightWidth: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style of the close icon on a dialog.
|
||||
*/
|
||||
closeStyle: {
|
||||
color: schemeColor('icon'),
|
||||
fontSize: MD_FONT_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* Base style of the dialogs.
|
||||
*/
|
||||
dialog: {
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: schemeColor('background'),
|
||||
borderColor: schemeColor('border'),
|
||||
borderRadius: BORDER_RADIUS,
|
||||
borderWidth: 1,
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
maxWidth: PREFERRED_DIALOG_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* Field on an input dialog.
|
||||
*/
|
||||
field: {
|
||||
...brandedDialogText,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: schemeColor('border'),
|
||||
margin: BoxModel.margin,
|
||||
textAlign: 'left'
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the field label on an input dialog.
|
||||
*/
|
||||
fieldLabel: {
|
||||
...brandedDialogText,
|
||||
margin: BoxModel.margin,
|
||||
textAlign: 'left'
|
||||
},
|
||||
|
||||
text: {
|
||||
...brandedDialogText
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { ColorSchemeRegistry } from '../color-scheme';
|
||||
import { toState } from '../redux';
|
||||
|
||||
/**
|
||||
@@ -15,3 +16,17 @@ import { toState } from '../redux';
|
||||
export function isDialogOpen(stateful: Function | Object, component: Object) {
|
||||
return toState(stateful)['features/base/dialog'].component === component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of any Dialog based component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {{
|
||||
* _dialogStyles: StyleType
|
||||
* }}
|
||||
*/
|
||||
export function _abstractMapStateToProps(state: Object): Object {
|
||||
return {
|
||||
_dialogStyles: ColorSchemeRegistry.get(state, 'Dialog')
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
VideoTrack
|
||||
} from '../../media';
|
||||
import { Container, TintedView } from '../../react';
|
||||
import { StyleType } from '../../styles';
|
||||
import { TestHint } from '../../testing/components';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../tracks';
|
||||
|
||||
@@ -97,6 +98,11 @@ type Props = {
|
||||
*/
|
||||
tintEnabled: boolean,
|
||||
|
||||
/**
|
||||
* The style of the tinting when applied.
|
||||
*/
|
||||
tintStyle: StyleType,
|
||||
|
||||
/**
|
||||
* The test hint id which can be used to locate the {@code ParticipantView}
|
||||
* on the jitsi-meet-torture side. If not provided, the
|
||||
@@ -186,11 +192,12 @@ class ParticipantView extends Component<Props> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
onPress,
|
||||
_avatar: avatar,
|
||||
_connectionStatus: connectionStatus,
|
||||
_renderVideo: renderVideo,
|
||||
_videoTrack: videoTrack
|
||||
_videoTrack: videoTrack,
|
||||
onPress,
|
||||
tintStyle
|
||||
} = this.props;
|
||||
|
||||
const waitForVideoStarted = false;
|
||||
@@ -199,9 +206,10 @@ class ParticipantView extends Component<Props> {
|
||||
const renderAvatar = Boolean(!renderVideo && avatar);
|
||||
|
||||
// If the connection has problems, we will "tint" the video / avatar.
|
||||
const connectionProblem
|
||||
= connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE;
|
||||
const useTint
|
||||
= connectionStatus !== JitsiParticipantConnectionStatus.ACTIVE
|
||||
|| this.props.tintEnabled;
|
||||
= connectionProblem || this.props.tintEnabled;
|
||||
|
||||
const testHintId
|
||||
= this.props.testHintId
|
||||
@@ -238,7 +246,9 @@ class ParticipantView extends Component<Props> {
|
||||
{ useTint
|
||||
|
||||
// If the connection has problems, tint the video / avatar.
|
||||
&& <TintedView /> }
|
||||
&& <TintedView
|
||||
style = {
|
||||
connectionProblem ? undefined : tintStyle } /> }
|
||||
|
||||
{ this.props.useConnectivityInfoLabel
|
||||
&& this._renderConnectionInfo(connectionStatus) }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import { ColorPalette } from '../../../styles';
|
||||
import { TINTED_VIEW_DEFAULT } from './styles';
|
||||
|
||||
/**
|
||||
* Base style for the {@code TintedView} component.
|
||||
@@ -24,16 +24,6 @@ type Props = {
|
||||
*/
|
||||
children?: React$Node,
|
||||
|
||||
/**
|
||||
* Color used as the background of the view. Defaults to
|
||||
*/
|
||||
color: string,
|
||||
|
||||
/**
|
||||
* Opacity for the
|
||||
*/
|
||||
opacity: number,
|
||||
|
||||
/**
|
||||
* Style to override the base style.
|
||||
*/
|
||||
@@ -45,15 +35,6 @@ type Props = {
|
||||
* the given color and opacity.
|
||||
*/
|
||||
export default class TintedView extends Component<Props> {
|
||||
/**
|
||||
* Default values for the component's props.
|
||||
*/
|
||||
static defaultProps = {
|
||||
color: ColorPalette.appBackground,
|
||||
opacity: 0.8,
|
||||
style: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -61,7 +42,7 @@ export default class TintedView extends Component<Props> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { children, color, opacity, style } = this.props;
|
||||
const { children, style } = this.props;
|
||||
|
||||
// XXX Don't tint the children, tint the background only.
|
||||
return (
|
||||
@@ -72,11 +53,8 @@ export default class TintedView extends Component<Props> {
|
||||
pointerEvents = 'none'
|
||||
style = { [
|
||||
BASE_STYLE,
|
||||
style,
|
||||
{
|
||||
backgroundColor: color,
|
||||
opacity
|
||||
}
|
||||
TINTED_VIEW_DEFAULT,
|
||||
style
|
||||
] } />
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
|
||||
@@ -324,6 +324,11 @@ const SIDEBAR_STYLES = {
|
||||
}
|
||||
};
|
||||
|
||||
export const TINTED_VIEW_DEFAULT = {
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
opacity: 0.8
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the generic React {@code Component}s implemented by the feature
|
||||
* base/react.
|
||||
|
||||
@@ -18,6 +18,7 @@ export const ColorPalette = {
|
||||
* the sake of consistency.
|
||||
*/
|
||||
black: BLACK,
|
||||
blackBlue: 'rgb(0, 3, 6)',
|
||||
blue: '#17A0DB',
|
||||
blueHighlight: '#1081b2',
|
||||
buttonUnderlay: '#495258',
|
||||
@@ -27,7 +28,7 @@ export const ColorPalette = {
|
||||
overflowMenuItemUnderlay: '#EEEEEE',
|
||||
red: '#D00000',
|
||||
transparent: 'rgba(0, 0, 0, 0)',
|
||||
white: 'white',
|
||||
white: '#FFFFFF',
|
||||
|
||||
/**
|
||||
* These are colors from the atlaskit to be used on mobile, when needed.
|
||||
|
||||
@@ -6,6 +6,23 @@ import { ColorPalette } from './components';
|
||||
declare type StyleSheet = Object;
|
||||
export type StyleType = StyleSheet | Array<StyleSheet>;
|
||||
|
||||
/**
|
||||
* RegExp pattern for long HEX color format.
|
||||
*/
|
||||
const HEX_LONG_COLOR_FORMAT
|
||||
= /^#([0-9A-F]{2,2})([0-9A-F]{2,2})([0-9A-F]{2,2})$/i;
|
||||
|
||||
/**
|
||||
* RegExp pattern for short HEX color format.
|
||||
*/
|
||||
const HEX_SHORT_COLOR_FORMAT
|
||||
= /^#([0-9A-F]{1,1})([0-9A-F]{1,1})([0-9A-F]{1,1})$/i;
|
||||
|
||||
/**
|
||||
* RegExp pattern for RGB color format.
|
||||
*/
|
||||
const RGB_COLOR_FORMAT = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/i;
|
||||
|
||||
/**
|
||||
* The list of the well-known style properties which may not be numbers on Web
|
||||
* but must be numbers on React Native.
|
||||
@@ -87,6 +104,49 @@ export function fixAndroidViewClipping<T: StyleSheet>(styles: T): T {
|
||||
return styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an rgba format of the provided color if it's in hex or rgb format.
|
||||
*
|
||||
* NOTE: The function will return the same color if it's not in one of those
|
||||
* two formats (e.g. 'white').
|
||||
*
|
||||
* @param {string} color - The string representation of the color in rgb or hex
|
||||
* format.
|
||||
* @param {number} alpha - The alpha value to apply.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getRGBAFormat(color: string, alpha: number): string {
|
||||
let match = color.match(HEX_LONG_COLOR_FORMAT);
|
||||
|
||||
if (match) {
|
||||
return `#${match[1]}${match[2]}${match[3]}${_getAlphaInHex(alpha)}`;
|
||||
}
|
||||
|
||||
match = color.match(HEX_SHORT_COLOR_FORMAT);
|
||||
if (match) {
|
||||
return `#${match[1]}${match[1]}${match[2]}${match[2]}${match[3]}${
|
||||
match[3]}${_getAlphaInHex(alpha)}`;
|
||||
}
|
||||
|
||||
match = color.match(RGB_COLOR_FORMAT);
|
||||
if (match) {
|
||||
return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${alpha})`;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an [0..1] alpha value into HEX.
|
||||
*
|
||||
* @param {number} alpha - The alpha value to convert.
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getAlphaInHex(alpha: number): string {
|
||||
return Number(Math.round(255 * alpha)).toString(16)
|
||||
.padStart(2, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shims style properties to work correctly on native. Allows us to minimize the
|
||||
* number of style declarations that need to be set or overridden for specific
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { Audio, MEDIA_TYPE } from '../../../base/media';
|
||||
import {
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
pinParticipant
|
||||
} from '../../../base/participants';
|
||||
import { Container } from '../../../base/react';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||
|
||||
import { RemoteVideoMenu } from '../../../remote-video-menu';
|
||||
@@ -53,6 +55,11 @@ type Props = {
|
||||
*/
|
||||
_onShowRemoteVideoMenu: ?Function,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* The Redux representation of the participant's video track.
|
||||
*/
|
||||
@@ -106,6 +113,7 @@ class Thumbnail extends Component<Props> {
|
||||
_largeVideo: largeVideo,
|
||||
_onClick,
|
||||
_onShowRemoteVideoMenu,
|
||||
_styles,
|
||||
_videoTrack: videoTrack,
|
||||
disablePin,
|
||||
disableTint,
|
||||
@@ -135,7 +143,7 @@ class Thumbnail extends Component<Props> {
|
||||
style = { [
|
||||
styles.thumbnail,
|
||||
participant.pinned && !disablePin
|
||||
? styles.thumbnailPinned : null,
|
||||
? _styles.thumbnailPinned : null,
|
||||
this.props.styleOverrides || null
|
||||
] }
|
||||
touchFeedback = { false }>
|
||||
@@ -148,7 +156,9 @@ class Thumbnail extends Component<Props> {
|
||||
<ParticipantView
|
||||
avatarSize = { AVATAR_SIZE }
|
||||
participantId = { participantId }
|
||||
style = { _styles.participantViewStyle }
|
||||
tintEnabled = { participantInLargeVideo && !disableTint }
|
||||
tintStyle = { _styles.activeThumbnailTint }
|
||||
zOrder = { 1 } />
|
||||
|
||||
{ participant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
@@ -219,6 +229,7 @@ function _mapDispatchToProps(dispatch: Function, ownProps): Object {
|
||||
* _audioTrack: Track,
|
||||
* _isModerator: boolean,
|
||||
* _largeVideo: Object,
|
||||
* _styles: StyleType,
|
||||
* _videoTrack: Track
|
||||
* }}
|
||||
*/
|
||||
@@ -238,6 +249,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
_audioTrack: audioTrack,
|
||||
_isModerator: isLocalParticipantModerator(state),
|
||||
_largeVideo: largeVideo,
|
||||
_styles: ColorSchemeRegistry.get(state, 'Thumbnail'),
|
||||
_videoTrack: videoTrack
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
|
||||
import { ColorPalette } from '../../base/styles';
|
||||
import { FILMSTRIP_SIZE } from '../constants';
|
||||
|
||||
@@ -134,19 +137,6 @@ export default {
|
||||
position: 'absolute'
|
||||
},
|
||||
|
||||
/**
|
||||
* Pinned video thumbnail style.
|
||||
*/
|
||||
thumbnailPinned: {
|
||||
borderColor: ColorPalette.blue,
|
||||
shadowColor: ColorPalette.black,
|
||||
shadowOffset: {
|
||||
height: 5,
|
||||
width: 5
|
||||
},
|
||||
shadowRadius: 5
|
||||
},
|
||||
|
||||
tileView: {
|
||||
alignSelf: 'center'
|
||||
},
|
||||
@@ -160,3 +150,36 @@ export default {
|
||||
justifyContent: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Color schemed styles for the @{code Thumbnail} component.
|
||||
*/
|
||||
ColorSchemeRegistry.register('Thumbnail', {
|
||||
|
||||
/**
|
||||
* Tinting style of the on-stage participant thumbnail.
|
||||
*/
|
||||
activeThumbnailTint: {
|
||||
backgroundColor: schemeColor('activeParticipantTint')
|
||||
},
|
||||
|
||||
/**
|
||||
* Coloring if the thumbnail background.
|
||||
*/
|
||||
participantViewStyle: {
|
||||
backgroundColor: schemeColor('background')
|
||||
},
|
||||
|
||||
/**
|
||||
* Pinned video thumbnail style.
|
||||
*/
|
||||
thumbnailPinned: {
|
||||
borderColor: schemeColor('activeParticipantHighlight'),
|
||||
shadowColor: schemeColor('activeParticipantHighlight'),
|
||||
shadowOffset: {
|
||||
height: 5,
|
||||
width: 5
|
||||
},
|
||||
shadowRadius: 5
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
/**
|
||||
* 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 redux action which will add pending invite request to the redux
|
||||
* store.
|
||||
|
||||
@@ -38,16 +38,6 @@ type Props = AbstractButtonProps & {
|
||||
_onShareRoom: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The indicator which determines (at bundle time) whether there should be a
|
||||
* button 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;
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to enter add/invite people to the
|
||||
* current call/conference/meeting.
|
||||
@@ -64,35 +54,12 @@ class InviteButton extends AbstractButton<Props, *> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
// FIXME: dispatch _onAddPeople here, when we have a dialog for it.
|
||||
const {
|
||||
_addPeopleEnabled,
|
||||
_dialOutEnabled,
|
||||
_onAddPeople,
|
||||
_onShareRoom
|
||||
} = this.props;
|
||||
|
||||
if (_addPeopleEnabled || _dialOutEnabled) {
|
||||
_onAddPeople();
|
||||
} else if (_SHARE_ROOM_TOOLBAR_BUTTON) {
|
||||
_onShareRoom();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
render() {
|
||||
const { _addPeopleEnabled, _dialOutEnabled } = this.props;
|
||||
|
||||
return (
|
||||
_SHARE_ROOM_TOOLBAR_BUTTON
|
||||
|| _addPeopleEnabled
|
||||
|| _dialOutEnabled
|
||||
? super.render()
|
||||
: null);
|
||||
_onShareRoom();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +77,7 @@ class InviteButton extends AbstractButton<Props, *> {
|
||||
function _mapDispatchToProps(dispatch: Dispatch<*>) {
|
||||
return {
|
||||
/**
|
||||
* Launches native invite dialog.
|
||||
* Launches the add people dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { getAppProp } from '../base/app';
|
||||
import { i18next } from '../base/i18n';
|
||||
import { isLocalParticipantModerator } from '../base/participants';
|
||||
import { doGetJSON, parseURIString } from '../base/util';
|
||||
@@ -275,17 +274,19 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
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));
|
||||
});
|
||||
return fetch(
|
||||
`${inviteServiceUrl}?token=${jwt}`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
'url': inviteUrl
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,18 +298,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
|
||||
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 addPeopleEnabled = getAppProp(state, 'addPeopleEnabled');
|
||||
|
||||
return (
|
||||
(typeof addPeopleEnabled === 'undefined')
|
||||
|| Boolean(addPeopleEnabled));
|
||||
}
|
||||
|
||||
return false;
|
||||
return !isGuest;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,21 +309,9 @@ export function isAddPeopleEnabled(state: Object): boolean {
|
||||
*/
|
||||
export function isDialOutEnabled(state: Object): boolean {
|
||||
const { conference } = state['features/base/conference'];
|
||||
let dialOutEnabled = isLocalParticipantModerator(state)
|
||||
&& conference
|
||||
&& conference.isSIPCallingSupported();
|
||||
|
||||
if (dialOutEnabled) {
|
||||
// XXX The mobile/react-native app is capable of disabling of dial-out.
|
||||
// Anyway, the Web/React app does not have that capability so default
|
||||
// appropriately.
|
||||
dialOutEnabled = getAppProp(state, 'dialOutEnabled');
|
||||
|
||||
return (
|
||||
(typeof dialOutEnabled === 'undefined') || Boolean(dialOutEnabled));
|
||||
}
|
||||
|
||||
return false;
|
||||
return isLocalParticipantModerator(state)
|
||||
&& conference && conference.isSIPCallingSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT, getAppProp } from '../base/app';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { invite } from './actions';
|
||||
import {
|
||||
BEGIN_ADD_PEOPLE,
|
||||
_SET_EMITTER_SUBSCRIPTIONS
|
||||
} from './actionTypes';
|
||||
import {
|
||||
getInviteResultsForQuery,
|
||||
isAddPeopleEnabled,
|
||||
isDialOutEnabled
|
||||
} 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 _SET_EMITTER_SUBSCRIPTIONS:
|
||||
return _setEmitterSubscriptions(store, next, action);
|
||||
|
||||
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#invite',
|
||||
_onInvite,
|
||||
context),
|
||||
emitter.addListener(
|
||||
'org.jitsi.meet:features/invite#performQuery',
|
||||
_onPerformQuery,
|
||||
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(store, 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 externalAPIScope = getAppProp(store, 'externalAPIScope');
|
||||
|
||||
externalAPIScope && Invite.beginAddPeople(externalAPIScope);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the {@code invite} event of the feature invite and invites specific
|
||||
* invitees to the current, ongoing conference.
|
||||
*
|
||||
* @param {Object} event - The details of the event.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onInvite({ addPeopleControllerScope, externalAPIScope, invitees }) {
|
||||
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
|
||||
|
||||
// If there are multiple JitsiMeetView instances alive, they will all get
|
||||
// the event, since there is a single bridge, so make sure we don't act if
|
||||
// the event is not for us.
|
||||
if (getAppProp(getState, 'externalAPIScope') !== externalAPIScope) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(invite(invitees))
|
||||
.then(failedInvitees =>
|
||||
Invite.inviteSettled(
|
||||
externalAPIScope,
|
||||
addPeopleControllerScope,
|
||||
failedInvitees));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the {@code performQuery} event of the feature invite and 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();
|
||||
|
||||
// If there are multiple JitsiMeetView instances alive, they will all get
|
||||
// the event, since there is a single bridge, so make sure we don't act if
|
||||
// the event is not for us.
|
||||
if (getAppProp(state, 'externalAPIScope') !== externalAPIScope) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature invite that the action
|
||||
* {@link _SET_EMITTER_SUBSCRIPTIONS} 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 _SET_EMITTER_SUBSCRIPTIONS}
|
||||
* which is being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {*}
|
||||
*/
|
||||
function _setEmitterSubscriptions({ getState }, next, action) {
|
||||
const { emitterSubscriptions } = getState()['features/invite'];
|
||||
|
||||
if (emitterSubscriptions) {
|
||||
for (const subscription of emitterSubscriptions) {
|
||||
subscription.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import { assign, ReducerRegistry } from '../base/redux';
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
_SET_EMITTER_SUBSCRIPTIONS,
|
||||
ADD_PENDING_INVITE_REQUEST,
|
||||
REMOVE_PENDING_INVITE_REQUESTS,
|
||||
SET_CALLEE_INFO_VISIBLE,
|
||||
@@ -26,9 +25,6 @@ const DEFAULT_STATE = {
|
||||
|
||||
ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case _SET_EMITTER_SUBSCRIPTIONS:
|
||||
return (
|
||||
assign(state, 'emitterSubscriptions', action.emitterSubscriptions));
|
||||
case ADD_PENDING_INVITE_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
@@ -37,6 +33,7 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
|
||||
action.request
|
||||
]
|
||||
};
|
||||
|
||||
case REMOVE_PENDING_INVITE_REQUESTS:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -3,27 +3,34 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { ParticipantView } from '../../base/participants';
|
||||
import { DimensionsDetector } from '../../base/responsive-ui';
|
||||
import { StyleType } from '../../base/styles';
|
||||
|
||||
import styles, { AVATAR_SIZE } from './styles';
|
||||
import { AVATAR_SIZE } from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@link LargeVideo}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant (to be) depicted by LargeVideo.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_participantId: string
|
||||
_participantId: string,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
|
||||
*/
|
||||
onClick: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -114,6 +121,7 @@ class LargeVideo extends Component<Props, State> {
|
||||
} = this.state;
|
||||
const {
|
||||
_participantId,
|
||||
_styles,
|
||||
onClick
|
||||
} = this.props;
|
||||
|
||||
@@ -124,7 +132,7 @@ class LargeVideo extends Component<Props, State> {
|
||||
avatarSize = { avatarSize }
|
||||
onPress = { onClick }
|
||||
participantId = { _participantId }
|
||||
style = { styles.largeVideo }
|
||||
style = { _styles.largeVideo }
|
||||
testHintId = 'org.jitsi.meet.LargeVideo'
|
||||
useConnectivityInfoLabel = { useConnectivityInfoLabel }
|
||||
zOrder = { 0 }
|
||||
@@ -140,12 +148,14 @@ class LargeVideo extends Component<Props, State> {
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _participantId: string
|
||||
* _participantId: string,
|
||||
* _styles: StyleType
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_participantId: state['features/large-video'].participantId
|
||||
_participantId: state['features/large-video'].participantId,
|
||||
_styles: ColorSchemeRegistry.get(state, 'LargeVideo')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,7 @@ export default class LargeVideo extends Component<{}> {
|
||||
|
||||
<div id = 'dominantSpeaker'>
|
||||
<div className = 'dynamic-shadow' />
|
||||
<img
|
||||
id = 'dominantSpeakerAvatar'
|
||||
src = '' />
|
||||
<div id = 'dominantSpeakerAvatarContainer' />
|
||||
</div>
|
||||
<div id = 'remotePresenceMessage' />
|
||||
<span id = 'remoteConnectionMessage' />
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { ColorPalette, createStyleSheet } from '../../base/styles';
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
|
||||
|
||||
/**
|
||||
* Size for the Avatar.
|
||||
*/
|
||||
export const AVATAR_SIZE = 200;
|
||||
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* Color schemed styles for the @{LargeVideo} component.
|
||||
*/
|
||||
ColorSchemeRegistry.register('LargeVideo', {
|
||||
|
||||
/**
|
||||
* Large video container style.
|
||||
@@ -15,7 +18,7 @@ export default createStyleSheet({
|
||||
largeVideo: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ type State = {
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class AbstractStreamKeyForm extends Component<Props, State> {
|
||||
export default class AbstractStreamKeyForm<P: Props>
|
||||
extends Component<P, State> {
|
||||
helpURL: string;
|
||||
_debouncedUpdateValidationErrorVisibility: Function;
|
||||
|
||||
@@ -61,7 +62,7 @@ export default class AbstractStreamKeyForm extends Component<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -88,7 +89,7 @@ export default class AbstractStreamKeyForm extends Component<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: P) {
|
||||
if (this.props.value !== prevProps.value) {
|
||||
this._debouncedUpdateValidationErrorVisibility();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
|
||||
import {
|
||||
GOOGLE_API_STATES,
|
||||
@@ -23,6 +25,11 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* The Redux dispatch Function.
|
||||
*/
|
||||
@@ -102,7 +109,7 @@ class GoogleSigninForm extends Component<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { _dialogStyles, t } = this.props;
|
||||
const { googleAPIState, googleResponse } = this.props;
|
||||
const signedInUser = googleResponse
|
||||
&& googleResponse.user
|
||||
@@ -121,7 +128,11 @@ class GoogleSigninForm extends Component<Props> {
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.helpText }>
|
||||
<Text style = { styles.text }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.text
|
||||
] }>
|
||||
{ userInfo }
|
||||
</Text>
|
||||
</View>
|
||||
@@ -225,6 +236,7 @@ function _mapStateToProps(state: Object) {
|
||||
const { googleAPIState, googleResponse } = state['features/google-api'];
|
||||
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
googleAPIState,
|
||||
googleResponse
|
||||
};
|
||||
|
||||
@@ -2,13 +2,24 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Linking, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
|
||||
import AbstractStreamKeyForm, {
|
||||
type Props
|
||||
type Props as AbstractProps
|
||||
} from '../AbstractStreamKeyForm';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType
|
||||
};
|
||||
|
||||
import styles, { PLACEHOLDER_COLOR } from './styles';
|
||||
|
||||
/**
|
||||
@@ -16,7 +27,7 @@ import styles, { PLACEHOLDER_COLOR } from './styles';
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
/**
|
||||
* Initializes a new {@code StreamKeyForm} instance.
|
||||
*
|
||||
@@ -37,11 +48,16 @@ class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { _dialogStyles, t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<Text style = { styles.streamKeyInputLabel }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.text,
|
||||
styles.streamKeyInputLabel
|
||||
] }>
|
||||
{
|
||||
t('dialog.streamKey')
|
||||
}
|
||||
@@ -56,7 +72,11 @@ class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
{
|
||||
this.state.showValidationError
|
||||
? <View style = { styles.formFooterItem }>
|
||||
<Text style = { styles.warningText }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.warningText
|
||||
] }>
|
||||
{ t('liveStreaming.invalidStreamKey') }
|
||||
</Text>
|
||||
</View>
|
||||
@@ -66,7 +86,11 @@ class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
<TouchableOpacity
|
||||
onPress = { this._onOpenHelp }
|
||||
style = { styles.streamKeyHelp } >
|
||||
<Text style = { styles.text }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.text
|
||||
] }>
|
||||
{
|
||||
t('liveStreaming.streamIdHelp')
|
||||
}
|
||||
@@ -98,4 +122,4 @@ class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StreamKeyForm);
|
||||
export default translate(connect(_abstractMapStateToProps)(StreamKeyForm));
|
||||
|
||||
@@ -8,8 +8,11 @@ import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { StyleType } from '../../../../base/styles';
|
||||
|
||||
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
|
||||
|
||||
@@ -17,6 +20,11 @@ import styles, { ACTIVE_OPACITY, TOUCHABLE_UNDERLAY } from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* The list of broadcasts the user can pick from.
|
||||
*/
|
||||
@@ -74,7 +82,7 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { broadcasts } = this.props;
|
||||
const { _dialogStyles, broadcasts } = this.props;
|
||||
|
||||
if (!broadcasts) {
|
||||
return null;
|
||||
@@ -85,7 +93,11 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
<View style = { styles.formWrapper }>
|
||||
<TouchableOpacity
|
||||
onPress = { this._onOpenYoutubeDashboard }>
|
||||
<Text style = { styles.warningText }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.warningText
|
||||
] }>
|
||||
{ this.props.t(
|
||||
'liveStreaming.getStreamKeyManually') }
|
||||
</Text>
|
||||
@@ -97,7 +109,11 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
return (
|
||||
<View style = { styles.formWrapper }>
|
||||
<View style = { styles.streamKeyPickerCta }>
|
||||
<Text style = { styles.text }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.text
|
||||
] }>
|
||||
{ this.props.t('liveStreaming.choose') }
|
||||
</Text>
|
||||
</View>
|
||||
@@ -113,7 +129,11 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
? styles.streamKeyPickerItemHighlight : null
|
||||
] }
|
||||
underlayColor = { TOUCHABLE_UNDERLAY }>
|
||||
<Text style = { styles.text }>
|
||||
<Text
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.text
|
||||
] }>
|
||||
{ broadcast.title }
|
||||
</Text>
|
||||
</TouchableHighlight>))
|
||||
@@ -155,4 +175,5 @@ class StreamKeyPicker extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StreamKeyPicker);
|
||||
export default translate(
|
||||
connect(_abstractMapStateToProps)(StreamKeyPicker));
|
||||
|
||||
@@ -99,8 +99,7 @@ export default createStyleSheet({
|
||||
* Label for the previous field.
|
||||
*/
|
||||
streamKeyInputLabel: {
|
||||
alignSelf: 'flex-start',
|
||||
color: ColorPalette.white
|
||||
alignSelf: 'flex-start'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -146,7 +145,8 @@ export default createStyleSheet({
|
||||
},
|
||||
|
||||
text: {
|
||||
color: ColorPalette.white
|
||||
fontSize: 14,
|
||||
textAlign: 'left'
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ import AbstractStreamKeyForm, {
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class StreamKeyForm extends AbstractStreamKeyForm {
|
||||
class StreamKeyForm extends AbstractStreamKeyForm<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code StreamKeyForm} instance.
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
createRecordingDialogEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { DialogContent } from '../../../base/dialog';
|
||||
import {
|
||||
DialogContent,
|
||||
_abstractMapStateToProps
|
||||
} from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import {
|
||||
Container,
|
||||
@@ -15,6 +18,7 @@ import {
|
||||
Switch,
|
||||
Text
|
||||
} from '../../../base/react';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
|
||||
|
||||
import styles from './styles';
|
||||
@@ -22,6 +26,11 @@ import { getRecordingDurationEstimation } from '../../functions';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Style of the dialogs feature.
|
||||
*/
|
||||
_dialogStyles: StyleType,
|
||||
|
||||
/**
|
||||
* The redux dispatch function.
|
||||
*/
|
||||
@@ -99,7 +108,7 @@ class StartRecordingDialogContent extends Component<Props> {
|
||||
*/
|
||||
_renderNoIntegrationsContent() {
|
||||
return (
|
||||
<DialogContent style = { styles.noIntegrationContent }>
|
||||
<DialogContent style = { this.props._dialogStyles.text }>
|
||||
{ this.props.t('recording.startRecordingBody') }
|
||||
</DialogContent>
|
||||
);
|
||||
@@ -112,7 +121,7 @@ class StartRecordingDialogContent extends Component<Props> {
|
||||
* @returns {React$Component}
|
||||
*/
|
||||
_renderIntegrationsContent() {
|
||||
const { isTokenValid, isValidating, t } = this.props;
|
||||
const { _dialogStyles, isTokenValid, isValidating, t } = this.props;
|
||||
|
||||
let content = null;
|
||||
|
||||
@@ -135,7 +144,10 @@ class StartRecordingDialogContent extends Component<Props> {
|
||||
style = { styles.header }>
|
||||
<Text
|
||||
className = 'recording-title'
|
||||
style = { styles.title }>
|
||||
style = { [
|
||||
_dialogStyles.text,
|
||||
styles.title
|
||||
] }>
|
||||
{ t('recording.authDropboxText') }
|
||||
</Text>
|
||||
<Switch
|
||||
@@ -248,4 +260,5 @@ class StartRecordingDialogContent extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(StartRecordingDialogContent));
|
||||
export default translate(
|
||||
connect(_abstractMapStateToProps)(StartRecordingDialogContent));
|
||||
|
||||
@@ -28,10 +28,6 @@ export default createStyleSheet({
|
||||
paddingBottom: _PADDING
|
||||
},
|
||||
|
||||
noIntegrationContent: {
|
||||
color: ColorPalette.white
|
||||
},
|
||||
|
||||
startRecordingText: {
|
||||
paddingBottom: _PADDING
|
||||
},
|
||||
@@ -42,7 +38,6 @@ export default createStyleSheet({
|
||||
},
|
||||
|
||||
title: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
|
||||
@@ -4,15 +4,16 @@ import React, { Component } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import {
|
||||
BottomSheet,
|
||||
bottomSheetItemStylesCombined
|
||||
BottomSheet
|
||||
} from '../../../base/dialog';
|
||||
import {
|
||||
Avatar,
|
||||
getAvatarURL,
|
||||
getParticipantDisplayName
|
||||
} from '../../../base/participants';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
|
||||
import { hideRemoteVideoMenu } from '../../actions';
|
||||
|
||||
@@ -42,6 +43,11 @@ type Props = {
|
||||
*/
|
||||
_avatarURL: string,
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the BottomSheet.
|
||||
*/
|
||||
_bottomSheetStyles: StyleType,
|
||||
|
||||
/**
|
||||
* Display name of the participant retreived from Redux.
|
||||
*/
|
||||
@@ -73,7 +79,7 @@ class RemoteVideoMenu extends Component<Props> {
|
||||
afterClick: this._onCancel,
|
||||
showLabel: true,
|
||||
participantID: this.props.participant.id,
|
||||
styles: bottomSheetItemStylesCombined
|
||||
styles: this.props._bottomSheetStyles
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -113,6 +119,7 @@ class RemoteVideoMenu extends Component<Props> {
|
||||
* @private
|
||||
* @returns {{
|
||||
* _avatarURL: string,
|
||||
* _bottomSheetStyles: StyleType,
|
||||
* _participantDisplayName: string
|
||||
* }}
|
||||
*/
|
||||
@@ -121,6 +128,8 @@ function _mapStateToProps(state, ownProps) {
|
||||
|
||||
return {
|
||||
_avatarURL: getAvatarURL(participant),
|
||||
_bottomSheetStyles:
|
||||
ColorSchemeRegistry.get(state, 'BottomSheet'),
|
||||
_participantDisplayName: getParticipantDisplayName(
|
||||
state, participant.id)
|
||||
};
|
||||
|
||||
@@ -4,11 +4,12 @@ import React, { Component } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import {
|
||||
BottomSheet,
|
||||
bottomSheetItemStylesCombined,
|
||||
hideDialog
|
||||
} from '../../../base/dialog';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { InviteButton } from '../../../invite';
|
||||
import { AudioRouteButton } from '../../../mobile/audio-mode';
|
||||
import { LiveStreamButton, RecordButton } from '../../../recording';
|
||||
@@ -26,10 +27,15 @@ declare var __DEV__;
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the dialog feature.
|
||||
*/
|
||||
_bottomSheetStyles: StyleType,
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -68,7 +74,7 @@ class OverflowMenu extends Component<Props> {
|
||||
const buttonProps = {
|
||||
afterClick: this._onCancel,
|
||||
showLabel: true,
|
||||
styles: bottomSheetItemStylesCombined
|
||||
styles: this.props._bottomSheetStyles
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -106,6 +112,22 @@ class OverflowMenu extends Component<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
OverflowMenu_ = connect()(OverflowMenu);
|
||||
/**
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _bottomSheetStyles: StyleType
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_bottomSheetStyles:
|
||||
ColorSchemeRegistry.get(state, 'BottomSheet')
|
||||
};
|
||||
}
|
||||
|
||||
OverflowMenu_ = connect(_mapStateToProps)(OverflowMenu);
|
||||
|
||||
export default OverflowMenu_;
|
||||
|
||||
@@ -5,21 +5,19 @@ import { View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Container } from '../../../base/react';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import { ChatButton } from '../../../chat';
|
||||
|
||||
import { isToolboxVisible } from '../../functions';
|
||||
import { HANGUP_BUTTON_SIZE } from '../../constants';
|
||||
|
||||
import AudioMuteButton from '../AudioMuteButton';
|
||||
import HangupButton from '../HangupButton';
|
||||
import VideoMuteButton from '../VideoMuteButton';
|
||||
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
import styles, {
|
||||
chatButtonOverride,
|
||||
hangupButtonStyles,
|
||||
toolbarButtonStyles,
|
||||
toolbarToggledButtonStyles
|
||||
} from './styles';
|
||||
import styles from './styles';
|
||||
import VideoMuteButton from '../VideoMuteButton';
|
||||
|
||||
/**
|
||||
* The number of buttons other than {@link HangupButton} to render in
|
||||
@@ -43,6 +41,11 @@ const _BUTTON_SIZE_FACTOR = 0.85;
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
_styles: StyleType,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the toolbox is visible.
|
||||
*/
|
||||
@@ -111,6 +114,7 @@ class Toolbox extends Component<Props, State> {
|
||||
* @returns {number}
|
||||
*/
|
||||
_calculateButtonSize() {
|
||||
const { _styles } = this.props;
|
||||
const { width } = this.state;
|
||||
|
||||
if (width <= 0) {
|
||||
@@ -118,8 +122,8 @@ class Toolbox extends Component<Props, State> {
|
||||
return width;
|
||||
}
|
||||
|
||||
const hangupButtonSize = styles.hangupButton.width;
|
||||
const { style } = toolbarButtonStyles;
|
||||
const hangupButtonSize = HANGUP_BUTTON_SIZE;
|
||||
const { style } = _styles.buttonStyles;
|
||||
let buttonSize
|
||||
= (width
|
||||
|
||||
@@ -155,12 +159,14 @@ class Toolbox extends Component<Props, State> {
|
||||
* @returns {Object | Array}
|
||||
*/
|
||||
_getChatButtonToggledStyle(baseStyle) {
|
||||
const { _styles } = this.props;
|
||||
|
||||
if (Array.isArray(baseStyle.style)) {
|
||||
return {
|
||||
...baseStyle,
|
||||
style: [
|
||||
...baseStyle.style,
|
||||
chatButtonOverride.toggled
|
||||
_styles.chatButtonOverride.toggled
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -169,7 +175,7 @@ class Toolbox extends Component<Props, State> {
|
||||
...baseStyle,
|
||||
style: [
|
||||
baseStyle.style,
|
||||
chatButtonOverride.toggled
|
||||
_styles.chatButtonOverride.toggled
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -196,9 +202,9 @@ class Toolbox extends Component<Props, State> {
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderToolbar() {
|
||||
const { _styles } = this.props;
|
||||
const buttonSize = this._calculateButtonSize();
|
||||
let buttonStyles = toolbarButtonStyles;
|
||||
let toggledButtonStyles = toolbarToggledButtonStyles;
|
||||
let { buttonStyles, toggledButtonStyles } = _styles;
|
||||
|
||||
if (buttonSize > 0) {
|
||||
const extraButtonStyle = {
|
||||
@@ -241,7 +247,8 @@ class Toolbox extends Component<Props, State> {
|
||||
<AudioMuteButton
|
||||
styles = { buttonStyles }
|
||||
toggledStyles = { toggledButtonStyles } />
|
||||
<HangupButton styles = { hangupButtonStyles } />
|
||||
<HangupButton
|
||||
styles = { _styles.hangupButtonStyles } />
|
||||
<VideoMuteButton
|
||||
styles = { buttonStyles }
|
||||
toggledStyles = { toggledButtonStyles } />
|
||||
@@ -261,11 +268,13 @@ class Toolbox extends Component<Props, State> {
|
||||
* {@code Toolbox} props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _styles: StyleType,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
return {
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../../base/styles';
|
||||
|
||||
import { HANGUP_BUTTON_SIZE } from '../../constants';
|
||||
@@ -9,7 +11,7 @@ import { HANGUP_BUTTON_SIZE } from '../../constants';
|
||||
* The style of toolbar buttons.
|
||||
*/
|
||||
const toolbarButton = {
|
||||
backgroundColor: ColorPalette.white,
|
||||
backgroundColor: schemeColor('button'),
|
||||
borderRadius: 20,
|
||||
borderWidth: 0,
|
||||
flex: 0,
|
||||
@@ -20,7 +22,6 @@ const toolbarButton = {
|
||||
// XXX We probably tested BoxModel.margin and discovered it to be too small
|
||||
// for our taste.
|
||||
marginHorizontal: 7,
|
||||
opacity: 0.7,
|
||||
width: 40
|
||||
};
|
||||
|
||||
@@ -33,29 +34,26 @@ const toolbarButtonIcon = {
|
||||
fontSize: 22
|
||||
};
|
||||
|
||||
/**
|
||||
* The style of toolbar buttons which display white icons.
|
||||
*/
|
||||
const whiteToolbarButton = {
|
||||
...toolbarButton,
|
||||
backgroundColor: schemeColor('buttonToggled')
|
||||
};
|
||||
|
||||
/**
|
||||
* The icon style of toolbar buttons which display white icons.
|
||||
*/
|
||||
const whiteToolbarButtonIcon = {
|
||||
...toolbarButtonIcon,
|
||||
color: ColorPalette.white
|
||||
};
|
||||
|
||||
/**
|
||||
* The Toolbox and toolbar related styles.
|
||||
*/
|
||||
const styles = createStyleSheet({
|
||||
/**
|
||||
* The style of the toolbar button which hangs the current conference up.
|
||||
*/
|
||||
hangupButton: {
|
||||
...toolbarButton,
|
||||
backgroundColor: ColorPalette.red,
|
||||
borderRadius: 30,
|
||||
height: HANGUP_BUTTON_SIZE,
|
||||
width: HANGUP_BUTTON_SIZE
|
||||
},
|
||||
|
||||
/**
|
||||
* The icon style of toolbar buttons which hangs the current conference up.
|
||||
*/
|
||||
hangupButtonIcon: {
|
||||
...toolbarButtonIcon,
|
||||
color: ColorPalette.white,
|
||||
fontSize: 24
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the toolbar.
|
||||
@@ -69,74 +67,60 @@ const styles = createStyleSheet({
|
||||
paddingHorizontal: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of toolbar buttons.
|
||||
*/
|
||||
toolbarButton,
|
||||
|
||||
/**
|
||||
* The icon style of the toolbar buttons.
|
||||
*/
|
||||
toolbarButtonIcon,
|
||||
|
||||
/**
|
||||
* The style of the root/top-level {@link Container} of {@link Toolbox}.
|
||||
*/
|
||||
toolbox: {
|
||||
flexDirection: 'column',
|
||||
flexGrow: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of toolbar buttons which display white icons.
|
||||
*/
|
||||
whiteToolbarButton: {
|
||||
...toolbarButton,
|
||||
backgroundColor: ColorPalette.buttonUnderlay
|
||||
},
|
||||
|
||||
/**
|
||||
* The icon style of toolbar buttons which display white icons.
|
||||
*/
|
||||
whiteToolbarButtonIcon: {
|
||||
...toolbarButtonIcon,
|
||||
color: ColorPalette.white
|
||||
}
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
||||
/**
|
||||
* Styles for the hangup button.
|
||||
* Color schemed styles for the @{Toolbox} component.
|
||||
*/
|
||||
export const hangupButtonStyles = {
|
||||
iconStyle: styles.whiteToolbarButtonIcon,
|
||||
style: styles.hangupButton,
|
||||
underlayColor: ColorPalette.buttonUnderlay
|
||||
};
|
||||
ColorSchemeRegistry.register('Toolbox', {
|
||||
/**
|
||||
* Styles for buttons in the toolbar.
|
||||
*/
|
||||
buttonStyles: {
|
||||
iconStyle: toolbarButtonIcon,
|
||||
style: toolbarButton
|
||||
},
|
||||
|
||||
/**
|
||||
* Styles for buttons in the toolbar.
|
||||
*/
|
||||
export const toolbarButtonStyles = {
|
||||
iconStyle: styles.toolbarButtonIcon,
|
||||
style: styles.toolbarButton
|
||||
};
|
||||
/**
|
||||
* Overrides to the standard styles that we apply to the chat button, as
|
||||
* that behaves slightly differently to other buttons.
|
||||
*/
|
||||
chatButtonOverride: {
|
||||
toggled: {
|
||||
backgroundColor: ColorPalette.blue
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Styles for toggled buttons in the toolbar.
|
||||
*/
|
||||
export const toolbarToggledButtonStyles = {
|
||||
iconStyle: styles.whiteToolbarButtonIcon,
|
||||
style: styles.whiteToolbarButton
|
||||
};
|
||||
hangupButtonStyles: {
|
||||
iconStyle: whiteToolbarButtonIcon,
|
||||
style: {
|
||||
...toolbarButton,
|
||||
backgroundColor: schemeColor('hangup'),
|
||||
borderRadius: HANGUP_BUTTON_SIZE / 2,
|
||||
height: HANGUP_BUTTON_SIZE,
|
||||
width: HANGUP_BUTTON_SIZE
|
||||
},
|
||||
underlayColor: ColorPalette.buttonUnderlay
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrides to the standard styles that we apply to the chat button, as that
|
||||
* behaves slightly differently to other buttons.
|
||||
*/
|
||||
export const chatButtonOverride = createStyleSheet({
|
||||
toggled: {
|
||||
backgroundColor: ColorPalette.blue
|
||||
/**
|
||||
* Styles for toggled buttons in the toolbar.
|
||||
*/
|
||||
toggledButtonStyles: {
|
||||
iconStyle: whiteToolbarButtonIcon,
|
||||
style: {
|
||||
...whiteToolbarButton,
|
||||
borderColor: schemeColor('buttonToggledBorder'),
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user