Compare commits

...

14 Commits

Author SHA1 Message Date
Aaron van Meerten
1a5e2763c1 use fetch syntax from virtuacoplenny 2019-02-13 16:23:26 -06:00
Aaron van Meerten
76638f524d invite post to use application/JSON
changed to .ajax from .post to allow content type to be set
2019-02-12 16:28:33 -06:00
Saúl Ibarra Corretgé
8ea693616d color-scheme: fix React warning
A reducer must always return a state or null to ignore it. When the color scheme
is undefined we should return the previous state.
2019-02-12 20:22:28 +01:00
Дамян Минков
2442ef80b0 Adds Afrikaans to languages. (#3884) 2019-02-12 13:18:56 +00:00
Saúl Ibarra Corretgé
87f171caa4 feat(ScreenSharing): use sane defaults
Both Chrome and Firefox can work without extensions now, so it no longer makes
sense to default to disabling it in Chrome. Moreover, rely on the fact that
undefined is falsey so no actual config needs to be provided.
2019-02-12 10:00:23 +00:00
Bettenbuk Zoltan
e094b6516a [RN] Add color scheme support - Components 2019-02-08 11:43:21 +01:00
Bettenbuk Zoltan
2941f5dde4 [RN] Add color scheme support - JS 2019-02-08 11:43:21 +01:00
Bettenbuk Zoltan
eec7a1b628 [RN] Add color scheme support - native 2019-02-08 11:43:21 +01:00
Saúl Ibarra Corretgé
5f7a515610 rn: drop {AddPeople,Invite}Controller
We are going to implement the invite dialog *inside* the SDK, so there is no
need to have all this machinery anymore.
2019-02-08 09:02:15 +01:00
virtuacoplenny
b7133f5717 fix(large-video): do not show avatar if no url (#3871)
* fix(large-video): do not show avatar if no url

By default the large video dominant speaker avatar
has an empty src, which will result in a broken
image displaying. There is also disconnect with
non-react code trying to set an undefined src.
To prevent such until local avatar generation
work is done in the future, just don't show the
avatar.

* fix(conference): set the room instance earlier

Set the room instance on APP.conference before triggering
a redux update of the conference being set,, because
middleware can then fire and call methods on APP.conference
that depend on the room being set.

* get local participant directly from store instead of from global
2019-02-06 19:19:02 -08:00
virtuacoplenny
f77e1dc591 fix(speaker-levels): convert calculation from string to float (#3870) 2019-02-06 10:49:20 -08:00
virtuacoplenny
4d817fc6c2 fix(home): fix plus button alignment for calendar events (#3869) 2019-02-06 09:33:44 -08:00
Paweł Domas
b8a7037959 Merge pull request #3866 from jitsi/ice_failed_notification
chore(deps): update LJM to get ICE failed notifications
2019-02-06 09:00:55 -06:00
paweldomas
1cfd6164f5 chore(deps): update LJM to get ICE failed notifications
Updates LJM to 2e1436e20d4d8fb6020497a87b2714dff38a6c86 which includes
the ICE failed notification logic.
2019-02-05 21:45:02 -06:00
82 changed files with 1024 additions and 2341 deletions

View File

@@ -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();
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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: {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -18,10 +18,6 @@
#import <JitsiMeet/JitsiMeet.h>
@interface ViewController
: UIViewController<
JitsiMeetViewDelegate,
JMAddPeopleControllerDelegate,
JMInviteControllerDelegate>
@interface ViewController : UIViewController<JitsiMeetViewDelegate>
@end

View File

@@ -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

View File

@@ -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 */,

View File

@@ -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>

View File

@@ -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;

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,5 +1,6 @@
{
"en": "English",
"af": "Afrikaans",
"az": "Azerbaijani",
"bg": "Bulgarian",
"cs": "Czech",

View File

@@ -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
};

View File

@@ -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);
}
}
/**

View File

@@ -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',

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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);
}

View File

@@ -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),

View 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();

View 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');

View 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
};
}

View 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
}
};

View 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;
}

View File

@@ -0,0 +1,8 @@
// @flow
export * from './actions';
export * from './actionTypes';
export * from './functions';
export { default as ColorSchemeRegistry } from './ColorSchemeRegistry';
import './reducer';

View 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);
});

View 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;
});

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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));

View File

@@ -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);

View File

@@ -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));

View File

@@ -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));

View File

@@ -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
}
});

View File

@@ -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')
};
}

View File

@@ -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) }

View File

@@ -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'

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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
};
}

View File

@@ -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
}
});

View File

@@ -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.

View File

@@ -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}

View File

@@ -1,6 +1,5 @@
// @flow
import { getAppProp } from '../base/app';
import { i18next } from '../base/i18n';
import { isLocalParticipantModerator } from '../base/participants';
import { doGetJSON, parseURIString } from '../base/util';
@@ -275,17 +274,19 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
return Promise.resolve();
}
return new Promise((resolve, reject) => {
$.post(
`${inviteServiceUrl}?token=${jwt}`,
JSON.stringify({
'invited': inviteItems,
'url': inviteUrl
}),
resolve,
'json')
.fail((jqxhr, textStatus, error) => reject(error));
});
return fetch(
`${inviteServiceUrl}?token=${jwt}`,
{
body: JSON.stringify({
'invited': inviteItems,
'url': inviteUrl
}),
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}
);
}
/**
@@ -297,18 +298,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
export function isAddPeopleEnabled(state: Object): boolean {
const { isGuest } = state['features/base/jwt'];
if (!isGuest) {
// XXX The mobile/react-native app is capable of disabling the
// adding/inviting of people in the current conference. Anyway, the
// Web/React app does not have that capability so default appropriately.
const addPeopleEnabled = getAppProp(state, 'addPeopleEnabled');
return (
(typeof addPeopleEnabled === 'undefined')
|| Boolean(addPeopleEnabled));
}
return false;
return !isGuest;
}
/**
@@ -319,21 +309,9 @@ export function isAddPeopleEnabled(state: Object): boolean {
*/
export function isDialOutEnabled(state: Object): boolean {
const { conference } = state['features/base/conference'];
let dialOutEnabled = isLocalParticipantModerator(state)
&& conference
&& conference.isSIPCallingSupported();
if (dialOutEnabled) {
// XXX The mobile/react-native app is capable of disabling of dial-out.
// Anyway, the Web/React app does not have that capability so default
// appropriately.
dialOutEnabled = getAppProp(state, 'dialOutEnabled');
return (
(typeof dialOutEnabled === 'undefined') || Boolean(dialOutEnabled));
}
return false;
return isLocalParticipantModerator(state)
&& conference && conference.isSIPCallingSupported();
}
/**

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -3,27 +3,34 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ColorSchemeRegistry } from '../../base/color-scheme';
import { ParticipantView } from '../../base/participants';
import { DimensionsDetector } from '../../base/responsive-ui';
import { StyleType } from '../../base/styles';
import styles, { AVATAR_SIZE } from './styles';
import { AVATAR_SIZE } from './styles';
/**
* The type of the React {@link Component} props of {@link LargeVideo}.
*/
type Props = {
/**
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
*/
onClick: Function,
/**
* The ID of the participant (to be) depicted by LargeVideo.
*
* @private
*/
_participantId: string
_participantId: string,
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* Callback to invoke when the {@code LargeVideo} is clicked/pressed.
*/
onClick: Function,
};
/**
@@ -114,6 +121,7 @@ class LargeVideo extends Component<Props, State> {
} = this.state;
const {
_participantId,
_styles,
onClick
} = this.props;
@@ -124,7 +132,7 @@ class LargeVideo extends Component<Props, State> {
avatarSize = { avatarSize }
onPress = { onClick }
participantId = { _participantId }
style = { styles.largeVideo }
style = { _styles.largeVideo }
testHintId = 'org.jitsi.meet.LargeVideo'
useConnectivityInfoLabel = { useConnectivityInfoLabel }
zOrder = { 0 }
@@ -140,12 +148,14 @@ class LargeVideo extends Component<Props, State> {
* @param {Object} state - Redux state.
* @private
* @returns {{
* _participantId: string
* _participantId: string,
* _styles: StyleType
* }}
*/
function _mapStateToProps(state) {
return {
_participantId: state['features/large-video'].participantId
_participantId: state['features/large-video'].participantId,
_styles: ColorSchemeRegistry.get(state, 'LargeVideo')
};
}

View File

@@ -34,9 +34,7 @@ export default class LargeVideo extends Component<{}> {
<div id = 'dominantSpeaker'>
<div className = 'dynamic-shadow' />
<img
id = 'dominantSpeakerAvatar'
src = '' />
<div id = 'dominantSpeakerAvatarContainer' />
</div>
<div id = 'remotePresenceMessage' />
<span id = 'remoteConnectionMessage' />

View File

@@ -1,13 +1,16 @@
import { StyleSheet } from 'react-native';
import { ColorPalette, createStyleSheet } from '../../base/styles';
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
/**
* Size for the Avatar.
*/
export const AVATAR_SIZE = 200;
export default createStyleSheet({
/**
* Color schemed styles for the @{LargeVideo} component.
*/
ColorSchemeRegistry.register('LargeVideo', {
/**
* Large video container style.
@@ -15,7 +18,7 @@ export default createStyleSheet({
largeVideo: {
...StyleSheet.absoluteFillObject,
alignItems: 'stretch',
backgroundColor: ColorPalette.appBackground,
backgroundColor: schemeColor('background'),
flex: 1,
justifyContent: 'center'
}

View File

@@ -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();
}

View File

@@ -4,7 +4,9 @@ import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { _abstractMapStateToProps } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { StyleType } from '../../../../base/styles';
import {
GOOGLE_API_STATES,
@@ -23,6 +25,11 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
*/
type Props = {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
/**
* The Redux dispatch Function.
*/
@@ -102,7 +109,7 @@ class GoogleSigninForm extends Component<Props> {
* @inheritdoc
*/
render() {
const { t } = this.props;
const { _dialogStyles, t } = this.props;
const { googleAPIState, googleResponse } = this.props;
const signedInUser = googleResponse
&& googleResponse.user
@@ -121,7 +128,11 @@ class GoogleSigninForm extends Component<Props> {
return (
<View style = { styles.formWrapper }>
<View style = { styles.helpText }>
<Text style = { styles.text }>
<Text
style = { [
_dialogStyles.text,
styles.text
] }>
{ userInfo }
</Text>
</View>
@@ -225,6 +236,7 @@ function _mapStateToProps(state: Object) {
const { googleAPIState, googleResponse } = state['features/google-api'];
return {
..._abstractMapStateToProps(state),
googleAPIState,
googleResponse
};

View File

@@ -2,13 +2,24 @@
import React from 'react';
import { Linking, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { connect } from 'react-redux';
import { _abstractMapStateToProps } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { StyleType } from '../../../../base/styles';
import AbstractStreamKeyForm, {
type Props
type Props as AbstractProps
} from '../AbstractStreamKeyForm';
type Props = AbstractProps & {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType
};
import styles, { PLACEHOLDER_COLOR } from './styles';
/**
@@ -16,7 +27,7 @@ import styles, { PLACEHOLDER_COLOR } from './styles';
*
* @extends Component
*/
class StreamKeyForm extends AbstractStreamKeyForm {
class StreamKeyForm extends AbstractStreamKeyForm<Props> {
/**
* Initializes a new {@code StreamKeyForm} instance.
*
@@ -37,11 +48,16 @@ class StreamKeyForm extends AbstractStreamKeyForm {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
const { _dialogStyles, t } = this.props;
return (
<View style = { styles.formWrapper }>
<Text style = { styles.streamKeyInputLabel }>
<Text
style = { [
_dialogStyles.text,
styles.text,
styles.streamKeyInputLabel
] }>
{
t('dialog.streamKey')
}
@@ -56,7 +72,11 @@ class StreamKeyForm extends AbstractStreamKeyForm {
{
this.state.showValidationError
? <View style = { styles.formFooterItem }>
<Text style = { styles.warningText }>
<Text
style = { [
_dialogStyles.text,
styles.warningText
] }>
{ t('liveStreaming.invalidStreamKey') }
</Text>
</View>
@@ -66,7 +86,11 @@ class StreamKeyForm extends AbstractStreamKeyForm {
<TouchableOpacity
onPress = { this._onOpenHelp }
style = { styles.streamKeyHelp } >
<Text style = { styles.text }>
<Text
style = { [
_dialogStyles.text,
styles.text
] }>
{
t('liveStreaming.streamIdHelp')
}
@@ -98,4 +122,4 @@ class StreamKeyForm extends AbstractStreamKeyForm {
}
}
export default translate(StreamKeyForm);
export default translate(connect(_abstractMapStateToProps)(StreamKeyForm));

View File

@@ -8,8 +8,11 @@ import {
TouchableOpacity,
View
} from 'react-native';
import { connect } from 'react-redux';
import { _abstractMapStateToProps } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n';
import { StyleType } from '../../../../base/styles';
import { YOUTUBE_LIVE_DASHBOARD_URL } from '../constants';
@@ -17,6 +20,11 @@ import styles, { ACTIVE_OPACITY, TOUCHABLE_UNDERLAY } from './styles';
type Props = {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
/**
* The list of broadcasts the user can pick from.
*/
@@ -74,7 +82,7 @@ class StreamKeyPicker extends Component<Props, State> {
* @inheritdoc
*/
render() {
const { broadcasts } = this.props;
const { _dialogStyles, broadcasts } = this.props;
if (!broadcasts) {
return null;
@@ -85,7 +93,11 @@ class StreamKeyPicker extends Component<Props, State> {
<View style = { styles.formWrapper }>
<TouchableOpacity
onPress = { this._onOpenYoutubeDashboard }>
<Text style = { styles.warningText }>
<Text
style = { [
_dialogStyles.text,
styles.warningText
] }>
{ this.props.t(
'liveStreaming.getStreamKeyManually') }
</Text>
@@ -97,7 +109,11 @@ class StreamKeyPicker extends Component<Props, State> {
return (
<View style = { styles.formWrapper }>
<View style = { styles.streamKeyPickerCta }>
<Text style = { styles.text }>
<Text
style = { [
_dialogStyles.text,
styles.text
] }>
{ this.props.t('liveStreaming.choose') }
</Text>
</View>
@@ -113,7 +129,11 @@ class StreamKeyPicker extends Component<Props, State> {
? styles.streamKeyPickerItemHighlight : null
] }
underlayColor = { TOUCHABLE_UNDERLAY }>
<Text style = { styles.text }>
<Text
style = { [
_dialogStyles.text,
styles.text
] }>
{ broadcast.title }
</Text>
</TouchableHighlight>))
@@ -155,4 +175,5 @@ class StreamKeyPicker extends Component<Props, State> {
}
}
export default translate(StreamKeyPicker);
export default translate(
connect(_abstractMapStateToProps)(StreamKeyPicker));

View File

@@ -99,8 +99,7 @@ export default createStyleSheet({
* Label for the previous field.
*/
streamKeyInputLabel: {
alignSelf: 'flex-start',
color: ColorPalette.white
alignSelf: 'flex-start'
},
/**
@@ -146,7 +145,8 @@ export default createStyleSheet({
},
text: {
color: ColorPalette.white
fontSize: 14,
textAlign: 'left'
},
/**

View File

@@ -14,7 +14,7 @@ import AbstractStreamKeyForm, {
*
* @extends Component
*/
class StreamKeyForm extends AbstractStreamKeyForm {
class StreamKeyForm extends AbstractStreamKeyForm<Props> {
/**
* Initializes a new {@code StreamKeyForm} instance.

View File

@@ -7,7 +7,10 @@ import {
createRecordingDialogEvent,
sendAnalytics
} from '../../../analytics';
import { DialogContent } from '../../../base/dialog';
import {
DialogContent,
_abstractMapStateToProps
} from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import {
Container,
@@ -15,6 +18,7 @@ import {
Switch,
Text
} from '../../../base/react';
import { StyleType } from '../../../base/styles';
import { authorizeDropbox, updateDropboxToken } from '../../../dropbox';
import styles from './styles';
@@ -22,6 +26,11 @@ import { getRecordingDurationEstimation } from '../../functions';
type Props = {
/**
* Style of the dialogs feature.
*/
_dialogStyles: StyleType,
/**
* The redux dispatch function.
*/
@@ -99,7 +108,7 @@ class StartRecordingDialogContent extends Component<Props> {
*/
_renderNoIntegrationsContent() {
return (
<DialogContent style = { styles.noIntegrationContent }>
<DialogContent style = { this.props._dialogStyles.text }>
{ this.props.t('recording.startRecordingBody') }
</DialogContent>
);
@@ -112,7 +121,7 @@ class StartRecordingDialogContent extends Component<Props> {
* @returns {React$Component}
*/
_renderIntegrationsContent() {
const { isTokenValid, isValidating, t } = this.props;
const { _dialogStyles, isTokenValid, isValidating, t } = this.props;
let content = null;
@@ -135,7 +144,10 @@ class StartRecordingDialogContent extends Component<Props> {
style = { styles.header }>
<Text
className = 'recording-title'
style = { styles.title }>
style = { [
_dialogStyles.text,
styles.title
] }>
{ t('recording.authDropboxText') }
</Text>
<Switch
@@ -248,4 +260,5 @@ class StartRecordingDialogContent extends Component<Props> {
}
}
export default translate(connect()(StartRecordingDialogContent));
export default translate(
connect(_abstractMapStateToProps)(StartRecordingDialogContent));

View File

@@ -28,10 +28,6 @@ export default createStyleSheet({
paddingBottom: _PADDING
},
noIntegrationContent: {
color: ColorPalette.white
},
startRecordingText: {
paddingBottom: _PADDING
},
@@ -42,7 +38,6 @@ export default createStyleSheet({
},
title: {
color: ColorPalette.white,
fontSize: 16,
fontWeight: 'bold'
},

View File

@@ -4,15 +4,16 @@ import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import {
BottomSheet,
bottomSheetItemStylesCombined
BottomSheet
} from '../../../base/dialog';
import {
Avatar,
getAvatarURL,
getParticipantDisplayName
} from '../../../base/participants';
import { StyleType } from '../../../base/styles';
import { hideRemoteVideoMenu } from '../../actions';
@@ -42,6 +43,11 @@ type Props = {
*/
_avatarURL: string,
/**
* The color-schemed stylesheet of the BottomSheet.
*/
_bottomSheetStyles: StyleType,
/**
* Display name of the participant retreived from Redux.
*/
@@ -73,7 +79,7 @@ class RemoteVideoMenu extends Component<Props> {
afterClick: this._onCancel,
showLabel: true,
participantID: this.props.participant.id,
styles: bottomSheetItemStylesCombined
styles: this.props._bottomSheetStyles
};
return (
@@ -113,6 +119,7 @@ class RemoteVideoMenu extends Component<Props> {
* @private
* @returns {{
* _avatarURL: string,
* _bottomSheetStyles: StyleType,
* _participantDisplayName: string
* }}
*/
@@ -121,6 +128,8 @@ function _mapStateToProps(state, ownProps) {
return {
_avatarURL: getAvatarURL(participant),
_bottomSheetStyles:
ColorSchemeRegistry.get(state, 'BottomSheet'),
_participantDisplayName: getParticipantDisplayName(
state, participant.id)
};

View File

@@ -4,11 +4,12 @@ import React, { Component } from 'react';
import { Platform } from 'react-native';
import { connect } from 'react-redux';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import {
BottomSheet,
bottomSheetItemStylesCombined,
hideDialog
} from '../../../base/dialog';
import { StyleType } from '../../../base/styles';
import { InviteButton } from '../../../invite';
import { AudioRouteButton } from '../../../mobile/audio-mode';
import { LiveStreamButton, RecordButton } from '../../../recording';
@@ -26,10 +27,15 @@ declare var __DEV__;
*/
type Props = {
/**
* The color-schemed stylesheet of the dialog feature.
*/
_bottomSheetStyles: StyleType,
/**
* Used for hiding the dialog when the selection was completed.
*/
dispatch: Function,
dispatch: Function
};
/**
@@ -68,7 +74,7 @@ class OverflowMenu extends Component<Props> {
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
styles: bottomSheetItemStylesCombined
styles: this.props._bottomSheetStyles
};
return (
@@ -106,6 +112,22 @@ class OverflowMenu extends Component<Props> {
}
}
OverflowMenu_ = connect()(OverflowMenu);
/**
* Function that maps parts of Redux state tree into component props.
*
* @param {Object} state - Redux state.
* @private
* @returns {{
* _bottomSheetStyles: StyleType
* }}
*/
function _mapStateToProps(state) {
return {
_bottomSheetStyles:
ColorSchemeRegistry.get(state, 'BottomSheet')
};
}
OverflowMenu_ = connect(_mapStateToProps)(OverflowMenu);
export default OverflowMenu_;

View File

@@ -5,21 +5,19 @@ import { View } from 'react-native';
import { connect } from 'react-redux';
import { Container } from '../../../base/react';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { StyleType } from '../../../base/styles';
import { ChatButton } from '../../../chat';
import { isToolboxVisible } from '../../functions';
import { HANGUP_BUTTON_SIZE } from '../../constants';
import AudioMuteButton from '../AudioMuteButton';
import HangupButton from '../HangupButton';
import VideoMuteButton from '../VideoMuteButton';
import OverflowMenuButton from './OverflowMenuButton';
import styles, {
chatButtonOverride,
hangupButtonStyles,
toolbarButtonStyles,
toolbarToggledButtonStyles
} from './styles';
import styles from './styles';
import VideoMuteButton from '../VideoMuteButton';
/**
* The number of buttons other than {@link HangupButton} to render in
@@ -43,6 +41,11 @@ const _BUTTON_SIZE_FACTOR = 0.85;
*/
type Props = {
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* The indicator which determines whether the toolbox is visible.
*/
@@ -111,6 +114,7 @@ class Toolbox extends Component<Props, State> {
* @returns {number}
*/
_calculateButtonSize() {
const { _styles } = this.props;
const { width } = this.state;
if (width <= 0) {
@@ -118,8 +122,8 @@ class Toolbox extends Component<Props, State> {
return width;
}
const hangupButtonSize = styles.hangupButton.width;
const { style } = toolbarButtonStyles;
const hangupButtonSize = HANGUP_BUTTON_SIZE;
const { style } = _styles.buttonStyles;
let buttonSize
= (width
@@ -155,12 +159,14 @@ class Toolbox extends Component<Props, State> {
* @returns {Object | Array}
*/
_getChatButtonToggledStyle(baseStyle) {
const { _styles } = this.props;
if (Array.isArray(baseStyle.style)) {
return {
...baseStyle,
style: [
...baseStyle.style,
chatButtonOverride.toggled
_styles.chatButtonOverride.toggled
]
};
}
@@ -169,7 +175,7 @@ class Toolbox extends Component<Props, State> {
...baseStyle,
style: [
baseStyle.style,
chatButtonOverride.toggled
_styles.chatButtonOverride.toggled
]
};
}
@@ -196,9 +202,9 @@ class Toolbox extends Component<Props, State> {
* @returns {React$Node}
*/
_renderToolbar() {
const { _styles } = this.props;
const buttonSize = this._calculateButtonSize();
let buttonStyles = toolbarButtonStyles;
let toggledButtonStyles = toolbarToggledButtonStyles;
let { buttonStyles, toggledButtonStyles } = _styles;
if (buttonSize > 0) {
const extraButtonStyle = {
@@ -241,7 +247,8 @@ class Toolbox extends Component<Props, State> {
<AudioMuteButton
styles = { buttonStyles }
toggledStyles = { toggledButtonStyles } />
<HangupButton styles = { hangupButtonStyles } />
<HangupButton
styles = { _styles.hangupButtonStyles } />
<VideoMuteButton
styles = { buttonStyles }
toggledStyles = { toggledButtonStyles } />
@@ -261,11 +268,13 @@ class Toolbox extends Component<Props, State> {
* {@code Toolbox} props.
* @private
* @returns {{
* _styles: StyleType,
* _visible: boolean
* }}
*/
function _mapStateToProps(state: Object): Object {
return {
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
_visible: isToolboxVisible(state)
};
}

View File

@@ -1,4 +1,6 @@
// @flow
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
import { BoxModel, ColorPalette, createStyleSheet } from '../../../base/styles';
import { HANGUP_BUTTON_SIZE } from '../../constants';
@@ -9,7 +11,7 @@ import { HANGUP_BUTTON_SIZE } from '../../constants';
* The style of toolbar buttons.
*/
const toolbarButton = {
backgroundColor: ColorPalette.white,
backgroundColor: schemeColor('button'),
borderRadius: 20,
borderWidth: 0,
flex: 0,
@@ -20,7 +22,6 @@ const toolbarButton = {
// XXX We probably tested BoxModel.margin and discovered it to be too small
// for our taste.
marginHorizontal: 7,
opacity: 0.7,
width: 40
};
@@ -33,29 +34,26 @@ const toolbarButtonIcon = {
fontSize: 22
};
/**
* The style of toolbar buttons which display white icons.
*/
const whiteToolbarButton = {
...toolbarButton,
backgroundColor: schemeColor('buttonToggled')
};
/**
* The icon style of toolbar buttons which display white icons.
*/
const whiteToolbarButtonIcon = {
...toolbarButtonIcon,
color: ColorPalette.white
};
/**
* The Toolbox and toolbar related styles.
*/
const styles = createStyleSheet({
/**
* The style of the toolbar button which hangs the current conference up.
*/
hangupButton: {
...toolbarButton,
backgroundColor: ColorPalette.red,
borderRadius: 30,
height: HANGUP_BUTTON_SIZE,
width: HANGUP_BUTTON_SIZE
},
/**
* The icon style of toolbar buttons which hangs the current conference up.
*/
hangupButtonIcon: {
...toolbarButtonIcon,
color: ColorPalette.white,
fontSize: 24
},
/**
* The style of the toolbar.
@@ -69,74 +67,60 @@ const styles = createStyleSheet({
paddingHorizontal: BoxModel.margin
},
/**
* The style of toolbar buttons.
*/
toolbarButton,
/**
* The icon style of the toolbar buttons.
*/
toolbarButtonIcon,
/**
* The style of the root/top-level {@link Container} of {@link Toolbox}.
*/
toolbox: {
flexDirection: 'column',
flexGrow: 0
},
/**
* The style of toolbar buttons which display white icons.
*/
whiteToolbarButton: {
...toolbarButton,
backgroundColor: ColorPalette.buttonUnderlay
},
/**
* The icon style of toolbar buttons which display white icons.
*/
whiteToolbarButtonIcon: {
...toolbarButtonIcon,
color: ColorPalette.white
}
});
export default styles;
/**
* Styles for the hangup button.
* Color schemed styles for the @{Toolbox} component.
*/
export const hangupButtonStyles = {
iconStyle: styles.whiteToolbarButtonIcon,
style: styles.hangupButton,
underlayColor: ColorPalette.buttonUnderlay
};
ColorSchemeRegistry.register('Toolbox', {
/**
* Styles for buttons in the toolbar.
*/
buttonStyles: {
iconStyle: toolbarButtonIcon,
style: toolbarButton
},
/**
* Styles for buttons in the toolbar.
*/
export const toolbarButtonStyles = {
iconStyle: styles.toolbarButtonIcon,
style: styles.toolbarButton
};
/**
* Overrides to the standard styles that we apply to the chat button, as
* that behaves slightly differently to other buttons.
*/
chatButtonOverride: {
toggled: {
backgroundColor: ColorPalette.blue
}
},
/**
* Styles for toggled buttons in the toolbar.
*/
export const toolbarToggledButtonStyles = {
iconStyle: styles.whiteToolbarButtonIcon,
style: styles.whiteToolbarButton
};
hangupButtonStyles: {
iconStyle: whiteToolbarButtonIcon,
style: {
...toolbarButton,
backgroundColor: schemeColor('hangup'),
borderRadius: HANGUP_BUTTON_SIZE / 2,
height: HANGUP_BUTTON_SIZE,
width: HANGUP_BUTTON_SIZE
},
underlayColor: ColorPalette.buttonUnderlay
},
/**
* Overrides to the standard styles that we apply to the chat button, as that
* behaves slightly differently to other buttons.
*/
export const chatButtonOverride = createStyleSheet({
toggled: {
backgroundColor: ColorPalette.blue
/**
* Styles for toggled buttons in the toolbar.
*/
toggledButtonStyles: {
iconStyle: whiteToolbarButtonIcon,
style: {
...whiteToolbarButton,
borderColor: schemeColor('buttonToggledBorder'),
borderWidth: 1
}
}
});