mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-28 19:47:46 +00:00
Compare commits
30 Commits
3198
...
invitation
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5e2763c1 | ||
|
|
76638f524d | ||
|
|
8ea693616d | ||
|
|
2442ef80b0 | ||
|
|
87f171caa4 | ||
|
|
e094b6516a | ||
|
|
2941f5dde4 | ||
|
|
eec7a1b628 | ||
|
|
5f7a515610 | ||
|
|
b7133f5717 | ||
|
|
f77e1dc591 | ||
|
|
4d817fc6c2 | ||
|
|
b8a7037959 | ||
|
|
6f95c50d6e | ||
|
|
9f3ef43daa | ||
|
|
46713cab3b | ||
|
|
8065cc0348 | ||
|
|
045a2d6aca | ||
|
|
d7d9bc4eeb | ||
|
|
33db155eb9 | ||
|
|
1cfd6164f5 | ||
|
|
d9cf33b4c4 | ||
|
|
2b56822a41 | ||
|
|
c34fee4305 | ||
|
|
ddc8a670f9 | ||
|
|
7c911eca96 | ||
|
|
f3c83f6e6d | ||
|
|
b9a14acd3c | ||
|
|
c998dbb47e | ||
|
|
573cc64fcd |
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
|
||||
<script>
|
||||
// IE11 and earlier can be identified via their user agent and be
|
||||
// redirected to a page that is known to have no newer js syntax.
|
||||
if (window.navigator.userAgent.match(/(MSIE|Trident)/)) {
|
||||
window.location.href = "static/recommendedBrowsers.html";
|
||||
}
|
||||
|
||||
window.indexLoadedTime = window.performance.now();
|
||||
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
|
||||
// XXX the code below listeners for errors and displays an error message
|
||||
|
||||
@@ -43,6 +43,8 @@ target 'JitsiMeet' do
|
||||
:path => '../node_modules/react-native-fast-image'
|
||||
pod 'react-native-keep-awake',
|
||||
:path => '../node_modules/react-native-keep-awake'
|
||||
pod 'BVLinearGradient',
|
||||
:path => '../node_modules/react-native-linear-gradient'
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
pod 'RNGoogleSignin',
|
||||
:path => '../node_modules/react-native-google-signin'
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
PODS:
|
||||
- boost-for-react-native (1.63.0)
|
||||
- BVLinearGradient (2.5.3):
|
||||
- React
|
||||
- Crashlytics (3.12.0):
|
||||
- Fabric (~> 1.9.0)
|
||||
- DoubleConversion (1.1.6)
|
||||
@@ -152,6 +154,7 @@ PODS:
|
||||
- yoga (0.57.8.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
|
||||
- Crashlytics
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- Fabric
|
||||
@@ -202,6 +205,8 @@ SPEC REPOS:
|
||||
- SDWebImage
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
BVLinearGradient:
|
||||
:path: "../node_modules/react-native-linear-gradient"
|
||||
DoubleConversion:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
Folly:
|
||||
@@ -231,6 +236,7 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
BVLinearGradient: b0b70acf63ee888829b7c2ebbf6b50e227396e55
|
||||
Crashlytics: 07fb167b1694128c1c9a5a5cc319b0e9c3ca0933
|
||||
DoubleConversion: bb338842f62ab1d708ceb63ec3d999f0f3d98ecd
|
||||
Fabric: f988e33c97f08930a413e08123064d2e5f68d655
|
||||
@@ -262,6 +268,6 @@ SPEC CHECKSUMS:
|
||||
SDWebImage: c5594f1a19c48d526d321e548902b56b479cd508
|
||||
yoga: b1ce48b6cf950b98deae82838f5173ea7cf89e85
|
||||
|
||||
PODFILE CHECKSUM: b5218184626a027e8b1ca12361d46100e2fa2f1f
|
||||
PODFILE CHECKSUM: 7d1909450626f31f9ea2de80122a66a50af2e1ea
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
typedef enum {
|
||||
kAudioModeDefault,
|
||||
kAudioModeAudioCall,
|
||||
kAudioModeVideoCall
|
||||
} JitsiMeetAudioMode;
|
||||
|
||||
@interface AudioMode : NSObject<RCTBridgeModule>
|
||||
|
||||
@property(nonatomic, strong) dispatch_queue_t workerQueue;
|
||||
@@ -27,18 +33,13 @@
|
||||
@end
|
||||
|
||||
@implementation AudioMode {
|
||||
NSString *_category;
|
||||
NSString *_mode;
|
||||
NSString *_avCategory;
|
||||
NSString *_avMode;
|
||||
JitsiMeetAudioMode _mode;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
typedef enum {
|
||||
kAudioModeDefault,
|
||||
kAudioModeAudioCall,
|
||||
kAudioModeVideoCall
|
||||
} JitsiMeetAudioMode;
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup {
|
||||
return NO;
|
||||
}
|
||||
@@ -54,13 +55,23 @@ typedef enum {
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_category = nil;
|
||||
_mode = nil;
|
||||
_avCategory = nil;
|
||||
_avMode = nil;
|
||||
_mode = kAudioModeDefault;
|
||||
|
||||
dispatch_queue_attr_t attributes =
|
||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
|
||||
QOS_CLASS_USER_INITIATED, -1);
|
||||
_workerQueue = dispatch_queue_create("AudioMode.queue", attributes);
|
||||
|
||||
// AVAudioSession is a singleton and other parts of the application such as
|
||||
// WebRTC may undo the settings. Make sure that the settings are reapplied
|
||||
// upon undoes.
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(routeChanged:)
|
||||
name:AVAudioSessionRouteChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -82,7 +93,7 @@ typedef enum {
|
||||
// needed. This notification is posted on a secondary thread, so make
|
||||
// sure we switch to our worker thread.
|
||||
dispatch_async(_workerQueue, ^{
|
||||
[self setCategory:self->_category mode:self->_mode error:nil];
|
||||
[self setCategory:self->_avCategory mode:self->_avMode error:nil];
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -97,6 +108,18 @@ typedef enum {
|
||||
error:(NSError * _Nullable *)outError {
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
|
||||
// We don't want to touch the category when setting the default mode.
|
||||
// This is to play well with other components which could be integrated
|
||||
// into the final application.
|
||||
if (_mode == kAudioModeDefault) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Nothing to do.
|
||||
if (category == nil && mode == nil) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (session.category != category
|
||||
&& ![session setCategory:category error:outError]) {
|
||||
RCTLogError(@"Failed to (re)apply specified AVAudioSession category!");
|
||||
@@ -114,8 +137,8 @@ typedef enum {
|
||||
RCT_EXPORT_METHOD(setMode:(int)mode
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *avCategory;
|
||||
NSString *avMode;
|
||||
NSString *avCategory = nil;
|
||||
NSString *avMode = nil;
|
||||
NSError *error;
|
||||
|
||||
switch (mode) {
|
||||
@@ -124,8 +147,6 @@ RCT_EXPORT_METHOD(setMode:(int)mode
|
||||
avMode = AVAudioSessionModeVoiceChat;
|
||||
break;
|
||||
case kAudioModeDefault:
|
||||
avCategory = AVAudioSessionCategorySoloAmbient;
|
||||
avMode = AVAudioSessionModeDefault;
|
||||
break;
|
||||
case kAudioModeVideoCall:
|
||||
avCategory = AVAudioSessionCategoryPlayAndRecord;
|
||||
@@ -136,29 +157,17 @@ RCT_EXPORT_METHOD(setMode:(int)mode
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the desired/specified category and mode so that they may be
|
||||
// reapplied.
|
||||
_avCategory = avCategory;
|
||||
_avMode = avMode;
|
||||
_mode = mode;
|
||||
|
||||
if (![self setCategory:avCategory mode:avMode error:&error] || error) {
|
||||
reject(@"setMode", error.localizedDescription, error);
|
||||
return;
|
||||
} else {
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
// Even though the specified category and mode were successfully set, the
|
||||
// AVAudioSession is a singleton and other parts of the application such as
|
||||
// WebRTC may undo the settings. Make sure that the settings are reapplied
|
||||
// upon undoes.
|
||||
if (!_category || !_mode) {
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(routeChanged:)
|
||||
name:AVAudioSessionRouteChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
// Save the desired/specified category and mode so that they may be
|
||||
// reapplied (upon undoes as described above).
|
||||
_category = avCategory;
|
||||
_mode = avMode;
|
||||
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
26
lang/languages-af.json
Normal file
26
lang/languages-af.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"en": "Engels",
|
||||
"az": "Azerbeidjans",
|
||||
"bg": "Bulgaars",
|
||||
"cs": "Tsjeggies",
|
||||
"de": "Duits",
|
||||
"el": "Grieks",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spaans",
|
||||
"fr": "Frans",
|
||||
"hy": "Armeens",
|
||||
"it": "Italiaans",
|
||||
"ja": "Japannees",
|
||||
"ko": "Koreaans",
|
||||
"nb": "Bokmal-Noorweegs",
|
||||
"oc": "Oksitaans",
|
||||
"pl": "Pools",
|
||||
"ptBR": "Portugees (Brasilië)",
|
||||
"ru": "Russies",
|
||||
"sk": "Slowaaks",
|
||||
"sl": "Sloweens",
|
||||
"sv": "Sweeds",
|
||||
"tr": "Turks",
|
||||
"vi": "Viëtnamees",
|
||||
"zhCN": "Sjinees (Sjina)"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"en": "English",
|
||||
"af": "Afrikaans",
|
||||
"az": "Azerbaijani",
|
||||
"bg": "Bulgarian",
|
||||
"cs": "Czech",
|
||||
|
||||
740
lang/main-af.json
Normal file
740
lang/main-af.json
Normal file
@@ -0,0 +1,740 @@
|
||||
{
|
||||
"contactlist_plural": "__count__ lede",
|
||||
"passwordSetRemotely": "ingestel deur ’n ander lid",
|
||||
"poweredby": "aangedryf deur",
|
||||
"inviteUrlDefaultMsg": "U konferensie word tans geskep...",
|
||||
"me": "ek",
|
||||
"speaker": "",
|
||||
"raisedHand": "Wil praat",
|
||||
"defaultNickname": "bv. Piet Pompies",
|
||||
"defaultLink": "bv. __url__",
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"headphones": "Oorfone",
|
||||
"phone": "Foon",
|
||||
"speaker": "Luidspreker"
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "Net klank",
|
||||
"featureToggleDisabled": "Wisseling van __feature__ is gedeaktiveer tydens Net klank-modus"
|
||||
},
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"electronGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "Kies <b><i>Deel gekose toestel</i></b> wanneer die blaaier vir toestemming vra.",
|
||||
"operaGrantPermissions": "Kies <b><i>Allow</i></b> wanneer die blaaier vir toestemming vra.",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "Kies <b><i>OK</i></b> wanneer die blaaier vir toestemming vra.",
|
||||
"nwjsGrantPermissions": "Gee asb. toestemming vir die gebruik van u kamera en mikrofoon",
|
||||
"edgeGrantPermissions": "Kies <b><i>Yes</i></b> wanneer die blaaier vir toestemming vra."
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "Sleutelbordkortpaaie",
|
||||
"raiseHand": "Steek hand op of laat sak hom",
|
||||
"pushToTalk": "Druk om te praat",
|
||||
"toggleScreensharing": "Wissel tussen kamera- en skermdeling",
|
||||
"toggleFilmstrip": "Wys of versteek duimnaels vir video’s",
|
||||
"toggleShortcuts": "Wys of versteek sleutelbordkortpaaie",
|
||||
"focusLocal": "Fokus op u video",
|
||||
"focusRemote": "Fokus op ’n ander persoon se video",
|
||||
"toggleChat": "Maak gesels oop of toe",
|
||||
"mute": "Demp of ontdemp jou mikrofoon",
|
||||
"fullScreen": "Bekyk of verlaat volskerm",
|
||||
"videoMute": "Begin of stop u kamera",
|
||||
"showSpeakerStats": "Wys sprekerstatistiek",
|
||||
"localRecording": "Wys of versteek kontroles vir plaaslike opname"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "Raak om aan te sluit",
|
||||
"roomname": ""
|
||||
},
|
||||
"appDescription": "Hou gerus ’n videogesprek met die hele span. Om die waarheid te sê, nooi sommer almal. __app__ is ’n 100% oopbronoplossing vir geënkripteerde videokonferensies wat mens heeldag, elke dag gratis kan geniet — geen rekening nodig nie.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Stem",
|
||||
"video": "Video"
|
||||
},
|
||||
"calendar": "",
|
||||
"connectCalendarText": "Koppel u kalender om alle vergaderings in __app__ te sien. Plus, voeg __app__-vergaderings by u kalender en begin hulle met een klik.",
|
||||
"connectCalendarButton": "Koppel u kalender",
|
||||
"enterRoomTitle": "Begin ’n nuwe vergadering",
|
||||
"go": "GAAN",
|
||||
"join": "SLUIT AAN",
|
||||
"privacy": "Privaatheid",
|
||||
"recentList": "Onlangs",
|
||||
"recentListDelete": "Skrap",
|
||||
"recentListEmpty": "Die lys van onlangse gesprekke is leeg. Gesels met u span al u onlangse gesprekke sal hier wees.",
|
||||
"roomname": "Gee kamernaam",
|
||||
"roomnameHint": "Gee die naam of URL van die kamer waar u wil aansluit. Dink gerus enige naam uit. Laat weet net die mense wat u ontmoet wat dit is sodat hulle die selfde naam gee.",
|
||||
"sendFeedback": "Stuur terugvoer",
|
||||
"terms": "Voorwaardes",
|
||||
"title": "Veilige en volledig gratis videokonferensies propvol funksionaliteit"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": "__app__ benodig u mikrofoon en kamera."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "U video-oproep is onderbreek omdat die rekenaar gaan slaap het.",
|
||||
"text": "Druk die <i>Sluit weer aan</i>-knoppie om te herkoppel.",
|
||||
"rejoinKeyTitle": "Sluit weer aan"
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "Wissel Net klank",
|
||||
"audioRoute": "",
|
||||
"callQuality": "",
|
||||
"chat": "Wissel geselsvenster",
|
||||
"cc": "Wissel onderskrifte",
|
||||
"document": "Wissel gedeelde dokument",
|
||||
"feedback": "",
|
||||
"fullScreen": "Wissel volskerm",
|
||||
"hangup": "Verlaat die oproep",
|
||||
"invite": "",
|
||||
"localRecording": "Wissel kontroles vir plaaslike opname",
|
||||
"lockRoom": "Wissel kamerslot",
|
||||
"moreActions": "Wissel kieslys vir meer aksies",
|
||||
"moreActionsMenu": "Kieslys vir meer aksies",
|
||||
"mute": "",
|
||||
"pip": "Wissel Prent-in-Prent-modus",
|
||||
"profile": "",
|
||||
"raiseHand": "Wissel handopsteek",
|
||||
"recording": "Wissel opname",
|
||||
"Settings": "Wissel instellings",
|
||||
"sharedvideo": "Wissel Youtube-videodeling",
|
||||
"shareRoom": "Nooi iemand",
|
||||
"shareYourScreen": "Wissel skermdeling",
|
||||
"shortcuts": "Wissel kortpaaie",
|
||||
"speakerStats": "Wissel sprekerstatistiek",
|
||||
"toggleCamera": "",
|
||||
"tileView": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"addPeople": "Voeg mense by die oproep",
|
||||
"audioonly": "Aktiveer / deaktiveer Net klank-modus (spaar bandwydte)",
|
||||
"audioOnlyOn": "Aktiveer Net klank-modus (spaar bandwydte)",
|
||||
"audioOnlyOff": "Deaktiveer Net klank-modus",
|
||||
"audioRoute": "Kies die klanktoestel",
|
||||
"callQuality": "Bestuur oproepkwaliteit",
|
||||
"enterFullScreen": "Volskermaansig",
|
||||
"exitFullScreen": "Verlaat volskerm",
|
||||
"feedback": "Laat terugvoer",
|
||||
"moreActions": "Meer aksies",
|
||||
"mute": "Demp / ontdemp",
|
||||
"videomute": "Begin / stop kamera",
|
||||
"authenticate": "Verifieer",
|
||||
"lock": "Sluit / ontsluit kamer",
|
||||
"chat": "Open / sluit gesels",
|
||||
"etherpad": "Open / sluit gedeelde dokument",
|
||||
"documentOpen": "Open gedeelde dokument",
|
||||
"documentClose": "Sluit gedeelde dokument",
|
||||
"shareRoom": "Deel kamer",
|
||||
"sharedvideo": "Deel ’n YouTube-video",
|
||||
"stopSharedVideo": "Stop YouTube-video",
|
||||
"fullscreen": "",
|
||||
"sip": "Bel SIP-nommer",
|
||||
"Settings": "",
|
||||
"hangup": "Verlaat",
|
||||
"login": "Meld aan",
|
||||
"logout": "",
|
||||
"sharedVideoMutedPopup": "Die gedeelde video is gedemp sodat u met ander lede kan praat.",
|
||||
"toggleCamera": "Wissel kamera",
|
||||
"micMutedPopup": "Die mikrofoon is gedemp vir beste ervaring van die gedeelde video.",
|
||||
"talkWhileMutedPopup": "Besig om te praat? U is gedemp.",
|
||||
"unableToUnmutePopup": "",
|
||||
"cameraDisabled": "Kamera is nie beskikbaar nie",
|
||||
"micDisabled": "Mikrofoon is nie beskikbaar nie",
|
||||
"filmstrip": "Wys / versteek video’s",
|
||||
"pip": "Betree Prent-in-Prent-modus",
|
||||
"profile": "Redigeer u profiel",
|
||||
"raiseHand": "Lig / laat sak u hand",
|
||||
"shortcuts": "Sien kortpaaie",
|
||||
"speakerStats": "Sprekerstatistiek",
|
||||
"tileViewToggle": "Wissel teëlaansig",
|
||||
"invite": "Nooi mense"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Gee ’n bynaam in die boksie hieronder",
|
||||
"popover": "Kies ’n bynaam"
|
||||
},
|
||||
"error": "",
|
||||
"messagebox": "Tik teks..."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "Ontkoppel",
|
||||
"microsoftSignIn": "Meld aan met Microsoft",
|
||||
"signedIn": "",
|
||||
"title": ""
|
||||
},
|
||||
"title": "",
|
||||
"update": "Werk by",
|
||||
"name": "",
|
||||
"startAudioMuted": "Almal begin gedemp",
|
||||
"startVideoMuted": "Almal begin versteek",
|
||||
"selectCamera": "Kamera",
|
||||
"selectMic": "Mikrofoon",
|
||||
"selectAudioOutput": "Klankafvoer",
|
||||
"followMe": "Almal volg my",
|
||||
"language": "Taal",
|
||||
"loggedIn": "Aangemeld as __name__",
|
||||
"noDevice": "",
|
||||
"cameraAndMic": "Kamera en mikrofoon",
|
||||
"moderator": "",
|
||||
"more": "Meer",
|
||||
"password": "STEL WAGWOORD",
|
||||
"audioVideo": "KLANK EN VIDEO",
|
||||
"devices": "Toestelle"
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "Stel u vertoonnaam",
|
||||
"setEmailLabel": "Stel u gravatar-e-posadres",
|
||||
"setEmailInput": "Gee e-posadres"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"moderator": "",
|
||||
"videomute": "Lid het die kamera gestop",
|
||||
"mute": "Lid is gedemp",
|
||||
"kick": "Skop uit",
|
||||
"muted": "Gedemp",
|
||||
"domute": "",
|
||||
"flip": "Swaai om",
|
||||
"remoteControl": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "",
|
||||
"connectedTo": "Gekoppel aan:",
|
||||
"bitrate": "Bistempo:",
|
||||
"bridgeCount": "Aantal bedieners: ",
|
||||
"packetloss": "Pakkies verloor:",
|
||||
"resolution": "Resolusie:",
|
||||
"framerate": "Raampietempo:",
|
||||
"less": "Wys minder",
|
||||
"more": "Wys meer",
|
||||
"address": "Adres:",
|
||||
"remoteport": "Afgeleë poort:",
|
||||
"remoteport_plural": "Afgeleë poorte:",
|
||||
"localport": "Plaaslike poort:",
|
||||
"localport_plural": "Plaaslike poorte:",
|
||||
"localaddress": "Plaaslike adres:",
|
||||
"localaddress_plural": "Plaaslike adresse:",
|
||||
"remoteaddress": "Afgeleë adres:",
|
||||
"remoteaddress_plural": "Afgeleë adresse:",
|
||||
"transport": "",
|
||||
"transport_plural": "",
|
||||
"bandwidth": "Geraamde bandwydte:",
|
||||
"na": "",
|
||||
"turn": "",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"inactive": "Onaktief",
|
||||
"lost": "",
|
||||
"nonoptimal": "",
|
||||
"poor": "Swak"
|
||||
},
|
||||
"status": "Verbinding:"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "ontkoppel",
|
||||
"moderator": "",
|
||||
"connectedOneMember": "__name__ het gekoppel",
|
||||
"connectedTwoMembers": "__first__ en __second__ het gekoppel",
|
||||
"connectedThreePlusMembers": "__name__ en __count__ ander het gekoppel",
|
||||
"somebody": "Iemand",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "__to__ is nou moderator!",
|
||||
"muted": "U het die gesprek gedemp begin.",
|
||||
"mutedTitle": "U is gedemp!",
|
||||
"raisedHand": "",
|
||||
"suboptimalExperienceTitle": "Blaaierwaarskuwing",
|
||||
"suboptimalExperienceDescription": "Gits... ons is bevrees u ervaring met __appName__ gaan nie so goed wees hier nie. Ons soek maniere om dit die hoof te bied, maar probeer intussen een van die <a href='static/recommendedBrowsers.html' target='_blank'>volledig ondersteunde blaaiers</a>."
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Regstreekse stroom"
|
||||
},
|
||||
"allow": "Laat toe",
|
||||
"confirm": "Bevestig",
|
||||
"kickMessage": "Eina! U is uit die gesprek geskop!",
|
||||
"kickTitle": "Uit vergadering geskop",
|
||||
"popupErrorTitle": "Opspringer geblok",
|
||||
"popupError": "U blaaier blokkeer opspringers vanaf hierdie werf. Aktiveer opspringers in die blaaier se sekuriteitopsies en probeer weer.",
|
||||
"passwordErrorTitle": "Wagwoordfout",
|
||||
"passwordError": "Hierdie gesprek word tans met ’n wagwoord beskerm. Slegs die eienaar van die konferensie kan ’n wagwoord instel.",
|
||||
"passwordError2": "Hierdie gesprek word nie tans met ’n wagwoord beskerm nie. Slegs die eienaar van die konferensie kan ’n wagwoord instel.",
|
||||
"connectError": "Oeps! Iets het skeefgeloop en ons kon nie aan die konferensie koppel nie.",
|
||||
"connectErrorWithMsg": "Oeps! Iets het skeefgeloop en ons kon nie aan die konferensie koppel nie: __msg__",
|
||||
"incorrectPassword": "Verkeerde gebruikernaam of wagwoord",
|
||||
"connecting": "",
|
||||
"copy": "Kopieer",
|
||||
"contactSupport": "Kontak ondersteuning",
|
||||
"error": "",
|
||||
"detectext": "",
|
||||
"failedpermissions": "Kon nie toestemming kry om die plaaslike mikrofoon en/of kamera te gebruik nie.",
|
||||
"conferenceReloadTitle": "Iets het ongelukkig skeefgeloop.",
|
||||
"conferenceReloadMsg": "Ons probeer om dit reg te stel. Gaan herkoppel oor __seconds__ sekondes...",
|
||||
"conferenceDisconnectTitle": "Die verbinding is verbreek.",
|
||||
"conferenceDisconnectMsg": "Kontroleer dalk die netwerkverbinding. Gaan oor __seconds__ sekondes weer koppel...",
|
||||
"dismiss": "",
|
||||
"rejoinNow": "Sluit nou weer aan",
|
||||
"maxUsersLimitReachedTitle": "Maksimumlede-limiet bereik",
|
||||
"maxUsersLimitReached": "Die limiet vir maksimum getal lede is bereik. Die konferensie is vol. Kontak asb. die eienaar van die vergadering of probeer weer later!",
|
||||
"lockRoom": "Sluit kamer",
|
||||
"lockTitle": "Sluit het misluk",
|
||||
"lockMessage": "Kon nie die konferensie sluit nie.",
|
||||
"warning": "",
|
||||
"passwordNotSupportedTitle": "Wagwoord nie ondersteun nie",
|
||||
"passwordNotSupported": "Die instel van ’n vergaderingwagwoord word nie ondersteun nie.",
|
||||
"internalErrorTitle": "Interne fout",
|
||||
"internalError": "Oeps! Iets het skeefgeloop. Die volgende fout het voorgekom: __error__",
|
||||
"unableToSwitch": "Kan nie videostroom wissel nie.",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "Oeps!",
|
||||
"currentPassword": "Die huidige wagwoord is",
|
||||
"passwordLabel": "Wagwoord",
|
||||
"defaultError": "Daar was een of ander soort fout",
|
||||
"passwordRequired": "Wagwoord vereis",
|
||||
"Ok": "Regso",
|
||||
"done": "Klaar",
|
||||
"Remove": "Verwyder",
|
||||
"removePassword": "Verwyder wagwoord",
|
||||
"shareVideoTitle": "Deel ’n video",
|
||||
"shareVideoLinkError": "Gee asb. ’n korrekte YouTube-skakel.",
|
||||
"removeSharedVideoTitle": "Verwyder gedeelde video",
|
||||
"removeSharedVideoMsg": "Wil u definitief u gedeelde video verwyder?",
|
||||
"alreadySharedVideoMsg": "’n Ander lid deel reeds ’n video. Dié konferensie laat slegs een gedeelde video op ’n slag toe.",
|
||||
"alreadySharedVideoTitle": "Slegs een gedeelde video op ’n slag word toegelaat",
|
||||
"WaitingForHost": "Wag tans vir die gasheer ...",
|
||||
"WaitForHostMsg": "Die konferensie <b>__room </b> het nog nie begin nie. As u die gasheer is, verifieer u identiteit. Wag andersins asb. vir die gasheer om op te daag.",
|
||||
"IamHost": "Ek is die gasheer",
|
||||
"Cancel": "Kanselleer",
|
||||
"Submit": "Dien in",
|
||||
"retry": "Herprobeer",
|
||||
"logoutTitle": "Meld af",
|
||||
"logoutQuestion": "Wil u definitief afmeld en die konferensie stop?",
|
||||
"sessTerminated": "Oproep gestaak",
|
||||
"hungUp": "U het neergesit",
|
||||
"joinAgain": "Sluit weer aan",
|
||||
"Share": "",
|
||||
"Save": "Stoor",
|
||||
"recording": "",
|
||||
"recordingToken": "",
|
||||
"Back": "Terug",
|
||||
"serviceUnavailable": "Diens nie beskikbaar nie",
|
||||
"gracefulShutdown": "Ons diens is tans buite werking t.w.v. onderhoud. Probeer gerus weer later.",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "Foutkode: __code__, boodskap: __msg__",
|
||||
"password": "Tik wagwoord in",
|
||||
"unlockRoom": "Ontsluit kamer",
|
||||
"userPassword": "gebruikerwagwoord",
|
||||
"token": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailed": "Jammer! U mag nie by dié oproep aansluit nie.",
|
||||
"displayNameRequired": "Vertoonnaam is nodig",
|
||||
"enterDisplayName": "Gee asb. u vertoonnaam",
|
||||
"feedbackHelp": "Terugvoer sal ons help om ons video-ervaring te verbeter.",
|
||||
"feedbackQuestion": "Vertel ons oor die oproep!",
|
||||
"thankYou": "Dankie dat u __appName__ gebruik!",
|
||||
"sorryFeedback": "Dis jammer om te hoor. Wil u meer vertel?",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "Sleutel vir regstreekse stroom",
|
||||
"startLiveStreaming": "Begin regstreekse stroom",
|
||||
"startRecording": "Begin opname",
|
||||
"stopStreamingWarning": "Wil u definitief die regstreekse stroom stop?",
|
||||
"stopRecordingWarning": "Wil u definitief die opname stop?",
|
||||
"stopLiveStreaming": "Stop regstreekse stroom",
|
||||
"stopRecording": "Stop opname",
|
||||
"doNotShowMessageAgain": "Moenie weer dié boodskap wys nie",
|
||||
"permissionDenied": "Toestemming gewyer",
|
||||
"screenSharingFailedToInstall": "Oeps! Die uitbreiding vir skermdeling kon nie installeer nie.",
|
||||
"screenSharingFailedToInstallTitle": "Uitbreiding vir skermdeling kon nie installeer nie",
|
||||
"screenSharingFirefoxPermissionDeniedError": "",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Oeps! Ons kon nie skermdeling begin nie!",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"cameraUnsupportedResolutionError": "Die kamera ondersteun nie die nodige videoresolusie nie.",
|
||||
"cameraUnknownError": "Kan weens onbekende rede nie die kamera gebruik nie.",
|
||||
"cameraPermissionDeniedError": "U het nie toestemming gegee om u kamera te gebruik nie. U kan steeds by die konferensie aansluit, maar ander sal u nie kan sien nie. Gebruik die kameraknoppie in die adresbalk om dit reg te stel.",
|
||||
"cameraNotFoundError": "Kamera is nie gevind nie.",
|
||||
"cameraConstraintFailedError": "Die kamera voldoen nie aan sekere van die vereistes nie.",
|
||||
"micUnknownError": "Kan weens onbekende rede nie die mikrofoon gebruik nie.",
|
||||
"micPermissionDeniedError": "U het nie toestemming gegee om u mikrofoon te gebruik nie. U kan steeds by die konferensie aansluit, maar ander sal u nie kan hoor nie. Gebruik die kameraknoppie in die adresbalk om dit reg te stel.",
|
||||
"micNotFoundError": "Mikrofoon is nie gevind nie.",
|
||||
"micConstraintFailedError": "Die mikrofoon voldoen nie aan sekere van die vereistes nie.",
|
||||
"micNotSendingDataTitle": "Kan nie toegang tot mikrofoon kry nie",
|
||||
"micNotSendingData": "Ons kry nie toegang tot u mikrofoon nie. Kies asb. ’n ander toestel by die instellingskieslys of probeer om die toepassing op nuut te laai.",
|
||||
"cameraNotSendingDataTitle": "Kan nie toegang tot kamera kry nie",
|
||||
"cameraNotSendingData": "Ons kry nie toegang tot u kamera nie. Kontroleer of ’n ander toepassing dié toestel gebruik, kies asb. ’n ander toestel by die instellingskieslys of probeer om die toepassing op nuut te laai.",
|
||||
"goToStore": "Gaan na die webwinkel",
|
||||
"externalInstallationTitle": "Uitbreiding is nodig",
|
||||
"externalInstallationMsg": "",
|
||||
"inlineInstallationMsg": "U moet ons uitbreiding vir werkskermdeling installeer.",
|
||||
"inlineInstallExtension": "Installeer nou",
|
||||
"muteParticipantTitle": "Demp dié lid?",
|
||||
"muteParticipantBody": "U sal hulle nie kan ontdemp nie, maar hulle sal hulself enige tyd kan ontdemp.",
|
||||
"muteParticipantButton": "Demp",
|
||||
"liveStreamingDisabledTooltip": "Begin van regstreekse stroom gedeaktiveer.",
|
||||
"liveStreamingDisabledForGuestTooltip": "Gaste kan nie regstreekse strome begin nie.",
|
||||
"recordingDisabledTooltip": "Begin van opname gedeaktiveer.",
|
||||
"recordingDisabledForGuestTooltip": "Gaste kan nie opnames begin nie.",
|
||||
"remoteControlTitle": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"remoteControlStopMessage": "",
|
||||
"close": "Sluit",
|
||||
"shareYourScreen": "Deel u skerm",
|
||||
"shareYourScreenDisabled": "Skermdeling gedeaktiveer.",
|
||||
"shareYourScreenDisabledForGuest": "Gaste kan nie skerms deel nie.",
|
||||
"yourEntireScreen": "U hele skerm",
|
||||
"applicationWindow": "Toepassingsvenster",
|
||||
"transcribing": "Transkribering"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Dié konferensie word met ’n wagwoord beskerm. Gebruik asb. die volgende PIN om aan te sluit:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Uitnodiging na ’n __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Hallo! Hierdie is ’n uitnodiging na ’n __appName__-konferensie wat ek pas opgestel het.",
|
||||
"",
|
||||
"",
|
||||
"Klik gerus op die volgende skakel om by die konferensie aan te sluit.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Let op dat __appName__ tans net deur die blaaiers __supportedBrowsers__ ondersteun word. Dus moet mens een van hulle gebruik.",
|
||||
"",
|
||||
"",
|
||||
"Hopelik praat ons gou!"
|
||||
],
|
||||
"and": "en"
|
||||
},
|
||||
"share": {
|
||||
"mainText": [
|
||||
"Klik die volgende skakel om by die vergadering aan te sluit:",
|
||||
"__roomUrl__"
|
||||
],
|
||||
"dialInfoText": [
|
||||
"",
|
||||
"",
|
||||
"=====",
|
||||
"",
|
||||
"Wil u bloot met u foon inbel?",
|
||||
"",
|
||||
"__defaultDialInNumber__Klik dié skakel om die inbelfoonnommers vir dié vergadering te sien",
|
||||
"__dialInfoPageUrl__"
|
||||
]
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Fout",
|
||||
"CONNECTING": "Koppel tans",
|
||||
"RECONNECTING": "’n Netwerkprobleem het voorgekom. Herkoppel tans...",
|
||||
"CONNFAIL": "Koppeling het misluk",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "Ontkoppel tans",
|
||||
"ATTACHED": ""
|
||||
},
|
||||
"recording": {
|
||||
"beta": "",
|
||||
"busy": "",
|
||||
"busyTitle": "Alle opnemers is tans besig",
|
||||
"buttonTooltip": "Begin / stop opname",
|
||||
"error": "Opname het misluk. Probeer gerus weer.",
|
||||
"expandedOff": "Opname het gestop",
|
||||
"expandedOn": "Die vergadering word tans opgeneem.",
|
||||
"expandedPending": "Opname word begin...",
|
||||
"failedToStart": "Kon nie begin opneem nie",
|
||||
"live": "",
|
||||
"off": "Opname gestop",
|
||||
"on": "Neem tans op",
|
||||
"pending": "Berei voor om vergadering op te neem...",
|
||||
"rec": "",
|
||||
"authDropboxText": "Laai op na Dropbox",
|
||||
"serviceName": "Opneemdiens",
|
||||
"signOut": "Meld af",
|
||||
"signIn": "meld aan",
|
||||
"loggedIn": "Aangemeld as __name__",
|
||||
"availableSpace": "Beskikbare spasie: __spaceLeft__ MB (ongeveer __duration__ minute se opname)",
|
||||
"startRecordingBody": "Wil u definitief die opname begin?",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"transcribing": {
|
||||
"pending": "",
|
||||
"off": "",
|
||||
"error": "",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"tr": "",
|
||||
"labelToolTip": "",
|
||||
"ccButtonTooltip": "",
|
||||
"start": "",
|
||||
"stop": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "Begin /stop regstreekse stroom",
|
||||
"changeSignIn": "Wissel rekeninge.",
|
||||
"choose": "Kies ’n regstreekse stroom",
|
||||
"chooseCTA": "Kies ’n stroomopsie. U is tans aangemeld as __email__.",
|
||||
"enterStreamKey": "Gee u sleutel vir regstreekse stroom by YouTube hier.",
|
||||
"error": "Kon nie regstreeks stroom nie. Probeer gerus weer.",
|
||||
"errorAPI": "’n Fout het voorgekom tydens toegang tot u YouTube-uitsendings. Probeer om weer aan te meld.",
|
||||
"errorLiveStreamNotEnabled": "Regstreekse stroom is nie geaktiveer op __email__ nie. Aktiveer asb. regstreekse strome of meld aan met ’n rekening met regstreekse strome geaktiveer.",
|
||||
"expandedOff": "Die regstreekse stroom het gestop",
|
||||
"expandedOn": "Die vergadering word tans gestroom na YouTube.",
|
||||
"expandedPending": "Die regstreekse stroom begin tans...",
|
||||
"failedToStart": "Regstreekse stroom kon nie begin nie",
|
||||
"off": "Regstreekse stroom het gestop",
|
||||
"on": "Regstreekse stroom",
|
||||
"pending": "Begin tans regstreekse stroom...",
|
||||
"serviceName": "Regstreekse stroomdiens",
|
||||
"signedInAs": "U is tans aangemeld as:",
|
||||
"signIn": "Meld aan met Google",
|
||||
"signOut": "Meld af",
|
||||
"signInCTA": "Meld aan of gee u sleutel vir regstreekse stroom vanaf YouTube.",
|
||||
"start": "Begin ’n regstreekse stroom",
|
||||
"streamIdHelp": "Wat’s dié?",
|
||||
"unavailableTitle": "Regstreekse strome nie beskikbaar nie"
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "Ons probeer tans hulpbronne vry te stel. Probeer gerus weer oor ’n paar minute.",
|
||||
"busyTitle": "Die Kamerdiens is tans besig",
|
||||
"errorInvite": "Konferensie is nog nie gestig nie. Probeer gerus weer later.",
|
||||
"errorInviteTitle": "",
|
||||
"errorAlreadyInvited": "__displayName__ is reeds genooi",
|
||||
"errorInviteFailedTitle": "Kon nie __displayName__ nooi nie",
|
||||
"errorInviteFailed": "Ons werk aan ’n oplossing vir die probleem. Probeer gerus weer later.",
|
||||
"pending": "__displayName__ is genooi",
|
||||
"serviceName": "Kamerdiens",
|
||||
"unavailableTitle": "Kamerdiens nie beskikbaar nie"
|
||||
},
|
||||
"speakerStats": {
|
||||
"hours": "__count__h",
|
||||
"minutes": "__count__m",
|
||||
"name": "Naam",
|
||||
"seconds": "__count__s",
|
||||
"speakerStats": "Sprekerstatistiek",
|
||||
"speakerTime": "Sprekertyd"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "Toestelinstellings",
|
||||
"noPermission": "Toestemming nie gegee nie",
|
||||
"previewUnavailable": "Voorskou nie beskikbaar nie",
|
||||
"selectADevice": "Kies 'n toestel",
|
||||
"testAudio": "Speel ’n toetsklank"
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "",
|
||||
"audioOnlyExpanded": "U is in Net klank-modus. Dié modus spaar bandwydte maar u sal nie video’s van ander sien nie.",
|
||||
"callQuality": "Oproepkwaliteit",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Sien tans hoëdefinisievideo",
|
||||
"highDefinition": "Hoëdefinisie",
|
||||
"labelTooltipAudioOnly": "Net klank-modus geaktiveer",
|
||||
"labelTooiltipNoVideo": "Geen video",
|
||||
"labelTooltipVideo": "Huidige videokwaliteit",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Sien tans laedefinisievideo",
|
||||
"lowDefinition": "Laedefinisie",
|
||||
"onlyAudioAvailable": "Net klank is beskikbaar",
|
||||
"onlyAudioSupported": "Op dié blaaier ondersteun ons slegs klank.",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"recHighDefinitionOnly": "",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Sien tans standaarddefinisievideo",
|
||||
"standardDefinition": "Standaarddefinisie",
|
||||
"qualityButtonTip": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "is nou __status__"
|
||||
},
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"countryNotSupported": "Ons ondersteun nog nie dié bestemming nie.",
|
||||
"countryReminder": "",
|
||||
"disabled": "",
|
||||
"footerText": "",
|
||||
"invite": "Nooi uit",
|
||||
"loading": "",
|
||||
"loadingNumber": "Valideer tans foonnommer",
|
||||
"loadingPeople": "",
|
||||
"noResults": "Geen soekresultate wat pas nie",
|
||||
"noValidNumbers": "Gee asseblief ’n foonnommer",
|
||||
"notAvailable": "U kan nie mense nooi nie.",
|
||||
"searchNumbers": "Voeg foonnommers by",
|
||||
"searchPeople": "Soek mense",
|
||||
"searchPeopleAndNumbers": "Soek mense of voeg hulle foonnommers by",
|
||||
"telephone": "Telefoon: __number__",
|
||||
"title": "Nooi mense na dié vergadering",
|
||||
"failedToAdd": "Kon nie lede byvoeg nie"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Ons het gestruikel.",
|
||||
"retry": "Probeer weer",
|
||||
"support": "Ondersteuning",
|
||||
"supportMsg": "Indien dit aanhou, maak kontak met"
|
||||
},
|
||||
"deviceError": {
|
||||
"cameraError": "Toegang na u kamera het misluk",
|
||||
"microphoneError": "Toegang na u mikrofoon het misluk",
|
||||
"cameraPermission": "Fout met verkryging van kameratoestemming",
|
||||
"microphonePermission": "Fout met verkryging van mikrofoontoestemming"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Gemiddeld",
|
||||
"bad": "Sleg",
|
||||
"good": "Goed",
|
||||
"detailsLabel": "Vertel ons meer.",
|
||||
"rateExperience": "",
|
||||
"veryBad": "Baie sleg",
|
||||
"veryGood": "Baie goed"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "Wys inligting",
|
||||
"addPassword": "Voeg wagwoord by",
|
||||
"cancelPassword": "Kanselleer wagwoord",
|
||||
"conferenceURL": "Skakel:",
|
||||
"country": "Land",
|
||||
"dialANumber": "Om by u vergadering aan te sluit, skakel een van dié nommers en gee dan dié PIN: __conferenceID__#",
|
||||
"dialInNumber": "Inbel:",
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "Jammer. Inbel word nie tans ondersteun nie.",
|
||||
"genericError": "Oeps! Iets het skeefgeloop.",
|
||||
"inviteLiveStream": "Om die regstreekse stroom van dié vergadering te sien, klik dié skakel: __url__",
|
||||
"invitePhone": "Om per telefoon aan te sluit, skakel __number__ en gee dié PIN: __conferenceID__#",
|
||||
"invitePhoneAlternatives": "Om meer foonnommers te sien, klik dié skakel: __url__",
|
||||
"inviteURL": "Om by die videovergadering aan te sluit, klik dié skakel: __url__",
|
||||
"liveStreamURL": "Regstreekse stroom:",
|
||||
"moreNumbers": "Meer nommers",
|
||||
"noNumbers": "Geen inbelnommers.",
|
||||
"noPassword": "Geen",
|
||||
"noRoom": "Geen kamer is gegee om na in te bel nie.",
|
||||
"numbers": "Inbelnommers",
|
||||
"password": "Wagwoord:",
|
||||
"title": "Deel",
|
||||
"tooltip": "Deelskakel en inbelinligting vir dié vergadering"
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "Regso",
|
||||
"alertTitle": "Waarskuwing",
|
||||
"alertURLText": "Die gegewe bediener-URL is ongeldig",
|
||||
"conferenceSection": "Konferensie",
|
||||
"displayName": "Vertoonnaam",
|
||||
"email": "E-pos",
|
||||
"header": "Instellings",
|
||||
"profileSection": "Profiel",
|
||||
"serverURL": "Bediener-URL",
|
||||
"startWithAudioMuted": "Begin met klank gedemp",
|
||||
"startWithVideoMuted": "Begin met video gedemp"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Voeg ’n vergaderingskakel by",
|
||||
"confirmAddLink": "Wil u ’n Jitsi-skakel by dié geleentheid voeg?",
|
||||
"confirmAddLinkTitle": "Kalender",
|
||||
"join": "Sluit aan",
|
||||
"joinTooltip": "Sluit aan by die vergadering",
|
||||
"nextMeeting": "volgende vergadering",
|
||||
"noEvents": "Geen komende geleenthede is geskeduleer nie.",
|
||||
"ongoingMeeting": "vergadering onderweg",
|
||||
"permissionButton": "Open instellings",
|
||||
"permissionMessage": "",
|
||||
"refresh": "Verfris kalender",
|
||||
"today": ""
|
||||
},
|
||||
"recentList": {
|
||||
"joinPastMeeting": "Sluit by ’n vorige vergadering aan"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": ""
|
||||
},
|
||||
"deepLinking": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"tryAgainButton": "",
|
||||
"launchWebButton": "",
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "",
|
||||
"openApp": ""
|
||||
},
|
||||
"presenceStatus": {
|
||||
"invited": "Uitgenooi",
|
||||
"ringing": "Lui tans...",
|
||||
"calling": "Bel tans...",
|
||||
"initializingCall": "Inisialiseer tans oproep...",
|
||||
"connected": "Gekoppel",
|
||||
"connecting": "Koppel tans...",
|
||||
"connecting2": "Koppel tans*...",
|
||||
"disconnected": "Ontkoppeld",
|
||||
"busy": "Besig",
|
||||
"rejected": "Geweier",
|
||||
"ignored": "Geïgnoreer",
|
||||
"expired": "Verval"
|
||||
},
|
||||
"dateUtils": {
|
||||
"today": "Vandag",
|
||||
"yesterday": "Gister",
|
||||
"earlier": "Vroeër"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Antwoord",
|
||||
"audioCallTitle": "Inkomende oproep",
|
||||
"decline": "Weier",
|
||||
"productLabel": "vanaf Jitsi Meet",
|
||||
"videoCallTitle": "Inkomende video-oproep"
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "Plaaslike opname",
|
||||
"dialogTitle": "Kontroles vir plaaslike opname",
|
||||
"start": "Begin opname",
|
||||
"stop": "Stop opname",
|
||||
"moderator": "Moderator",
|
||||
"me": "Ek",
|
||||
"duration": "Duur",
|
||||
"durationNA": "",
|
||||
"encoding": "Enkodering",
|
||||
"participantStats": "Deelnemerstatistiek",
|
||||
"participant": "Deelnemer",
|
||||
"sessionToken": "",
|
||||
"clientState": {
|
||||
"on": "Aan",
|
||||
"off": "Af",
|
||||
"unknown": "Onbekend"
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": "U is nie die moderator nie. U kan nie ’n plaaslike opname begin of stop nie."
|
||||
},
|
||||
"yes": "Ja",
|
||||
"no": "Nee",
|
||||
"label": "",
|
||||
"labelToolTip": ""
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
@@ -145,6 +145,22 @@ export function forEachConference(
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name of the conference.
|
||||
*
|
||||
* @param {Function | Object} stateful - Reference that can be resolved to Redux
|
||||
* state with the {@code toState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getConferenceName(stateful: Function | Object): string {
|
||||
const state = toState(stateful);
|
||||
const { callee } = state['features/base/jwt'];
|
||||
|
||||
return state['features/base/config'].callDisplayName
|
||||
|| (callee && callee.name)
|
||||
|| state['features/base/conference'].room;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@code JitsiConference} which is joining or joined and is
|
||||
* not leaving. Please note the contrast with merely reading the
|
||||
|
||||
@@ -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
react/features/conference/components/index.native.js
Normal file
3
react/features/conference/components/index.native.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
3
react/features/conference/components/index.web.js
Normal file
3
react/features/conference/components/index.web.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
@@ -5,29 +5,31 @@ import React, { Component } from 'react';
|
||||
import { BackHandler, StatusBar, View } from 'react-native';
|
||||
import { connect as reactReduxConnect } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../app';
|
||||
import { connect, disconnect } from '../../base/connection';
|
||||
import { getParticipantCount } from '../../base/participants';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../base/react';
|
||||
import { appNavigate } from '../../../app';
|
||||
import { connect, disconnect } from '../../../base/connection';
|
||||
import { getParticipantCount } from '../../../base/participants';
|
||||
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
|
||||
import {
|
||||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import { TestConnectionInfo } from '../../base/testing';
|
||||
import { createDesiredLocalTracks } from '../../base/tracks';
|
||||
import { ConferenceNotification } from '../../calendar-sync';
|
||||
import { Chat } from '../../chat';
|
||||
} from '../../../base/responsive-ui';
|
||||
import { TestConnectionInfo } from '../../../base/testing';
|
||||
import { createDesiredLocalTracks } from '../../../base/tracks';
|
||||
import { ConferenceNotification } from '../../../calendar-sync';
|
||||
import { Chat } from '../../../chat';
|
||||
import {
|
||||
Filmstrip,
|
||||
isFilmstripVisible,
|
||||
TileView
|
||||
} from '../../filmstrip';
|
||||
import { LargeVideo } from '../../large-video';
|
||||
import { CalleeInfoContainer } from '../../invite';
|
||||
import { Captions } from '../../subtitles';
|
||||
import { setToolboxVisible, Toolbox } from '../../toolbox';
|
||||
import { shouldDisplayTileView } from '../../video-layout';
|
||||
} from '../../../filmstrip';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { Captions } from '../../../subtitles';
|
||||
import { setToolboxVisible, Toolbox } from '../../../toolbox';
|
||||
import { shouldDisplayTileView } from '../../../video-layout';
|
||||
|
||||
import DisplayNameLabel from './DisplayNameLabel';
|
||||
import Labels from './Labels';
|
||||
import NavigationBar from './NavigationBar';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
@@ -282,6 +284,8 @@ class Conference extends Component<Props> {
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolboxAndFilmstripContainer }>
|
||||
|
||||
<Labels />
|
||||
|
||||
<Captions onPress = { this._onClick } />
|
||||
|
||||
<DisplayNameLabel />
|
||||
@@ -303,6 +307,8 @@ class Conference extends Component<Props> {
|
||||
}
|
||||
</View>
|
||||
|
||||
<NavigationBar />
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
||||
{
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
getLocalParticipant,
|
||||
getParticipantDisplayName,
|
||||
shouldRenderParticipantVideo
|
||||
} from '../../base/participants';
|
||||
} from '../../../base/participants';
|
||||
|
||||
import { shouldDisplayTileView } from '../../video-layout';
|
||||
import { shouldDisplayTileView } from '../../../video-layout';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
@@ -4,21 +4,23 @@ import React from 'react';
|
||||
import { TouchableOpacity, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import {
|
||||
RecordingExpandedLabel
|
||||
} from '../../recording';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
makeAspectRatioAware
|
||||
} from '../../base/responsive-ui';
|
||||
import { TranscribingExpandedLabel } from '../../transcribing';
|
||||
import { VideoQualityExpandedLabel } from '../../video-quality';
|
||||
} from '../../../base/responsive-ui';
|
||||
import {
|
||||
RecordingExpandedLabel
|
||||
} from '../../../recording';
|
||||
import { isToolboxVisible } from '../../../toolbox';
|
||||
import { TranscribingExpandedLabel } from '../../../transcribing';
|
||||
import { shouldDisplayTileView } from '../../../video-layout';
|
||||
import { VideoQualityExpandedLabel } from '../../../video-quality';
|
||||
|
||||
import AbstractLabels, {
|
||||
_abstractMapStateToProps,
|
||||
type Props as AbstractLabelsProps
|
||||
} from './AbstractLabels';
|
||||
} from '../AbstractLabels';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,12 @@ type Props = AbstractLabelsProps & {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_reducedUI: boolean
|
||||
_reducedUI: boolean,
|
||||
|
||||
/**
|
||||
* True if the labels should be visible, false otherwise.
|
||||
*/
|
||||
_visible: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -148,6 +155,10 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const wide = !isNarrowAspectRatio(this);
|
||||
const { _filmstripVisible, _reducedUI } = this.props;
|
||||
|
||||
@@ -344,13 +355,15 @@ class Labels extends AbstractLabels<Props, State> {
|
||||
* @private
|
||||
* @returns {{
|
||||
* _filmstripVisible: boolean,
|
||||
* _reducedUI: boolean
|
||||
* _reducedUI: boolean,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_reducedUI: state['features/base/responsive-ui'].reducedUI
|
||||
_reducedUI: state['features/base/responsive-ui'].reducedUI,
|
||||
_visible: !isToolboxVisible(state) && !shouldDisplayTileView(state)
|
||||
};
|
||||
}
|
||||
|
||||
96
react/features/conference/components/native/NavigationBar.js
Normal file
96
react/features/conference/components/native/NavigationBar.js
Normal file
@@ -0,0 +1,96 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { SafeAreaView, Text, View } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getConferenceName } from '../../../base/conference';
|
||||
import { PictureInPictureButton } from '../../../mobile/picture-in-picture';
|
||||
import { isToolboxVisible } from '../../../toolbox';
|
||||
|
||||
import styles, { NAVBAR_GRADIENT_COLORS } from './styles';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Name of the meeting we're currently in.
|
||||
*/
|
||||
_meetingName: string,
|
||||
|
||||
/**
|
||||
* True if the navigation bar should be visible.
|
||||
*/
|
||||
_visible: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a navigation bar component that is rendered on top of the
|
||||
* conference screen.
|
||||
*/
|
||||
class NavigationBar extends Component<Props> {
|
||||
/**
|
||||
* Implements {@Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.navBarContainer }>
|
||||
<LinearGradient
|
||||
colors = { NAVBAR_GRADIENT_COLORS }
|
||||
pointerEvents = 'none'
|
||||
style = { styles.gradient }>
|
||||
<SafeAreaView>
|
||||
<View style = { styles.gradientStretch } />
|
||||
</SafeAreaView>
|
||||
</LinearGradient>
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.navBarSafeView }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.navBarWrapper }>
|
||||
<PictureInPictureButton
|
||||
styles = { styles.navBarButton } />
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.roomNameWrapper }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.roomName }>
|
||||
{ this.props._meetingName }
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {{
|
||||
* _meetingName: string,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_meetingName: _.startCase(getConferenceName(state)),
|
||||
_visible: isToolboxVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(NavigationBar);
|
||||
137
react/features/conference/components/native/styles.js
Normal file
137
react/features/conference/components/native/styles.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
BoxModel,
|
||||
ColorPalette,
|
||||
createStyleSheet,
|
||||
fixAndroidViewClipping
|
||||
} from '../../../base/styles';
|
||||
|
||||
import { FILMSTRIP_SIZE } from '../../../filmstrip';
|
||||
|
||||
export const NAVBAR_GRADIENT_COLORS = [ 'black', '#00000000' ];
|
||||
|
||||
/**
|
||||
* The styles of the feature conference.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* {@code Conference} style.
|
||||
*/
|
||||
conference: fixAndroidViewClipping({
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
flex: 1
|
||||
}),
|
||||
|
||||
displayNameBackdrop: {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: 'rgba(28, 32, 37, 0.6)',
|
||||
borderRadius: 4,
|
||||
margin: 16,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 4
|
||||
},
|
||||
|
||||
displayNameText: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 14
|
||||
},
|
||||
|
||||
gradient: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
gradientStretch: {
|
||||
height: 116
|
||||
},
|
||||
|
||||
/**
|
||||
* View that contains the indicators.
|
||||
*/
|
||||
indicatorContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
margin: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicator container for wide aspect ratio.
|
||||
*/
|
||||
indicatorContainerWide: {
|
||||
marginRight: FILMSTRIP_SIZE + BoxModel.margin
|
||||
},
|
||||
|
||||
labelWrapper: {
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
navBarButton: {
|
||||
iconStyle: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 24
|
||||
},
|
||||
|
||||
underlayColor: 'transparent'
|
||||
},
|
||||
|
||||
navBarContainer: {
|
||||
flexDirection: 'column',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
navBarSafeView: {
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0
|
||||
},
|
||||
|
||||
navBarWrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 44,
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 14
|
||||
},
|
||||
|
||||
roomName: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 17,
|
||||
fontWeight: '400'
|
||||
},
|
||||
|
||||
roomNameWrapper: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
left: 0,
|
||||
paddingHorizontal: 48,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the {@link View} which expands over the whole
|
||||
* {@link Conference} area and splits it between the {@link Filmstrip} and
|
||||
* the {@link Toolbox}.
|
||||
*/
|
||||
toolboxAndFilmstripContainer: {
|
||||
bottom: BoxModel.margin,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
|
||||
// Both on Android and iOS there is the status bar which may be visible.
|
||||
// On iPhone X there is the notch. In the two cases BoxModel.margin is
|
||||
// not enough.
|
||||
top: BoxModel.margin * 3
|
||||
}
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
import {
|
||||
BoxModel,
|
||||
ColorPalette,
|
||||
createStyleSheet,
|
||||
fixAndroidViewClipping
|
||||
} from '../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the feature conference.
|
||||
*/
|
||||
export default createStyleSheet({
|
||||
/**
|
||||
* {@code Conference} style.
|
||||
*/
|
||||
conference: fixAndroidViewClipping({
|
||||
alignSelf: 'stretch',
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
flex: 1
|
||||
}),
|
||||
|
||||
displayNameBackdrop: {
|
||||
alignSelf: 'center',
|
||||
backgroundColor: 'rgba(28, 32, 37, 0.6)',
|
||||
borderRadius: 4,
|
||||
margin: 16,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 4
|
||||
},
|
||||
|
||||
displayNameText: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 14
|
||||
},
|
||||
|
||||
/**
|
||||
* The style of the {@link View} which expands over the whole
|
||||
* {@link Conference} area and splits it between the {@link Filmstrip} and
|
||||
* the {@link Toolbox}.
|
||||
*/
|
||||
toolboxAndFilmstripContainer: {
|
||||
bottom: BoxModel.margin,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
|
||||
// Both on Android and iOS there is the status bar which may be visible.
|
||||
// On iPhone X there is the notch. In the two cases BoxModel.margin is
|
||||
// not enough.
|
||||
top: BoxModel.margin * 3
|
||||
}
|
||||
});
|
||||
@@ -4,31 +4,33 @@ import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect as reactReduxConnect } from 'react-redux';
|
||||
|
||||
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
|
||||
import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
||||
|
||||
import { obtainConfig } from '../../base/config';
|
||||
import { connect, disconnect } from '../../base/connection';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Chat } from '../../chat';
|
||||
import { Filmstrip } from '../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../invite';
|
||||
import { LargeVideo } from '../../large-video';
|
||||
import { NotificationsContainer } from '../../notifications';
|
||||
import { obtainConfig } from '../../../base/config';
|
||||
import { connect, disconnect } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Chat } from '../../../chat';
|
||||
import { Filmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { NotificationsContainer } from '../../../notifications';
|
||||
import {
|
||||
LAYOUTS,
|
||||
getCurrentLayout,
|
||||
shouldDisplayTileView
|
||||
} from '../../video-layout';
|
||||
} from '../../../video-layout';
|
||||
|
||||
import { default as Notice } from './Notice';
|
||||
import {
|
||||
Toolbox,
|
||||
fullScreenChanged,
|
||||
setToolboxAlwaysVisible,
|
||||
showToolbox
|
||||
} from '../../toolbox';
|
||||
} from '../../../toolbox';
|
||||
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../../functions';
|
||||
|
||||
import Labels from './Labels';
|
||||
import { default as Notice } from './Notice';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var config: Object;
|
||||
@@ -216,8 +218,9 @@ class Conference extends Component<Props> {
|
||||
onMouseMove = { this._onShowToolbar }>
|
||||
<Notice />
|
||||
<div id = 'videospace'>
|
||||
<LargeVideo
|
||||
hideVideoQualityLabel = { hideVideoQualityLabel } />
|
||||
<LargeVideo />
|
||||
{ hideVideoQualityLabel
|
||||
|| <Labels /> }
|
||||
<Filmstrip filmstripOnly = { filmstripOnly } />
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
|
||||
import AbstractLabels, {
|
||||
_abstractMapStateToProps as _mapStateToProps,
|
||||
type Props
|
||||
} from './AbstractLabels';
|
||||
} from '../AbstractLabels';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link Labels}.
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
declare var config: Object;
|
||||
|
||||
3
react/features/conference/components/web/index.js
Normal file
3
react/features/conference/components/web/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export { default as Conference } from './Conference';
|
||||
@@ -515,12 +515,14 @@ class ConnectionStatsTable extends Component<Props> {
|
||||
|| transport[0].remoteCandidateType === 'relay';
|
||||
}
|
||||
|
||||
let additionalData = null;
|
||||
const additionalData = [];
|
||||
|
||||
if (isP2P) {
|
||||
additionalData = isTURN
|
||||
? <span>{ t('connectionindicator.turn') }</span>
|
||||
: <span>{ t('connectionindicator.peer_to_peer') }</span>;
|
||||
additionalData.push(
|
||||
<span>{ t('connectionindicator.peer_to_peer') }</span>);
|
||||
}
|
||||
if (isTURN) {
|
||||
additionalData.push(<span>{ t('connectionindicator.turn') }</span>);
|
||||
}
|
||||
|
||||
// First show remote statistics, then local, and then transport type.
|
||||
|
||||
@@ -54,7 +54,7 @@ export function generateDeepLinkingURL() {
|
||||
const url = href.replace(regex, '').substr(2);
|
||||
const pkg = interfaceConfig.ANDROID_APP_PACKAGE || 'org.jitsi.meet';
|
||||
|
||||
return `intent://${url}/#Intent;scheme=${appScheme};package=${pkg};end`;
|
||||
return `intent://${url}#Intent;scheme=${appScheme};package=${pkg};end`;
|
||||
}
|
||||
|
||||
// iOS: Replace the protocol part with the app scheme.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -174,7 +174,7 @@ export function updateDialInNumbers() {
|
||||
const { room } = state['features/base/conference'];
|
||||
|
||||
Promise.all([
|
||||
getDialInNumbers(dialInNumbersUrl),
|
||||
getDialInNumbers(dialInNumbersUrl, room, mucURL),
|
||||
getDialInConferenceID(dialInConfCodeUrl, room, mucURL)
|
||||
])
|
||||
.then(([ dialInNumbers, { conference, id, message } ]) => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -190,13 +190,25 @@ class DialInSummary extends Component<Props, State> {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getNumbers() {
|
||||
const { dialInNumbersUrl } = config;
|
||||
const { room } = this.props;
|
||||
const { dialInNumbersUrl, hosts } = config;
|
||||
const mucURL = hosts && hosts.muc;
|
||||
let URLSuffix = '';
|
||||
|
||||
if (!dialInNumbersUrl) {
|
||||
return Promise.reject(this.props.t('info.dialInNotSupported'));
|
||||
}
|
||||
|
||||
return fetch(dialInNumbersUrl)
|
||||
// when room and mucURL are available
|
||||
// provide conference when looking up dial in numbers
|
||||
|
||||
if (room && mucURL) {
|
||||
URLSuffix = `?conference=${room}@${mucURL}`;
|
||||
}
|
||||
const conferenceIDURL
|
||||
= `${dialInNumbersUrl}${URLSuffix}`;
|
||||
|
||||
return fetch(conferenceIDURL)
|
||||
.then(response => response.json())
|
||||
.catch(() => Promise.reject(this.props.t('info.genericError')));
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -56,12 +55,22 @@ export function getDialInConferenceID(
|
||||
* Sends a GET request for phone numbers used to dial into a conference.
|
||||
*
|
||||
* @param {string} url - The service that returns confernce dial-in numbers.
|
||||
* @param {string} roomName - The conference name to find the associated
|
||||
* conference ID.
|
||||
* @param {string} mucURL - In which MUC the conference exists.
|
||||
* @returns {Promise} - The promise created by the request. The returned numbers
|
||||
* may be an array of numbers or an object with countries as keys and arrays of
|
||||
* phone number strings.
|
||||
*/
|
||||
export function getDialInNumbers(url: string): Promise<*> {
|
||||
return doGetJSON(url);
|
||||
export function getDialInNumbers(
|
||||
url: string,
|
||||
roomName: string,
|
||||
mucURL: string
|
||||
): Promise<*> {
|
||||
|
||||
const fullUrl = `${url}?conference=${roomName}@${mucURL}`;
|
||||
|
||||
return doGetJSON(fullUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,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'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,7 +430,7 @@ export function getShareInfoText(
|
||||
}
|
||||
|
||||
numbersPromise = Promise.all([
|
||||
getDialInNumbers(dialInNumbersUrl),
|
||||
getDialInNumbers(dialInNumbersUrl, room, mucURL),
|
||||
getDialInConferenceID(dialInConfCodeUrl, room, mucURL)
|
||||
]).then(([ { defaultCountry, numbers }, {
|
||||
conference, id, message } ]) => {
|
||||
|
||||
@@ -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,28 +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 Labels from './Labels';
|
||||
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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -115,6 +121,7 @@ class LargeVideo extends Component<Props, State> {
|
||||
} = this.state;
|
||||
const {
|
||||
_participantId,
|
||||
_styles,
|
||||
onClick
|
||||
} = this.props;
|
||||
|
||||
@@ -125,12 +132,11 @@ 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 }
|
||||
zoomEnabled = { true } />
|
||||
<Labels />
|
||||
</DimensionsDetector>
|
||||
);
|
||||
}
|
||||
@@ -142,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')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,28 +5,15 @@ import React, { Component } from 'react';
|
||||
import { Watermarks } from '../../base/react';
|
||||
import { Captions } from '../../subtitles/';
|
||||
|
||||
import Labels from './Labels';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link LargeVideo}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* True if the {@code VideoQualityLabel} should not be displayed.
|
||||
*/
|
||||
hideVideoQualityLabel: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which represents the large video (a.k.a.
|
||||
* the conference participant who is on the local stage) on Web/React.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export default class LargeVideo extends Component<Props> {
|
||||
export default class LargeVideo extends Component<{}> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -47,9 +34,7 @@ export default class LargeVideo extends Component<Props> {
|
||||
|
||||
<div id = 'dominantSpeaker'>
|
||||
<div className = 'dynamic-shadow' />
|
||||
<img
|
||||
id = 'dominantSpeakerAvatar'
|
||||
src = '' />
|
||||
<div id = 'dominantSpeakerAvatarContainer' />
|
||||
</div>
|
||||
<div id = 'remotePresenceMessage' />
|
||||
<span id = 'remoteConnectionMessage' />
|
||||
@@ -74,8 +59,6 @@ export default class LargeVideo extends Component<Props> {
|
||||
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES
|
||||
|| <Captions /> }
|
||||
<span id = 'localConnectionMessage' />
|
||||
{ this.props.hideVideoQualityLabel
|
||||
|| <Labels /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,42 +1,16 @@
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { BoxModel, ColorPalette, createStyleSheet } from '../../base/styles';
|
||||
import { FILMSTRIP_SIZE } from '../../filmstrip';
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
|
||||
|
||||
/**
|
||||
* Size for the Avatar.
|
||||
*/
|
||||
export const AVATAR_SIZE = 200;
|
||||
|
||||
export default createStyleSheet({
|
||||
|
||||
/**
|
||||
* View that contains the indicators.
|
||||
*/
|
||||
indicatorContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
margin: BoxModel.margin
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicator container for wide aspect ratio.
|
||||
*/
|
||||
indicatorContainerWide: {
|
||||
marginRight: FILMSTRIP_SIZE + BoxModel.margin
|
||||
},
|
||||
|
||||
labelWrapper: {
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
|
||||
// Both on Android and iOS there is the status bar which may be visible.
|
||||
// On iPhone X there is the notch. In the two cases BoxModel.margin is
|
||||
// not enough.
|
||||
top: BoxModel.margin * 3
|
||||
},
|
||||
/**
|
||||
* Color schemed styles for the @{LargeVideo} component.
|
||||
*/
|
||||
ColorSchemeRegistry.register('LargeVideo', {
|
||||
|
||||
/**
|
||||
* Large video container style.
|
||||
@@ -44,7 +18,7 @@ export default createStyleSheet({
|
||||
largeVideo: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: ColorPalette.appBackground,
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_JOINED,
|
||||
SET_AUDIO_ONLY,
|
||||
getConferenceName,
|
||||
getCurrentConference
|
||||
} from '../../base/conference';
|
||||
import { getInviteURL } from '../../base/connection';
|
||||
@@ -226,12 +227,7 @@ function _conferenceWillJoin({ dispatch, getState }, next, action) {
|
||||
|
||||
CallIntegration.startCall(conference.callUUID, handle, hasVideo)
|
||||
.then(() => {
|
||||
const { callee } = state['features/base/jwt'];
|
||||
const displayName
|
||||
= state['features/base/config'].callDisplayName
|
||||
|| (callee && callee.name)
|
||||
|| state['features/base/conference'].room;
|
||||
|
||||
const displayName = getConferenceName(state);
|
||||
const muted
|
||||
= isLocalTrackMuted(
|
||||
state['features/base/tracks'],
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import { StatusBar } from 'react-native';
|
||||
import { Immersive } from 'react-native-immersive';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
|
||||
@@ -91,10 +90,6 @@ function _setFullScreen(fullScreen: boolean) {
|
||||
// throws on other platforms.
|
||||
if (Platform.OS === 'android') {
|
||||
fullScreen ? Immersive.on() : Immersive.off();
|
||||
} else {
|
||||
// On platforms other than Android go with whatever React Native itself
|
||||
// supports.
|
||||
StatusBar.setHidden(fullScreen, 'slide');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user