mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-06 14:52:28 +00:00
Compare commits
39 Commits
6293
...
android-sd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2039feae94 | ||
|
|
2842d3acef | ||
|
|
00e1a1d79d | ||
|
|
1843516ec7 | ||
|
|
7f34818cd4 | ||
|
|
c8229a590c | ||
|
|
e339325afc | ||
|
|
2aecbcaab1 | ||
|
|
f8037966f4 | ||
|
|
057dceb178 | ||
|
|
f8917b2d3f | ||
|
|
2dd3c72473 | ||
|
|
4a4856f3de | ||
|
|
47bdf800e7 | ||
|
|
f6d088149c | ||
|
|
cbe3d6d505 | ||
|
|
b41c71e80b | ||
|
|
a4b997362a | ||
|
|
a5da90ddaf | ||
|
|
dffa71666c | ||
|
|
892751154c | ||
|
|
935e4d3261 | ||
|
|
f115028961 | ||
|
|
d910b9db57 | ||
|
|
b2b576f6fb | ||
|
|
a39d9f283d | ||
|
|
0913cf2c4f | ||
|
|
51f7b46628 | ||
|
|
d029045fda | ||
|
|
ddab27e292 | ||
|
|
6df2e4009c | ||
|
|
21cf7f23c2 | ||
|
|
bac1347961 | ||
|
|
c4f39e9c34 | ||
|
|
ee266160f9 | ||
|
|
730d42cba1 | ||
|
|
3f795cd1ff | ||
|
|
252441da29 | ||
|
|
b89c470366 |
@@ -26,5 +26,5 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=22.3.0
|
||||
sdkVersion=5.2.0
|
||||
appVersion=22.4.0
|
||||
sdkVersion=6.0.0
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
android:name=".JitsiMeetActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/JitsiMeetActivityStyle"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:windowSoftInputMode="adjustResize"/>
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2018 Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Base class for all views which are backed by a React Native view.
|
||||
*/
|
||||
public abstract class BaseReactView<ListenerT>
|
||||
extends FrameLayout {
|
||||
|
||||
/**
|
||||
* Background color used by {@code BaseReactView} and the React Native root
|
||||
* view.
|
||||
*/
|
||||
protected static int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* The collection of all existing {@code BaseReactView}s. Used to find the
|
||||
* {@code BaseReactView} when delivering events coming from
|
||||
* {@link ExternalAPIModule}.
|
||||
*/
|
||||
static final Set<BaseReactView> views
|
||||
= Collections.newSetFromMap(new WeakHashMap<BaseReactView, Boolean>());
|
||||
|
||||
/**
|
||||
* Finds a {@code BaseReactView} which matches a specific external API
|
||||
* scope.
|
||||
*
|
||||
* @param externalAPIScope - The external API scope associated with the
|
||||
* {@code BaseReactView} to find.
|
||||
* @return The {@code BaseReactView}, if any, associated with the specified
|
||||
* {@code externalAPIScope}; otherwise, {@code null}.
|
||||
*/
|
||||
public static BaseReactView findViewByExternalAPIScope(
|
||||
String externalAPIScope) {
|
||||
synchronized (views) {
|
||||
for (BaseReactView view : views) {
|
||||
if (view.externalAPIScope.equals(externalAPIScope)) {
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all registered React views.
|
||||
*
|
||||
* @return An {@link ArrayList} containing all views currently held by React.
|
||||
*/
|
||||
static ArrayList<BaseReactView> getViews() {
|
||||
return new ArrayList<>(views);
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique identifier of this {@code BaseReactView} within the process
|
||||
* for the purposes of {@link ExternalAPIModule}. The name scope was
|
||||
* inspired by postis which we use on Web for the similar purposes of the
|
||||
* iframe-based external API.
|
||||
*/
|
||||
protected String externalAPIScope;
|
||||
|
||||
/**
|
||||
* The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
|
||||
* events occurring in Jitsi Meet.
|
||||
*/
|
||||
@Deprecated
|
||||
private ListenerT listener;
|
||||
|
||||
/**
|
||||
* React Native root view.
|
||||
*/
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
public BaseReactView(@NonNull Context context) {
|
||||
super(context);
|
||||
initialize((Activity)context);
|
||||
}
|
||||
|
||||
public BaseReactView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize((Activity)context);
|
||||
}
|
||||
|
||||
public BaseReactView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize((Activity)context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@code ReactRootView} for the given app name with the given
|
||||
* props. Once created it's set as the view of this {@code FrameLayout}.
|
||||
*
|
||||
* @param appName - The name of the "app" (in React Native terms) to load.
|
||||
* @param props - The React Component props to pass to the app.
|
||||
*/
|
||||
public void createReactRootView(String appName, @Nullable Bundle props) {
|
||||
if (props == null) {
|
||||
props = new Bundle();
|
||||
}
|
||||
|
||||
props.putString("externalAPIScope", externalAPIScope);
|
||||
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
appName,
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
reactRootView.setAppProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the React resources (specifically the {@link ReactRootView})
|
||||
* associated with this view.
|
||||
*
|
||||
* MUST be called when the {@link Activity} holding this view is destroyed,
|
||||
* typically in the {@code onDestroy} method.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView.unmountReactApplication();
|
||||
reactRootView = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the listener set on this {@code BaseReactView}.
|
||||
*
|
||||
* @return The listener set on this {@code BaseReactView}.
|
||||
*/
|
||||
@Deprecated
|
||||
public ListenerT getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method called by {@link ExternalAPIModule} when an event is
|
||||
* received for this view.
|
||||
*
|
||||
* @param name - The name of the event.
|
||||
* @param data - The details of the event associated with/specific to the
|
||||
* specified {@code name}.
|
||||
*/
|
||||
@Deprecated
|
||||
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
|
||||
|
||||
@Deprecated
|
||||
protected void onExternalAPIEvent(
|
||||
Map<String, Method> listenerMethods,
|
||||
String name, ReadableMap data) {
|
||||
ListenerT listener = getListener();
|
||||
|
||||
if (listener != null) {
|
||||
ListenerUtils.runListenerMethod(
|
||||
listener, listenerMethods, name, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific listener on this {@code BaseReactView}.
|
||||
*
|
||||
* @param listener The listener to set on this {@code BaseReactView}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setListener(ListenerT listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void initialize(Activity activity) {
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager(activity);
|
||||
|
||||
// Hook this BaseReactView into ExternalAPI.
|
||||
externalAPIScope = UUID.randomUUID().toString();
|
||||
synchronized (views) {
|
||||
views.add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,31 +102,18 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
|
||||
/**
|
||||
* Dispatches an event that occurred on the JavaScript side of the SDK to
|
||||
* the specified {@link BaseReactView}'s listener.
|
||||
* the native side.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
* @param scope
|
||||
*/
|
||||
@ReactMethod
|
||||
public void sendEvent(String name, ReadableMap data, String scope) {
|
||||
public void sendEvent(String name, ReadableMap data) {
|
||||
// Keep track of the current ongoing conference.
|
||||
OngoingConferenceTracker.getInstance().onExternalAPIEvent(name, data);
|
||||
|
||||
// The JavaScript App needs to provide uniquely identifying information
|
||||
// to the native ExternalAPI module so that the latter may match the
|
||||
// former to the native BaseReactView which hosts it.
|
||||
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
|
||||
|
||||
if (view != null) {
|
||||
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
|
||||
try {
|
||||
view.onExternalAPIEvent(name, data);
|
||||
broadcastEmitter.sendBroadcast(name, data);
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.e(e, TAG + " onExternalAPIEvent: error sending event");
|
||||
}
|
||||
}
|
||||
JitsiMeetLogger.d(TAG + " Sending event: " + name + " with data: " + data);
|
||||
broadcastEmitter.sendBroadcast(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,12 +167,9 @@ public class JitsiMeetActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
public void leave() {
|
||||
if (this.jitsiView != null) {
|
||||
this.jitsiView .leave();
|
||||
} else {
|
||||
JitsiMeetLogger.w("Cannot leave, view is null");
|
||||
}
|
||||
protected void leave() {
|
||||
Intent hangupBroadcastIntent = BroadcastIntentHelper.buildHangUpIntent();
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(hangupBroadcastIntent);
|
||||
}
|
||||
|
||||
private @Nullable
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright @ 2019-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
|
||||
* contains all the required wiring between the {@code JitsiMeetView} and
|
||||
* the Fragment lifecycle methods already implemented.
|
||||
*
|
||||
* In this fragment we use a single {@code JitsiMeetView} instance. This
|
||||
* instance gives us access to a view which displays the welcome page and the
|
||||
* conference itself. All lifecycle methods associated with this Fragment are
|
||||
* hooked to the React Native subsystem via proxy calls through the
|
||||
* {@code JitsiMeetActivityDelegate} static methods.
|
||||
*
|
||||
* @deprecated use {@link JitsiMeetActivity} or directly {@link JitsiMeetView}
|
||||
*/
|
||||
@Deprecated
|
||||
public class JitsiMeetFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* Instance of the {@link JitsiMeetView} which this activity will display.
|
||||
*/
|
||||
private JitsiMeetView view;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return this.view = new JitsiMeetView(getActivity());
|
||||
}
|
||||
|
||||
public JitsiMeetView getJitsiView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostDestroy(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostResume(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
JitsiMeetActivityDelegate.onHostPause(getActivity());
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
|
||||
private boolean isAudioMuted;
|
||||
|
||||
static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
public static void launch(Context context, HashMap<String, Object> extraData) {
|
||||
OngoingNotification.createOngoingConferenceNotificationChannel();
|
||||
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
@@ -80,7 +80,7 @@ public class JitsiMeetOngoingConferenceService extends Service
|
||||
}
|
||||
}
|
||||
|
||||
static void abort(Context context) {
|
||||
public static void abort(Context context) {
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
context.stopService(intent);
|
||||
}
|
||||
|
||||
@@ -16,36 +16,33 @@
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.rnimmersive.RNImmersiveModule;
|
||||
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
implements OngoingConferenceTracker.OngoingConferenceListener {
|
||||
public class JitsiMeetView extends FrameLayout {
|
||||
|
||||
/**
|
||||
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
|
||||
* redux action types.
|
||||
* Background color used by {@code BaseReactView} and the React Native root
|
||||
* view.
|
||||
*/
|
||||
private static final Map<String, Method> LISTENER_METHODS
|
||||
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
|
||||
private static final int BACKGROUND_COLOR = 0xFF111111;
|
||||
|
||||
/**
|
||||
* The URL of the current conference.
|
||||
* React Native root view.
|
||||
*/
|
||||
// XXX Currently, one thread writes and one thread reads, so it should be
|
||||
// fine to have this field volatile without additional synchronization.
|
||||
private volatile String url;
|
||||
private ReactRootView reactRootView;
|
||||
|
||||
/**
|
||||
* Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
|
||||
@@ -109,10 +106,19 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Releases the React resources (specifically the {@link ReactRootView})
|
||||
* associated with this view.
|
||||
*
|
||||
* MUST be called when the {@link Activity} holding this view is destroyed,
|
||||
* typically in the {@code onDestroy} method.
|
||||
*/
|
||||
public void dispose() {
|
||||
OngoingConferenceTracker.getInstance().removeListener(this);
|
||||
super.dispose();
|
||||
if (reactRootView != null) {
|
||||
removeView(reactRootView);
|
||||
reactRootView.unmountReactApplication();
|
||||
reactRootView = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,8 +136,7 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
PictureInPictureModule.class);
|
||||
if (pipModule != null
|
||||
&& pipModule.isPictureInPictureSupported()
|
||||
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
|
||||
&& this.url != null) {
|
||||
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
} catch (RuntimeException re) {
|
||||
@@ -151,10 +156,40 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the currently active conference.
|
||||
* Creates the {@code ReactRootView} for the given app name with the given
|
||||
* props. Once created it's set as the view of this {@code FrameLayout}.
|
||||
*
|
||||
* @param appName - The name of the "app" (in React Native terms) to load.
|
||||
* @param props - The React Component props to pass to the app.
|
||||
*/
|
||||
public void leave() {
|
||||
setProps(new Bundle());
|
||||
private void createReactRootView(String appName, @Nullable Bundle props) {
|
||||
if (props == null) {
|
||||
props = new Bundle();
|
||||
}
|
||||
|
||||
if (reactRootView == null) {
|
||||
reactRootView = new ReactRootView(getContext());
|
||||
reactRootView.startReactApplication(
|
||||
ReactInstanceManagerHolder.getReactInstanceManager(),
|
||||
appName,
|
||||
props);
|
||||
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
|
||||
addView(reactRootView);
|
||||
} else {
|
||||
reactRootView.setAppProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize(@NonNull Context context) {
|
||||
// Check if the parent Activity implements JitsiMeetActivityInterface,
|
||||
// otherwise things may go wrong.
|
||||
if (!(context instanceof JitsiMeetActivityInterface)) {
|
||||
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
|
||||
}
|
||||
|
||||
setBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
ReactInstanceManagerHolder.initReactInstanceManager((Activity) context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,46 +214,27 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
|
||||
createReactRootView("App", props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for {@link OngoingConferenceTracker} events.
|
||||
* @param conferenceUrl
|
||||
*/
|
||||
@Override
|
||||
public void onCurrentConferenceChanged(String conferenceUrl) {
|
||||
// This property was introduced in order to address
|
||||
// an exception in the Picture-in-Picture functionality which arose
|
||||
// because of delays related to bridging between JavaScript and Java. To
|
||||
// reduce these delays do not wait for the call to be transferred to the
|
||||
// UI thread.
|
||||
this.url = conferenceUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for {@link ExternalAPIModule} events.
|
||||
*
|
||||
* @param name The name of the event.
|
||||
* @param data The details/specifics of the event to send determined
|
||||
* by/associated with the specified {@code name}.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
protected void onExternalAPIEvent(String name, ReadableMap data) {
|
||||
onExternalAPIEvent(LISTENER_METHODS, name, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
dispose();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
private void initialize(@NonNull Context context) {
|
||||
// Check if the parent Activity implements JitsiMeetActivityInterface,
|
||||
// otherwise things may go wrong.
|
||||
if (!(context instanceof JitsiMeetActivityInterface)) {
|
||||
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
|
||||
}
|
||||
/**
|
||||
* Called when the window containing this view gains or loses focus.
|
||||
*
|
||||
* @param hasFocus If the window of this view now has focus, {@code true};
|
||||
* otherwise, {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
|
||||
OngoingConferenceTracker.getInstance().addListener(this);
|
||||
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
|
||||
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
|
||||
|
||||
if (hasFocus && immersive != null) {
|
||||
immersive.emitImmersiveStateChangeEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface for listening to events coming from Jitsi Meet.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface JitsiMeetViewListener {
|
||||
/**
|
||||
* Called when a conference was joined.
|
||||
*
|
||||
* @param data Map with a "url" key with the conference URL.
|
||||
*/
|
||||
void onConferenceJoined(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called when the active conference ends, be it because of user choice or
|
||||
* because of a failure.
|
||||
*
|
||||
* @param data Map with an "error" key with the error and a "url" key with
|
||||
* the conference URL. If the conference finished gracefully no `error`
|
||||
* key will be present. The possible values for "error" are described here:
|
||||
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConnectionErrors.js
|
||||
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConferenceErrors.js
|
||||
*/
|
||||
void onConferenceTerminated(Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* Called before the conference is joined.
|
||||
*
|
||||
* @param data Map with a "url" key with the conference URL.
|
||||
*/
|
||||
void onConferenceWillJoin(Map<String, Object> data);
|
||||
}
|
||||
@@ -1,167 +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.
|
||||
*/
|
||||
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility methods for helping with transforming {@link ExternalAPIModule}
|
||||
* events into listener methods. Used with descendants of {@link BaseReactView}.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class ListenerUtils {
|
||||
/**
|
||||
* Extracts the methods defined in a listener and creates a mapping of this
|
||||
* form: event name -> method.
|
||||
*
|
||||
* @param listener - The listener whose methods we want to slurp.
|
||||
* @return A mapping with event names - methods.
|
||||
*/
|
||||
public static Map<String, Method> mapListenerMethods(Class listener) {
|
||||
Map<String, Method> methods = new HashMap<>();
|
||||
|
||||
// Figure out the mapping between the listener methods
|
||||
// and the events i.e. redux action types.
|
||||
Pattern onPattern = Pattern.compile("^on[A-Z]+");
|
||||
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
|
||||
|
||||
for (Method method : listener.getDeclaredMethods()) {
|
||||
// * The method must be public (because it is declared by an
|
||||
// interface).
|
||||
// * The method must be/return void.
|
||||
if (!Modifier.isPublic(method.getModifiers())
|
||||
|| !Void.TYPE.equals(method.getReturnType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method name must start with "on" followed by a
|
||||
// capital/uppercase letter (in agreement with the camelcase
|
||||
// coding style customary to Java in general and the projects of
|
||||
// the Jitsi community in particular).
|
||||
String name = method.getName();
|
||||
|
||||
if (!onPattern.matcher(name).find()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// * The method must accept/have exactly 1 parameter of a type
|
||||
// assignable from HashMap.
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
if (parameterTypes.length != 1
|
||||
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert the method name to an event name.
|
||||
name
|
||||
= camelcasePattern.matcher(name.substring(2))
|
||||
.replaceAll("$1_$2")
|
||||
.toUpperCase(Locale.ROOT);
|
||||
methods.put(name, method);
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the right listener method for the given event.
|
||||
* NOTE: This function will run asynchronously on the UI thread.
|
||||
*
|
||||
* @param listener - The listener on which the method will be called.
|
||||
* @param listenerMethods - Mapping with event names and the matching
|
||||
* methods.
|
||||
* @param eventName - Name of the event.
|
||||
* @param eventData - Data associated with the event.
|
||||
*/
|
||||
public static void runListenerMethod(
|
||||
final Object listener,
|
||||
final Map<String, Method> listenerMethods,
|
||||
final String eventName,
|
||||
final ReadableMap eventData) {
|
||||
// Make sure listener methods are invoked on the UI thread. It
|
||||
// was requested by SDK consumers.
|
||||
if (UiThreadUtil.isOnUiThread()) {
|
||||
runListenerMethodOnUiThread(
|
||||
listener, listenerMethods, eventName, eventData);
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runListenerMethodOnUiThread(
|
||||
listener, listenerMethods, eventName, eventData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper companion for {@link ListenerUtils#runListenerMethod} which runs
|
||||
* in the UI thread.
|
||||
*/
|
||||
private static void runListenerMethodOnUiThread(
|
||||
Object listener,
|
||||
Map<String, Method> listenerMethods,
|
||||
String eventName,
|
||||
ReadableMap eventData) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
Method method = listenerMethods.get(eventName);
|
||||
if (method != null) {
|
||||
try {
|
||||
method.invoke(listener, toHashMap(eventData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new {@code HashMap} instance with the key-value
|
||||
* associations of a specific {@code ReadableMap}.
|
||||
*
|
||||
* @param readableMap the {@code ReadableMap} specifying the key-value
|
||||
* associations with which the new {@code HashMap} instance is to be
|
||||
* initialized.
|
||||
* @return a new {@code HashMap} instance initialized with the key-value
|
||||
* associations of the specified {@code readableMap}.
|
||||
*/
|
||||
private static HashMap<String, Object> toHashMap(ReadableMap readableMap) {
|
||||
HashMap<String, Object> hashMap = new HashMap<>();
|
||||
|
||||
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
|
||||
i.hasNextKey();) {
|
||||
String key = i.nextKey();
|
||||
|
||||
hashMap.put(key, readableMap.getString(key));
|
||||
}
|
||||
|
||||
return hashMap;
|
||||
}
|
||||
}
|
||||
3
android/sdk/src/main/res/values/styles.xml
Normal file
3
android/sdk/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<style name="JitsiMeetActivityStyle" parent="Theme.AppCompat.Light.NoActionBar"/>
|
||||
</resources>
|
||||
@@ -3094,34 +3094,12 @@ export default {
|
||||
* @param email {string} the new email
|
||||
*/
|
||||
changeLocalEmail(email = '') {
|
||||
const localParticipant = getLocalParticipant(APP.store.getState());
|
||||
|
||||
const formattedEmail = String(email).trim();
|
||||
|
||||
if (formattedEmail === localParticipant.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localId = localParticipant.id;
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id: localId,
|
||||
local: true,
|
||||
email: formattedEmail
|
||||
}));
|
||||
|
||||
APP.store.dispatch(updateSettings({
|
||||
email: formattedEmail
|
||||
}));
|
||||
APP.API.notifyEmailChanged(localId, {
|
||||
email: formattedEmail
|
||||
});
|
||||
|
||||
sendData(commands.EMAIL, formattedEmail);
|
||||
},
|
||||
|
||||
@@ -3130,29 +3108,12 @@ export default {
|
||||
* @param url {string} the new url
|
||||
*/
|
||||
changeLocalAvatarUrl(url = '') {
|
||||
const { avatarURL, id } = getLocalParticipant(APP.store.getState());
|
||||
|
||||
const formattedUrl = String(url).trim();
|
||||
|
||||
if (formattedUrl === avatarURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
avatarURL: formattedUrl
|
||||
}));
|
||||
|
||||
APP.store.dispatch(updateSettings({
|
||||
avatarURL: formattedUrl
|
||||
}));
|
||||
|
||||
sendData(commands.AVATAR_URL, url);
|
||||
},
|
||||
|
||||
@@ -3193,23 +3154,6 @@ export default {
|
||||
*/
|
||||
changeLocalDisplayName(nickname = '') {
|
||||
const formattedNickname = getNormalizedDisplayName(nickname);
|
||||
const { id, name } = getLocalParticipant(APP.store.getState());
|
||||
|
||||
if (formattedNickname === name) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
// XXX Only the local participant is allowed to update without
|
||||
// stating the JitsiConference instance (i.e. participant property
|
||||
// `conference` for a remote participant) because the local
|
||||
// participant is uniquely identified by the very fact that there is
|
||||
// only one local participant.
|
||||
|
||||
id,
|
||||
local: true,
|
||||
name: formattedNickname
|
||||
}));
|
||||
|
||||
APP.store.dispatch(updateSettings({
|
||||
displayName: formattedNickname
|
||||
|
||||
87
config.js
87
config.js
@@ -143,6 +143,7 @@ var config = {
|
||||
|
||||
// Disable measuring of audio levels.
|
||||
// disableAudioLevels: false,
|
||||
|
||||
// audioLevelsInterval: 200,
|
||||
|
||||
// Enabling this will run the lib-jitsi-meet no audio detection module which
|
||||
@@ -271,8 +272,9 @@ var config = {
|
||||
|
||||
// Recording
|
||||
|
||||
// Whether to enable file recording or not.
|
||||
// DEPRECATED. Use recordingService.enabled instead.
|
||||
// fileRecordingsEnabled: false,
|
||||
|
||||
// Enable the dropbox integration.
|
||||
// dropbox: {
|
||||
// appKey: '<APP_KEY>' // Specify your app key here.
|
||||
@@ -282,14 +284,27 @@ var config = {
|
||||
// redirectURI:
|
||||
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html'
|
||||
// },
|
||||
// When integrations like dropbox are enabled only that will be shown,
|
||||
// by enabling fileRecordingsServiceEnabled, we show both the integrations
|
||||
// and the generic recording service (its configuration and storage type
|
||||
// depends on jibri configuration)
|
||||
|
||||
// recordingService: {
|
||||
// // When integrations like dropbox are enabled only that will be shown,
|
||||
// // by enabling fileRecordingsServiceEnabled, we show both the integrations
|
||||
// // and the generic recording service (its configuration and storage type
|
||||
// // depends on jibri configuration)
|
||||
// enabled: false,
|
||||
|
||||
// // Whether to show the possibility to share file recording with other people
|
||||
// // (e.g. meeting participants), based on the actual implementation
|
||||
// // on the backend.
|
||||
// sharingEnabled: false,
|
||||
|
||||
// // Hide the warning that says we only store the recording for 24 hours.
|
||||
// hideStorageWarning: false
|
||||
// },
|
||||
|
||||
// DEPRECATED. Use recordingService.enabled instead.
|
||||
// fileRecordingsServiceEnabled: false,
|
||||
// Whether to show the possibility to share file recording with other people
|
||||
// (e.g. meeting participants), based on the actual implementation
|
||||
// on the backend.
|
||||
|
||||
// DEPRECATED. Use recordingService.sharingEnabled instead.
|
||||
// fileRecordingsServiceSharingEnabled: false,
|
||||
|
||||
// Whether to enable live streaming or not.
|
||||
@@ -303,25 +318,43 @@ var config = {
|
||||
// notifyAllParticipants: false
|
||||
// },
|
||||
|
||||
// Transcription (in interface_config,
|
||||
// subtitles and buttons can be configured)
|
||||
// DEPRECATED. Use transcription.enabled instead.
|
||||
// transcribingEnabled: false,
|
||||
|
||||
// If true transcriber will use the application language.
|
||||
// The application language is either explicitly set by participants in their settings or automatically
|
||||
// detected based on the environment, e.g. if the app is opened in a chrome instance which is using french as its
|
||||
// default language then transcriptions for that participant will be in french.
|
||||
// Defaults to true.
|
||||
// DEPRECATED. Use transcription.useAppLanguage instead.
|
||||
// transcribeWithAppLanguage: true,
|
||||
|
||||
// Transcriber language. This settings will only work if "transcribeWithAppLanguage" is explicitly set to false.
|
||||
// Available languages can be found in
|
||||
// ./src/react/features/transcribing/transcriber-langs.json.
|
||||
// DEPRECATED. Use transcription.preferredLanguage instead.
|
||||
// preferredTranscribeLanguage: 'en-US',
|
||||
|
||||
// Enables automatic turning on captions when recording is started
|
||||
// DEPRECATED. Use transcription.autoCaptionOnRecord instead.
|
||||
// autoCaptionOnRecord: false,
|
||||
|
||||
// Transcription options.
|
||||
// transcription: {
|
||||
// // Whether the feature should be enabled or not.
|
||||
// enabled: false,
|
||||
|
||||
// // If true transcriber will use the application language.
|
||||
// // The application language is either explicitly set by participants in their settings or automatically
|
||||
// // detected based on the environment, e.g. if the app is opened in a chrome instance which
|
||||
// // is using french as its default language then transcriptions for that participant will be in french.
|
||||
// // Defaults to true.
|
||||
// useAppLanguage: true,
|
||||
|
||||
// // Transcriber language. This settings will only work if "useAppLanguage"
|
||||
// // is explicitly set to false.
|
||||
// // Available languages can be found in
|
||||
// // ./src/react/features/transcribing/transcriber-langs.json.
|
||||
// preferredLanguage: 'en-US',
|
||||
|
||||
// // Disable start transcription for all participants.
|
||||
// disableStartForAll: false,
|
||||
|
||||
// // Enables automatic turning on captions when recording is started
|
||||
// autoCaptionOnRecord: false
|
||||
// },
|
||||
|
||||
// Misc
|
||||
|
||||
// Default value for the channel "last N" attribute. -1 for unlimited.
|
||||
@@ -383,7 +416,7 @@ var config = {
|
||||
// // This will result in Safari not being able to decode video from endpoints sending VP9 video.
|
||||
// // When set to false, the conference falls back to VP8 whenever there is an endpoint that doesn't support the
|
||||
// // preferred codec and goes back to the preferred codec when that endpoint leaves.
|
||||
// // enforcePreferredCodec: false,
|
||||
// enforcePreferredCodec: false,
|
||||
//
|
||||
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
|
||||
// // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values
|
||||
@@ -551,8 +584,8 @@ var config = {
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// When enabled the password used for locking a room is restricted to up to the number of digits specified
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
// default: roomPasswordNumberOfDigits: false,
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
@@ -1173,7 +1206,8 @@ var config = {
|
||||
// 'transcribing',
|
||||
// 'video-quality',
|
||||
// 'insecure-room',
|
||||
// 'highlight-moment'
|
||||
// 'highlight-moment',
|
||||
// 'top-panel-toggle'
|
||||
// ]
|
||||
// },
|
||||
|
||||
@@ -1367,7 +1401,14 @@ var config = {
|
||||
|
||||
// // Disables the stage filmstrip
|
||||
// // (displaying multiple participants on stage besides the vertical filmstrip)
|
||||
// disableStageFilmstrip: false
|
||||
// disableStageFilmstrip: false,
|
||||
|
||||
// // Disables the top panel (only shown when a user is sharing their screen).
|
||||
// disableTopPanel: false,
|
||||
|
||||
// // The minimum number of participants that must be in the call for
|
||||
// // the top panel layout to be used.
|
||||
// minParticipantCountForTopPanel: 50
|
||||
// },
|
||||
|
||||
// Tile view related config options.
|
||||
|
||||
@@ -21,8 +21,7 @@ import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-mee
|
||||
import { getCustomerDetails } from './react/features/jaas/actions.any';
|
||||
import { isVpaasMeeting, getJaasJWT } from './react/features/jaas/functions';
|
||||
import {
|
||||
setPrejoinDisplayNameRequired,
|
||||
setPrejoinPageVisibility
|
||||
setPrejoinDisplayNameRequired
|
||||
} from './react/features/prejoin/actions';
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
@@ -247,7 +246,6 @@ function requestAuth(roomName) {
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(setPrejoinPageVisibility(false));
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
|
||||
@@ -467,7 +467,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNReanimated (1.13.4):
|
||||
- React-Core
|
||||
- RNScreens (3.10.1):
|
||||
- RNScreens (3.13.1):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNSound (0.11.1):
|
||||
@@ -767,7 +767,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: e5c7cab5f214503dcefd6b2b0cefb050e1f51c4a
|
||||
RNGoogleSignin: c4381751eefd73c552b923ba347a9bfc6f18771c
|
||||
RNReanimated: c1b56d030d1616239861534d9adb531f8cffab68
|
||||
RNScreens: 522705f2e5c9d27efb17f24aceb2bf8335bc7b8e
|
||||
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
||||
RNWatch: 99637948ec9b5c9ec5a41920642594ad5ba07e80
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>22.3.0</string>
|
||||
<string>22.4.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>22.3.0</string>
|
||||
<string>22.4.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>22.3.0</string>
|
||||
<string>22.4.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>22.3.0</string>
|
||||
<string>22.4.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -24,8 +24,14 @@
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */; };
|
||||
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */; };
|
||||
4EBA6E61286072E300B31882 /* JitsiMeetViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */; };
|
||||
4EBA6E62286072E300B31882 /* JitsiMeetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */; };
|
||||
4EBA6E652860B1E800B31882 /* JitsiMeetRenderingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */; };
|
||||
4EBA6E662860B1E800B31882 /* JitsiMeetRenderingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */; };
|
||||
4ED4FFF32721B9B90074E620 /* JitsiAudioSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4ED4FFF42721B9B90074E620 /* JitsiAudioSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */; };
|
||||
4EEC9630286C73A2008705FA /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */; };
|
||||
4EEC9631286C73A2008705FA /* JitsiMeetView+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */; };
|
||||
6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */; };
|
||||
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
|
||||
C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
|
||||
@@ -79,9 +85,15 @@
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScheenshareEventEmiter.h; sourceTree = "<group>"; };
|
||||
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScheenshareEventEmiter.m; sourceTree = "<group>"; };
|
||||
4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetViewController.h; sourceTree = "<group>"; };
|
||||
4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetViewController.m; sourceTree = "<group>"; };
|
||||
4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetRenderingView.h; sourceTree = "<group>"; };
|
||||
4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetRenderingView.m; sourceTree = "<group>"; };
|
||||
4ED4FFF12721B9B90074E620 /* JitsiAudioSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiAudioSession.h; sourceTree = "<group>"; };
|
||||
4ED4FFF22721B9B90074E620 /* JitsiAudioSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiAudioSession.m; sourceTree = "<group>"; };
|
||||
4ED4FFF52721BAE10074E620 /* JitsiAudioSession+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiAudioSession+Private.h"; sourceTree = "<group>"; };
|
||||
4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "JitsiMeetView+Private.m"; sourceTree = "<group>"; };
|
||||
891FE43DAD30BC8976683100 /* Pods-JitsiMeetSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeetSDK.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeetSDK/Pods-JitsiMeetSDK.release.xcconfig"; sourceTree = "<group>"; };
|
||||
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -94,7 +106,6 @@
|
||||
C69EFA0B209A0F660027712B /* JMCallKitListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitListener.swift; sourceTree = "<group>"; };
|
||||
C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
|
||||
C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
|
||||
C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExternalAPI.h; sourceTree = "<group>"; };
|
||||
C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfoPlistUtil.h; sourceTree = "<group>"; };
|
||||
C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InfoPlistUtil.m; sourceTree = "<group>"; };
|
||||
@@ -194,12 +205,17 @@
|
||||
DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */,
|
||||
DE81A2DD2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
4EEC962E286C73A2008705FA /* JitsiMeetView+Private.h */,
|
||||
4EEC962F286C73A2008705FA /* JitsiMeetView+Private.m */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
4EBA6E632860B1E800B31882 /* JitsiMeetRenderingView.h */,
|
||||
4EBA6E642860B1E800B31882 /* JitsiMeetRenderingView.m */,
|
||||
4EBA6E5F286072E300B31882 /* JitsiMeetViewController.h */,
|
||||
4EBA6E60286072E300B31882 /* JitsiMeetViewController.m */,
|
||||
DE81A2D72316AC7600AE1940 /* LogBridge.m */,
|
||||
DE65AAC92317FFCD00290BEC /* LogUtils.h */,
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */,
|
||||
DEAFA778229EAD520033A7FA /* RNRootView.m */,
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
|
||||
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */,
|
||||
C6A3426B204F127900E062DD /* picture-in-picture */,
|
||||
@@ -284,11 +300,14 @@
|
||||
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
|
||||
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */,
|
||||
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
|
||||
4EBA6E652860B1E800B31882 /* JitsiMeetRenderingView.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
4ED4FFF32721B9B90074E620 /* JitsiAudioSession.h in Headers */,
|
||||
4EEC9630286C73A2008705FA /* JitsiMeetView+Private.h in Headers */,
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
||||
DE81A2D42316AC4D00AE1940 /* JitsiMeetLogger.h in Headers */,
|
||||
DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */,
|
||||
4EBA6E61286072E300B31882 /* JitsiMeetViewController.h in Headers */,
|
||||
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */,
|
||||
C81E9AB925AC5AD800B134D9 /* ExternalAPI.h in Headers */,
|
||||
C8AFD27F2462C613000293D2 /* InfoPlistUtil.h in Headers */,
|
||||
@@ -449,6 +468,7 @@
|
||||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
DE81A2DF2317ED5400AE1940 /* JitsiMeetBaseLogHandler.m in Sources */,
|
||||
4EBA6E662860B1E800B31882 /* JitsiMeetRenderingView.m in Sources */,
|
||||
4ED4FFF42721B9B90074E620 /* JitsiAudioSession.m in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
DE81A2D92316AC7600AE1940 /* LogBridge.m in Sources */,
|
||||
@@ -465,9 +485,11 @@
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
|
||||
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
|
||||
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
|
||||
4EEC9631286C73A2008705FA /* JitsiMeetView+Private.m in Sources */,
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */,
|
||||
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
|
||||
4EBA6E62286072E300B31882 /* JitsiMeetViewController.m in Sources */,
|
||||
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
|
||||
DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,
|
||||
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
|
||||
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
|
||||
|
||||
- (void)sendHangUp;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
#import "ExternalAPI.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
// Events
|
||||
static NSString * const hangUpAction = @"org.jitsi.meet.HANG_UP";
|
||||
@@ -89,33 +88,15 @@ RCT_EXPORT_MODULE();
|
||||
* @param scope
|
||||
*/
|
||||
RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
data:(NSDictionary *)data
|
||||
scope:(NSString *)scope) {
|
||||
// The JavaScript App needs to provide uniquely identifying information to
|
||||
// the native ExternalAPI module so that the latter may match the former
|
||||
// to the native JitsiMeetView which hosts it.
|
||||
JitsiMeetView *view = [JitsiMeetView viewForExternalAPIScope:scope];
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
id delegate = view.delegate;
|
||||
|
||||
if (!delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
data:(NSDictionary *)data) {
|
||||
if ([name isEqual: @"PARTICIPANTS_INFO_RETRIEVED"]) {
|
||||
[self onParticipantsInfoRetrieved: data];
|
||||
return;
|
||||
}
|
||||
|
||||
SEL sel = NSSelectorFromString([self methodNameFromEventName:name]);
|
||||
|
||||
if (sel && [delegate respondsToSelector:sel]) {
|
||||
[delegate performSelector:sel withObject:data];
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:sendEventNotificationName
|
||||
object:nil
|
||||
userInfo:@{@"name": name, @"data": data}];
|
||||
}
|
||||
|
||||
- (void) onParticipantsInfoRetrieved:(NSDictionary *)data {
|
||||
@@ -127,28 +108,6 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[participantInfoCompletionHandlers removeObjectForKey:completionHandlerId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a specific event name i.e. redux action type description to a
|
||||
* method name.
|
||||
*
|
||||
* @param eventName The event name to convert to a method name.
|
||||
* @return A method name constructed from the specified `eventName`.
|
||||
*/
|
||||
- (NSString *)methodNameFromEventName:(NSString *)eventName {
|
||||
NSMutableString *methodName
|
||||
= [NSMutableString stringWithCapacity:eventName.length];
|
||||
|
||||
for (NSString *c in [eventName componentsSeparatedByString:@"_"]) {
|
||||
if (c.length) {
|
||||
[methodName appendString:
|
||||
methodName.length ? c.capitalizedString : c.lowercaseString];
|
||||
}
|
||||
}
|
||||
[methodName appendString:@":"];
|
||||
|
||||
return methodName;
|
||||
}
|
||||
|
||||
- (void)sendHangUp {
|
||||
[self sendEventWithName:hangUpAction body:nil];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.2.0</string>
|
||||
<string>6.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
#import <Intents/Intents.h>
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
|
||||
#import "Dropbox.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
@@ -25,9 +27,6 @@
|
||||
#import "RNSplashScreen.h"
|
||||
#import "ScheenshareEventEmiter.h"
|
||||
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
|
||||
@implementation JitsiMeet {
|
||||
RCTBridgeWrapper *_bridgeWrapper;
|
||||
NSDictionary *_launchOptions;
|
||||
@@ -87,8 +86,12 @@
|
||||
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *))restorationHandler {
|
||||
|
||||
JitsiMeetConferenceOptions *options = [self optionsFromUserActivity:userActivity];
|
||||
if (options) {
|
||||
[JitsiMeetView updateProps:[options asProps]];
|
||||
return true;
|
||||
}
|
||||
|
||||
return options && [JitsiMeetView setPropsInViews:[options asProps]];
|
||||
return false;
|
||||
}
|
||||
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
@@ -112,8 +115,9 @@
|
||||
JitsiMeetConferenceOptions *conferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
|
||||
builder.room = [url absoluteString];
|
||||
}];
|
||||
[JitsiMeetView updateProps:[conferenceOptions asProps]];
|
||||
|
||||
return [JitsiMeetView setPropsInViews:[conferenceOptions asProps]];
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark - Utility methods
|
||||
|
||||
30
ios/sdk/src/JitsiMeetRenderingView.h
Normal file
30
ios/sdk/src/JitsiMeetRenderingView.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JitsiMeetRenderingView : UIView
|
||||
|
||||
@property (nonatomic, assign) BOOL isPiPEnabled;
|
||||
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
83
ios/sdk/src/JitsiMeetRenderingView.m
Normal file
83
ios/sdk/src/JitsiMeetRenderingView.m
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#import "JitsiMeetRenderingView.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNRootView.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
|
||||
/**
|
||||
* Backwards compatibility: turn the boolean prop into a feature flag.
|
||||
*/
|
||||
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
|
||||
@interface JitsiMeetRenderingView ()
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetRenderingView {
|
||||
/**
|
||||
* React Native view where the entire content will be rendered.
|
||||
*/
|
||||
RNRootView *rootView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the given props to the React Native application. The props which we pass
|
||||
* are a combination of 3 different sources:
|
||||
*
|
||||
* - JitsiMeet.defaultConferenceOptions
|
||||
* - This function's parameters
|
||||
* - Some extras which are added by this function
|
||||
*/
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
||||
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
||||
|
||||
// Set the PiP flag if it wasn't manually set.
|
||||
NSMutableDictionary *featureFlags = props[@"flags"];
|
||||
// TODO: temporary implementation
|
||||
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
|
||||
featureFlags[PiPEnabledFeatureFlag] = @(self.isPiPEnabled);
|
||||
}
|
||||
|
||||
// This method is supposed to be imperative i.e. a second
|
||||
// invocation with one and the same URL is expected to join the respective
|
||||
// conference again if the first invocation was followed by leaving the
|
||||
// conference. However, React and, respectively,
|
||||
// appProperties/initialProperties are declarative expressions i.e. one and
|
||||
// the same URL will not trigger an automatic re-render in the JavaScript
|
||||
// source code. The workaround implemented below introduces imperativeness
|
||||
// in React Component props by defining a unique value per invocation.
|
||||
props[@"timestamp"] = @(mach_absolute_time());
|
||||
|
||||
if (rootView) {
|
||||
// Update props with the new URL.
|
||||
rootView.appProperties = props;
|
||||
} else {
|
||||
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
|
||||
rootView = [[RNRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
rootView.backgroundColor = self.backgroundColor;
|
||||
|
||||
// Add rootView as a subview which completely covers this one.
|
||||
[rootView setFrame:[self bounds]];
|
||||
rootView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[self addSubview:rootView];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright @ 2018-present 8x8, Inc.
|
||||
* Copyright @ 2017-2018 Atlassian Pty Ltd
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,11 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "JitsiMeetView.h"
|
||||
#import <JitsiMeetSDK/JitsiMeetSDK.h>
|
||||
|
||||
@interface JitsiMeetView ()
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+ (instancetype _Nullable)viewForExternalAPIScope:(NSString *_Nonnull)externalAPIScope;
|
||||
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps;
|
||||
static NSString * const updateViewPropsNotificationName = @"org.jitsi.meet.UpdateViewProps";
|
||||
|
||||
@interface JitsiMeetView (Private)
|
||||
|
||||
+ (void)updateProps:(NSDictionary *_Nonnull)newProps;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
25
ios/sdk/src/JitsiMeetView+Private.m
Normal file
25
ios/sdk/src/JitsiMeetView+Private.m
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* 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 "JitsiMeetView+Private.h"
|
||||
|
||||
@implementation JitsiMeetView (Private)
|
||||
|
||||
+ (void)updateProps:(NSDictionary *_Nonnull)newProps {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:updateViewPropsNotificationName object:nil userInfo:@{@"props": newProps}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -20,43 +20,22 @@
|
||||
#import "ExternalAPI.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "JitsiMeetView.h"
|
||||
#import "JitsiMeetViewController.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNRootView.h"
|
||||
|
||||
@interface JitsiMeetView ()
|
||||
|
||||
/**
|
||||
* Backwards compatibility: turn the boolean prop into a feature flag.
|
||||
*/
|
||||
static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
@property (nonatomic, strong) JitsiMeetViewController *jitsiMeetViewController;
|
||||
@property (nonatomic, strong) UINavigationController *navController;
|
||||
@property (nonatomic, readonly) BOOL isPiPEnabled;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetView {
|
||||
/**
|
||||
* The unique identifier of this `JitsiMeetView` within the process for the
|
||||
* purposes of `ExternalAPI`. The name scope was inspired by postis which we
|
||||
* use on Web for the similar purposes of the iframe-based external API.
|
||||
*/
|
||||
NSString *externalAPIScope;
|
||||
@implementation JitsiMeetView
|
||||
|
||||
/**
|
||||
* React Native view where the entire content will be rendered.
|
||||
*/
|
||||
RNRootView *rootView;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `JitsiMeetView`s associated with their `ExternalAPI` scopes (i.e. unique
|
||||
* identifiers within the process).
|
||||
*/
|
||||
static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||
/**
|
||||
* This gets called automagically when the program starts.
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void initializeViewsMap() {
|
||||
views = [NSMapTable strongToWeakObjectsMapTable];
|
||||
}
|
||||
@dynamic isPiPEnabled;
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
@@ -87,6 +66,10 @@ static void initializeViewsMap() {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal initialization:
|
||||
*
|
||||
@@ -94,70 +77,57 @@ static void initializeViewsMap() {
|
||||
* - initializes the external API scope
|
||||
*/
|
||||
- (void)initWithXXX {
|
||||
// Hook this JitsiMeetView into ExternalAPI.
|
||||
externalAPIScope = [NSUUID UUID].UUIDString;
|
||||
[views setObject:self forKey:externalAPIScope];
|
||||
|
||||
// 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.
|
||||
self.backgroundColor
|
||||
= [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
|
||||
self.jitsiMeetViewController = [[JitsiMeetViewController alloc] init];
|
||||
self.jitsiMeetViewController.view.frame = [self bounds];
|
||||
[self addSubview:self.jitsiMeetViewController.view];
|
||||
|
||||
[self registerObservers];
|
||||
}
|
||||
|
||||
#pragma mark API
|
||||
|
||||
- (void)join:(JitsiMeetConferenceOptions *)options {
|
||||
[self setProps:options == nil ? @{} : [options asProps]];
|
||||
[self.jitsiMeetViewController join:options withPiP:self.isPiPEnabled];
|
||||
}
|
||||
|
||||
- (void)leave {
|
||||
[self setProps:@{}];
|
||||
[self.jitsiMeetViewController leave];
|
||||
}
|
||||
|
||||
- (void)hangUp {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendHangUp];
|
||||
[self.jitsiMeetViewController hangUp];
|
||||
}
|
||||
|
||||
- (void)setAudioMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
[self.jitsiMeetViewController setAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
[self.jitsiMeetViewController sendEndpointTextMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare:(BOOL)enabled {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI toggleScreenShare:enabled];
|
||||
[self.jitsiMeetViewController toggleScreenShare:enabled];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
[self.jitsiMeetViewController retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
|
||||
- (void)openChat:(NSString*)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI openChat:to];
|
||||
[self.jitsiMeetViewController openChat:to];
|
||||
}
|
||||
|
||||
- (void)closeChat {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI closeChat];
|
||||
[self.jitsiMeetViewController closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
[self.jitsiMeetViewController sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)setVideoMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetVideoMuted:muted];
|
||||
[self.jitsiMeetViewController setVideoMuted:muted];
|
||||
}
|
||||
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled {
|
||||
@@ -165,79 +135,47 @@ static void initializeViewsMap() {
|
||||
[externalAPI sendSetClosedCaptionsEnabled:enabled];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
#pragma mark Private
|
||||
|
||||
- (BOOL)isPiPEnabled {
|
||||
return self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)];
|
||||
}
|
||||
|
||||
- (void)registerObservers {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSendEventNotification:) name:sendEventNotificationName object:nil];
|
||||
}
|
||||
|
||||
- (void)handleSendEventNotification:(NSNotification *)notification {
|
||||
NSString *eventName = notification.userInfo[@"name"];
|
||||
NSString *eventData = notification.userInfo[@"data"];
|
||||
|
||||
SEL sel = NSSelectorFromString([self methodNameFromEventName:eventName]);
|
||||
|
||||
if (sel && [self.delegate respondsToSelector:sel]) {
|
||||
[self.delegate performSelector:sel withObject:eventData];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the given props to the React Native application. The props which we pass
|
||||
* are a combination of 3 different sources:
|
||||
* Converts a specific event name i.e. redux action type description to a
|
||||
* method name.
|
||||
*
|
||||
* - JitsiMeet.defaultConferenceOptions
|
||||
* - This function's parameters
|
||||
* - Some extras which are added by this function
|
||||
* @param eventName The event name to convert to a method name.
|
||||
* @return A method name constructed from the specified `eventName`.
|
||||
*/
|
||||
- (void)setProps:(NSDictionary *_Nonnull)newProps {
|
||||
NSMutableDictionary *props = mergeProps([[JitsiMeet sharedInstance] getDefaultProps], newProps);
|
||||
- (NSString *)methodNameFromEventName:(NSString *)eventName {
|
||||
NSMutableString *methodName
|
||||
= [NSMutableString stringWithCapacity:eventName.length];
|
||||
|
||||
// Set the PiP flag if it wasn't manually set.
|
||||
NSMutableDictionary *featureFlags = props[@"flags"];
|
||||
if (featureFlags[PiPEnabledFeatureFlag] == nil) {
|
||||
featureFlags[PiPEnabledFeatureFlag]
|
||||
= [NSNumber numberWithBool:
|
||||
self.delegate && [self.delegate respondsToSelector:@selector(enterPictureInPicture:)]];
|
||||
}
|
||||
for (NSString *c in [eventName componentsSeparatedByString:@"_"]) {
|
||||
if (c.length) {
|
||||
[methodName appendString:
|
||||
methodName.length ? c.capitalizedString : c.lowercaseString];
|
||||
}
|
||||
}
|
||||
[methodName appendString:@":"];
|
||||
|
||||
props[@"externalAPIScope"] = externalAPIScope;
|
||||
|
||||
// This method is supposed to be imperative i.e. a second
|
||||
// invocation with one and the same URL is expected to join the respective
|
||||
// conference again if the first invocation was followed by leaving the
|
||||
// conference. However, React and, respectively,
|
||||
// appProperties/initialProperties are declarative expressions i.e. one and
|
||||
// the same URL will not trigger an automatic re-render in the JavaScript
|
||||
// source code. The workaround implemented below introduces imperativeness
|
||||
// in React Component props by defining a unique value per invocation.
|
||||
props[@"timestamp"] = @(mach_absolute_time());
|
||||
|
||||
if (rootView) {
|
||||
// Update props with the new URL.
|
||||
rootView.appProperties = props;
|
||||
} else {
|
||||
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
|
||||
rootView
|
||||
= [[RNRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
rootView.backgroundColor = self.backgroundColor;
|
||||
|
||||
// Add rootView as a subview which completely covers this one.
|
||||
[rootView setFrame:[self bounds]];
|
||||
rootView.autoresizingMask
|
||||
= UIViewAutoresizingFlexibleWidth
|
||||
| UIViewAutoresizingFlexibleHeight;
|
||||
[self addSubview:rootView];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)setPropsInViews:(NSDictionary *_Nonnull)newProps {
|
||||
BOOL handled = NO;
|
||||
|
||||
if (views) {
|
||||
for (NSString *externalAPIScope in views) {
|
||||
JitsiMeetView *view
|
||||
= [self viewForExternalAPIScope:externalAPIScope];
|
||||
|
||||
if (view) {
|
||||
[view setProps:newProps];
|
||||
handled = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
+ (instancetype)viewForExternalAPIScope:(NSString *)externalAPIScope {
|
||||
return [views objectForKey:externalAPIScope];
|
||||
return methodName;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
38
ios/sdk/src/JitsiMeetViewController.h
Normal file
38
ios/sdk/src/JitsiMeetViewController.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* 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 <UIKit/UIKit.h>
|
||||
#import "JitsiMeetConferenceOptions.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface JitsiMeetViewController : UIViewController
|
||||
|
||||
- (void)join:(JitsiMeetConferenceOptions *)options withPiP:(BOOL)enablePiP;
|
||||
- (void)leave;
|
||||
- (void)hangUp;
|
||||
- (void)setAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)toggleScreenShare:(BOOL)enabled;
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
127
ios/sdk/src/JitsiMeetViewController.m
Normal file
127
ios/sdk/src/JitsiMeetViewController.m
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright @ 2022-present 8x8, Inc.
|
||||
*
|
||||
* 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 "JitsiMeetViewController.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetRenderingView.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
|
||||
@interface JitsiMeetViewController ()
|
||||
|
||||
@property (strong, nonatomic) JitsiMeetRenderingView *view;
|
||||
|
||||
@end
|
||||
|
||||
@implementation JitsiMeetViewController
|
||||
|
||||
@dynamic view;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self registerObservers];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
[super loadView];
|
||||
|
||||
self.view = [[JitsiMeetRenderingView alloc] init];
|
||||
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
// 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.
|
||||
self.view.backgroundColor = [UIColor colorWithRed:.07f green:.07f blue:.07f alpha:1];
|
||||
}
|
||||
|
||||
- (void)join:(JitsiMeetConferenceOptions *)options withPiP:(BOOL)enablePiP {
|
||||
self.view.isPiPEnabled = enablePiP;
|
||||
[self.view setProps:options == nil ? @{} : [options asProps]];
|
||||
}
|
||||
|
||||
- (void)leave {
|
||||
[self.view setProps:@{}];
|
||||
}
|
||||
|
||||
- (void)hangUp {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendHangUp];
|
||||
}
|
||||
|
||||
- (void)setAudioMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare:(BOOL)enabled {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI toggleScreenShare:enabled];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
|
||||
- (void)openChat:(NSString*)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI openChat:to];
|
||||
}
|
||||
|
||||
- (void)closeChat {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)setVideoMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetVideoMuted:muted];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)registerObservers {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUpdateViewPropsNotification:) name:updateViewPropsNotificationName object:nil];
|
||||
}
|
||||
|
||||
- (void)handleUpdateViewPropsNotification:(NSNotification *)notification {
|
||||
NSDictionary *props = [notification.userInfo objectForKey:@"props"];
|
||||
[self.view setProps:props];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -896,6 +896,8 @@
|
||||
"live": "LIVE",
|
||||
"localRecordingNoNotificationWarning": "The recording will not be announced to other participants. You will need to let them know that the meeting is recorded.",
|
||||
"localRecordingNoVideo": "Video is not being recorded",
|
||||
"localRecordingStartWarning": "Please make sure you stop the recording before exiting the meeting in order to save it.",
|
||||
"localRecordingStartWarningTitle": "Stop the recording to save it",
|
||||
"localRecordingVideoStop": "Stopping your video will also stop the local recording. Are you sure you want to continue?",
|
||||
"localRecordingVideoWarning": "To record your video you must have it on when starting the recording",
|
||||
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio. The recording is currently limited to 1GB, which is around 100 minutes.",
|
||||
@@ -1031,6 +1033,7 @@
|
||||
"termsView": {
|
||||
"header": "Terms"
|
||||
},
|
||||
"toggleTopPanelLabel": "Toggle top panel",
|
||||
"toolbar": {
|
||||
"Settings": "Settings",
|
||||
"accessibilityLabel": {
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { getReplaceParticipant } from '../../../react/features/base/config/functions';
|
||||
import { isDialogOpen } from '../../../react/features/base/dialog';
|
||||
import { setJWT } from '../../../react/features/base/jwt';
|
||||
import { setPrejoinPageVisibility } from '../../../react/features/prejoin';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import ExternalLoginDialog from './LoginDialog';
|
||||
@@ -181,7 +180,6 @@ function authenticate(room: Object, lockPassword: string) {
|
||||
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
|
||||
doExternalAuth(room, lockPassword);
|
||||
} else {
|
||||
APP.store.dispatch(setPrejoinPageVisibility(false));
|
||||
APP.store.dispatch(openLoginDialog());
|
||||
}
|
||||
}
|
||||
|
||||
190
package-lock.json
generated
190
package-lock.json
generated
@@ -49,7 +49,7 @@
|
||||
"@react-navigation/elements": "1.2.1",
|
||||
"@react-navigation/material-top-tabs": "6.0.6",
|
||||
"@react-navigation/native": "6.0.6",
|
||||
"@react-navigation/native-stack": "6.6.2",
|
||||
"@react-navigation/stack": "6.2.2",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@tensorflow/tfjs-backend-wasm": "3.13.0",
|
||||
"@tensorflow/tfjs-core": "3.13.0",
|
||||
@@ -74,7 +74,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1461.0.0+96664436/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1464.0.0+28aab9fc/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.2",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -104,7 +104,7 @@
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-reanimated": "https://git@github.com/software-mansion/react-native-reanimated#c4a6b6f687ede090f6081064abe83a2ef9a05784",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "3.10.1",
|
||||
"react-native-screens": "3.13.1",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.1.0",
|
||||
@@ -143,6 +143,7 @@
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@babel/runtime": "7.16.0",
|
||||
"@jitsi/eslint-config": "4.0.0",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-native": "0.67.6",
|
||||
"@types/react-redux": "7.1.24",
|
||||
@@ -5025,33 +5026,6 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/native-stack": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.6.2.tgz",
|
||||
"integrity": "sha512-pFMuzhxbPml5MBvJVAzHWoaUkQaefAOKpuUnAs/AxNQuHQwwnxRmDit1PQLuIPo7g7DlfwFXagDHE1R0tbnS8Q==",
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.3.3",
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 3.0.0",
|
||||
"react-native-screens": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/native-stack/node_modules/@react-navigation/elements": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.3.tgz",
|
||||
"integrity": "sha512-Lv2lR7si5gNME8dRsqz57d54m4FJtrwHRjNQLOyQO546ZxO+g864cSvoLC6hQedQU0+IJnPTsZiEI2hHqfpEpw==",
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/routers": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.0.tgz",
|
||||
@@ -5060,6 +5034,63 @@
|
||||
"nanoid": "^3.1.23"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/stack": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.2.2.tgz",
|
||||
"integrity": "sha512-P9ZfmluOXNmbs7YdG1UWS1fAh87Yse9aX8TgqOz4FlHEm5q7g5eaM35QgWByt+wif3UiqE40D8wXpqRQvMgPWg==",
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": "^1.3.4",
|
||||
"color": "^4.2.3",
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": ">= 1.0.0",
|
||||
"react-native-safe-area-context": ">= 3.0.0",
|
||||
"react-native-screens": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/stack/node_modules/@react-navigation/elements": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.4.tgz",
|
||||
"integrity": "sha512-O0jICpjn3jskVo4yiWzZozmj7DZy1ZBbn3O7dbenuUjZSj/cscjwaapmZZFGcI/IMmjmx8UTKsybhCFEIbGf3g==",
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^6.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/stack/node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/stack/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-navigation/stack/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/@sideway/address": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
|
||||
@@ -5443,6 +5474,12 @@
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.182",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
|
||||
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/long": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
|
||||
@@ -7888,9 +7925,9 @@
|
||||
"integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@@ -12169,8 +12206,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1461.0.0+96664436/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-DbtYpqJ9qsZtugeQIGDEmQOactpGQLSjCHd2obVU+1gdYxp2N6STVTxx7pw7zzlvW7S2pwkIXK/b5B6NFrt2iA==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1464.0.0+28aab9fc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-7kQM0uGBW0m0x5651i3Z7qj8eF0jNF4ZdiOemMcXhxb/NnwBaIb6a9cqPaCkWuwY2m5ZFSB2YogcHgvcwqWbBw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -15535,9 +15572,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-screens": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.10.1.tgz",
|
||||
"integrity": "sha512-ZF/XHnRsuinvDY1XiCWLXxoUoSf+NgsAes2SZfX9rFQQcv128zmh/+19SSavGrSf6rQNzqytEMdRGI6yr4Gbjw==",
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.13.1.tgz",
|
||||
"integrity": "sha512-xcrnuUs0qUrGpc2gOTDY4VgHHADQwp80mwR1prU/Q0JqbZN5W3koLhuOsT6FkSRKjR5t40l+4LcjhHdpqRB2HA==",
|
||||
"dependencies": {
|
||||
"react-freeze": "^1.0.0",
|
||||
"warn-once": "^0.1.0"
|
||||
@@ -23759,22 +23796,6 @@
|
||||
"nanoid": "^3.1.23"
|
||||
}
|
||||
},
|
||||
"@react-navigation/native-stack": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.6.2.tgz",
|
||||
"integrity": "sha512-pFMuzhxbPml5MBvJVAzHWoaUkQaefAOKpuUnAs/AxNQuHQwwnxRmDit1PQLuIPo7g7DlfwFXagDHE1R0tbnS8Q==",
|
||||
"requires": {
|
||||
"@react-navigation/elements": "^1.3.3",
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.3.tgz",
|
||||
"integrity": "sha512-Lv2lR7si5gNME8dRsqz57d54m4FJtrwHRjNQLOyQO546ZxO+g864cSvoLC6hQedQU0+IJnPTsZiEI2hHqfpEpw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-navigation/routers": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.0.tgz",
|
||||
@@ -23783,6 +23804,45 @@
|
||||
"nanoid": "^3.1.23"
|
||||
}
|
||||
},
|
||||
"@react-navigation/stack": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.2.2.tgz",
|
||||
"integrity": "sha512-P9ZfmluOXNmbs7YdG1UWS1fAh87Yse9aX8TgqOz4FlHEm5q7g5eaM35QgWByt+wif3UiqE40D8wXpqRQvMgPWg==",
|
||||
"requires": {
|
||||
"@react-navigation/elements": "^1.3.4",
|
||||
"color": "^4.2.3",
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/elements": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.4.tgz",
|
||||
"integrity": "sha512-O0jICpjn3jskVo4yiWzZozmj7DZy1ZBbn3O7dbenuUjZSj/cscjwaapmZZFGcI/IMmjmx8UTKsybhCFEIbGf3g=="
|
||||
},
|
||||
"color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@sideway/address": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
|
||||
@@ -24109,6 +24169,12 @@
|
||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.182",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
|
||||
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/long": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
|
||||
@@ -26086,9 +26152,9 @@
|
||||
"integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
@@ -29375,8 +29441,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1461.0.0+96664436/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-DbtYpqJ9qsZtugeQIGDEmQOactpGQLSjCHd2obVU+1gdYxp2N6STVTxx7pw7zzlvW7S2pwkIXK/b5B6NFrt2iA==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1464.0.0+28aab9fc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-7kQM0uGBW0m0x5651i3Z7qj8eF0jNF4ZdiOemMcXhxb/NnwBaIb6a9cqPaCkWuwY2m5ZFSB2YogcHgvcwqWbBw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -32024,9 +32090,9 @@
|
||||
"integrity": "sha512-yOwiiPJ1rk+/nfK13eafbpW6sKW0jOnsRem2C1LPJjM3tfTof6hlvV5eWHATye3XOpu2cJ7N+HdkUvUDGwFD2Q=="
|
||||
},
|
||||
"react-native-screens": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.10.1.tgz",
|
||||
"integrity": "sha512-ZF/XHnRsuinvDY1XiCWLXxoUoSf+NgsAes2SZfX9rFQQcv128zmh/+19SSavGrSf6rQNzqytEMdRGI6yr4Gbjw==",
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.13.1.tgz",
|
||||
"integrity": "sha512-xcrnuUs0qUrGpc2gOTDY4VgHHADQwp80mwR1prU/Q0JqbZN5W3koLhuOsT6FkSRKjR5t40l+4LcjhHdpqRB2HA==",
|
||||
"requires": {
|
||||
"react-freeze": "^1.0.0",
|
||||
"warn-once": "^0.1.0"
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"@react-navigation/elements": "1.2.1",
|
||||
"@react-navigation/material-top-tabs": "6.0.6",
|
||||
"@react-navigation/native": "6.0.6",
|
||||
"@react-navigation/native-stack": "6.6.2",
|
||||
"@react-navigation/stack": "6.2.2",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@tensorflow/tfjs-backend-wasm": "3.13.0",
|
||||
"@tensorflow/tfjs-core": "3.13.0",
|
||||
@@ -79,7 +79,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1461.0.0+96664436/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1464.0.0+28aab9fc/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.2",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -109,7 +109,7 @@
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-reanimated": "https://git@github.com/software-mansion/react-native-reanimated#c4a6b6f687ede090f6081064abe83a2ef9a05784",
|
||||
"react-native-safe-area-context": "3.3.2",
|
||||
"react-native-screens": "3.10.1",
|
||||
"react-native-screens": "3.13.1",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.1.0",
|
||||
@@ -148,6 +148,7 @@
|
||||
"@babel/preset-react": "7.16.0",
|
||||
"@babel/runtime": "7.16.0",
|
||||
"@jitsi/eslint-config": "4.0.0",
|
||||
"@types/lodash": "4.14.182",
|
||||
"@types/react": "17.0.14",
|
||||
"@types/react-native": "0.67.6",
|
||||
"@types/react-redux": "7.1.24",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
|
||||
|
||||
@@ -30,6 +28,22 @@ const DEFAULT_STATE = {
|
||||
}
|
||||
};
|
||||
|
||||
interface Value {
|
||||
startedTime: number,
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface IAnalyticsState {
|
||||
localTracksDuration: {
|
||||
audio: Value,
|
||||
video: {
|
||||
camera: Value,
|
||||
desktop: Value
|
||||
},
|
||||
conference: Value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions which changes the state of the analytics feature.
|
||||
*
|
||||
@@ -38,7 +52,7 @@ const DEFAULT_STATE = {
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register('features/analytics', (state = DEFAULT_STATE, action) => {
|
||||
ReducerRegistry.register('features/analytics', (state: IAnalyticsState = DEFAULT_STATE, action: any) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_LOCAL_TRACKS_DURATION:
|
||||
return {
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { FullWindowOverlay } from 'react-native-screens';
|
||||
import SplashScreen from 'react-native-splash-screen';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
@@ -24,7 +23,6 @@ import '../reducers';
|
||||
declare var __DEV__;
|
||||
|
||||
const DialogContainerWrapper = Platform.select({
|
||||
ios: FullWindowOverlay,
|
||||
default: View
|
||||
});
|
||||
|
||||
@@ -33,11 +31,6 @@ const DialogContainerWrapper = Platform.select({
|
||||
*/
|
||||
type Props = AbstractAppProps & {
|
||||
|
||||
/**
|
||||
* Identifier for this app on the native side.
|
||||
*/
|
||||
externalAPIScope: string,
|
||||
|
||||
/**
|
||||
* An object with the feature flags.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
import { IAnalyticsState } from "../analytics/reducer"
|
||||
import { IAuthenticationState } from "../authentication/reducer"
|
||||
import { IAVModerationState } from "../av-moderation/reducer"
|
||||
import { IAppState } from "../base/app/reducer"
|
||||
import { IAudioOnlyState } from "../base/audio-only/reducer"
|
||||
import { IConferenceState } from "../base/conference/reducer"
|
||||
import { IConfig } from "../base/config/configType"
|
||||
|
||||
export interface IStore {
|
||||
getState: Function,
|
||||
dispatch: Function
|
||||
}
|
||||
|
||||
export interface IState {
|
||||
'features/analytics': IAnalyticsState,
|
||||
'features/authentication': IAuthenticationState,
|
||||
'features/av-moderation': IAVModerationState,
|
||||
'features/base/app': IAppState,
|
||||
'features/base/audio-only': IAudioOnlyState,
|
||||
'features/base/conference': IConferenceState,
|
||||
'features/base/config': IConfig,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
JitsiConnectionErrors
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { setPrejoinPageVisibility } from '../prejoin';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
@@ -121,7 +120,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
&& error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
&& typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
store.dispatch(setPrejoinPageVisibility(false));
|
||||
store.dispatch(openLoginDialog());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { assign, ReducerRegistry } from '../base/redux';
|
||||
// @ts-ignore
|
||||
import { assign } from '../base/redux/functions';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
@@ -10,6 +10,13 @@ import {
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
|
||||
export interface IAuthenticationState {
|
||||
error?: Object|undefined;
|
||||
progress?: number|undefined;
|
||||
thenableWithCancel?: Object|undefined;
|
||||
waitForOwnerTimeoutID?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for actions which change the state of the authentication feature.
|
||||
*
|
||||
@@ -18,7 +25,7 @@ import {
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register('features/authentication', (state = {}, action) => {
|
||||
ReducerRegistry.register('features/authentication', (state: IAuthenticationState = {}, action: any) => {
|
||||
switch (action.type) {
|
||||
case CANCEL_LOGIN:
|
||||
return assign(state, {
|
||||
@@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import { MEDIA_TYPE, type MediaType } from '../base/media/constants';
|
||||
|
||||
/**
|
||||
* Mapping between a media type and the witelist reducer key.
|
||||
*/
|
||||
export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: {[key: MediaType]: string} = {
|
||||
export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: { [key: string]: string } = {
|
||||
[MEDIA_TYPE.AUDIO]: 'audioWhitelist',
|
||||
[MEDIA_TYPE.VIDEO]: 'videoWhitelist'
|
||||
};
|
||||
@@ -13,7 +11,7 @@ export const MEDIA_TYPE_TO_WHITELIST_STORE_KEY: {[key: MediaType]: string} = {
|
||||
/**
|
||||
* Mapping between a media type and the pending reducer key.
|
||||
*/
|
||||
export const MEDIA_TYPE_TO_PENDING_STORE_KEY: {[key: MediaType]: string} = {
|
||||
export const MEDIA_TYPE_TO_PENDING_STORE_KEY: {[key: string]: string} = {
|
||||
[MEDIA_TYPE.AUDIO]: 'pendingAudio',
|
||||
[MEDIA_TYPE.VIDEO]: 'pendingVideo'
|
||||
};
|
||||
@@ -1,12 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
import type { MediaType } from '../base/media/constants';
|
||||
import {
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED
|
||||
// @ts-ignore
|
||||
} from '../base/participants';
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
DISABLE_MODERATION,
|
||||
@@ -29,8 +28,19 @@ const initialState = {
|
||||
pendingVideo: []
|
||||
};
|
||||
|
||||
export interface IAVModerationState {
|
||||
audioModerationEnabled: boolean;
|
||||
videoModerationEnabled: boolean;
|
||||
audioWhitelist: { [id: string]: boolean };
|
||||
videoWhitelist: { [id: string]: boolean };
|
||||
pendingAudio: Array<{ id: string }>;
|
||||
pendingVideo: Array<{ id: string }>;
|
||||
audioUnmuteApproved?: boolean|undefined;
|
||||
videoUnmuteApproved?: boolean|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
Updates a participant in the state for the specified media type.
|
||||
* Updates a participant in the state for the specified media type.
|
||||
*
|
||||
* @param {MediaType} mediaType - The media type.
|
||||
* @param {Object} participant - Information about participant to be modified.
|
||||
@@ -38,11 +48,11 @@ const initialState = {
|
||||
* @private
|
||||
* @returns {boolean} - Whether state instance was modified.
|
||||
*/
|
||||
function _updatePendingParticipant(mediaType: MediaType, participant, state: Object = {}) {
|
||||
function _updatePendingParticipant(mediaType: MediaType, participant: any, state: any = {}) {
|
||||
let arrayItemChanged = false;
|
||||
const storeKey = MEDIA_TYPE_TO_PENDING_STORE_KEY[mediaType];
|
||||
const arr = state[storeKey];
|
||||
const newArr = arr.map(pending => {
|
||||
const newArr = arr.map((pending: { id: string} ) => {
|
||||
if (pending.id === participant.id) {
|
||||
arrayItemChanged = true;
|
||||
|
||||
@@ -64,7 +74,7 @@ function _updatePendingParticipant(mediaType: MediaType, participant, state: Obj
|
||||
return false;
|
||||
}
|
||||
|
||||
ReducerRegistry.register('features/av-moderation', (state = initialState, action) => {
|
||||
ReducerRegistry.register('features/av-moderation', (state: IAVModerationState = initialState, action: any) => {
|
||||
|
||||
switch (action.type) {
|
||||
case DISABLE_MODERATION: {
|
||||
@@ -1,10 +1,12 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../redux';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
|
||||
|
||||
ReducerRegistry.register('features/base/app', (state = {}, action) => {
|
||||
export interface IAppState {
|
||||
app?: Object|undefined;
|
||||
}
|
||||
|
||||
ReducerRegistry.register('features/base/app', (state: IAppState = {}, action) => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT: {
|
||||
const { app } = action;
|
||||
@@ -1,16 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../redux';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
|
||||
import { SET_AUDIO_ONLY } from './actionTypes';
|
||||
|
||||
export interface IAudioOnlyState {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
enabled: false
|
||||
};
|
||||
|
||||
|
||||
ReducerRegistry.register('features/base/audio-only', (state = DEFAULT_STATE, action) => {
|
||||
ReducerRegistry.register('features/base/audio-only', (state: IAudioOnlyState = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case SET_AUDIO_ONLY:
|
||||
return {
|
||||
@@ -46,10 +46,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case CONFERENCE_FAILED: {
|
||||
const errorName = action.error?.name;
|
||||
|
||||
if (errorName === JitsiConferenceErrors.MEMBERS_ONLY_ERROR
|
||||
|| errorName === JitsiConferenceErrors.PASSWORD_REQUIRED) {
|
||||
dispatch(setPrejoinPageVisibility(false));
|
||||
} else if (enableForcedReload && errorName === JitsiConferenceErrors.CONFERENCE_RESTARTED) {
|
||||
if (enableForcedReload && errorName === JitsiConferenceErrors.CONFERENCE_RESTARTED) {
|
||||
dispatch(setSkipPrejoinOnReload(true));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// @flow
|
||||
|
||||
// @ts-ignore
|
||||
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
|
||||
// @ts-ignore
|
||||
import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection';
|
||||
// @ts-ignore
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import { assign, ReducerRegistry, set } from '../redux';
|
||||
// @ts-ignore
|
||||
import { assign, set } from '../redux';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
AUTH_STATUS_CHANGED,
|
||||
@@ -25,6 +28,7 @@ import {
|
||||
SET_START_MUTED_POLICY,
|
||||
SET_START_REACTIONS_MUTED
|
||||
} from './actionTypes';
|
||||
// @ts-ignore
|
||||
import { isRoomValid } from './functions';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
@@ -38,13 +42,26 @@ const DEFAULT_STATE = {
|
||||
passwordRequired: undefined
|
||||
};
|
||||
|
||||
export interface IConferenceState {
|
||||
conference: Object|undefined;
|
||||
e2eeSupported: boolean|undefined;
|
||||
joining: Object|undefined;
|
||||
leaving: Object|undefined;
|
||||
locked: string|undefined;
|
||||
membersOnly: boolean|undefined;
|
||||
password: string|undefined;
|
||||
passwordRequired: boolean|undefined;
|
||||
authEnabled?: boolean|undefined;
|
||||
authLogin?: string|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions that contain the conference object, so that it can be
|
||||
* stored for use by other action creators.
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/base/conference',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
(state: IConferenceState = DEFAULT_STATE, action: any) => {
|
||||
switch (action.type) {
|
||||
case AUTH_STATUS_CHANGED:
|
||||
return _authStatusChanged(state, action);
|
||||
@@ -125,7 +142,7 @@ ReducerRegistry.register(
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _authStatusChanged(state, { authEnabled, authLogin }) {
|
||||
function _authStatusChanged(state: any, { authEnabled, authLogin }: {authEnabled: boolean, authLogin: string}) {
|
||||
return assign(state, {
|
||||
authEnabled,
|
||||
authLogin
|
||||
@@ -142,7 +159,7 @@ function _authStatusChanged(state, { authEnabled, authLogin }) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceFailed(state, { conference, error }) {
|
||||
function _conferenceFailed(state: any, { conference, error }: {conference: Object, error: any}) {
|
||||
// The current (similar to getCurrentConference in
|
||||
// base/conference/functions.any.js) conference which is joining or joined:
|
||||
const conference_ = state.conference || state.joining;
|
||||
@@ -208,7 +225,7 @@ function _conferenceFailed(state, { conference, error }) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceJoined(state, { conference }) {
|
||||
function _conferenceJoined(state: any, { conference }: {conference: any}) {
|
||||
// FIXME The indicator which determines whether a JitsiConference is locked
|
||||
// i.e. password-protected is private to lib-jitsi-meet. However, the
|
||||
// library does not fire LOCK_STATE_CHANGED upon joining a JitsiConference
|
||||
@@ -254,7 +271,7 @@ function _conferenceJoined(state, { conference }) {
|
||||
* @returns {Object} The next/new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceLeftOrWillLeave(state, { conference, type }) {
|
||||
function _conferenceLeftOrWillLeave(state: any, { conference, type }: {conference: Object, type: string}) {
|
||||
const nextState = { ...state };
|
||||
|
||||
// The redux action CONFERENCE_LEFT is the last time that we should be
|
||||
@@ -308,7 +325,7 @@ function _conferenceLeftOrWillLeave(state, { conference, type }) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceWillJoin(state, { conference }) {
|
||||
function _conferenceWillJoin(state: any, { conference }: {conference: Object}) {
|
||||
return assign(state, {
|
||||
error: undefined,
|
||||
joining: conference
|
||||
@@ -325,7 +342,7 @@ function _conferenceWillJoin(state, { conference }) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _lockStateChanged(state, { conference, locked }) {
|
||||
function _lockStateChanged(state: any, { conference, locked }: {conference: Object, locked: boolean}) {
|
||||
if (state.conference !== conference) {
|
||||
return state;
|
||||
}
|
||||
@@ -346,7 +363,7 @@ function _lockStateChanged(state, { conference, locked }) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _p2pStatusChanged(state, action) {
|
||||
function _p2pStatusChanged(state: any, action: any) {
|
||||
return set(state, 'p2p', action.p2p);
|
||||
}
|
||||
|
||||
@@ -359,7 +376,7 @@ function _p2pStatusChanged(state, action) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setPassword(state, { conference, method, password }) {
|
||||
function _setPassword(state: any, { conference, method, password }: {conference: any, method: Object, password: string}) {
|
||||
switch (method) {
|
||||
case conference.join:
|
||||
return assign(state, {
|
||||
@@ -406,7 +423,7 @@ function _setPassword(state, { conference, method, password }) {
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setRoom(state, action) {
|
||||
function _setRoom(state: any, action: any) {
|
||||
let { room } = action;
|
||||
|
||||
if (!isRoomValid(room)) {
|
||||
467
react/features/base/config/configType.ts
Normal file
467
react/features/base/config/configType.ts
Normal file
@@ -0,0 +1,467 @@
|
||||
type ToolbarButtons = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
'desktop' |
|
||||
'dock-iframe' |
|
||||
'download' |
|
||||
'embedmeeting' |
|
||||
'etherpad' |
|
||||
'feedback' |
|
||||
'filmstrip' |
|
||||
'fullscreen' |
|
||||
'hangup' |
|
||||
'help' |
|
||||
'highlight' |
|
||||
'invite' |
|
||||
'linktosalesforce' |
|
||||
'livestreaming' |
|
||||
'microphone' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
'recording' |
|
||||
'security' |
|
||||
'select-background' |
|
||||
'settings' |
|
||||
'shareaudio' |
|
||||
'sharedvideo' |
|
||||
'shortcuts' |
|
||||
'stats' |
|
||||
'tileview' |
|
||||
'toggle-camera' |
|
||||
'undock-iframe' |
|
||||
'videoquality' |
|
||||
'__end';
|
||||
|
||||
type ButtonsWithNotifyClick = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
'desktop' |
|
||||
'download' |
|
||||
'embedmeeting' |
|
||||
'etherpad' |
|
||||
'feedback' |
|
||||
'filmstrip' |
|
||||
'fullscreen' |
|
||||
'hangup' |
|
||||
'help' |
|
||||
'invite' |
|
||||
'livestreaming' |
|
||||
'microphone' |
|
||||
'mute-everyone' |
|
||||
'mute-video-everyone' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
'recording' |
|
||||
'security' |
|
||||
'select-background' |
|
||||
'settings' |
|
||||
'shareaudio' |
|
||||
'sharedvideo' |
|
||||
'shortcuts' |
|
||||
'stats' |
|
||||
'tileview' |
|
||||
'toggle-camera' |
|
||||
'videoquality' |
|
||||
'add-passcode' |
|
||||
'__end';
|
||||
|
||||
type Sounds = 'ASKED_TO_UNMUTE_SOUND' |
|
||||
'E2EE_OFF_SOUND' |
|
||||
'E2EE_ON_SOUND' |
|
||||
'INCOMING_MSG_SOUND' |
|
||||
'KNOCKING_PARTICIPANT_SOUND' |
|
||||
'LIVE_STREAMING_OFF_SOUND' |
|
||||
'LIVE_STREAMING_ON_SOUND' |
|
||||
'NO_AUDIO_SIGNAL_SOUND' |
|
||||
'NOISY_AUDIO_INPUT_SOUND' |
|
||||
'OUTGOING_CALL_EXPIRED_SOUND' |
|
||||
'OUTGOING_CALL_REJECTED_SOUND' |
|
||||
'OUTGOING_CALL_RINGING_SOUND' |
|
||||
'OUTGOING_CALL_START_SOUND' |
|
||||
'PARTICIPANT_JOINED_SOUND' |
|
||||
'PARTICIPANT_LEFT_SOUND' |
|
||||
'RAISE_HAND_SOUND' |
|
||||
'REACTION_SOUND' |
|
||||
'RECORDING_OFF_SOUND' |
|
||||
'RECORDING_ON_SOUND' |
|
||||
'TALK_WHILE_MUTED_SOUND';
|
||||
|
||||
export interface IConfig {
|
||||
hosts?: {
|
||||
domain: string;
|
||||
anonymousdomain?: string;
|
||||
authdomain?: string;
|
||||
focus?: string;
|
||||
muc: string;
|
||||
};
|
||||
bosh?: string;
|
||||
websocket?: string;
|
||||
focusUserJid?: string;
|
||||
testing?: {
|
||||
disableE2EE?: boolean;
|
||||
enableThumbnailReordering?: boolean;
|
||||
mobileXmppWsThreshold?: number;
|
||||
p2pTestMode?: boolean;
|
||||
testMode?: boolean;
|
||||
noAutoPlayVideo?: boolean;
|
||||
capScreenshareBitrate?: number;
|
||||
setScreenSharingResolutionConstraints?: boolean;
|
||||
callStatsThreshold?: number;
|
||||
};
|
||||
flags?: {
|
||||
sourceNameSignaling?: boolean;
|
||||
sendMultipleVideoStreams?: boolean;
|
||||
};
|
||||
disableModeratorIndicator?: boolean;
|
||||
disableReactions?: boolean;
|
||||
disableReactionsModeration?: boolean;
|
||||
disablePolls?: boolean;
|
||||
disableSelfView?: boolean;
|
||||
disableSelfViewSettings?: boolean;
|
||||
screenshotCapture?: {
|
||||
enabled?: boolean;
|
||||
mode?: 'always' | 'recording';
|
||||
};
|
||||
webrtcIceUdpDisable?: boolean;
|
||||
webrtcIceTcpDisable?: boolean;
|
||||
enableUnifiedOnChrome?: boolean;
|
||||
disableAudioLevels?: boolean;
|
||||
audioLevelsInterval?: number;
|
||||
enableNoAudioDetection?: boolean;
|
||||
enableSaveLogs?: boolean;
|
||||
disableShowMoreStats?: boolean;
|
||||
enableNoisyMicDetection?: boolean;
|
||||
startAudioOnly?: boolean;
|
||||
startAudioMuted?: boolean;
|
||||
startWithAudioMuted?: boolean;
|
||||
startSilent?: boolean;
|
||||
enableOpusRed?: boolean;
|
||||
audioQuality?: {
|
||||
stereo?: boolean;
|
||||
opusMaxAverageBitrate?: number|null;
|
||||
};
|
||||
stereo?: boolean;
|
||||
opusMaxAverageBitrate?: number;
|
||||
resolution?: number;
|
||||
disableRemoveRaisedHandOnFocus?: boolean;
|
||||
disableSpeakerStatsSearch?: boolean;
|
||||
speakerStatsOrder?: Array<'role'|'name'|'hasLeft'>;
|
||||
maxFullResolutionParticipants?: number;
|
||||
constraints?: {
|
||||
video?: {
|
||||
height?: {
|
||||
ideal?: number;
|
||||
max?: number;
|
||||
min?: number;
|
||||
}
|
||||
}
|
||||
};
|
||||
disableSimulcast?: boolean;
|
||||
enableLayerSuspension?: boolean;
|
||||
startVideoMuted?: number;
|
||||
startWithVideoMuted?: boolean;
|
||||
preferH264?: boolean;
|
||||
disableH264?: boolean;
|
||||
desktopSharingFrameRate?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
};
|
||||
startScreenSharing?: boolean;
|
||||
fileRecordingsEnabled?: boolean;
|
||||
dropbox?: {
|
||||
appKey: string;
|
||||
redirectURI?: string;
|
||||
};
|
||||
recordingService?: {
|
||||
enabled?: boolean;
|
||||
sharingEnabled?: boolean;
|
||||
hideStorageWarning?: boolean;
|
||||
};
|
||||
fileRecordingsServiceEnabled?: boolean;
|
||||
fileRecordingsServiceSharingEnabled?: boolean;
|
||||
liveStreamingEnabled?: boolean;
|
||||
localRecording?: {
|
||||
disable?: boolean;
|
||||
notifyAllParticipants?: boolean;
|
||||
};
|
||||
transcribingEnabled?: boolean;
|
||||
transcribeWithAppLanguage?: boolean;
|
||||
preferredTranscribeLanguage?: string;
|
||||
autoCaptionOnRecord?: boolean;
|
||||
transcription?: {
|
||||
enabled?: boolean;
|
||||
useAppLanguage?: boolean;
|
||||
preferredLanguage?: string;
|
||||
disableStartForAll?: boolean;
|
||||
autoCaptionOnRecord?: boolean;
|
||||
};
|
||||
channelLastN?: number;
|
||||
connectionIndicators?: {
|
||||
autoHide?: boolean;
|
||||
autoHideTimeout?: number;
|
||||
disabled?: boolean;
|
||||
disableDetails?: boolean;
|
||||
inactiveDisabled?: boolean;
|
||||
};
|
||||
startLastN?: number;
|
||||
lastNLimits?: {
|
||||
[key: number]: number;
|
||||
};
|
||||
useNewBandwidthAllocationStrategy?: boolean;
|
||||
videoQuality?: {
|
||||
disabledCodec?: string;
|
||||
preferredCodec?: string;
|
||||
enforcePreferredCodec?: boolean;
|
||||
maxBitratesVideo?: {
|
||||
[key: string]: {
|
||||
low?: number;
|
||||
standard?: number;
|
||||
high?: number;
|
||||
}
|
||||
};
|
||||
minHeightForQualityLvl: {
|
||||
[key: number]: string;
|
||||
};
|
||||
resizeDesktopForPresenter?: boolean;
|
||||
};
|
||||
notificationTimeouts?: {
|
||||
short?: number;
|
||||
medium?: number;
|
||||
long?: number;
|
||||
};
|
||||
recordingLimit?: {
|
||||
limit?: number;
|
||||
appName?: string;
|
||||
appURL?: string;
|
||||
};
|
||||
disableRtx?: boolean;
|
||||
disableBeforeUnloadHandlers?: boolean;
|
||||
enableTcc?: boolean;
|
||||
enableRemb?: boolean;
|
||||
enableIceRestart?: boolean;
|
||||
enableForcedReload?: boolean;
|
||||
useTurnUdp?: boolean;
|
||||
enableEncodedTransformSupport?: boolean;
|
||||
disableResponsiveTiles?: boolean;
|
||||
hideLobbyButton?: boolean;
|
||||
autoKnockLobby?: boolean;
|
||||
enableLobbyChat?: boolean;
|
||||
hideAddRoomButton?: boolean;
|
||||
requireDisplayName?: boolean;
|
||||
enableWelcomePage?: boolean;
|
||||
disableShortcuts?: boolean;
|
||||
disableInitialGUM?: boolean;
|
||||
enableClosePage?: boolean;
|
||||
disable1On1Mode?: boolean|null;
|
||||
defaultLocalDisplayName?: string;
|
||||
defaultRemoteDisplayName?: string;
|
||||
hideDisplayName?: boolean;
|
||||
hideDominantSpeakerBadge?: boolean;
|
||||
defaultLanguage?: string;
|
||||
disableProfile?: boolean;
|
||||
hideEmailInSettings?: boolean;
|
||||
enableFeaturesBasedOnToken?: boolean;
|
||||
roomPasswordNumberOfDigits?: number;
|
||||
noticeMessage?: string;
|
||||
enableCalendarIntegration?: boolean;
|
||||
prejoinConfig?: {
|
||||
enabled?: boolean;
|
||||
hideDisplayName?: boolean;
|
||||
hideExtraJoinButtons?: Array<string>;
|
||||
};
|
||||
prejoinPageEnabled?: boolean;
|
||||
readOnlyName?: boolean;
|
||||
openSharedDocumentOnJoin?: boolean;
|
||||
enableInsecureRoomNameWarning?: boolean;
|
||||
enableAutomaticUrlCopy?: boolean;
|
||||
corsAvatarURLs?: Array<string>;
|
||||
gravatarBaseURL?: string;
|
||||
gravatar?: {
|
||||
baseUrl?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
inviteAppName?: string|null;
|
||||
toolbarButtons?: Array<ToolbarButtons>;
|
||||
toolbarConfig?: {
|
||||
initialTimeout?: number;
|
||||
timeout?: number;
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
};
|
||||
buttonsWithNotifyClick?: Array<ButtonsWithNotifyClick | { key: ButtonsWithNotifyClick; preventExecution: boolean }>;
|
||||
hiddenPremeetingButtons?: Array<'microphone' | 'camera' | 'select-background' | 'invite' | 'settings'>;
|
||||
gatherStats?: boolean;
|
||||
pcStatsInterval?: number;
|
||||
callStatsID?: string;
|
||||
callStatsSecret?: string;
|
||||
callStatsConfigParams?: {
|
||||
disableBeforeUnloadHandler?: boolean;
|
||||
applicationVersion?: string;
|
||||
disablePrecalltest?: boolean;
|
||||
siteID?: string;
|
||||
additionalIDs?: {
|
||||
customerID?: string;
|
||||
tenantID?: string;
|
||||
productName?: string;
|
||||
meetingsName?: string;
|
||||
serverName?: string;
|
||||
pbxID?: string;
|
||||
pbxExtensionID?: string;
|
||||
fqExtensionID?: string;
|
||||
sessionID?: string;
|
||||
};
|
||||
collectLegacyStats?: boolean;
|
||||
collectIP?: boolean;
|
||||
};
|
||||
enableDisplayNameInStats?: boolean;
|
||||
enableEmailInStats?: boolean;
|
||||
faceLandmarks?: {
|
||||
enableFaceCentering?: boolean;
|
||||
enableFaceExpressionsDetection?: boolean;
|
||||
enableDisplayFaceExpressions?: boolean;
|
||||
enableRTCStats?: boolean;
|
||||
faceCenteringThreshold?: number;
|
||||
captureInterval?: number;
|
||||
};
|
||||
feedbackPercentage?: number;
|
||||
disableThirdPartyRequests?: boolean;
|
||||
p2p?: {
|
||||
enabled?: boolean;
|
||||
enableUnifiedOnChrome?: boolean;
|
||||
iceTransportPolicy?: string;
|
||||
preferH264?: boolean;
|
||||
preferredCodec?: string;
|
||||
disableH264?: boolean;
|
||||
disabledCodec?: string;
|
||||
backToP2PDelay?: number;
|
||||
stunServers?: Array<{urls: string}>;
|
||||
};
|
||||
analytics?: {
|
||||
disabled?: boolean;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
matomoEndpoint?: string;
|
||||
matomoSiteID?: string;
|
||||
amplitudeAPPKey?: string;
|
||||
obfuscateRoomName?: boolean;
|
||||
rtcstatsEnabled?: boolean;
|
||||
rtcstatsEndpoint?: string;
|
||||
rtcstatsPolIInterval?: number;
|
||||
scriptURLs?: Array<string>;
|
||||
};
|
||||
apiLogLevels?: Array<'warn' | 'log' | 'error' | 'info' | 'debug'>;
|
||||
deploymentInfo?: {
|
||||
shard?: string;
|
||||
region?: string;
|
||||
userRegion?: string;
|
||||
};
|
||||
disabledSounds?: Array<Sounds>;
|
||||
disableRecordAudioNotification?: boolean;
|
||||
disableJoinLeaveSounds?: boolean;
|
||||
disableIncomingMessageSound?: boolean;
|
||||
chromeExtensionBanner?: {
|
||||
url?: string;
|
||||
edgeUrl?: string;
|
||||
chromeExtensionsInfo?: Array<{id: string; path: string}>;
|
||||
};
|
||||
e2ee?: {
|
||||
labels?: {
|
||||
tooltip?: string;
|
||||
description?: string;
|
||||
label?: string;
|
||||
warning?: string;
|
||||
};
|
||||
externallyManagedKey?: boolean;
|
||||
e2eeLabels?: {
|
||||
tooltip?: string;
|
||||
description?: string;
|
||||
label?: string;
|
||||
warning?: string;
|
||||
};
|
||||
};
|
||||
e2eeLabels?: {
|
||||
tooltip?: string;
|
||||
description?: string;
|
||||
label?: string;
|
||||
warning?: string;
|
||||
};
|
||||
e2eping?: {
|
||||
enabled?: boolean;
|
||||
numRequests?: number;
|
||||
maxConferenceSize?: number;
|
||||
maxMessagesPerSecond?: number;
|
||||
};
|
||||
_desktopSharingSourceDevice?: string;
|
||||
disableDeepLinking?: boolean;
|
||||
disableLocalVideoFlip?: boolean;
|
||||
doNotFlipLocalVideo?: boolean;
|
||||
disableInviteFunctions?: boolean;
|
||||
doNotStoreRoom?: boolean;
|
||||
deploymentUrls?: {
|
||||
userDocumentationURL?: string;
|
||||
downloadAppsUrl?: string;
|
||||
};
|
||||
remoteVideoMenu?: {
|
||||
disabled?: boolean;
|
||||
disableKick?: boolean;
|
||||
disableGrantModerator?: boolean;
|
||||
disablePrivateChat?: boolean;
|
||||
};
|
||||
salesforceUrl?: string;
|
||||
disableRemoteMute?: boolean;
|
||||
enableLipSync?: boolean;
|
||||
dynamicBrandingUrl?: string;
|
||||
participantsPane?: {
|
||||
hideModeratorSettingsTab?: boolean;
|
||||
hideMoreActionsButton?: boolean;
|
||||
hideMuteAllButton?: boolean;
|
||||
};
|
||||
breakoutRooms?: {
|
||||
hideAddRoomButton?: boolean;
|
||||
hideAutoAssignButton?: boolean;
|
||||
hideJoinRoomButton?: boolean;
|
||||
};
|
||||
disableAddingBackgroundImages?: boolean;
|
||||
disableScreensharingVirtualBackground?: boolean;
|
||||
backgroundAlpha?: number;
|
||||
moderatedRoomServiceUrl?: string;
|
||||
disableTileView?: boolean;
|
||||
disableTileEnlargement?: boolean;
|
||||
conferenceInfo?: {
|
||||
alwaysVisible?: Array<string>;
|
||||
autoHide?: Array<string>;
|
||||
};
|
||||
hideConferenceSubject?: boolean;
|
||||
hideConferenceTimer?: boolean;
|
||||
hideRecordingLabel?: boolean;
|
||||
hideParticipantsStats?: boolean;
|
||||
subject?: string;
|
||||
localSubject?: string;
|
||||
useHostPageLocalStorage?: boolean;
|
||||
etherpad_base?: string;
|
||||
dialInNumbersUrl?: string;
|
||||
dialInConfCodeUrl?: string;
|
||||
brandingRoomAlias?: string;
|
||||
mouseMoveCallbackInterval?: number;
|
||||
notifications?: Array<string>;
|
||||
disabledNotifications?: Array<string>;
|
||||
disableFilmstripAutohiding?: boolean;
|
||||
filmstrip?: {
|
||||
disableResizable?: boolean;
|
||||
disableStageFilmstrip?: boolean;
|
||||
disableTopPanel?: boolean;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
};
|
||||
tileView?: {
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
disableChatSmileys?: boolean;
|
||||
giphy?: {
|
||||
enabled?: boolean;
|
||||
sdkKey?: '';
|
||||
displayMode?: 'all' | 'tile' | 'chat';
|
||||
tileTime?: number;
|
||||
};
|
||||
locationURL?: string;
|
||||
}
|
||||
@@ -222,6 +222,7 @@ export default [
|
||||
'toolbarConfig',
|
||||
'tileView',
|
||||
'transcribingEnabled',
|
||||
'transcription',
|
||||
'useHostPageLocalStorage',
|
||||
'useTurnUdp',
|
||||
'videoQuality',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { CONFERENCE_INFO } from '../../conference/components/constants';
|
||||
import { equals, ReducerRegistry } from '../redux';
|
||||
// @ts-ignore
|
||||
import { equals } from '../redux';
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
UPDATE_CONFIG,
|
||||
@@ -12,9 +12,11 @@ import {
|
||||
SET_CONFIG,
|
||||
OVERWRITE_CONFIG
|
||||
} from './actionTypes';
|
||||
import { IConfig } from './configType';
|
||||
// @ts-ignore
|
||||
import { _cleanupConfig } from './functions';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
declare var interfaceConfig: any;
|
||||
|
||||
/**
|
||||
* The initial state of the feature base/config when executing in a
|
||||
@@ -26,7 +28,7 @@ declare var interfaceConfig: Object;
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const INITIAL_NON_RN_STATE = {
|
||||
const INITIAL_NON_RN_STATE: IConfig = {
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -38,7 +40,7 @@ const INITIAL_NON_RN_STATE = {
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const INITIAL_RN_STATE = {
|
||||
const INITIAL_RN_STATE: IConfig = {
|
||||
analytics: {},
|
||||
|
||||
// FIXME The support for audio levels in lib-jitsi-meet polls the statistics
|
||||
@@ -61,14 +63,14 @@ const INITIAL_RN_STATE = {
|
||||
* Mapping between old configs controlling the conference info headers visibility and the
|
||||
* new configs. Needed in order to keep backwards compatibility.
|
||||
*/
|
||||
const CONFERENCE_HEADER_MAPPING = {
|
||||
const CONFERENCE_HEADER_MAPPING: any = {
|
||||
hideConferenceTimer: [ 'conference-timer' ],
|
||||
hideConferenceSubject: [ 'subject' ],
|
||||
hideParticipantsStats: [ 'participants-count' ],
|
||||
hideRecordingLabel: [ 'recording' ]
|
||||
};
|
||||
|
||||
ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {
|
||||
ReducerRegistry.register('features/base/config', (state: IConfig = _getInitialState(), action: any) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_CONFIG:
|
||||
return _updateConfig(state, action);
|
||||
@@ -139,12 +141,12 @@ function _getInitialState() {
|
||||
* Reduces a specific Redux action SET_CONFIG of the feature
|
||||
* base/lib-jitsi-meet.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/config.
|
||||
* @param {IConfig} state - The Redux state of the feature base/config.
|
||||
* @param {Action} action - The Redux action SET_CONFIG to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state after the reduction of the specified action.
|
||||
*/
|
||||
function _setConfig(state, { config }) {
|
||||
function _setConfig(state: IConfig, { config }: {config: IConfig}) {
|
||||
// The mobile app bundles jitsi-meet and lib-jitsi-meet at build time and
|
||||
// does not download them at runtime from the deployment on which it will
|
||||
// join a conference. The downloading is planned for implementation in the
|
||||
@@ -187,10 +189,10 @@ function _setConfig(state, { config }) {
|
||||
/**
|
||||
* Processes the conferenceInfo object against the defaults.
|
||||
*
|
||||
* @param {Object} config - The old config.
|
||||
* @param {IConfig} config - The old config.
|
||||
* @returns {Object} The processed conferenceInfo object.
|
||||
*/
|
||||
function _getConferenceInfo(config) {
|
||||
function _getConferenceInfo(config: IConfig) {
|
||||
const { conferenceInfo } = config;
|
||||
|
||||
if (conferenceInfo) {
|
||||
@@ -207,11 +209,7 @@ function _getConferenceInfo(config) {
|
||||
|
||||
/**
|
||||
* Constructs a new config {@code Object}, if necessary, out of a specific
|
||||
* config {@code Object} which is in the latest format supported by jitsi-meet.
|
||||
* Such a translation from an old config format to a new/the latest config
|
||||
* format is necessary because the mobile app bundles jitsi-meet and
|
||||
* lib-jitsi-meet at build time and does not download them at runtime from the
|
||||
* deployment on which it will join a conference.
|
||||
* interface_config {@code Object} which is in the latest format supported by jitsi-meet.
|
||||
*
|
||||
* @param {Object} oldValue - The config {@code Object} which may or may not be
|
||||
* in the latest form supported by jitsi-meet and from which a new config
|
||||
@@ -219,11 +217,11 @@ function _getConferenceInfo(config) {
|
||||
* @returns {Object} A config {@code Object} which is in the latest format
|
||||
* supported by jitsi-meet.
|
||||
*/
|
||||
function _translateLegacyConfig(oldValue: Object) {
|
||||
function _translateInterfaceConfig(oldValue: IConfig) {
|
||||
const newValue = oldValue;
|
||||
|
||||
if (!Array.isArray(oldValue.toolbarButtons)
|
||||
&& typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
|
||||
&& typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
|
||||
newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
|
||||
}
|
||||
|
||||
@@ -231,6 +229,7 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
oldValue.toolbarConfig = {};
|
||||
}
|
||||
|
||||
newValue.toolbarConfig = oldValue.toolbarConfig || {};
|
||||
if (typeof oldValue.toolbarConfig.alwaysVisible !== 'boolean'
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE === 'boolean') {
|
||||
@@ -249,32 +248,11 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
newValue.toolbarConfig.timeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key]);
|
||||
|
||||
if (filteredConferenceInfo.length) {
|
||||
newValue.conferenceInfo = _getConferenceInfo(oldValue);
|
||||
|
||||
filteredConferenceInfo.forEach(key => {
|
||||
// hideRecordingLabel does not mean not render it at all, but autoHide it
|
||||
if (key === 'hideRecordingLabel') {
|
||||
newValue.conferenceInfo.alwaysVisible
|
||||
= newValue.conferenceInfo.alwaysVisible.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
|
||||
newValue.conferenceInfo.autoHide
|
||||
= _.union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
|
||||
} else {
|
||||
newValue.conferenceInfo.alwaysVisible
|
||||
= newValue.conferenceInfo.alwaysVisible.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
|
||||
newValue.conferenceInfo.autoHide
|
||||
= newValue.conferenceInfo.autoHide.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!oldValue.connectionIndicators
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
|
||||
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
|
||||
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& (interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_DISABLED')
|
||||
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_ENABLED')
|
||||
|| interfaceConfig.hasOwnProperty('CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT'))) {
|
||||
newValue.connectionIndicators = {
|
||||
disabled: interfaceConfig.CONNECTION_INDICATOR_DISABLED,
|
||||
autoHide: interfaceConfig.CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
|
||||
@@ -282,6 +260,68 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.disableModeratorIndicator === undefined
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) {
|
||||
newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
|
||||
}
|
||||
|
||||
if (oldValue.defaultLocalDisplayName === undefined
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) {
|
||||
newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
if (oldValue.defaultRemoteDisplayName === undefined
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) {
|
||||
newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new config {@code Object}, if necessary, out of a specific
|
||||
* config {@code Object} which is in the latest format supported by jitsi-meet.
|
||||
* Such a translation from an old config format to a new/the latest config
|
||||
* format is necessary because the mobile app bundles jitsi-meet and
|
||||
* lib-jitsi-meet at build time and does not download them at runtime from the
|
||||
* deployment on which it will join a conference.
|
||||
*
|
||||
* @param {Object} oldValue - The config {@code Object} which may or may not be
|
||||
* in the latest form supported by jitsi-meet and from which a new config
|
||||
* {@code Object} is to be constructed if necessary.
|
||||
* @returns {Object} A config {@code Object} which is in the latest format
|
||||
* supported by jitsi-meet.
|
||||
*/
|
||||
function _translateLegacyConfig(oldValue: IConfig) {
|
||||
const newValue = _translateInterfaceConfig(oldValue);
|
||||
|
||||
// Translate deprecated config values to new config values.
|
||||
|
||||
const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key as keyof IConfig]);
|
||||
|
||||
if (filteredConferenceInfo.length) {
|
||||
newValue.conferenceInfo = _getConferenceInfo(oldValue);
|
||||
|
||||
filteredConferenceInfo.forEach(key => {
|
||||
newValue.conferenceInfo = oldValue.conferenceInfo ?? {};
|
||||
// hideRecordingLabel does not mean not render it at all, but autoHide it
|
||||
if (key === 'hideRecordingLabel') {
|
||||
newValue.conferenceInfo.alwaysVisible
|
||||
= (newValue.conferenceInfo?.alwaysVisible ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
|
||||
newValue.conferenceInfo.autoHide
|
||||
= _.union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
|
||||
} else {
|
||||
newValue.conferenceInfo.alwaysVisible
|
||||
= (newValue.conferenceInfo.alwaysVisible ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
|
||||
newValue.conferenceInfo.autoHide
|
||||
= (newValue.conferenceInfo.autoHide ?? []).filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
newValue.prejoinConfig = oldValue.prejoinConfig || {};
|
||||
if (oldValue.hasOwnProperty('prejoinPageEnabled')
|
||||
&& !newValue.prejoinConfig.hasOwnProperty('enabled')
|
||||
@@ -315,33 +355,15 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.disableModeratorIndicator === undefined
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.hasOwnProperty('DISABLE_FOCUS_INDICATOR')) {
|
||||
newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
|
||||
}
|
||||
|
||||
newValue.e2ee = newValue.e2ee || {};
|
||||
|
||||
if (oldValue.e2eeLabels) {
|
||||
newValue.e2ee.e2eeLabels = oldValue.e2eeLabels;
|
||||
}
|
||||
|
||||
if (oldValue.defaultLocalDisplayName === undefined
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.hasOwnProperty('DEFAULT_LOCAL_DISPLAY_NAME')) {
|
||||
newValue.defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
newValue.defaultLocalDisplayName
|
||||
= newValue.defaultLocalDisplayName || 'me';
|
||||
|
||||
if (oldValue.defaultRemoteDisplayName === undefined
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& interfaceConfig.hasOwnProperty('DEFAULT_REMOTE_DISPLAY_NAME')) {
|
||||
newValue.defaultRemoteDisplayName = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
if (oldValue.hideAddRoomButton) {
|
||||
newValue.breakoutRooms = {
|
||||
/* eslint-disable-next-line no-extra-parens */
|
||||
@@ -353,6 +375,46 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
newValue.defaultRemoteDisplayName
|
||||
= newValue.defaultRemoteDisplayName || 'Fellow Jitster';
|
||||
|
||||
newValue.transcription = newValue.transcription || {};
|
||||
if (oldValue.transcribingEnabled !== undefined) {
|
||||
newValue.transcription = {
|
||||
...newValue.transcription,
|
||||
enabled: oldValue.transcribingEnabled
|
||||
};
|
||||
}
|
||||
if (oldValue.transcribeWithAppLanguage !== undefined) {
|
||||
newValue.transcription = {
|
||||
...newValue.transcription,
|
||||
useAppLanguage: oldValue.transcribeWithAppLanguage
|
||||
};
|
||||
}
|
||||
if (oldValue.preferredTranscribeLanguage !== undefined) {
|
||||
newValue.transcription = {
|
||||
...newValue.transcription,
|
||||
preferredLanguage: oldValue.preferredTranscribeLanguage
|
||||
};
|
||||
}
|
||||
if (oldValue.autoCaptionOnRecord !== undefined) {
|
||||
newValue.transcription = {
|
||||
...newValue.transcription,
|
||||
autoCaptionOnRecord: oldValue.autoCaptionOnRecord
|
||||
};
|
||||
}
|
||||
|
||||
newValue.recordingService = newValue.recordingService || {};
|
||||
if (oldValue.fileRecordingsServiceEnabled !== undefined) {
|
||||
newValue.recordingService = {
|
||||
...newValue.recordingService,
|
||||
enabled: oldValue.fileRecordingsServiceEnabled
|
||||
};
|
||||
}
|
||||
if (oldValue.fileRecordingsServiceSharingEnabled !== undefined) {
|
||||
newValue.recordingService = {
|
||||
...newValue.recordingService,
|
||||
sharingEnabled: oldValue.fileRecordingsServiceSharingEnabled
|
||||
};
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
@@ -364,7 +426,7 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
* @private
|
||||
* @returns {Object} The new state after the reduction of the specified action.
|
||||
*/
|
||||
function _updateConfig(state, { config }) {
|
||||
function _updateConfig(state: IConfig, { config }: {config: IConfig}) {
|
||||
const newState = _.merge({}, state, config);
|
||||
|
||||
_cleanupConfig(newState);
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The set of facing modes for camera.
|
||||
*
|
||||
@@ -17,7 +15,7 @@ export type MediaType = 'audio' | 'video' | 'presenter' | 'screenshare';
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const MEDIA_TYPE = {
|
||||
export const MEDIA_TYPE: {[key: string]: MediaType} = {
|
||||
AUDIO: 'audio',
|
||||
PRESENTER: 'presenter',
|
||||
SCREENSHARE: 'screenshare',
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { getDefaultHeaderHeight } from '@react-navigation/elements';
|
||||
import { useHeaderHeight } from '@react-navigation/elements';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Keyboard,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Platform,
|
||||
StatusBar
|
||||
} from 'react-native';
|
||||
import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import { StyleType } from '../../styles';
|
||||
|
||||
@@ -34,11 +34,6 @@ type Props = {
|
||||
*/
|
||||
hasTabNavigator: boolean,
|
||||
|
||||
/**
|
||||
* Is the screen presented as a modal?
|
||||
*/
|
||||
isModalPresentation: boolean,
|
||||
|
||||
/**
|
||||
* Additional style to be appended to the KeyboardAvoidingView.
|
||||
*/
|
||||
@@ -51,33 +46,26 @@ const JitsiKeyboardAvoidingView = (
|
||||
contentContainerStyle,
|
||||
hasTabNavigator,
|
||||
hasBottomTextInput,
|
||||
isModalPresentation,
|
||||
style
|
||||
}: Props) => {
|
||||
const frame = useSafeAreaFrame();
|
||||
const headerHeight = useHeaderHeight();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [ bottomPadding, setBottomPadding ] = useState(insets.bottom);
|
||||
const [ topPadding, setTopPadding ] = useState(insets.top);
|
||||
|
||||
useEffect(() => {
|
||||
// This useEffect is needed because insets are undefined at first for some reason
|
||||
// https://github.com/th3rdwave/react-native-safe-area-context/issues/54
|
||||
setBottomPadding(insets.bottom);
|
||||
setTopPadding(insets.top);
|
||||
}, [ insets.bottom, insets.top ]);
|
||||
}, [ insets.bottom ]);
|
||||
|
||||
const headerHeight = getDefaultHeaderHeight(frame, isModalPresentation, topPadding);
|
||||
|
||||
// Notch devices have in general a header height between 103 and 106px
|
||||
const topNotchDevice = headerHeight > 100;
|
||||
const deviceHeight = topNotchDevice ? headerHeight - 50 : headerHeight;
|
||||
const tabNavigatorPadding
|
||||
= hasTabNavigator ? deviceHeight : 0;
|
||||
= hasTabNavigator ? headerHeight : 0;
|
||||
const noNotchDevicePadding = bottomPadding || 10;
|
||||
const iosVerticalOffset
|
||||
= deviceHeight + noNotchDevicePadding + tabNavigatorPadding;
|
||||
= headerHeight + noNotchDevicePadding + tabNavigatorPadding;
|
||||
const androidVerticalOffset = hasBottomTextInput
|
||||
? deviceHeight + StatusBar.currentHeight : deviceHeight;
|
||||
? headerHeight + StatusBar.currentHeight : headerHeight;
|
||||
|
||||
// Tells the view what to do with taps
|
||||
const shouldSetResponse = useCallback(() => true);
|
||||
|
||||
@@ -37,11 +37,6 @@ type Props = {
|
||||
*/
|
||||
hasTabNavigator?: boolean,
|
||||
|
||||
/**
|
||||
* Is the screen presented as a modal?
|
||||
*/
|
||||
isModalPresentation?: boolean,
|
||||
|
||||
/**
|
||||
* Insets for the SafeAreaView.
|
||||
*/
|
||||
@@ -59,7 +54,6 @@ const JitsiScreen = ({
|
||||
footerComponent,
|
||||
hasTabNavigator = false,
|
||||
hasBottomTextInput = false,
|
||||
isModalPresentation = true,
|
||||
safeAreaInsets = [ 'left', 'right' ],
|
||||
style
|
||||
}: Props) => (
|
||||
@@ -69,7 +63,6 @@ const JitsiScreen = ({
|
||||
contentContainerStyle = { contentContainerStyle }
|
||||
hasBottomTextInput = { hasBottomTextInput }
|
||||
hasTabNavigator = { hasTabNavigator }
|
||||
isModalPresentation = { isModalPresentation }
|
||||
style = { style }>
|
||||
<SafeAreaView
|
||||
edges = { safeAreaInsets }
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Text, TouchableOpacity } from 'react-native';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* React Elements to display within the component.
|
||||
*/
|
||||
children: React$Node | Object,
|
||||
|
||||
/**
|
||||
* Handler called when the user presses the button.
|
||||
*/
|
||||
onValueChange: Function,
|
||||
|
||||
/**
|
||||
* The component's external style.
|
||||
*/
|
||||
style: Object
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a button.
|
||||
*/
|
||||
export default class ButtonImpl extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}, renders the button.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress = { this.props.onValueChange } >
|
||||
<Text style = { this.props.style }>
|
||||
{ this.props.children }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
99
react/features/base/react/components/native/Button.tsx
Normal file
99
react/features/base/react/components/native/Button.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Button as NativePaperButton,
|
||||
Text,
|
||||
TouchableRipple
|
||||
} from 'react-native-paper';
|
||||
|
||||
import BaseTheme from '../../../ui/components/BaseTheme.native';
|
||||
import styles from './styles';
|
||||
import { BUTTON_MODES, BUTTON_TYPES } from '../../constants';
|
||||
import { ButtonProps } from '../../types';
|
||||
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
accessibilityLabel,
|
||||
color: buttonColor,
|
||||
disabled,
|
||||
icon,
|
||||
label,
|
||||
labelStyle,
|
||||
onPress,
|
||||
style,
|
||||
type
|
||||
}: ButtonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { CONTAINED } = BUTTON_MODES;
|
||||
const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
|
||||
let buttonLabelStyles;
|
||||
let buttonStyles;
|
||||
let color;
|
||||
let mode;
|
||||
|
||||
if (type === PRIMARY) {
|
||||
buttonLabelStyles = styles.buttonLabelPrimary;
|
||||
color = BaseTheme.palette.action01;
|
||||
mode = CONTAINED
|
||||
} else if (type === SECONDARY) {
|
||||
buttonLabelStyles = styles.buttonLabelSecondary;
|
||||
color = BaseTheme.palette.action02;
|
||||
mode = CONTAINED
|
||||
} else if (type === DESTRUCTIVE) {
|
||||
color = BaseTheme.palette.actionDanger;
|
||||
buttonLabelStyles = styles.buttonLabelDestructive;
|
||||
mode = CONTAINED
|
||||
} else {
|
||||
color = buttonColor;
|
||||
buttonLabelStyles = styles.buttonLabel;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
buttonLabelStyles = styles.buttonLabelDisabled;
|
||||
buttonStyles = styles.buttonDisabled;
|
||||
} else {
|
||||
buttonStyles = styles.button;
|
||||
}
|
||||
|
||||
if ( type === TERTIARY) {
|
||||
return (
|
||||
<TouchableRipple
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = 'transparent'
|
||||
style = { [
|
||||
buttonStyles,
|
||||
style
|
||||
] }>
|
||||
<Text
|
||||
style = { [
|
||||
buttonLabelStyles,
|
||||
labelStyle
|
||||
] }>{ t(label) }</Text>
|
||||
</TouchableRipple>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NativePaperButton
|
||||
accessibilityLabel = { t(accessibilityLabel) }
|
||||
children = { t(label) }
|
||||
color = { color }
|
||||
disabled = { disabled }
|
||||
icon = { icon }
|
||||
labelStyle = { [
|
||||
buttonLabelStyles,
|
||||
labelStyle
|
||||
] }
|
||||
mode = { mode }
|
||||
onPress = { onPress }
|
||||
style = { [
|
||||
buttonStyles,
|
||||
style
|
||||
] } />
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
64
react/features/base/react/components/native/IconButton.tsx
Normal file
64
react/features/base/react/components/native/IconButton.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { TouchableRipple } from 'react-native-paper';
|
||||
|
||||
import { Icon } from '../../../icons';
|
||||
import BaseTheme from '../../../ui/components/BaseTheme.native';
|
||||
import styles from './styles';
|
||||
import { BUTTON_TYPES } from '../../constants';
|
||||
import { IconButtonProps } from '../../types';
|
||||
|
||||
|
||||
const IconButton: React.FC<IconButtonProps> = ({
|
||||
accessibilityLabel,
|
||||
color: iconColor,
|
||||
disabled,
|
||||
onPress,
|
||||
size,
|
||||
src,
|
||||
style,
|
||||
tapColor,
|
||||
type
|
||||
}: IconButtonProps) => {
|
||||
const { PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
|
||||
|
||||
let color;
|
||||
let rippleColor;
|
||||
let iconButtonContainerStyles;
|
||||
|
||||
if (type === PRIMARY) {
|
||||
color = BaseTheme.palette.icon01;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerPrimary;
|
||||
rippleColor = BaseTheme.palette.action01;
|
||||
} else if (type === SECONDARY) {
|
||||
color = BaseTheme.palette.icon02;
|
||||
iconButtonContainerStyles = styles.iconButtonContainerSecondary;
|
||||
rippleColor = BaseTheme.palette.action02;
|
||||
} else if ( type === TERTIARY) {
|
||||
color = BaseTheme.palette.icon01;
|
||||
iconButtonContainerStyles = styles.iconButtonContainer;
|
||||
rippleColor = BaseTheme.palette.action03;
|
||||
} else {
|
||||
color = iconColor;
|
||||
rippleColor = tapColor;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
accessibilityLabel = { accessibilityLabel }
|
||||
disabled = { disabled }
|
||||
onPress = { onPress }
|
||||
rippleColor = { rippleColor }
|
||||
style = { [
|
||||
iconButtonContainerStyles,
|
||||
style
|
||||
] }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 20 || size }
|
||||
src = { src } />
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconButton;
|
||||
@@ -1,296 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { SafeAreaView, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { Icon } from '../../../icons';
|
||||
import { connect } from '../../../redux';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link PagedList}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The zero-based index of the page that should be rendered (selected) by
|
||||
* default.
|
||||
*/
|
||||
defaultPage: number,
|
||||
|
||||
/**
|
||||
* Indicates if the list is disabled or not.
|
||||
*/
|
||||
disabled: boolean,
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Callback to execute on page change.
|
||||
*/
|
||||
onSelectPage: ?Function,
|
||||
|
||||
/**
|
||||
* The pages of the PagedList component to be rendered.
|
||||
*
|
||||
* NOTE 1: An element's {@code component} may be {@code undefined} and then
|
||||
* it won't need to be rendered.
|
||||
*
|
||||
* NOTE 2: There must be at least one page available and enabled.
|
||||
*/
|
||||
pages: Array<{
|
||||
component: ?Object,
|
||||
icon: string | number,
|
||||
title: string
|
||||
}>
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link PagedList}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The currently selected page.
|
||||
*/
|
||||
pageIndex: number
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that renders a paged list.
|
||||
*
|
||||
* @augments PagedList
|
||||
*/
|
||||
class PagedList extends Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code PagedList} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pageIndex: this._validatePageIndex(props.defaultPage)
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._maybeRefreshSelectedPage
|
||||
= this._maybeRefreshSelectedPage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const pages = this.props.pages.filter(({ component }) => component);
|
||||
let children;
|
||||
|
||||
if (pages.length > 1) {
|
||||
children = this._renderPagedList(disabled);
|
||||
} else {
|
||||
children = React.createElement(
|
||||
|
||||
// $FlowExpectedError
|
||||
/* type */ pages[0].component,
|
||||
/* props */ {
|
||||
disabled,
|
||||
style: styles.pagedList
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { [
|
||||
styles.pagedListContainer,
|
||||
disabled ? styles.pagedListContainerDisabled : null
|
||||
] }>
|
||||
{
|
||||
|
||||
// $FlowExpectedError
|
||||
children
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the style of an indicator.
|
||||
*
|
||||
* @param {number} indicatorIndex - The index of the indicator.
|
||||
* @private
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getIndicatorStyle(indicatorIndex) {
|
||||
if (this.state.pageIndex === indicatorIndex) {
|
||||
return styles.pageIndicatorActive;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_maybeRefreshSelectedPage: ?boolean => void;
|
||||
|
||||
/**
|
||||
* Components that this PagedList displays may have a refresh function to
|
||||
* refresh its content when displayed (or based on custom logic). This
|
||||
* function invokes this logic if it's present.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} isInteractive - If true this refresh was caused by
|
||||
* direct user interaction, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeRefreshSelectedPage(isInteractive: boolean = true) {
|
||||
const selectedPage = this.props.pages[this.state.pageIndex];
|
||||
let component;
|
||||
|
||||
if (selectedPage && (component = selectedPage.component)) {
|
||||
// react-i18n / react-redux wrap components and thus we cannot access
|
||||
// the wrapped component's static methods directly.
|
||||
const component_ = component.WrappedComponent || component;
|
||||
const { refresh } = component_;
|
||||
|
||||
refresh.call(component, this.props.dispatch, isInteractive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected page.
|
||||
*
|
||||
* @param {number} pageIndex - The index of the selected page.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSelectPage(pageIndex: number) {
|
||||
return () => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
pageIndex = this._validatePageIndex(pageIndex);
|
||||
|
||||
const { onSelectPage } = this.props;
|
||||
|
||||
onSelectPage && onSelectPage(pageIndex);
|
||||
|
||||
this.setState({ pageIndex }, this._maybeRefreshSelectedPage);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single page of the page list.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page to render.
|
||||
* @param {boolean} disabled - Renders the page disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPage(page, disabled) {
|
||||
if (!page.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.pageContainer }>
|
||||
{
|
||||
React.createElement(
|
||||
page.component,
|
||||
{
|
||||
disabled
|
||||
})
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the paged list if multiple pages are to be rendered.
|
||||
*
|
||||
* @param {boolean} disabled - True if the rendered lists should be
|
||||
* disabled.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderPagedList(disabled) {
|
||||
const { pages } = this.props;
|
||||
const { pageIndex } = this.state;
|
||||
|
||||
return (
|
||||
<View style = { styles.pagedListContainer }>
|
||||
{
|
||||
this._renderPage(pages[pageIndex], disabled)
|
||||
}
|
||||
<SafeAreaView style = { styles.pageIndicatorContainer }>
|
||||
{
|
||||
pages.map((page, index) => this._renderPageIndicator(
|
||||
page, index, disabled
|
||||
))
|
||||
}
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a page indicator (icon) for the page.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} page - The page the indicator is rendered for.
|
||||
* @param {number} index - The index of the page the indicator is rendered
|
||||
* for.
|
||||
* @param {boolean} disabled - Renders the indicator disabled.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
_renderPageIndicator(page, index, disabled) {
|
||||
if (!page.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
disabled = { disabled }
|
||||
key = { index }
|
||||
onPress = { this._onSelectPage(index) }
|
||||
style = { styles.pageIndicator } >
|
||||
<View style = { styles.pageIndicatorContent }>
|
||||
<Icon
|
||||
src = { page.icon }
|
||||
style = { [
|
||||
styles.pageIndicatorIcon,
|
||||
this._getIndicatorStyle(index)
|
||||
] } />
|
||||
<Text
|
||||
style = { [
|
||||
styles.pageIndicatorText,
|
||||
this._getIndicatorStyle(index)
|
||||
] }>
|
||||
{ page.title }
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the requested page index and returns a safe value.
|
||||
*
|
||||
* @private
|
||||
* @param {number} pageIndex - The requested page index.
|
||||
* @returns {number}
|
||||
*/
|
||||
_validatePageIndex(pageIndex) {
|
||||
// pageIndex may point to a non-existing page if some of the pages are
|
||||
// disabled (their component property is undefined).
|
||||
const maxPageIndex
|
||||
= this.props.pages.filter(({ component }) => component).length - 1;
|
||||
|
||||
return Math.max(0, Math.min(maxPageIndex, pageIndex));
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(PagedList);
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
export { default as AvatarListItem } from './AvatarListItem';
|
||||
export { default as BaseIndicator } from './BaseIndicator';
|
||||
export { default as Button } from './Button';
|
||||
export { default as Container } from './Container';
|
||||
export { default as Image } from './Image';
|
||||
export { default as Link } from './Link';
|
||||
@@ -15,7 +14,6 @@ export { default as NavigateSectionListItem }
|
||||
from './NavigateSectionListItem';
|
||||
export { default as NavigateSectionListSectionHeader }
|
||||
from './NavigateSectionListSectionHeader';
|
||||
export { default as PagedList } from './PagedList';
|
||||
export { default as Pressable } from './Pressable';
|
||||
export { default as SectionList } from './SectionList';
|
||||
export { default as SlidingView } from './SlidingView';
|
||||
|
||||
@@ -5,93 +5,12 @@ import BaseTheme from '../../../ui/components/BaseTheme.native';
|
||||
|
||||
const OVERLAY_FONT_COLOR = 'rgba(255, 255, 255, 0.6)';
|
||||
const SECONDARY_ACTION_BUTTON_SIZE = 30;
|
||||
const BUTTON_HEIGHT = BaseTheme.spacing[7];
|
||||
const BUTTON_WIDTH = BaseTheme.spacing[7];
|
||||
|
||||
export const AVATAR_SIZE = 65;
|
||||
export const UNDERLAY_COLOR = 'rgba(255, 255, 255, 0.2)';
|
||||
|
||||
/**
|
||||
* Style classes of the PagedList-based components.
|
||||
*/
|
||||
const PAGED_LIST_STYLES = {
|
||||
|
||||
/**
|
||||
* Outermost container of a page in {@code PagedList}.
|
||||
*/
|
||||
pageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style of the page indicator (Android).
|
||||
*/
|
||||
pageIndicator: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
padding: BoxModel.padding / 2
|
||||
},
|
||||
|
||||
/**
|
||||
* Additional style for the active indicator icon (Android).
|
||||
*/
|
||||
pageIndicatorActive: {
|
||||
color: ColorPalette.white
|
||||
},
|
||||
|
||||
/**
|
||||
* Container for the page indicators (Android).
|
||||
*/
|
||||
pageIndicatorContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.blue,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around'
|
||||
},
|
||||
|
||||
pageIndicatorContent: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* Icon of the page indicator (Android).
|
||||
*/
|
||||
pageIndicatorIcon: {
|
||||
color: ColorPalette.blueHighlight,
|
||||
fontSize: 24
|
||||
},
|
||||
|
||||
/**
|
||||
* Label of the page indicator (Android).
|
||||
*/
|
||||
pageIndicatorText: {
|
||||
color: ColorPalette.blueHighlight
|
||||
},
|
||||
|
||||
/**
|
||||
* Top level style of the paged list.
|
||||
*/
|
||||
pagedList: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* The paged list container View.
|
||||
*/
|
||||
pagedListContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* Disabled style for the container.
|
||||
*/
|
||||
pagedListContainerDisabled: {
|
||||
opacity: 0.2
|
||||
}
|
||||
};
|
||||
|
||||
const SECTION_LIST_STYLES = {
|
||||
/**
|
||||
* The style of the avatar container that makes the avatar rounded.
|
||||
@@ -217,11 +136,82 @@ export const BASE_INDICATOR = {
|
||||
justifyContent: 'center'
|
||||
};
|
||||
|
||||
const button = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BUTTON_HEIGHT
|
||||
};
|
||||
|
||||
const buttonLabel = {
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
padding: 6,
|
||||
textTransform: 'capitalize'
|
||||
};
|
||||
|
||||
const iconButtonContainer = {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BUTTON_HEIGHT,
|
||||
width: BUTTON_WIDTH
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the generic React {@code Component}s implemented by the feature
|
||||
* base/react.
|
||||
*/
|
||||
export default {
|
||||
...PAGED_LIST_STYLES,
|
||||
...SECTION_LIST_STYLES
|
||||
...SECTION_LIST_STYLES,
|
||||
|
||||
button: {
|
||||
...button
|
||||
},
|
||||
|
||||
buttonLabel: {
|
||||
...buttonLabel
|
||||
},
|
||||
|
||||
buttonLabelDisabled: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text03
|
||||
},
|
||||
|
||||
buttonDisabled: {
|
||||
...button,
|
||||
backgroundColor: BaseTheme.palette.actionDisabled
|
||||
},
|
||||
|
||||
buttonLabelPrimary: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
buttonLabelSecondary: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
buttonLabelDestructive: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
buttonLabelTertiary: {
|
||||
...buttonLabel,
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
iconButtonContainer: {
|
||||
...iconButtonContainer
|
||||
},
|
||||
|
||||
iconButtonContainerPrimary: {
|
||||
...iconButtonContainer,
|
||||
backgroundColor: BaseTheme.palette.action01
|
||||
},
|
||||
|
||||
iconButtonContainerSecondary: {
|
||||
...iconButtonContainer,
|
||||
backgroundColor: BaseTheme.palette.action02
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,3 +5,20 @@
|
||||
* everything, such as modal-type of components, or dialogs.
|
||||
*/
|
||||
export const OVERLAY_Z_INDEX = 1000;
|
||||
|
||||
/**
|
||||
* The types of the buttons.
|
||||
*/
|
||||
export const BUTTON_TYPES = {
|
||||
PRIMARY: 'primary',
|
||||
SECONDARY: 'secondary',
|
||||
TERTIARY: 'tertiary',
|
||||
DESTRUCTIVE: 'destructive'
|
||||
};
|
||||
|
||||
/**
|
||||
* The modes of the buttons.
|
||||
*/
|
||||
export const BUTTON_MODES = {
|
||||
CONTAINED: 'contained'
|
||||
};
|
||||
|
||||
23
react/features/base/react/types.ts
Normal file
23
react/features/base/react/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface ButtonProps {
|
||||
accessibilityLabel?: string;
|
||||
color?: string;
|
||||
disabled?: boolean;
|
||||
icon?: JSX.Element;
|
||||
label?: string;
|
||||
labelStyle?: Object|undefined;
|
||||
onPress?: Function;
|
||||
style?: Object|undefined;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface IconButtonProps {
|
||||
accessibilityLabel?: string;
|
||||
color?: string;
|
||||
disabled?: boolean;
|
||||
onPress?: Function;
|
||||
size?: number|string;
|
||||
src?: Function;
|
||||
style?: Object|undefined;
|
||||
tapColor?: string;
|
||||
type?: string;
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
/* @flow */
|
||||
|
||||
import { combineReducers } from 'redux';
|
||||
import { Action, combineReducers } from 'redux';
|
||||
import type { Reducer } from 'redux';
|
||||
|
||||
/**
|
||||
* The type of the dictionary/map which associates a reducer (function) with the
|
||||
* name of he Redux state property managed by the reducer.
|
||||
*/
|
||||
declare type NameReducerMap<S, A> = { [name: string]: Reducer<S, A> };
|
||||
declare type NameReducerMap<S, A> = { [name: string]: Reducer<S, Action<any>> };
|
||||
|
||||
/**
|
||||
* A registry for Redux reducers, allowing features to register themselves
|
||||
* without needing to create additional inter-feature dependencies.
|
||||
*/
|
||||
class ReducerRegistry {
|
||||
_elements: NameReducerMap<*, *>;
|
||||
_elements: NameReducerMap<any, any>;
|
||||
|
||||
/**
|
||||
* Creates a ReducerRegistry instance.
|
||||
@@ -37,7 +35,7 @@ class ReducerRegistry {
|
||||
* included (such as reducers from third-party modules).
|
||||
* @returns {Function}
|
||||
*/
|
||||
combineReducers(additional: NameReducerMap<*, *> = {}) {
|
||||
combineReducers(additional: NameReducerMap<any, any> = {}) {
|
||||
// $FlowExpectedError
|
||||
return combineReducers({
|
||||
...this._elements,
|
||||
@@ -55,7 +53,7 @@ class ReducerRegistry {
|
||||
* @param {Reducer} reducer - A Redux reducer.
|
||||
* @returns {void}
|
||||
*/
|
||||
register(name: string, reducer: Reducer<*, *>) {
|
||||
register(name: string, reducer: Reducer<any, any>) {
|
||||
this._elements[name] = reducer;
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,8 @@ export const colorMap = {
|
||||
// Status bar
|
||||
status01Bar: 'primary11',
|
||||
|
||||
actionDisabled: 'surface09',
|
||||
|
||||
// Hover state for primary buttons
|
||||
action01Hover: 'primary06',
|
||||
|
||||
@@ -102,7 +104,7 @@ export const colorMap = {
|
||||
action01Disabled: 'primary02',
|
||||
|
||||
// Secondary buttons
|
||||
action02: 'surface04',
|
||||
action02: 'surface10',
|
||||
|
||||
// Hover state for secondary buttons
|
||||
action02Hover: 'surface05',
|
||||
@@ -156,7 +158,7 @@ export const colorMap = {
|
||||
text01: 'surface11',
|
||||
|
||||
// Secondary text with medium contrast
|
||||
text02: 'surface09',
|
||||
text02: 'surface01',
|
||||
|
||||
// Tertiary text with low contrast – placeholders, disabled actions, label for disabled buttons
|
||||
text03: 'surface07',
|
||||
@@ -180,7 +182,7 @@ export const colorMap = {
|
||||
icon01: 'surface11',
|
||||
|
||||
// Secondary color for input fields
|
||||
icon02: 'surface09',
|
||||
icon02: 'surface01',
|
||||
|
||||
// Tertiary color for disabled actions
|
||||
icon03: 'surface07',
|
||||
|
||||
@@ -35,11 +35,3 @@ export const SMALL_WIDTH_THRESHOLD = 580;
|
||||
* Lobby message type.
|
||||
*/
|
||||
export const LOBBY_CHAT_MESSAGE = 'LOBBY_CHAT_MESSAGE';
|
||||
|
||||
/**
|
||||
* The modes of the buttons of the chat and polls tabs.
|
||||
*/
|
||||
export const BUTTON_MODES = {
|
||||
CONTAINED: 'contained',
|
||||
TEXT: 'text'
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ export const CONFERENCE_INFO = {
|
||||
'e2ee',
|
||||
'transcribing',
|
||||
'video-quality',
|
||||
'insecure-room'
|
||||
'insecure-room',
|
||||
'top-panel-toggle'
|
||||
]
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
@@ -192,7 +193,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
*/
|
||||
componentDidMount() {
|
||||
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,7 +233,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
|
||||
clearTimeout(this._expandedLabelTimeout.current);
|
||||
setPictureInPictureEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,4 +560,21 @@ function _mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default withSafeAreaInsets(connect(_mapStateToProps)(Conference));
|
||||
export default withSafeAreaInsets(connect(_mapStateToProps)(props => {
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
setPictureInPictureEnabled(true);
|
||||
} else {
|
||||
setPictureInPictureEnabled(false);
|
||||
}
|
||||
|
||||
// We also need to disable PiP when we are back on the WelcomePage
|
||||
return () => setPictureInPictureEnabled(false);
|
||||
}, [ isFocused ]);
|
||||
|
||||
return (
|
||||
<Conference { ...props } />
|
||||
);
|
||||
}));
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import { getFeatureFlag, INVITE_ENABLED } from '../../../base/flags';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconAddPeople } from '../../../base/icons';
|
||||
import { getParticipantCountWithFake } from '../../../base/participants';
|
||||
import Button from '../../../base/react/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/react/constants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { doInvitePeople } from '../../../invite/actions.native';
|
||||
@@ -57,6 +59,19 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
this._onPress = this._onPress.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the "add people" icon.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderAddPeopleIcon() {
|
||||
return (
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { IconAddPeople } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
@@ -80,17 +95,13 @@ class LonelyMeetingExperience extends PureComponent<Props> {
|
||||
{ t('lonelyMeetingExperience.youAreAlone') }
|
||||
</Text>
|
||||
{ !_isInviteFunctionsDiabled && !_isInBreakoutRoom && (
|
||||
<TouchableOpacity
|
||||
<Button
|
||||
accessibilityLabel = 'lonelyMeetingExperience.button'
|
||||
icon = { this._renderAddPeopleIcon }
|
||||
label = 'lonelyMeetingExperience.button'
|
||||
onPress = { this._onPress }
|
||||
style = { styles.lonelyButton }>
|
||||
<Icon
|
||||
size = { 24 }
|
||||
src = { IconAddPeople }
|
||||
style = { styles.lonelyButtonComponents } />
|
||||
<Text style = { styles.lonelyButtonComponents }>
|
||||
{ t('lonelyMeetingExperience.button') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
style = { styles.lonelyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
) }
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, View } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
@@ -12,20 +11,18 @@ import { setIsCarmode } from '../../../../video-layout/actions';
|
||||
import ConferenceTimer from '../../ConferenceTimer';
|
||||
import { isConnecting } from '../../functions';
|
||||
|
||||
import EndMeetingButton from './EndMeetingButton';
|
||||
import CarModeFooter from './CarModeFooter';
|
||||
import MicrophoneButton from './MicrophoneButton';
|
||||
import SoundDeviceButton from './SoundDeviceButton';
|
||||
import TitleBar from './TitleBar';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Implements the carmode tab.
|
||||
* Implements the carmode component.
|
||||
*
|
||||
* @returns { JSX.Element} - The carmode tab.
|
||||
* @returns { JSX.Element} - The carmode component.
|
||||
*/
|
||||
const CarmodeTab = (): JSX.Element => {
|
||||
const CarMode = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const connecting = useSelector(isConnecting);
|
||||
const isSharing = useSelector(isLocalVideoTrackDesktop);
|
||||
|
||||
@@ -42,7 +39,9 @@ const CarmodeTab = (): JSX.Element => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<JitsiScreen style = { styles.conference }>
|
||||
<JitsiScreen
|
||||
footerComponent = { CarModeFooter }
|
||||
style = { styles.conference }>
|
||||
{/*
|
||||
* The activity/loading indicator goes above everything, except
|
||||
* the toolbox/toolbars and the dialogs.
|
||||
@@ -66,17 +65,8 @@ const CarmodeTab = (): JSX.Element => {
|
||||
style = { styles.microphoneContainer }>
|
||||
<MicrophoneButton />
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.bottomContainer }>
|
||||
<Text style = { styles.videoStoppedLabel }>
|
||||
{t('carmode.labels.videoStopped')}
|
||||
</Text>
|
||||
<SoundDeviceButton />
|
||||
<EndMeetingButton />
|
||||
</View>
|
||||
</JitsiScreen>
|
||||
);
|
||||
};
|
||||
|
||||
export default withSafeAreaInsets(CarmodeTab);
|
||||
export default withSafeAreaInsets(CarMode);
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Text, View } from 'react-native';
|
||||
|
||||
import EndMeetingButton from './EndMeetingButton';
|
||||
import SoundDeviceButton from './SoundDeviceButton';
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Implements the car mode footer component.
|
||||
*
|
||||
* @returns { JSX.Element} - The car mode footer component.
|
||||
*/
|
||||
const CarModeFooter = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.bottomContainer }>
|
||||
<Text style = { styles.videoStoppedLabel }>
|
||||
{t('carmode.labels.videoStopped')}
|
||||
</Text>
|
||||
<SoundDeviceButton />
|
||||
<EndMeetingButton />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CarModeFooter;
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../../analytics';
|
||||
import { appNavigate } from '../../../../app/actions';
|
||||
import Button from '../../../../base/react/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../../base/react/constants';
|
||||
|
||||
import EndMeetingIcon from './EndMeetingIcon';
|
||||
import styles from './styles';
|
||||
@@ -15,7 +15,6 @@ import styles from './styles';
|
||||
* @returns {JSX.Element} - The end meeting button.
|
||||
*/
|
||||
const EndMeetingButton = () : JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
@@ -26,13 +25,12 @@ const EndMeetingButton = () : JSX.Element => {
|
||||
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { t('carmode.actions.leaveMeeting') }
|
||||
children = { t('carmode.actions.leaveMeeting') }
|
||||
accessibilityLabel = 'carmode.actions.leaveMeeting'
|
||||
icon = { EndMeetingIcon }
|
||||
labelStyle = { styles.endMeetingButtonLabel }
|
||||
mode = 'contained'
|
||||
label = 'carmode.actions.leaveMeeting'
|
||||
onPress = { onSelect }
|
||||
style = { styles.endMeetingButton } />
|
||||
style = { styles.endMeetingButton }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openSheet } from '../../../../base/dialog/actions';
|
||||
import Button from '../../../../base/react/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../../base/react/constants';
|
||||
import AudioRoutePickerDialog from '../../../../mobile/audio-mode/components/AudioRoutePickerDialog';
|
||||
|
||||
import AudioIcon from './AudioIcon';
|
||||
@@ -15,7 +15,6 @@ import styles from './styles';
|
||||
* @returns {JSX.Element} - The sound device button.
|
||||
*/
|
||||
const SelectSoundDevice = () : JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() =>
|
||||
@@ -24,13 +23,12 @@ const SelectSoundDevice = () : JSX.Element => {
|
||||
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { t('carmode.actions.selectSoundDevice') }
|
||||
children = { t('carmode.actions.selectSoundDevice') }
|
||||
accessibilityLabel = 'carmode.actions.selectSoundDevice'
|
||||
icon = { AudioIcon }
|
||||
labelStyle = { styles.soundDeviceButtonLabel }
|
||||
mode = 'contained'
|
||||
label = 'carmode.actions.selectSoundDevice'
|
||||
onPress = { onSelect }
|
||||
style = { styles.soundDeviceButton } />
|
||||
style = { styles.soundDeviceButton }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,29 +5,6 @@ import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
|
||||
*/
|
||||
const MICROPHONE_SIZE = 180;
|
||||
|
||||
/**
|
||||
* Base button style.
|
||||
*/
|
||||
const baseButton = {
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BaseTheme.spacing[7],
|
||||
marginTop: BaseTheme.spacing[3],
|
||||
marginLeft: BaseTheme.spacing[10],
|
||||
marginRight: BaseTheme.spacing[10],
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
width: 300
|
||||
};
|
||||
|
||||
/**
|
||||
* Base label style.
|
||||
*/
|
||||
const baseLabel = {
|
||||
display: 'flex',
|
||||
fontSize: 16,
|
||||
textTransform: 'capitalize'
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the safe area view that contains the title bar.
|
||||
*/
|
||||
@@ -47,7 +24,7 @@ export default {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
bottom: 0,
|
||||
bottom: BaseTheme.spacing[8],
|
||||
left: 0,
|
||||
right: 0,
|
||||
position: 'absolute'
|
||||
@@ -106,10 +83,8 @@ export default {
|
||||
},
|
||||
|
||||
roomTimer: {
|
||||
color: BaseTheme.palette.text01,
|
||||
...BaseTheme.typography.bodyShortBold,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6,
|
||||
color: BaseTheme.palette.text01,
|
||||
textAlign: 'center'
|
||||
},
|
||||
|
||||
@@ -129,25 +104,13 @@ export default {
|
||||
color: BaseTheme.palette.text02
|
||||
},
|
||||
|
||||
soundDeviceButtonLabel: {
|
||||
...baseLabel,
|
||||
color: BaseTheme.palette.text06
|
||||
},
|
||||
|
||||
soundDeviceButton: {
|
||||
...baseButton,
|
||||
backgroundColor: BaseTheme.palette.section01
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
width: 240
|
||||
},
|
||||
|
||||
endMeetingButton: {
|
||||
...baseButton,
|
||||
backgroundColor: BaseTheme.palette.actionDanger,
|
||||
marginBottom: 60
|
||||
},
|
||||
|
||||
endMeetingButtonLabel: {
|
||||
...baseLabel,
|
||||
color: BaseTheme.palette.text01
|
||||
width: 240
|
||||
},
|
||||
|
||||
headerLabels: {
|
||||
@@ -196,13 +159,14 @@ export default {
|
||||
},
|
||||
|
||||
titleBar: {
|
||||
alignSelf: 'center'
|
||||
alignSelf: 'center',
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
videoStoppedLabel: {
|
||||
...BaseTheme.typography.bodyShortRegularLarge,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginBottom: 32,
|
||||
...BaseTheme.typography.bodyShortRegularLarge
|
||||
marginBottom: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
connectionIndicatorIcon: {
|
||||
|
||||
@@ -41,14 +41,20 @@ export default {
|
||||
width: BaseTheme.spacing[6]
|
||||
},
|
||||
|
||||
headerNavigationIcon: {
|
||||
marginLeft: 12
|
||||
},
|
||||
|
||||
headerNavigationText: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginLeft: BaseTheme.spacing[3],
|
||||
fontSize: HEADER_ACTION_BUTTON_SIZE
|
||||
},
|
||||
|
||||
headerNavigationTextBold: {
|
||||
...BaseTheme.typography.labelButton,
|
||||
color: BaseTheme.palette.text01,
|
||||
marginRight: BaseTheme.spacing[3],
|
||||
fontSize: HEADER_ACTION_BUTTON_SIZE
|
||||
},
|
||||
|
||||
@@ -79,18 +85,8 @@ export default {
|
||||
},
|
||||
|
||||
lonelyButton: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.action01,
|
||||
borderRadius: 24,
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[6],
|
||||
justifyContent: 'space-around',
|
||||
paddingHorizontal: 12
|
||||
},
|
||||
|
||||
lonelyButtonComponents: {
|
||||
color: BaseTheme.palette.text01,
|
||||
marginHorizontal: 6
|
||||
borderRadius: BaseTheme.spacing[4],
|
||||
paddingHorizontal: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
lonelyMeetingContainer: {
|
||||
@@ -170,7 +166,7 @@ export default {
|
||||
},
|
||||
|
||||
roomTimerView: {
|
||||
backgroundColor: BaseTheme.palette.action02,
|
||||
backgroundColor: BaseTheme.palette.ui03,
|
||||
borderRadius: 3,
|
||||
justifyContent: 'center',
|
||||
minWidth: 50
|
||||
|
||||
@@ -11,7 +11,7 @@ import { translate } from '../../../base/i18n';
|
||||
import { connect as reactReduxConnect } from '../../../base/redux';
|
||||
import { setColorAlpha } from '../../../base/util';
|
||||
import { Chat } from '../../../chat';
|
||||
import { MainFilmstrip, StageFilmstrip } from '../../../filmstrip';
|
||||
import { MainFilmstrip, StageFilmstrip, ScreenshareFilmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { LobbyScreen } from '../../../lobby';
|
||||
@@ -239,6 +239,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
{
|
||||
_showPrejoin || _showLobby || (<>
|
||||
<StageFilmstrip />
|
||||
<ScreenshareFilmstrip />
|
||||
<MainFilmstrip />
|
||||
</>)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import InsecureRoomNameLabel from './InsecureRoomNameLabel';
|
||||
import ParticipantsCount from './ParticipantsCount';
|
||||
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
|
||||
import SubjectText from './SubjectText';
|
||||
import ToggleTopPanelLabel from './ToggleTopPanelLabel';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Subject}.
|
||||
@@ -82,6 +83,10 @@ const COMPONENTS = [
|
||||
{
|
||||
Component: InsecureRoomNameLabel,
|
||||
id: 'insecure-room'
|
||||
},
|
||||
{
|
||||
Component: ToggleTopPanelLabel,
|
||||
id: 'top-panel-toggle'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// @ts-ignore
|
||||
import { IconMenuDown } from '../../../base/icons';
|
||||
// @ts-ignore
|
||||
import { Label } from '../../../base/label';
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
// @ts-ignore
|
||||
import { setTopPanelVisible } from '../../../filmstrip/actions.web';
|
||||
|
||||
const ToggleTopPanelLabel = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const topPanelHidden = !useSelector((state: any) => state['features/filmstrip'].topPanelVisible);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(setTopPanelVisible(true));
|
||||
}, []);
|
||||
|
||||
return topPanelHidden && (<Tooltip
|
||||
content={t('toggleTopPanelLabel') }
|
||||
position = { 'bottom' }>
|
||||
<Label
|
||||
icon={IconMenuDown}
|
||||
onClick = { onClick }/>
|
||||
</Tooltip>);
|
||||
};
|
||||
|
||||
export default ToggleTopPanelLabel;
|
||||
@@ -22,7 +22,7 @@ export function fetchCustomBrandingData() {
|
||||
const { customizationReady } = state['features/dynamic-branding'];
|
||||
|
||||
if (!customizationReady) {
|
||||
const url = await getDynamicBrandingUrl();
|
||||
const url = await getDynamicBrandingUrl(state);
|
||||
|
||||
if (url) {
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
setDynamicBrandingFailed,
|
||||
setDynamicBrandingReady
|
||||
} from './actions.any';
|
||||
import { getDynamicBrandingUrl } from './functions.any';
|
||||
import logger from './logger';
|
||||
|
||||
|
||||
@@ -19,7 +20,7 @@ import logger from './logger';
|
||||
export function fetchCustomBrandingData() {
|
||||
return async function(dispatch: Function, getState: Function) {
|
||||
const state = getState();
|
||||
const { dynamicBrandingUrl } = state['features/base/config'];
|
||||
const dynamicBrandingUrl = await getDynamicBrandingUrl(state);
|
||||
|
||||
if (dynamicBrandingUrl) {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { loadConfig } from '../base/lib-jitsi-meet/functions';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the fqn part from a path, where fqn represents
|
||||
@@ -29,10 +30,13 @@ export function extractFqnFromPath(state?: Object) {
|
||||
/**
|
||||
* Returns the url used for fetching dynamic branding.
|
||||
*
|
||||
* @param {Object | Function} stateful - The redux store, state, or
|
||||
* {@code getState} function.
|
||||
* @returns {string}
|
||||
*/
|
||||
export async function getDynamicBrandingUrl() {
|
||||
const config = await loadConfig(window.location.href);
|
||||
export async function getDynamicBrandingUrl(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const config = state['features/base/config'];
|
||||
const { dynamicBrandingUrl } = config;
|
||||
|
||||
if (dynamicBrandingUrl) {
|
||||
@@ -40,7 +44,7 @@ export async function getDynamicBrandingUrl() {
|
||||
}
|
||||
|
||||
const { brandingDataUrl: baseUrl } = config;
|
||||
const fqn = extractFqnFromPath();
|
||||
const fqn = extractFqnFromPath(state);
|
||||
|
||||
if (baseUrl && fqn) {
|
||||
return `${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`;
|
||||
|
||||
@@ -3,13 +3,9 @@ import { View } from 'react-native';
|
||||
import { WebView } from 'react-native-webview';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconArrowBack } from '../../../base/icons';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import HeaderNavigationButton
|
||||
from '../../../mobile/navigation/components/HeaderNavigationButton';
|
||||
import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { getSharedDocumentUrl } from '../../functions';
|
||||
|
||||
import styles, { INDICATOR_COLOR } from './styles';
|
||||
@@ -50,25 +46,6 @@ class SharedDocument extends PureComponent<Props> {
|
||||
this._renderLoading = this._renderLoading.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}. Invoked
|
||||
* immediately after this component is mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<HeaderNavigationButton
|
||||
onPress = { goBack }
|
||||
src = { IconArrowBack } />
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -82,9 +59,11 @@ class SharedDocument extends PureComponent<Props> {
|
||||
addHeaderHeightValue = { true }
|
||||
style = { styles.sharedDocContainer }>
|
||||
<WebView
|
||||
hideKeyboardAccessoryView = { true }
|
||||
renderLoading = { this._renderLoading }
|
||||
source = {{ uri: _documentUrl }}
|
||||
startInLoadingState = { true } />
|
||||
startInLoadingState = { true }
|
||||
style = { styles.sharedDoc } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
// @flow
|
||||
|
||||
import { ColorPalette } from '../../../base/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
export const INDICATOR_COLOR = ColorPalette.lightGrey;
|
||||
|
||||
export const INDICATOR_COLOR = BaseTheme.palette.indicatorColor;
|
||||
|
||||
export default {
|
||||
indicatorWrapper: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: ColorPalette.white,
|
||||
backgroundColor: BaseTheme.palette.ui12,
|
||||
height: '100%',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
sharedDocContainer: {
|
||||
flex: 1
|
||||
backgroundColor: BaseTheme.palette.ui12,
|
||||
flex: 1,
|
||||
paddingRight: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
sharedDoc: {
|
||||
marginBottom: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
webView: {
|
||||
backgroundColor: 'rgb(242, 242, 242)'
|
||||
backgroundColor: BaseTheme.palette.ui12
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ StateListenerRegistry.register(
|
||||
const localParticipant = getLocalParticipant(store.getState());
|
||||
const { defaultLocalDisplayName } = store.getState()['features/base/config'];
|
||||
|
||||
// Initial setting of the display name occurs happens on app
|
||||
// Initial setting of the display name happens on app
|
||||
// initialization, before the local participant is ready. The initial
|
||||
// settings is not desired to be fired anyways, only changes.
|
||||
if (localParticipant) {
|
||||
@@ -39,6 +39,23 @@ StateListenerRegistry.register(
|
||||
}
|
||||
});
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/settings'].email,
|
||||
/* listener */ (email, store) => {
|
||||
const localParticipant = getLocalParticipant(store.getState());
|
||||
|
||||
// Initial setting of the email happens on app
|
||||
// initialization, before the local participant is ready. The initial
|
||||
// settings is not desired to be fired anyways, only changes.
|
||||
if (localParticipant) {
|
||||
const { id } = localParticipant;
|
||||
|
||||
APP.API.notifyEmailChanged(id, {
|
||||
email
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the on stage participant value.
|
||||
*/
|
||||
|
||||
@@ -92,6 +92,15 @@ export const SET_VOLUME = 'SET_VOLUME';
|
||||
*/
|
||||
export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS';
|
||||
|
||||
/**
|
||||
* The type of action which sets the height for the top panel filmstrip.
|
||||
* {
|
||||
* type: SET_FILMSTRIP_HEIGHT,
|
||||
* height: number
|
||||
* }
|
||||
*/
|
||||
export const SET_FILMSTRIP_HEIGHT = 'SET_FILMSTRIP_HEIGHT';
|
||||
|
||||
/**
|
||||
* The type of action which sets the width for the vertical filmstrip.
|
||||
* {
|
||||
@@ -101,6 +110,15 @@ export const SET_VISIBLE_REMOTE_PARTICIPANTS = 'SET_VISIBLE_REMOTE_PARTICIPANTS'
|
||||
*/
|
||||
export const SET_FILMSTRIP_WIDTH = 'SET_FILMSTRIP_WIDTH';
|
||||
|
||||
/**
|
||||
* The type of action which sets the height for the top panel filmstrip (user resized).
|
||||
* {
|
||||
* type: SET_USER_FILMSTRIP_HEIGHT,
|
||||
* height: number
|
||||
* }
|
||||
*/
|
||||
export const SET_USER_FILMSTRIP_HEIGHT = 'SET_USER_FILMSTRIP_HEIGHT';
|
||||
|
||||
/**
|
||||
* The type of action which sets the width for the vertical filmstrip (user resized).
|
||||
* {
|
||||
@@ -187,3 +205,19 @@ export const TOGGLE_PIN_STAGE_PARTICIPANT = 'TOGGLE_PIN_STAGE_PARTICIPANT';
|
||||
* }
|
||||
*/
|
||||
export const CLEAR_STAGE_PARTICIPANTS = 'CLEAR_STAGE_PARTICIPANTS';
|
||||
|
||||
/**
|
||||
* The type of Redux action which sets the dimensions of the screenshare tile.
|
||||
* {
|
||||
* type: SET_SCREENSHARING_TILE_DIMENSIONS
|
||||
* }
|
||||
*/
|
||||
export const SET_SCREENSHARING_TILE_DIMENSIONS = 'SET_SCREENSHARING_TILE_DIMENSIONS';
|
||||
|
||||
/**
|
||||
* The type of Redux action which sets the visibility of the top panel.
|
||||
* {
|
||||
* type: SET_TOP_PANEL_VISIBILITY
|
||||
* }
|
||||
*/
|
||||
export const SET_TOP_PANEL_VISIBILITY = 'SET_TOP_PANEL_VISIBILITY';
|
||||
|
||||
@@ -25,7 +25,11 @@ import {
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
TOGGLE_PIN_STAGE_PARTICIPANT,
|
||||
CLEAR_STAGE_PARTICIPANTS
|
||||
CLEAR_STAGE_PARTICIPANTS,
|
||||
SET_SCREENSHARING_TILE_DIMENSIONS,
|
||||
SET_USER_FILMSTRIP_HEIGHT,
|
||||
SET_FILMSTRIP_HEIGHT,
|
||||
SET_TOP_PANEL_VISIBILITY
|
||||
} from './actionTypes';
|
||||
import {
|
||||
HORIZONTAL_FILMSTRIP_MARGIN,
|
||||
@@ -33,11 +37,13 @@ import {
|
||||
SCROLL_SIZE,
|
||||
STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER,
|
||||
TILE_HORIZONTAL_MARGIN,
|
||||
TILE_MIN_HEIGHT_SMALL,
|
||||
TILE_VERTICAL_CONTAINER_HORIZONTAL_MARGIN,
|
||||
TILE_VERTICAL_MARGIN,
|
||||
TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
|
||||
TILE_VIEW_GRID_HORIZONTAL_MARGIN,
|
||||
TILE_VIEW_GRID_VERTICAL_MARGIN,
|
||||
TOP_FILMSTRIP_HEIGHT,
|
||||
VERTICAL_FILMSTRIP_VERTICAL_MARGIN
|
||||
} from './constants';
|
||||
import {
|
||||
@@ -48,6 +54,7 @@ import {
|
||||
getNumberOfPartipantsForTileView,
|
||||
getVerticalViewMaxWidth,
|
||||
isFilmstripResizable,
|
||||
isStageFilmstripTopPanel,
|
||||
showGridInVerticalView
|
||||
} from './functions';
|
||||
import { isStageFilmstripAvailable } from './functions.web';
|
||||
@@ -270,7 +277,7 @@ export function setStageFilmstripViewDimensions() {
|
||||
const {
|
||||
tileView = {}
|
||||
} = state['features/base/config'];
|
||||
const { visible } = state['features/filmstrip'];
|
||||
const { visible, topPanelHeight } = state['features/filmstrip'];
|
||||
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
|
||||
const { numberOfVisibleTiles = MAX_ACTIVE_PARTICIPANTS } = tileView;
|
||||
const numberOfParticipants = state['features/filmstrip'].activeParticipants.length;
|
||||
@@ -280,6 +287,7 @@ export function setStageFilmstripViewDimensions() {
|
||||
disableResponsiveTiles: false,
|
||||
disableTileEnlargement: false
|
||||
});
|
||||
const topPanel = isStageFilmstripTopPanel(state);
|
||||
|
||||
const {
|
||||
height,
|
||||
@@ -288,12 +296,13 @@ export function setStageFilmstripViewDimensions() {
|
||||
rows
|
||||
} = calculateResponsiveTileViewDimensions({
|
||||
clientWidth: availableWidth,
|
||||
clientHeight,
|
||||
clientHeight: topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : clientHeight,
|
||||
disableTileEnlargement: false,
|
||||
maxColumns,
|
||||
noHorizontalContainerMargin: verticalWidth > 0,
|
||||
numberOfParticipants,
|
||||
numberOfVisibleTiles
|
||||
numberOfVisibleTiles,
|
||||
minTileHeight: topPanel ? TILE_MIN_HEIGHT_SMALL : null
|
||||
});
|
||||
const thumbnailsTotalHeight = rows * (TILE_VERTICAL_MARGIN + height);
|
||||
const hasScroll = clientHeight < thumbnailsTotalHeight;
|
||||
@@ -368,6 +377,22 @@ export function setVolume(participantId: string, volume: number) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the top filmstrip's height.
|
||||
*
|
||||
* @param {number} height - The new height of the filmstrip.
|
||||
* @returns {{
|
||||
* type: SET_FILMSTRIP_HEIGHT,
|
||||
* height: number
|
||||
* }}
|
||||
*/
|
||||
export function setFilmstripHeight(height: number) {
|
||||
return {
|
||||
type: SET_FILMSTRIP_HEIGHT,
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filmstrip's width.
|
||||
*
|
||||
@@ -384,6 +409,22 @@ export function setFilmstripWidth(width: number) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filmstrip's height and the user preferred height.
|
||||
*
|
||||
* @param {number} height - The new height of the filmstrip.
|
||||
* @returns {{
|
||||
* type: SET_USER_FILMSTRIP_WIDTH,
|
||||
* height: number
|
||||
* }}
|
||||
*/
|
||||
export function setUserFilmstripHeight(height: number) {
|
||||
return {
|
||||
type: SET_USER_FILMSTRIP_HEIGHT,
|
||||
height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filmstrip's width and the user preferred width.
|
||||
*
|
||||
@@ -490,3 +531,45 @@ export function clearStageParticipants() {
|
||||
type: CLEAR_STAGE_PARTICIPANTS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the screensharing tile dimensions.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setScreensharingTileDimensions() {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
const { visible, topPanelHeight, topPanelVisible } = state['features/filmstrip'];
|
||||
const verticalWidth = visible ? getVerticalViewMaxWidth(state) : 0;
|
||||
const availableWidth = clientWidth - verticalWidth;
|
||||
const topPanel = isStageFilmstripTopPanel(state) && topPanelVisible;
|
||||
const availableHeight = clientHeight - (topPanel ? topPanelHeight?.current || TOP_FILMSTRIP_HEIGHT : 0);
|
||||
|
||||
dispatch({
|
||||
type: SET_SCREENSHARING_TILE_DIMENSIONS,
|
||||
dimensions: {
|
||||
filmstripHeight: availableHeight,
|
||||
filmstripWidth: availableWidth,
|
||||
thumbnailSize: {
|
||||
width: availableWidth - TILE_HORIZONTAL_MARGIN,
|
||||
height: availableHeight - TILE_VERTICAL_MARGIN
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the top panel.
|
||||
*
|
||||
* @param {boolean} visible - Whether it should be visible or not.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setTopPanelVisible(visible) {
|
||||
return {
|
||||
type: SET_TOP_PANEL_VISIBILITY,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,20 +23,26 @@ import { isButtonEnabled, isToolboxVisible } from '../../../toolbox/functions.we
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import {
|
||||
setFilmstripVisible,
|
||||
setVisibleRemoteParticipants,
|
||||
setUserFilmstripHeight,
|
||||
setUserFilmstripWidth,
|
||||
setUserIsResizing
|
||||
setUserIsResizing,
|
||||
setTopPanelVisible,
|
||||
setVisibleRemoteParticipants
|
||||
} from '../../actions';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
DEFAULT_FILMSTRIP_WIDTH,
|
||||
FILMSTRIP_TYPE,
|
||||
MIN_STAGE_VIEW_HEIGHT,
|
||||
MIN_STAGE_VIEW_WIDTH,
|
||||
TILE_HORIZONTAL_MARGIN,
|
||||
TILE_VERTICAL_MARGIN
|
||||
TILE_VERTICAL_MARGIN,
|
||||
TOP_FILMSTRIP_HEIGHT
|
||||
} from '../../constants';
|
||||
import {
|
||||
getVerticalViewMaxWidth,
|
||||
shouldRemoteVideosBeVisible
|
||||
shouldRemoteVideosBeVisible,
|
||||
isStageFilmstripTopPanel
|
||||
} from '../../functions';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
@@ -112,11 +118,21 @@ type Props = {
|
||||
*/
|
||||
_localScreenShare: Object,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip videos should currently be displayed.
|
||||
*/
|
||||
_mainFilmstripVisible: boolean,
|
||||
|
||||
/**
|
||||
* The maximum width of the vertical filmstrip.
|
||||
*/
|
||||
_maxFilmstripWidth: number,
|
||||
|
||||
/**
|
||||
* The maximum height of the top panel.
|
||||
*/
|
||||
_maxTopPanelHeight: number,
|
||||
|
||||
/**
|
||||
* The participants in the call.
|
||||
*/
|
||||
@@ -137,11 +153,6 @@ type Props = {
|
||||
*/
|
||||
_rows: number,
|
||||
|
||||
/**
|
||||
* Whether or not this is the stage filmstrip.
|
||||
*/
|
||||
_stageFilmstrip: boolean,
|
||||
|
||||
/**
|
||||
* The height of the thumbnail.
|
||||
*/
|
||||
@@ -157,6 +168,26 @@ type Props = {
|
||||
*/
|
||||
_thumbnailsReordered: Boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip is top panel.
|
||||
*/
|
||||
_topPanelFilmstrip: boolean,
|
||||
|
||||
/**
|
||||
* The max height of the top panel.
|
||||
*/
|
||||
_topPanelMaxHeight: number,
|
||||
|
||||
/**
|
||||
* The height of the top panel (user resized).
|
||||
*/
|
||||
_topPanelHeight: ?number,
|
||||
|
||||
/**
|
||||
* Whether or not the top panel is visible.
|
||||
*/
|
||||
_topPanelVisible: boolean,
|
||||
|
||||
/**
|
||||
* The width of the vertical filmstrip (user resized).
|
||||
*/
|
||||
@@ -182,11 +213,6 @@ type Props = {
|
||||
*/
|
||||
_videosClassName: string,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip videos should currently be displayed.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* An object containing the CSS classes.
|
||||
*/
|
||||
@@ -197,6 +223,11 @@ type Props = {
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* The type of filmstrip to be displayed.
|
||||
*/
|
||||
filmstripType: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
@@ -218,7 +249,12 @@ type State = {
|
||||
/**
|
||||
* Initial filmstrip width on drag handle mouse down.
|
||||
*/
|
||||
dragFilmstripWidth: ?number
|
||||
dragFilmstripWidth: ?number,
|
||||
|
||||
/**
|
||||
* Initial top panel height on drag handle mouse down.
|
||||
*/
|
||||
dragFilmstripHeight: ?number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,25 +343,45 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
_currentLayout,
|
||||
_disableSelfView,
|
||||
_localScreenShare,
|
||||
_mainFilmstripVisible,
|
||||
_resizableFilmstrip,
|
||||
_stageFilmstrip,
|
||||
_visible,
|
||||
_topPanelFilmstrip,
|
||||
_topPanelMaxHeight,
|
||||
_topPanelVisible,
|
||||
_verticalViewBackground,
|
||||
_verticalViewGrid,
|
||||
_verticalViewMaxWidth,
|
||||
classes
|
||||
classes,
|
||||
filmstripType
|
||||
} = this.props;
|
||||
const { isMouseDown } = this.state;
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
|
||||
if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && _stageFilmstrip) {
|
||||
if (_visible) {
|
||||
if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.STAGE) {
|
||||
if (_topPanelFilmstrip) {
|
||||
filmstripStyle.maxHeight = `${_topPanelMaxHeight}px`;
|
||||
filmstripStyle.zIndex = 1;
|
||||
|
||||
if (!_topPanelVisible) {
|
||||
filmstripStyle.top = `-${_topPanelMaxHeight}px`;
|
||||
}
|
||||
}
|
||||
if (_mainFilmstripVisible) {
|
||||
filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
|
||||
}
|
||||
} else if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
|
||||
if (_mainFilmstripVisible) {
|
||||
filmstripStyle.maxWidth = `calc(100% - ${_verticalViewMaxWidth}px)`;
|
||||
}
|
||||
if (_topPanelVisible) {
|
||||
filmstripStyle.maxHeight = `calc(100% - ${_topPanelMaxHeight}px)`;
|
||||
}
|
||||
filmstripStyle.bottom = 0;
|
||||
filmstripStyle.top = 'auto';
|
||||
} else if (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
|| (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && !_stageFilmstrip)) {
|
||||
|| (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.MAIN)) {
|
||||
filmstripStyle.maxWidth = _verticalViewMaxWidth;
|
||||
if (!_visible) {
|
||||
if (!_mainFilmstripVisible) {
|
||||
filmstripStyle.right = `-${filmstripStyle.maxWidth}px`;
|
||||
}
|
||||
}
|
||||
@@ -333,14 +389,17 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
let toolbar = null;
|
||||
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW && !_stageFilmstrip) {
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
|
||||
toolbar = this._renderToggleButton();
|
||||
}
|
||||
|
||||
const filmstrip = (<>
|
||||
<div
|
||||
className = { clsx(this.props._videosClassName,
|
||||
!tileViewActive && !_stageFilmstrip && !_resizableFilmstrip && 'filmstrip-hover',
|
||||
!tileViewActive && (filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))
|
||||
&& !_resizableFilmstrip && 'filmstrip-hover',
|
||||
_verticalViewGrid && 'vertical-view-grid') }
|
||||
id = 'remoteVideos'>
|
||||
{!_disableSelfView && !_verticalViewGrid && (
|
||||
@@ -348,8 +407,10 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
className = 'filmstrip__videos'
|
||||
id = 'filmstripLocalVideo'>
|
||||
{
|
||||
!tileViewActive && !_stageFilmstrip && <div id = 'filmstripLocalVideoThumbnail'>
|
||||
!tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
&& <div id = 'filmstripLocalVideoThumbnail'>
|
||||
<Thumbnail
|
||||
filmstripType = { FILMSTRIP_TYPE.MAIN }
|
||||
key = 'local' />
|
||||
</div>
|
||||
}
|
||||
@@ -361,10 +422,9 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
id = 'filmstripLocalScreenShare'>
|
||||
<div id = 'filmstripLocalScreenShareThumbnail'>
|
||||
{
|
||||
!tileViewActive && !_stageFilmstrip && <Thumbnail
|
||||
!tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN && <Thumbnail
|
||||
key = 'localScreenShare'
|
||||
participantID = { _localScreenShare.id } />
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -385,11 +445,14 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
style = { filmstripStyle }>
|
||||
{ toolbar }
|
||||
{_resizableFilmstrip
|
||||
? <div className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer) }>
|
||||
? <div
|
||||
className = { clsx('resizable-filmstrip', classes.resizableFilmstripContainer,
|
||||
_topPanelFilmstrip && 'top-panel-filmstrip') }>
|
||||
<div
|
||||
className = { clsx('dragHandleContainer',
|
||||
classes.dragHandleContainer,
|
||||
isMouseDown && 'visible')
|
||||
isMouseDown && 'visible',
|
||||
_topPanelFilmstrip && 'top-panel')
|
||||
}
|
||||
onMouseDown = { this._onDragHandleMouseDown }>
|
||||
<div className = { clsx(classes.dragHandle, 'dragHandle') } />
|
||||
@@ -412,10 +475,13 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDragHandleMouseDown(e) {
|
||||
const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props;
|
||||
|
||||
this.setState({
|
||||
isMouseDown: true,
|
||||
mousePosition: e.clientX,
|
||||
dragFilmstripWidth: this.props._verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH
|
||||
mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX,
|
||||
dragFilmstripWidth: _verticalFilmstripWidth || DEFAULT_FILMSTRIP_WIDTH,
|
||||
dragFilmstripHeight: _topPanelHeight || TOP_FILMSTRIP_HEIGHT
|
||||
});
|
||||
this.props.dispatch(setUserIsResizing(true));
|
||||
}
|
||||
@@ -446,16 +512,36 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
*/
|
||||
_onFilmstripResize(e) {
|
||||
if (this.state.isMouseDown) {
|
||||
const { dispatch, _verticalFilmstripWidth, _maxFilmstripWidth } = this.props;
|
||||
const { dragFilmstripWidth, mousePosition } = this.state;
|
||||
const diff = mousePosition - e.clientX;
|
||||
const width = Math.max(
|
||||
Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth),
|
||||
DEFAULT_FILMSTRIP_WIDTH
|
||||
);
|
||||
const {
|
||||
dispatch,
|
||||
_verticalFilmstripWidth,
|
||||
_maxFilmstripWidth,
|
||||
_topPanelHeight,
|
||||
_maxTopPanelHeight,
|
||||
_topPanelFilmstrip
|
||||
} = this.props;
|
||||
const { dragFilmstripWidth, dragFilmstripHeight, mousePosition } = this.state;
|
||||
|
||||
if (width !== _verticalFilmstripWidth) {
|
||||
dispatch(setUserFilmstripWidth(width));
|
||||
if (_topPanelFilmstrip) {
|
||||
const diff = e.clientY - mousePosition;
|
||||
const height = Math.max(
|
||||
Math.min(dragFilmstripHeight + diff, _maxTopPanelHeight),
|
||||
TOP_FILMSTRIP_HEIGHT
|
||||
);
|
||||
|
||||
if (height !== _topPanelHeight) {
|
||||
dispatch(setUserFilmstripHeight(height));
|
||||
}
|
||||
} else {
|
||||
const diff = mousePosition - e.clientX;
|
||||
const width = Math.max(
|
||||
Math.min(dragFilmstripWidth + diff, _maxFilmstripWidth),
|
||||
DEFAULT_FILMSTRIP_WIDTH
|
||||
);
|
||||
|
||||
if (width !== _verticalFilmstripWidth) {
|
||||
dispatch(setUserFilmstripWidth(width));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,7 +581,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTabIn() {
|
||||
if (!this.props._isToolboxVisible && this.props._visible) {
|
||||
if (!this.props._isToolboxVisible && this.props._mainFilmstripVisible) {
|
||||
this.props.dispatch(showToolbox());
|
||||
}
|
||||
}
|
||||
@@ -605,10 +691,10 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
_remoteParticipantsLength,
|
||||
_resizableFilmstrip,
|
||||
_rows,
|
||||
_stageFilmstrip,
|
||||
_thumbnailHeight,
|
||||
_thumbnailWidth,
|
||||
_verticalViewGrid
|
||||
_verticalViewGrid,
|
||||
filmstripType
|
||||
} = this.props;
|
||||
|
||||
if (!_thumbnailWidth || isNaN(_thumbnailWidth) || !_thumbnailHeight
|
||||
@@ -617,7 +703,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || _stageFilmstrip) {
|
||||
if (_currentLayout === LAYOUTS.TILE_VIEW || _verticalViewGrid || filmstripType !== FILMSTRIP_TYPE.MAIN) {
|
||||
return (
|
||||
<FixedSizeGrid
|
||||
className = 'filmstrip__videos remote-videos'
|
||||
@@ -626,7 +712,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
height = { _filmstripHeight }
|
||||
initialScrollLeft = { 0 }
|
||||
initialScrollTop = { 0 }
|
||||
itemData = {{ stageFilmstrip: _stageFilmstrip }}
|
||||
itemData = {{ filmstripType }}
|
||||
itemKey = { this._gridItemKey }
|
||||
onItemsRendered = { this._onGridItemsRendered }
|
||||
overscanRowCount = { 1 }
|
||||
@@ -694,7 +780,11 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_doToggleFilmstrip() {
|
||||
this.props.dispatch(setFilmstripVisible(!this.props._visible));
|
||||
const { dispatch, _mainFilmstripVisible, _topPanelFilmstrip, _topPanelVisible } = this.props;
|
||||
|
||||
_topPanelFilmstrip
|
||||
? dispatch(setTopPanelVisible(!_topPanelVisible))
|
||||
: dispatch(setFilmstripVisible(!_mainFilmstripVisible));
|
||||
}
|
||||
|
||||
_onShortcutToggleFilmstrip: () => void;
|
||||
@@ -710,7 +800,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'toggle.filmstrip',
|
||||
{
|
||||
enable: this.props._visible
|
||||
enable: this.props._mainFilmstripVisible
|
||||
}));
|
||||
|
||||
this._doToggleFilmstrip();
|
||||
@@ -729,7 +819,7 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.filmstrip.button',
|
||||
{
|
||||
enable: this.props._visible
|
||||
enable: this.props._mainFilmstripVisible
|
||||
}));
|
||||
|
||||
this._doToggleFilmstrip();
|
||||
@@ -758,8 +848,15 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderToggleButton() {
|
||||
const icon = this.props._visible ? IconMenuDown : IconMenuUp;
|
||||
const { t, classes, _isVerticalFilmstrip } = this.props;
|
||||
const {
|
||||
t,
|
||||
classes,
|
||||
_isVerticalFilmstrip,
|
||||
_mainFilmstripVisible,
|
||||
_topPanelFilmstrip,
|
||||
_topPanelVisible
|
||||
} = this.props;
|
||||
const icon = (_topPanelFilmstrip ? _topPanelVisible : _mainFilmstripVisible) ? IconMenuDown : IconMenuUp;
|
||||
const actions = isMobileBrowser()
|
||||
? { onTouchStart: this._onToggleButtonTouch }
|
||||
: { onClick: this._onToolbarToggleFilmstrip };
|
||||
@@ -768,9 +865,11 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
<div
|
||||
className = { clsx(classes.toggleFilmstripContainer,
|
||||
_isVerticalFilmstrip && classes.toggleVerticalFilmstripContainer,
|
||||
_topPanelFilmstrip && classes.toggleTopPanelContainer,
|
||||
_topPanelFilmstrip && !_topPanelVisible && classes.toggleTopPanelContainerHidden,
|
||||
'toggleFilmstripContainer') }>
|
||||
<button
|
||||
aria-expanded = { this.props._visible }
|
||||
aria-expanded = { this.props._mainFilmstripVisible }
|
||||
aria-label = { t('toolbar.accessibilityLabel.toggleFilmstrip') }
|
||||
className = { classes.toggleFilmstripButton }
|
||||
id = 'toggleFilmstripButton'
|
||||
@@ -795,32 +894,38 @@ class Filmstrip extends PureComponent <Props, State> {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { _hasScroll = false } = ownProps;
|
||||
const { _hasScroll = false, filmstripType, _topPanelFilmstrip, _remoteParticipants } = ownProps;
|
||||
const toolbarButtons = getToolbarButtons(state);
|
||||
const { testing = {}, iAmRecorder } = state['features/base/config'];
|
||||
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
|
||||
const { visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
|
||||
const { topPanelHeight, topPanelVisible, visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
|
||||
const { localScreenShare } = state['features/base/participants'];
|
||||
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
|
||||
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
|
||||
const { isOpen: shiftRight } = state['features/chat'];
|
||||
const disableSelfView = shouldHideSelfView(state);
|
||||
const { clientWidth } = state['features/base/responsive-ui'];
|
||||
const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
|
||||
|
||||
const collapseTileView = reduceHeight
|
||||
&& isMobileBrowser()
|
||||
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
|
||||
|
||||
const shouldReduceHeight = reduceHeight && isMobileBrowser();
|
||||
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
|
||||
|
||||
const videosClassName = `filmstrip__videos${visible ? '' : ' hidden'}${_hasScroll ? ' has-scroll' : ''}`;
|
||||
let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
|
||||
if (_topPanelFilmstrip) {
|
||||
isVisible = _topPanelVisible;
|
||||
}
|
||||
const videosClassName = `filmstrip__videos${isVisible ? '' : ' hidden'}${_hasScroll ? ' has-scroll' : ''}`;
|
||||
const className = `${remoteVideosVisible || ownProps._verticalViewGrid ? '' : 'hide-videos'} ${
|
||||
shouldReduceHeight ? 'reduce-height' : ''
|
||||
} ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${visible ? '' : 'hidden'}`.trim();
|
||||
} ${shiftRight ? 'shift-right' : ''} ${collapseTileView ? 'collapse' : ''} ${isVisible ? '' : 'hidden'}`.trim();
|
||||
|
||||
const _currentLayout = getCurrentLayout(state);
|
||||
const _isVerticalFilmstrip = _currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW
|
||||
|| (!ownProps._stageFilmstrip && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
|
||||
|| (filmstripType === FILMSTRIP_TYPE.MAIN && _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
|
||||
|
||||
return {
|
||||
_className: className,
|
||||
@@ -833,8 +938,14 @@ function _mapStateToProps(state, ownProps) {
|
||||
_isToolboxVisible: isToolboxVisible(state),
|
||||
_isVerticalFilmstrip,
|
||||
_localScreenShare: getSourceNameSignalingFeatureFlag(state) && localScreenShare,
|
||||
_mainFilmstripVisible: visible,
|
||||
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
|
||||
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
|
||||
_remoteParticipantsLength: _remoteParticipants.length,
|
||||
_thumbnailsReordered: enableThumbnailReordering,
|
||||
_topPanelHeight: topPanelHeight.current,
|
||||
_topPanelMaxHeight: topPanelHeight.current || TOP_FILMSTRIP_HEIGHT,
|
||||
_topPanelVisible,
|
||||
_verticalFilmstripWidth: verticalFilmstripWidth.current,
|
||||
_verticalViewMaxWidth: getVerticalViewMaxWidth(state),
|
||||
_videosClassName: videosClassName
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
FILMSTRIP_BREAKPOINT,
|
||||
FILMSTRIP_BREAKPOINT_OFFSET,
|
||||
FILMSTRIP_TYPE,
|
||||
TOOLBAR_HEIGHT,
|
||||
TOOLBAR_HEIGHT_MOBILE } from '../../constants';
|
||||
import { isFilmstripResizable, showGridInVerticalView } from '../../functions.web';
|
||||
@@ -85,15 +86,16 @@ type Props = {
|
||||
/**
|
||||
* Additional CSS class names to add to the container of all the thumbnails.
|
||||
*/
|
||||
_videosClassName: string,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip videos should currently be displayed.
|
||||
*/
|
||||
_visible: boolean
|
||||
_videosClassName: string
|
||||
};
|
||||
|
||||
const MainFilmstrip = (props: Props) => <span><Filmstrip { ...props } /></span>;
|
||||
const MainFilmstrip = (props: Props) => (
|
||||
<span>
|
||||
<Filmstrip
|
||||
{ ...props }
|
||||
filmstripType = { FILMSTRIP_TYPE.MAIN } />
|
||||
</span>
|
||||
);
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
|
||||
@@ -104,7 +106,7 @@ const MainFilmstrip = (props: Props) => <span><Filmstrip { ...props } /></span>;
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const toolbarButtons = getToolbarButtons(state);
|
||||
const { visible, remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
|
||||
const { remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
|
||||
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
|
||||
const {
|
||||
gridDimensions: dimensions = {},
|
||||
@@ -189,13 +191,11 @@ function _mapStateToProps(state) {
|
||||
_filmstripHeight: remoteFilmstripHeight,
|
||||
_filmstripWidth: remoteFilmstripWidth,
|
||||
_hasScroll,
|
||||
_remoteParticipantsLength: remoteParticipants.length,
|
||||
_remoteParticipants: remoteParticipants,
|
||||
_resizableFilmstrip,
|
||||
_rows: gridDimensions.rows,
|
||||
_thumbnailWidth: _thumbnailSize?.width,
|
||||
_thumbnailHeight: _thumbnailSize?.height,
|
||||
_visible: visible,
|
||||
_verticalViewGrid,
|
||||
_verticalViewBackground: verticalFilmstripWidth.current + FILMSTRIP_BREAKPOINT_OFFSET >= FILMSTRIP_BREAKPOINT
|
||||
};
|
||||
|
||||
125
react/features/filmstrip/components/web/ScreenshareFilmstrip.js
Normal file
125
react/features/filmstrip/components/web/ScreenshareFilmstrip.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import {
|
||||
FILMSTRIP_TYPE
|
||||
} from '../../constants';
|
||||
|
||||
import Filmstrip from './Filmstrip';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current layout of the filmstrip.
|
||||
*/
|
||||
_currentLayout: string,
|
||||
|
||||
/**
|
||||
* The number of columns in tile view.
|
||||
*/
|
||||
_columns: number,
|
||||
|
||||
/**
|
||||
* The width of the filmstrip.
|
||||
*/
|
||||
_filmstripWidth: number,
|
||||
|
||||
/**
|
||||
* The height of the filmstrip.
|
||||
*/
|
||||
_filmstripHeight: number,
|
||||
|
||||
/**
|
||||
* Whether or not the current layout is vertical filmstrip.
|
||||
*/
|
||||
_isVerticalFilmstrip: boolean,
|
||||
|
||||
/**
|
||||
* The participants in the call.
|
||||
*/
|
||||
_remoteParticipants: Array<Object>,
|
||||
|
||||
/**
|
||||
* The length of the remote participants array.
|
||||
*/
|
||||
_remoteParticipantsLength: number,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip should be user-resizable.
|
||||
*/
|
||||
_resizableFilmstrip: boolean,
|
||||
|
||||
/**
|
||||
* The number of rows in tile view.
|
||||
*/
|
||||
_rows: number,
|
||||
|
||||
/**
|
||||
* The height of the thumbnail.
|
||||
*/
|
||||
_thumbnailHeight: number,
|
||||
|
||||
/**
|
||||
* The width of the thumbnail.
|
||||
*/
|
||||
_thumbnailWidth: number,
|
||||
|
||||
/**
|
||||
* Whether or not the vertical filmstrip should have a background color.
|
||||
*/
|
||||
_verticalViewBackground: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the vertical filmstrip should be displayed as grid.
|
||||
*/
|
||||
_verticalViewGrid: boolean,
|
||||
|
||||
/**
|
||||
* Additional CSS class names to add to the container of all the thumbnails.
|
||||
*/
|
||||
_videosClassName: string
|
||||
};
|
||||
|
||||
const ScreenshareFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW
|
||||
&& props._remoteParticipants.length === 1 && (
|
||||
<span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
|
||||
<Filmstrip
|
||||
{ ...props }
|
||||
filmstripType = { FILMSTRIP_TYPE.SCREENSHARE } />
|
||||
</span>
|
||||
);
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code Filmstrip}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const {
|
||||
filmstripHeight,
|
||||
filmstripWidth,
|
||||
thumbnailSize
|
||||
} = state['features/filmstrip'].screenshareFilmstripDimensions;
|
||||
const screenshares = state['features/video-layout'].remoteScreenShares;
|
||||
|
||||
return {
|
||||
_columns: 1,
|
||||
_currentLayout: getCurrentLayout(state),
|
||||
_filmstripHeight: filmstripHeight,
|
||||
_filmstripWidth: filmstripWidth,
|
||||
_remoteParticipants: screenshares.length ? [ screenshares[0] ] : [],
|
||||
_resizableFilmstrip: false,
|
||||
_rows: 1,
|
||||
_thumbnailWidth: thumbnailSize?.width,
|
||||
_thumbnailHeight: thumbnailSize?.height,
|
||||
_verticalViewGrid: false,
|
||||
_verticalViewBackground: false
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(ScreenshareFilmstrip);
|
||||
@@ -8,9 +8,11 @@ import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
FILMSTRIP_TYPE,
|
||||
TOOLBAR_HEIGHT_MOBILE
|
||||
} from '../../constants';
|
||||
import { getActiveParticipantsIds } from '../../functions';
|
||||
import { isFilmstripResizable, isStageFilmstripTopPanel } from '../../functions.web';
|
||||
|
||||
import Filmstrip from './Filmstrip';
|
||||
|
||||
@@ -84,19 +86,14 @@ type Props = {
|
||||
/**
|
||||
* Additional CSS class names to add to the container of all the thumbnails.
|
||||
*/
|
||||
_videosClassName: string,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip videos should currently be displayed.
|
||||
*/
|
||||
_visible: boolean
|
||||
_videosClassName: string
|
||||
};
|
||||
|
||||
const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && (
|
||||
<span className = { LAYOUT_CLASSNAMES[LAYOUTS.TILE_VIEW] }>
|
||||
<Filmstrip
|
||||
{ ...props }
|
||||
_stageFilmstrip = { true } />
|
||||
filmstripType = { FILMSTRIP_TYPE.STAGE } />
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -109,7 +106,6 @@ const StageFilmstrip = (props: Props) => props._currentLayout === LAYOUTS.STAGE_
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const toolbarButtons = getToolbarButtons(state);
|
||||
const { visible } = state['features/filmstrip'];
|
||||
const activeParticipants = getActiveParticipantsIds(state);
|
||||
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
|
||||
const {
|
||||
@@ -139,19 +135,19 @@ function _mapStateToProps(state) {
|
||||
&& clientWidth <= ASPECT_RATIO_BREAKPOINT;
|
||||
|
||||
const remoteFilmstripHeight = filmstripHeight - (collapseTileView && filmstripPadding > 0 ? filmstripPadding : 0);
|
||||
const _topPanelFilmstrip = isStageFilmstripTopPanel(state);
|
||||
|
||||
return {
|
||||
_columns: gridDimensions.columns,
|
||||
_currentLayout: getCurrentLayout(state),
|
||||
_filmstripHeight: remoteFilmstripHeight,
|
||||
_filmstripWidth: filmstripWidth,
|
||||
_remoteParticipantsLength: activeParticipants.length,
|
||||
_remoteParticipants: activeParticipants,
|
||||
_resizableFilmstrip: false,
|
||||
_resizableFilmstrip: isFilmstripResizable(state) && _topPanelFilmstrip,
|
||||
_rows: gridDimensions.rows,
|
||||
_thumbnailWidth: thumbnailSize?.width,
|
||||
_thumbnailHeight: thumbnailSize?.height,
|
||||
_visible: visible,
|
||||
_topPanelFilmstrip,
|
||||
_verticalViewGrid: false,
|
||||
_verticalViewBackground: false
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ import { togglePinStageParticipant } from '../../actions';
|
||||
import {
|
||||
DISPLAY_MODE_TO_CLASS_NAME,
|
||||
DISPLAY_VIDEO,
|
||||
FILMSTRIP_TYPE,
|
||||
SHOW_TOOLBAR_CONTEXT_MENU_AFTER,
|
||||
THUMBNAIL_TYPE,
|
||||
VIDEO_TEST_EVENTS
|
||||
@@ -234,6 +235,11 @@ export type Props = {|
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* The type of filmstrip the tile is displayed in.
|
||||
*/
|
||||
filmstripType: string,
|
||||
|
||||
/**
|
||||
* The horizontal offset in px for the thumbnail. Used to center the thumbnails from the last row in tile view.
|
||||
*/
|
||||
@@ -244,11 +250,6 @@ export type Props = {|
|
||||
*/
|
||||
participantID: ?string,
|
||||
|
||||
/**
|
||||
* Whether the tile is displayed in the stage filmstrip or not.
|
||||
*/
|
||||
stageFilmstrip: boolean,
|
||||
|
||||
/**
|
||||
* Styles that will be set to the Thumbnail's main span element.
|
||||
*/
|
||||
@@ -993,7 +994,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
_thumbnailType,
|
||||
_videoTrack,
|
||||
classes,
|
||||
stageFilmstrip
|
||||
filmstripType
|
||||
} = this.props;
|
||||
const { id } = _participant || {};
|
||||
const { isHovered, popoverVisible } = this.state;
|
||||
@@ -1031,8 +1032,8 @@ class Thumbnail extends Component<Props, State> {
|
||||
<span
|
||||
className = { containerClassName }
|
||||
id = { local
|
||||
? `localVideoContainer${stageFilmstrip ? '_stage' : ''}`
|
||||
: `participant_${id}${stageFilmstrip ? '_stage' : ''}`
|
||||
? `localVideoContainer${filmstripType === FILMSTRIP_TYPE.MAIN ? '' : `_${filmstripType}`}`
|
||||
: `participant_${id}${filmstripType === FILMSTRIP_TYPE.MAIN ? '' : `_${filmstripType}`}`
|
||||
}
|
||||
{ ...(_isMobile
|
||||
? {
|
||||
@@ -1168,7 +1169,7 @@ class Thumbnail extends Component<Props, State> {
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps): Object {
|
||||
const { participantID, stageFilmstrip } = ownProps;
|
||||
const { participantID, filmstripType = FILMSTRIP_TYPE.MAIN } = ownProps;
|
||||
|
||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||
const id = participant?.id;
|
||||
@@ -1199,7 +1200,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
const _isMobile = isMobileBrowser();
|
||||
const activeParticipants = getActiveParticipantsIds(state);
|
||||
const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip);
|
||||
const tileType = getThumbnailTypeFromLayout(_currentLayout, filmstripType);
|
||||
|
||||
switch (tileType) {
|
||||
case THUMBNAIL_TYPE.VERTICAL:
|
||||
@@ -1244,7 +1245,8 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
const {
|
||||
stageFilmstripDimensions = {
|
||||
thumbnailSize: {}
|
||||
}
|
||||
},
|
||||
screenshareFilmstripDimensions
|
||||
} = state['features/filmstrip'];
|
||||
|
||||
size = {
|
||||
@@ -1252,9 +1254,16 @@ function _mapStateToProps(state, ownProps): Object {
|
||||
_height: thumbnailSize?.height
|
||||
};
|
||||
|
||||
if (stageFilmstrip) {
|
||||
if (filmstripType === FILMSTRIP_TYPE.STAGE) {
|
||||
const { width: _width, height: _height } = stageFilmstripDimensions.thumbnailSize;
|
||||
|
||||
size = {
|
||||
_width,
|
||||
_height
|
||||
};
|
||||
} else if (filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
|
||||
const { width: _width, height: _height } = screenshareFilmstripDimensions.thumbnailSize;
|
||||
|
||||
size = {
|
||||
_width,
|
||||
_height
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getLocalParticipant } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { shouldHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import { TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN } from '../../constants';
|
||||
import { TILE_ASPECT_RATIO, TILE_HORIZONTAL_MARGIN, FILMSTRIP_TYPE } from '../../constants';
|
||||
import { showGridInVerticalView, getActiveParticipantsIds } from '../../functions';
|
||||
|
||||
import Thumbnail from './Thumbnail';
|
||||
@@ -22,6 +22,11 @@ type Props = {
|
||||
*/
|
||||
_disableSelfView: boolean,
|
||||
|
||||
/**
|
||||
* The type of filmstrip this thumbnail is displayed in.
|
||||
*/
|
||||
_filmstripType: string,
|
||||
|
||||
/**
|
||||
* The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view.
|
||||
*/
|
||||
@@ -37,11 +42,6 @@ type Props = {
|
||||
*/
|
||||
_isLocalScreenShare: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the filmstrip is used a stage filmstrip.
|
||||
*/
|
||||
_stageFilmstrip: boolean,
|
||||
|
||||
/**
|
||||
* The width of the thumbnail. Used for expanding the width of the thumbnails on last row in case
|
||||
* there is empty space.
|
||||
@@ -97,10 +97,10 @@ class ThumbnailWrapper extends Component<Props> {
|
||||
render() {
|
||||
const {
|
||||
_disableSelfView,
|
||||
_filmstripType = FILMSTRIP_TYPE.MAIN,
|
||||
_isLocalScreenShare = false,
|
||||
_horizontalOffset = 0,
|
||||
_participantID,
|
||||
_stageFilmstrip,
|
||||
_thumbnailWidth,
|
||||
style
|
||||
} = this.props;
|
||||
@@ -112,9 +112,9 @@ class ThumbnailWrapper extends Component<Props> {
|
||||
if (_participantID === 'local') {
|
||||
return _disableSelfView ? null : (
|
||||
<Thumbnail
|
||||
filmstripType = { _filmstripType }
|
||||
horizontalOffset = { _horizontalOffset }
|
||||
key = 'local'
|
||||
stageFilmstrip = { _stageFilmstrip }
|
||||
style = { style }
|
||||
width = { _thumbnailWidth } />);
|
||||
}
|
||||
@@ -122,20 +122,20 @@ class ThumbnailWrapper extends Component<Props> {
|
||||
if (_isLocalScreenShare) {
|
||||
return _disableSelfView ? null : (
|
||||
<Thumbnail
|
||||
filmstripType = { _filmstripType }
|
||||
horizontalOffset = { _horizontalOffset }
|
||||
key = 'localScreenShare'
|
||||
participantID = { _participantID }
|
||||
stageFilmstrip = { _stageFilmstrip }
|
||||
style = { style }
|
||||
width = { _thumbnailWidth } />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Thumbnail
|
||||
filmstripType = { _filmstripType }
|
||||
horizontalOffset = { _horizontalOffset }
|
||||
key = { `remote_${_participantID}` }
|
||||
participantID = { _participantID }
|
||||
stageFilmstrip = { _stageFilmstrip }
|
||||
style = { style }
|
||||
width = { _thumbnailWidth } />);
|
||||
}
|
||||
@@ -158,7 +158,8 @@ function _mapStateToProps(state, ownProps) {
|
||||
const enableThumbnailReordering = testing.enableThumbnailReordering ?? true;
|
||||
const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
|
||||
const _verticalViewGrid = showGridInVerticalView(state);
|
||||
const stageFilmstrip = ownProps.data?.stageFilmstrip;
|
||||
const filmstripType = ownProps.data?.filmstripType;
|
||||
const stageFilmstrip = filmstripType === FILMSTRIP_TYPE.STAGE;
|
||||
const sortedActiveParticipants = activeParticipants.sort();
|
||||
const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote;
|
||||
const remoteParticipantsLength = remoteParticipants.length;
|
||||
@@ -235,9 +236,9 @@ function _mapStateToProps(state, ownProps) {
|
||||
if (stageFilmstrip) {
|
||||
return {
|
||||
_disableSelfView: disableSelfView,
|
||||
_filmstripType: filmstripType,
|
||||
_participantID: remoteParticipants[index] === localId ? 'local' : remoteParticipants[index],
|
||||
_horizontalOffset: horizontalOffset,
|
||||
_stageFilmstrip: stageFilmstrip,
|
||||
_thumbnailWidth: thumbnailWidth
|
||||
};
|
||||
}
|
||||
@@ -260,6 +261,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
if (!iAmRecorder && index === localIndex) {
|
||||
return {
|
||||
_disableSelfView: disableSelfView,
|
||||
_filmstripType: filmstripType,
|
||||
_participantID: 'local',
|
||||
_horizontalOffset: horizontalOffset,
|
||||
_thumbnailWidth: thumbnailWidth
|
||||
@@ -269,6 +271,7 @@ function _mapStateToProps(state, ownProps) {
|
||||
if (sourceNameSignalingEnabled && !iAmRecorder && localScreenShare && index === localScreenShareIndex) {
|
||||
return {
|
||||
_disableSelfView: disableSelfView,
|
||||
_filmstripType: filmstripType,
|
||||
_isLocalScreenShare: true,
|
||||
_participantID: localScreenShare?.id,
|
||||
_horizontalOffset: horizontalOffset,
|
||||
@@ -277,12 +280,22 @@ function _mapStateToProps(state, ownProps) {
|
||||
}
|
||||
|
||||
return {
|
||||
_filmstripType: filmstripType,
|
||||
_participantID: remoteParticipants[remoteIndex],
|
||||
_horizontalOffset: horizontalOffset,
|
||||
_thumbnailWidth: thumbnailWidth
|
||||
};
|
||||
}
|
||||
|
||||
if (_currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW && filmstripType === FILMSTRIP_TYPE.SCREENSHARE) {
|
||||
const { remoteScreenShares } = state['features/video-layout'];
|
||||
|
||||
return {
|
||||
_filmstripType: filmstripType,
|
||||
_participantID: remoteScreenShares[remoteScreenShares.length - 1]
|
||||
};
|
||||
}
|
||||
|
||||
const { index } = ownProps;
|
||||
|
||||
if (typeof index !== 'number' || remoteParticipantsLength <= index) {
|
||||
|
||||
@@ -5,6 +5,7 @@ export { default as Filmstrip } from './Filmstrip';
|
||||
export { default as MainFilmstrip } from './MainFilmstrip';
|
||||
export { default as ModeratorIndicator } from './ModeratorIndicator';
|
||||
export { default as RaisedHandIndicator } from './RaisedHandIndicator';
|
||||
export { default as ScreenshareFilmstrip } from './ScreenshareFilmstrip';
|
||||
export { default as StageFilmstrip } from './StageFilmstrip';
|
||||
export { default as StatusIndicators } from './StatusIndicators';
|
||||
export { default as Thumbnail } from './Thumbnail';
|
||||
|
||||
@@ -23,6 +23,7 @@ export const styles = theme => {
|
||||
left: 'calc(50% - 16px)',
|
||||
opacity: 0,
|
||||
transition: 'opacity .3s',
|
||||
zIndex: 1,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.ui02
|
||||
@@ -53,8 +54,18 @@ export const styles = theme => {
|
||||
top: 'calc(50% - 12px)'
|
||||
},
|
||||
|
||||
toggleTopPanelContainer: {
|
||||
transform: 'rotate(180deg)',
|
||||
bottom: 'calc(-24px - 6px)',
|
||||
top: 'auto'
|
||||
},
|
||||
|
||||
toggleTopPanelContainerHidden: {
|
||||
visibility: 'hidden'
|
||||
},
|
||||
|
||||
filmstrip: {
|
||||
transition: 'background .2s ease-in-out, right 1s, bottom 1s, height .3s ease-in',
|
||||
transition: 'background .2s ease-in-out, right 1s, bottom 1s, top 1s, height .3s ease-in',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
|
||||
@@ -111,6 +122,10 @@ export const styles = theme => {
|
||||
'& .avatar-container': {
|
||||
maxWidth: 'initial',
|
||||
maxHeight: 'initial'
|
||||
},
|
||||
|
||||
'&.top-panel-filmstrip': {
|
||||
flexDirection: 'column'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -137,6 +152,18 @@ export const styles = theme => {
|
||||
'& .dragHandle': {
|
||||
backgroundColor: theme.palette.icon01
|
||||
}
|
||||
},
|
||||
|
||||
'&.top-panel': {
|
||||
order: 2,
|
||||
width: '100%',
|
||||
height: '9px',
|
||||
cursor: 'row-resize',
|
||||
|
||||
'& .dragHandle': {
|
||||
height: '3px',
|
||||
width: '100px'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -281,6 +281,12 @@ export const FILMSTRIP_GRID_BREAKPOINT = 300;
|
||||
*/
|
||||
export const FILMSTRIP_BREAKPOINT_OFFSET = 5;
|
||||
|
||||
/**
|
||||
* The minimum height for the stage view
|
||||
* (used to determine the maximum height of the user-resizable top panel).
|
||||
*/
|
||||
export const MIN_STAGE_VIEW_HEIGHT = 700;
|
||||
|
||||
/**
|
||||
* The minimum width for the stage view
|
||||
* (used to determine the maximum width of the user-resizable vertical filmstrip).
|
||||
@@ -298,7 +304,21 @@ export const VERTICAL_VIEW_HORIZONTAL_MARGIN = VERTICAL_FILMSTRIP_MIN_HORIZONTAL
|
||||
*/
|
||||
export const ACTIVE_PARTICIPANT_TIMEOUT = 1000 * 60;
|
||||
|
||||
/**
|
||||
* The types of filmstrip.
|
||||
*/
|
||||
export const FILMSTRIP_TYPE = {
|
||||
MAIN: 'main',
|
||||
STAGE: 'stage',
|
||||
SCREENSHARE: 'screenshare'
|
||||
};
|
||||
|
||||
/**
|
||||
* The max number of participants to be displayed on the stage filmstrip.
|
||||
*/
|
||||
export const MAX_ACTIVE_PARTICIPANTS = 6;
|
||||
|
||||
/**
|
||||
* Top filmstrip default height.
|
||||
*/
|
||||
export const TOP_FILMSTRIP_HEIGHT = 180;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MEDIA_TYPE } from '../base/media';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getParticipantCount,
|
||||
getParticipantCountWithFake,
|
||||
getPinnedParticipant
|
||||
} from '../base/participants';
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
DISPLAY_AVATAR,
|
||||
DISPLAY_VIDEO,
|
||||
FILMSTRIP_GRID_BREAKPOINT,
|
||||
FILMSTRIP_TYPE,
|
||||
INDICATORS_TOOLTIP_POSITION,
|
||||
SCROLL_SIZE,
|
||||
SQUARE_TILE_ASPECT_RATIO,
|
||||
@@ -296,7 +298,8 @@ export function calculateResponsiveTileViewDimensions({
|
||||
noHorizontalContainerMargin = false,
|
||||
maxColumns,
|
||||
numberOfParticipants,
|
||||
desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES
|
||||
desiredNumberOfVisibleTiles = TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
|
||||
minTileHeight
|
||||
}) {
|
||||
let height, width;
|
||||
let columns, rows;
|
||||
@@ -324,7 +327,8 @@ export function calculateResponsiveTileViewDimensions({
|
||||
clientHeight,
|
||||
disableTileEnlargement,
|
||||
disableResponsiveTiles: false,
|
||||
noHorizontalContainerMargin
|
||||
noHorizontalContainerMargin,
|
||||
minTileHeight
|
||||
});
|
||||
|
||||
if (size) {
|
||||
@@ -413,10 +417,11 @@ export function calculateThumbnailSizeForTileView({
|
||||
clientHeight,
|
||||
disableResponsiveTiles = false,
|
||||
disableTileEnlargement = false,
|
||||
noHorizontalContainerMargin = false
|
||||
noHorizontalContainerMargin = false,
|
||||
minTileHeight
|
||||
}: Object) {
|
||||
const aspectRatio = getTileDefaultAspectRatio(disableResponsiveTiles, disableTileEnlargement, clientWidth);
|
||||
const minHeight = getThumbnailMinHeight(clientWidth);
|
||||
const minHeight = minTileHeight || getThumbnailMinHeight(clientWidth);
|
||||
const viewWidth = clientWidth - (columns * TILE_HORIZONTAL_MARGIN)
|
||||
- (noHorizontalContainerMargin ? SCROLL_SIZE : TILE_VIEW_GRID_HORIZONTAL_MARGIN);
|
||||
const availableHeight = clientHeight - TILE_VIEW_GRID_VERTICAL_MARGIN;
|
||||
@@ -506,6 +511,7 @@ export function getVerticalFilmstripVisibleAreaWidth() {
|
||||
*/
|
||||
export function computeDisplayModeFromInput(input: Object) {
|
||||
const {
|
||||
filmstripType,
|
||||
isActiveParticipant,
|
||||
isAudioOnly,
|
||||
isCurrentlyOnLargeVideo,
|
||||
@@ -515,7 +521,6 @@ export function computeDisplayModeFromInput(input: Object) {
|
||||
isRemoteParticipant,
|
||||
multipleVideoSupport,
|
||||
stageParticipantsVisible,
|
||||
stageFilmstrip,
|
||||
tileViewActive
|
||||
} = input;
|
||||
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
|
||||
@@ -534,8 +539,8 @@ export function computeDisplayModeFromInput(input: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
|
||||
|| (stageParticipantsVisible && isActiveParticipant && !stageFilmstrip))) {
|
||||
if (!tileViewActive && filmstripType === FILMSTRIP_TYPE.MAIN && ((isScreenSharing && isRemoteParticipant)
|
||||
|| (stageParticipantsVisible && isActiveParticipant))) {
|
||||
return DISPLAY_AVATAR;
|
||||
} else if (isCurrentlyOnLargeVideo && !tileViewActive) {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
@@ -569,12 +574,13 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||
_participant,
|
||||
_stageParticipantsVisible,
|
||||
_videoTrack,
|
||||
stageFilmstrip
|
||||
filmstripType = FILMSTRIP_TYPE.MAIN
|
||||
} = props;
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
const { canPlayEventReceived } = state;
|
||||
|
||||
return {
|
||||
filmstripType,
|
||||
isActiveParticipant: _isActiveParticipant,
|
||||
isCurrentlyOnLargeVideo: _isCurrentlyOnLargeVideo,
|
||||
isAudioOnly: _isAudioOnly,
|
||||
@@ -588,7 +594,6 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||
isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant,
|
||||
multipleVideoSupport: _multipleVideoSupport,
|
||||
stageParticipantsVisible: _stageParticipantsVisible,
|
||||
stageFilmstrip,
|
||||
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
|
||||
};
|
||||
}
|
||||
@@ -717,8 +722,24 @@ export function isStageFilmstripAvailable(state, minParticipantCount = 0) {
|
||||
const { remoteScreenShares } = state['features/video-layout'];
|
||||
const sharedVideo = isSharingStatus(state['features/shared-video']?.status);
|
||||
|
||||
return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo
|
||||
&& activeParticipants.length >= minParticipantCount;
|
||||
return isStageFilmstripEnabled(state) && !sharedVideo
|
||||
&& activeParticipants.length >= minParticipantCount
|
||||
&& (isTopPanelEnabled(state) || remoteScreenShares.length === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the stage filmstrip should be displayed on the top.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {number} minParticipantCount - The min number of participants for the stage filmstrip
|
||||
* to be displayed.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isStageFilmstripTopPanel(state, minParticipantCount = 0) {
|
||||
const { remoteScreenShares } = state['features/video-layout'];
|
||||
|
||||
return isTopPanelEnabled(state)
|
||||
&& isStageFilmstripAvailable(state, minParticipantCount) && remoteScreenShares.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -737,10 +758,10 @@ export function isStageFilmstripEnabled(state) {
|
||||
* Gets the thumbnail type by filmstrip type.
|
||||
*
|
||||
* @param {string} currentLayout - Current app layout.
|
||||
* @param {boolean} isStageFilmstrip - Whether the filmstrip is stage filmstrip or not.
|
||||
* @param {string} filmstripType - The current filmstrip type.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = false) {
|
||||
export function getThumbnailTypeFromLayout(currentLayout, filmstripType) {
|
||||
switch (currentLayout) {
|
||||
case LAYOUTS.TILE_VIEW:
|
||||
return THUMBNAIL_TYPE.TILE;
|
||||
@@ -749,10 +770,24 @@ export function getThumbnailTypeFromLayout(currentLayout, isStageFilmstrip = fal
|
||||
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
|
||||
return THUMBNAIL_TYPE.HORIZONTAL;
|
||||
case LAYOUTS.STAGE_FILMSTRIP_VIEW:
|
||||
if (isStageFilmstrip) {
|
||||
if (filmstripType !== FILMSTRIP_TYPE.MAIN) {
|
||||
return THUMBNAIL_TYPE.TILE;
|
||||
}
|
||||
|
||||
return THUMBNAIL_TYPE.VERTICAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the top panel is enabled.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isTopPanelEnabled(state) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
const participantsCount = getParticipantCount(state);
|
||||
|
||||
return !filmstrip?.disableTopPanel && participantsCount >= (filmstrip?.minParticipantCountForTopPanel ?? 50);
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import {
|
||||
addStageParticipant,
|
||||
removeStageParticipant,
|
||||
setFilmstripHeight,
|
||||
setFilmstripWidth,
|
||||
setStageParticipants
|
||||
} from './actions';
|
||||
@@ -38,7 +39,9 @@ import {
|
||||
ACTIVE_PARTICIPANT_TIMEOUT,
|
||||
DEFAULT_FILMSTRIP_WIDTH,
|
||||
MAX_ACTIVE_PARTICIPANTS,
|
||||
MIN_STAGE_VIEW_WIDTH
|
||||
MIN_STAGE_VIEW_HEIGHT,
|
||||
MIN_STAGE_VIEW_WIDTH,
|
||||
TOP_FILMSTRIP_HEIGHT
|
||||
} from './constants';
|
||||
import {
|
||||
isFilmstripResizable,
|
||||
@@ -77,19 +80,27 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const state = store.getState();
|
||||
|
||||
if (isFilmstripResizable(state)) {
|
||||
const { width: filmstripWidth } = state['features/filmstrip'];
|
||||
const { clientWidth } = action;
|
||||
let width;
|
||||
const { width: filmstripWidth, topPanelHeight } = state['features/filmstrip'];
|
||||
const { clientWidth, clientHeight } = action;
|
||||
let height, width;
|
||||
|
||||
if (filmstripWidth.current > clientWidth - MIN_STAGE_VIEW_WIDTH) {
|
||||
width = Math.max(clientWidth - MIN_STAGE_VIEW_WIDTH, DEFAULT_FILMSTRIP_WIDTH);
|
||||
} else {
|
||||
width = Math.min(clientWidth - MIN_STAGE_VIEW_WIDTH, filmstripWidth.userSet);
|
||||
}
|
||||
|
||||
if (width !== filmstripWidth.current) {
|
||||
store.dispatch(setFilmstripWidth(width));
|
||||
}
|
||||
|
||||
if (topPanelHeight.current > clientHeight - MIN_STAGE_VIEW_HEIGHT) {
|
||||
height = Math.max(clientHeight - MIN_STAGE_VIEW_HEIGHT, TOP_FILMSTRIP_HEIGHT);
|
||||
} else {
|
||||
height = Math.min(clientHeight - MIN_STAGE_VIEW_HEIGHT, topPanelHeight.userSet);
|
||||
}
|
||||
if (height !== topPanelHeight.current) {
|
||||
store.dispatch(setFilmstripHeight(height));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ import {
|
||||
SET_VISIBLE_REMOTE_PARTICIPANTS,
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
CLEAR_STAGE_PARTICIPANTS
|
||||
CLEAR_STAGE_PARTICIPANTS,
|
||||
SET_SCREENSHARING_TILE_DIMENSIONS,
|
||||
SET_USER_FILMSTRIP_HEIGHT,
|
||||
SET_FILMSTRIP_HEIGHT,
|
||||
SET_TOP_PANEL_VISIBILITY
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
@@ -76,6 +80,11 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
remoteParticipants: [],
|
||||
|
||||
/**
|
||||
* The dimensions of the screenshare filmstrip.
|
||||
*/
|
||||
screenshareFilmstripDimensions: {},
|
||||
|
||||
/**
|
||||
* The stage filmstrip view dimensions.
|
||||
*
|
||||
@@ -92,6 +101,27 @@ const DEFAULT_STATE = {
|
||||
*/
|
||||
tileViewDimensions: {},
|
||||
|
||||
/**
|
||||
* The height of the resizable top panel.
|
||||
*/
|
||||
topPanelHeight: {
|
||||
/**
|
||||
* Current height. Affected by: user top panel resize,
|
||||
* window resize.
|
||||
*/
|
||||
current: null,
|
||||
|
||||
/**
|
||||
* Height set by user resize. Used as the preferred height.
|
||||
*/
|
||||
userSet: null
|
||||
},
|
||||
|
||||
/**
|
||||
* The indicator determines if the top panel is visible.
|
||||
*/
|
||||
topPanelVisible: true,
|
||||
|
||||
/**
|
||||
* The vertical view dimensions.
|
||||
*
|
||||
@@ -227,6 +257,15 @@ ReducerRegistry.register(
|
||||
...state
|
||||
};
|
||||
}
|
||||
case SET_FILMSTRIP_HEIGHT:{
|
||||
return {
|
||||
...state,
|
||||
topPanelHeight: {
|
||||
...state.topPanelHeight,
|
||||
current: action.height
|
||||
}
|
||||
};
|
||||
}
|
||||
case SET_FILMSTRIP_WIDTH: {
|
||||
return {
|
||||
...state,
|
||||
@@ -236,6 +275,17 @@ ReducerRegistry.register(
|
||||
}
|
||||
};
|
||||
}
|
||||
case SET_USER_FILMSTRIP_HEIGHT: {
|
||||
const { height } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
topPanelHeight: {
|
||||
current: height,
|
||||
userSet: height
|
||||
}
|
||||
};
|
||||
}
|
||||
case SET_USER_FILMSTRIP_WIDTH: {
|
||||
const { width } = action;
|
||||
|
||||
@@ -283,6 +333,18 @@ ReducerRegistry.register(
|
||||
activeParticipants: []
|
||||
};
|
||||
}
|
||||
case SET_SCREENSHARING_TILE_DIMENSIONS: {
|
||||
return {
|
||||
...state,
|
||||
screenshareFilmstripDimensions: action.dimensions
|
||||
};
|
||||
}
|
||||
case SET_TOP_PANEL_VISIBILITY: {
|
||||
return {
|
||||
...state,
|
||||
topPanelVisible: action.visible
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getCurrentLayout, shouldDisplayTileView, LAYOUTS } from '../video-layou
|
||||
import {
|
||||
clearStageParticipants,
|
||||
setHorizontalViewDimensions,
|
||||
setScreensharingTileDimensions,
|
||||
setStageFilmstripViewDimensions,
|
||||
setTileViewDimensions,
|
||||
setVerticalViewDimensions
|
||||
@@ -23,7 +24,8 @@ import {
|
||||
DISPLAY_DRAWER_THRESHOLD
|
||||
} from './constants';
|
||||
import {
|
||||
isFilmstripResizable
|
||||
isFilmstripResizable,
|
||||
isTopPanelEnabled
|
||||
} from './functions';
|
||||
|
||||
import './subscriber.any';
|
||||
@@ -176,7 +178,8 @@ StateListenerRegistry.register(
|
||||
visible: state['features/filmstrip'].visible,
|
||||
clientWidth: state['features/base/responsive-ui'].clientWidth,
|
||||
clientHeight: state['features/base/responsive-ui'].clientHeight,
|
||||
tileView: state['features/video-layout'].tileViewEnabled
|
||||
tileView: state['features/video-layout'].tileViewEnabled,
|
||||
height: state['features/filmstrip'].topPanelHeight?.current
|
||||
};
|
||||
},
|
||||
/* listener */(_, store) => {
|
||||
@@ -198,3 +201,27 @@ StateListenerRegistry.register(
|
||||
store.dispatch(selectParticipantInLargeVideo());
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Listens for changes to determine the size of the screenshare filmstrip.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
return {
|
||||
length: state['features/video-layout'].remoteScreenShares.length,
|
||||
clientWidth: state['features/base/responsive-ui'].clientWidth,
|
||||
clientHeight: state['features/base/responsive-ui'].clientHeight,
|
||||
height: state['features/filmstrip'].topPanelHeight?.current,
|
||||
width: state['features/filmstrip'].width?.current,
|
||||
visible: state['features/filmstrip'].visible,
|
||||
topPanelVisible: state['features/filmstrip'].topPanelVisible
|
||||
};
|
||||
},
|
||||
/* listener */({ length }, store) => {
|
||||
if (length >= 1 && isTopPanelEnabled(store.getState())) {
|
||||
store.dispatch(setScreensharingTileDimensions());
|
||||
}
|
||||
}, {
|
||||
deepEquals: true
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { GiphyContent, GiphyGridView, GiphyMediaType } from '@giphy/react-native-sdk';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Image, Keyboard, Text, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { createGifSentEvent, sendAnalytics } from '../../../analytics';
|
||||
@@ -11,12 +10,13 @@ import { goBack } from '../../../mobile/navigation/components/conference/Confere
|
||||
import ClearableInput from '../../../participants-pane/components/native/ClearableInput';
|
||||
import { formatGifUrlMessage, getGifUrl } from '../../functions';
|
||||
|
||||
import GifsMenuFooter from './GifsMenuFooter';
|
||||
import styles from './styles';
|
||||
|
||||
const GifsMenu = () => {
|
||||
const [ searchQuery, setSearchQuery ] = useState('');
|
||||
const dispatch = useDispatch();
|
||||
const insets = useSafeAreaInsets();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const content = searchQuery === ''
|
||||
? GiphyContent.trending({ mediaType: GiphyMediaType.Gif })
|
||||
@@ -34,33 +34,22 @@ const GifsMenu = () => {
|
||||
goBack();
|
||||
}, []);
|
||||
|
||||
const onScroll = useCallback(Keyboard.dismiss, []);
|
||||
|
||||
return (<JitsiScreen
|
||||
style = { styles.container }>
|
||||
<ClearableInput
|
||||
autoFocus = { true }
|
||||
customStyles = { styles.clearableInput }
|
||||
onChange = { setSearchQuery }
|
||||
placeholder = 'Search GIPHY'
|
||||
value = { searchQuery } />
|
||||
<GiphyGridView
|
||||
cellPadding = { 5 }
|
||||
content = { content }
|
||||
onMediaSelect = { sendGif }
|
||||
onScroll = { onScroll }
|
||||
style = { styles.grid } />
|
||||
<View
|
||||
style = { [ styles.credit, {
|
||||
bottom: insets.bottom,
|
||||
left: insets.left,
|
||||
right: insets.right
|
||||
} ] }>
|
||||
<Text
|
||||
style = { styles.creditText }>Powered by</Text>
|
||||
<Image source = { require('../../../../../images/GIPHY_logo.png') } />
|
||||
</View>
|
||||
</JitsiScreen>);
|
||||
return (
|
||||
<JitsiScreen
|
||||
footerComponent = { GifsMenuFooter }
|
||||
style = { styles.container }>
|
||||
<ClearableInput
|
||||
customStyles = { styles.clearableInput }
|
||||
onChange = { setSearchQuery }
|
||||
placeholder = { t('giphy.search') }
|
||||
value = { searchQuery } />
|
||||
<GiphyGridView
|
||||
cellPadding = { 5 }
|
||||
content = { content }
|
||||
onMediaSelect = { sendGif }
|
||||
style = { styles.grid } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
};
|
||||
|
||||
export default GifsMenu;
|
||||
|
||||
26
react/features/gifs/components/native/GifsMenuFooter.tsx
Normal file
26
react/features/gifs/components/native/GifsMenuFooter.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Implements the gifs menu footer component.
|
||||
*
|
||||
* @returns { JSX.Element} - The gifs menu footer component.
|
||||
*/
|
||||
const GifsMenuFooter = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return(
|
||||
<View style={ styles.credit }>
|
||||
<Text
|
||||
style={ styles.creditText }>{ t('poweredby') }</Text>
|
||||
<Image
|
||||
source = { require('../../../../../images/GIPHY_logo.png') } />
|
||||
</View>
|
||||
)
|
||||
};
|
||||
|
||||
export default GifsMenuFooter;
|
||||
@@ -12,7 +12,9 @@ export default {
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
input: { textAlign: 'left' }
|
||||
input: {
|
||||
textAlign: 'left'
|
||||
}
|
||||
},
|
||||
|
||||
grid: {
|
||||
@@ -22,19 +24,19 @@ export default {
|
||||
},
|
||||
|
||||
credit: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: BaseTheme.palette.ui01,
|
||||
width: '100%',
|
||||
height: 40,
|
||||
position: 'absolute',
|
||||
marginBottom: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
height: 56,
|
||||
justifyContent: 'center',
|
||||
marginBottom: BaseTheme.spacing[0],
|
||||
paddingBottom: BaseTheme.spacing[4],
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
creditText: {
|
||||
color: 'white',
|
||||
color: BaseTheme.palette.text01,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, View, TouchableOpacity, TextInput } from 'react-native';
|
||||
import { Text, View, TextInput } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import Button from '../../../base/react/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/react/constants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
@@ -22,7 +24,6 @@ import AbstractLobbyScreen, {
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
@@ -185,31 +186,21 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderPasswordJoinButtons() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<View style = { styles.passwordJoinButtonsWrapper }>
|
||||
<TouchableOpacity
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.backToKnockModeButton'
|
||||
label = 'lobby.backToKnockModeButton'
|
||||
onPress = { this._onSwitchToKnockMode }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.backToKnockModeButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style = { styles.lobbyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'lobby.passwordJoinButton'
|
||||
disabled = { !this.state.password }
|
||||
label = 'lobby.passwordJoinButton'
|
||||
onPress = { this._onJoinWithPassword }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.passwordJoinButton') }
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
style = { styles.lobbyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -245,44 +236,39 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderStandardButtons() {
|
||||
const { _knocking, _renderPassword, _isLobbyChatActive, t } = this.props;
|
||||
const { _knocking, _renderPassword, _isLobbyChatActive } = this.props;
|
||||
const { displayName } = this.state;
|
||||
const askToJoinButtonStyles
|
||||
= displayName ? styles.primaryButton : styles.primaryButtonDisabled;
|
||||
|
||||
return (
|
||||
<View style = { styles.standardButtonWrapper }>
|
||||
{ _knocking && _isLobbyChatActive && <TouchableOpacity
|
||||
onPress = { this._onNavigateToLobbyChat }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('toolbar.openChat') }
|
||||
</Text>
|
||||
</TouchableOpacity>}
|
||||
{ _knocking || <TouchableOpacity
|
||||
disabled = { !displayName }
|
||||
onPress = { this._onAskToJoin }
|
||||
style = { [
|
||||
styles.button,
|
||||
askToJoinButtonStyles
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.knockButton') }
|
||||
</Text>
|
||||
</TouchableOpacity> }
|
||||
{ _renderPassword && <TouchableOpacity
|
||||
onPress = { this._onSwitchToPasswordMode }
|
||||
style = { [
|
||||
styles.button,
|
||||
styles.primaryButton
|
||||
] }>
|
||||
<Text style = { styles.primaryButtonText }>
|
||||
{ t('lobby.enterPasswordButton') }
|
||||
</Text>
|
||||
</TouchableOpacity> }
|
||||
{
|
||||
_knocking && _isLobbyChatActive
|
||||
&& <Button
|
||||
accessibilityLabel = 'toolbar.openChat'
|
||||
label = 'toolbar.openChat'
|
||||
onPress = { this._onNavigateToLobbyChat }
|
||||
style = { styles.lobbyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
}
|
||||
{
|
||||
_knocking
|
||||
|| <Button
|
||||
accessibilityLabel = 'lobby.knockButton'
|
||||
disabled = { !displayName }
|
||||
label = 'lobby.knockButton'
|
||||
onPress = { this._onAskToJoin }
|
||||
style = { styles.lobbyButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
}
|
||||
{
|
||||
_renderPassword
|
||||
&& <Button
|
||||
accessibilityLabel = 'lobby.enterPasswordButton'
|
||||
label = 'lobby.enterPasswordButton'
|
||||
onPress = { this._onSwitchToPasswordMode }
|
||||
style = { styles.enterPasswordButton }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user