Compare commits

...

11 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
dbabdf3351 chore(rn,versions) bump app versions 2022-03-29 15:25:27 +02:00
Saúl Ibarra Corretgé
3bcde982ac chore(rn,versions) bump SDK versions 2022-03-29 15:24:08 +02:00
Saúl Ibarra Corretgé
beb301e691 fix(android,back-button) rework back button handling on Android
We used to have a registry which registered a single handlerwith RN.
THis was registered really early in the app.

When React Navigation was introduced we ddidn't realize it interacts
with the back button. In a stack nagigator it will navigate to the
previous screen. This meant our back button handling was broken.

This commit removes our previous registry and uses the RN back button
handler directly in the 2 components that use it: the conference and
bottom sheets.

Since these handlers are registered after navigation, our handlers are
going to run first so we cna implement the behavior we need, namely to
dismiss an open botom sheet or set the conference in PiP mode.
2022-03-29 15:21:40 +02:00
Calin Chitu
d0836ff651 feat(participants/native) - fix joining breakout room 2022-03-29 15:19:45 +02:00
Saúl Ibarra Corretgé
c028511aaf fix(rn,lobby) fix lobby not showing up on subsequent tries
We need to make sure to hide it explicitly so the Redux state is in sync
with reality.
2022-03-29 15:19:44 +02:00
Saúl Ibarra Corretgé
664552bc05 chore(rn,versions) bump SDK versions 2022-03-28 11:22:36 +02:00
Saúl Ibarra Corretgé
ac35eea08e feat(ios) enable Dropbox recording 2022-03-25 17:24:06 +01:00
Saúl Ibarra Corretgé
172683d645 fix(rn,recording) fix recording dialog state not updating 2022-03-25 17:23:30 +01:00
Calinteodor
858e83b09e fix(rn,recording) fix start button not being enabled 2022-03-25 11:53:30 +02:00
Calin Chitu
e2750ee58e feat(participants/native) - updated container styles 2022-03-25 11:51:01 +02:00
Calin Chitu
fe132581d4 fix(mobile/navigation) - fixed bottom color glitch 2022-03-23 19:09:52 +02:00
24 changed files with 148 additions and 183 deletions

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=22.1.0
sdkVersion=5.0.0
appVersion=22.1.1
sdkVersion=5.0.2

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>22.1.0</string>
<string>22.1.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -37,13 +37,8 @@
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
[builder setFeatureFlag:@"resolution" withValue:@(360)];
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
// Apple rejected our app because they claim requiring a
// Dropbox account for recording is not acceptable.
#if DEBUG
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
#endif
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
}];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>22.1.0</string>
<string>22.1.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>22.1.0</string>
<string>22.1.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>22.1.0</string>
<string>22.1.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>5.0.0</string>
<string>5.0.2</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -2,7 +2,6 @@
import '../authentication/middleware';
import '../mobile/audio-mode/middleware';
import '../mobile/back-button/middleware';
import '../mobile/background/middleware';
import '../mobile/call-integration/middleware';
import '../mobile/external-api/middleware';

View File

@@ -3,12 +3,12 @@
import React, { PureComponent, type Node } from 'react';
import {
Animated,
BackHandler,
Dimensions,
TouchableWithoutFeedback,
View
} from 'react-native';
import { BackButtonRegistry } from '../../../../mobile/back-button';
import { type StyleType } from '../../../styles';
import styles from './slidingviewstyles';
@@ -121,7 +121,7 @@ export default class SlidingView extends PureComponent<Props, State> {
* @inheritdoc
*/
componentDidMount() {
BackButtonRegistry.addListener(this._onHardwareBackPress, true);
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
this._mounted = true;
this._setShow(this.props.show);
@@ -146,7 +146,7 @@ export default class SlidingView extends PureComponent<Props, State> {
* @inheritdoc
*/
componentWillUnmount() {
BackButtonRegistry.removeListener(this._onHardwareBackPress);
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
this._mounted = false;
}
@@ -229,13 +229,9 @@ export default class SlidingView extends PureComponent<Props, State> {
* @returns {boolean}
*/
_onHardwareBackPress() {
const { onHide } = this.props;
this._onHide();
if (typeof onHide === 'function') {
return onHide();
}
return false;
return true;
}
_onHide: () => void;

View File

@@ -1,7 +1,7 @@
// @flow
import React from 'react';
import { NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
import { BackHandler, NativeModules, SafeAreaView, StatusBar, View } from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { appNavigate } from '../../../app/actions';
@@ -22,7 +22,6 @@ import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { KnockingParticipantList } from '../../../lobby/components/native';
import { getIsLobbyVisible } from '../../../lobby/functions';
import { BackButtonRegistry } from '../../../mobile/back-button';
import { navigate }
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
@@ -166,7 +165,7 @@ class Conference extends AbstractConference<Props, State> {
* @returns {void}
*/
componentDidMount() {
BackButtonRegistry.addListener(this._onHardwareBackPress);
BackHandler.addEventListener('hardwareBackPress', this._onHardwareBackPress);
}
/**
@@ -196,7 +195,7 @@ class Conference extends AbstractConference<Props, State> {
*/
componentWillUnmount() {
// Tear handling any hardware button presses for back navigation down.
BackButtonRegistry.removeListener(this._onHardwareBackPress);
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
clearTimeout(this._expandedLabelTimeout.current);
}

View File

@@ -1,9 +1,9 @@
// @flow
import { type Dispatch } from 'redux';
import { batch } from 'react-redux';
import { appNavigate } from '../app/actions';
import { hideLobbyScreen, setKnockingState } from './actions.any';
export * from './actions.any';
/**
@@ -12,8 +12,11 @@ export * from './actions.any';
* @returns {Function}
*/
export function cancelKnocking() {
return async (dispatch: Dispatch<any>) => {
dispatch(appNavigate(undefined));
return dispatch => {
batch(() => {
dispatch(setKnockingState(false));
dispatch(hideLobbyScreen());
dispatch(appNavigate(undefined));
});
};
}

View File

@@ -58,7 +58,7 @@ export function showLobbyChatButton(
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
const conference = getCurrentConference(state);
const lobbyLocalId = conference.myLobbyUserId();
const lobbyLocalId = conference?.myLobbyUserId();
if (!enableLobbyChat) {
return false;

View File

@@ -1,66 +0,0 @@
// @flow
/**
* An registry that dispatches hardware back button events for subscribers with a custom logic.
*/
class BackButtonRegistry {
_listeners: Array<Function>;
/**
* Instantiates a new instance of the registry.
*/
constructor() {
this._listeners = [];
}
/**
* Adds a listener to the registry.
*
* NOTE: Due to the different order of component mounts, we allow a component to register
* its listener to the top of the list, so then that will be invoked before other, 'non-top'
* listeners. For example a 'non-top' listener can be the one that puts the app into PiP mode,
* while a 'top' listener is the one that closes a modal in a conference.
*
* @param {Function} listener - The listener function.
* @param {boolean?} top - If true, the listener will be put on the top (eg for modal-like components).
* @returns {void}
*/
addListener(listener: Function, top: boolean = false) {
if (top) {
this._listeners.splice(0, 0, listener);
} else {
this._listeners.push(listener);
}
}
/**
* Removes a listener from the registry.
*
* @param {Function} listener - The listener to remove.
* @returns {void}
*/
removeListener(listener: Function) {
this._listeners = this._listeners.filter(f => f !== listener);
}
onHardwareBackPress: () => boolean;
/**
* Callback for the back button press event.
*
* @returns {boolean}
*/
onHardwareBackPress() {
for (const listener of this._listeners) {
const result = listener();
if (result === true) {
return true;
}
}
return false;
}
}
export default new BackButtonRegistry();

View File

@@ -1,3 +0,0 @@
// @flow
export { default as BackButtonRegistry } from './BackButtonRegistry';

View File

@@ -1,36 +0,0 @@
// @flow
import { BackHandler } from 'react-native';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app';
import { MiddlewareRegistry } from '../../base/redux';
import BackButtonRegistry from './BackButtonRegistry';
// Binding function to the proper instance, so then the event emitter won't replace the
// underlying instance.
BackButtonRegistry.onHardwareBackPress = BackButtonRegistry.onHardwareBackPress.bind(BackButtonRegistry);
/**
* Middleware that captures App lifetime actions and subscribes to application
* state changes. When the application state changes it will fire the action
* required to mute or unmute the local video in case the application goes to
* the background or comes back from it.
*
* @param {Store} store - The redux store.
* @returns {Function}
* @see {@link https://facebook.github.io/react-native/docs/appstate.html}
*/
MiddlewareRegistry.register(() => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
BackHandler.addEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
break;
case APP_WILL_UNMOUNT:
BackHandler.removeEventListener('hardwareBackPress', BackButtonRegistry.onHardwareBackPress);
break;
}
return next(action);
});

View File

@@ -1,5 +1,3 @@
import { StyleSheet } from 'react-native';
import { BoxModel } from '../../../base/styles';
import BaseTheme from '../../../base/ui/components/BaseTheme';
@@ -11,13 +9,12 @@ export const TEXT_COLOR = BaseTheme.palette.text01;
*/
export const navigationStyles = {
connectingScreenContainer: {
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1
},
connectingScreenContent: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1,
flexDirection: 'column',
justifyContent: 'center'

View File

@@ -71,8 +71,8 @@ export default {
},
transparentButton: {
...baseButton,
backgroundColor: 'transparent'
backgroundColor: 'transparent',
marginTop: BaseTheme.spacing[3]
},
leaveButtonLabel: {

View File

@@ -31,7 +31,7 @@ type Props = {
}
const CollapsibleList = ({ children, containerStyle, onLongPress, title }: Props) => {
const [ collapsed, setCollapsed ] = useState(true);
const [ collapsed, setCollapsed ] = useState(false);
const _toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [ collapsed ]);

View File

@@ -50,11 +50,21 @@ type Props = {
*/
_sortedRemoteParticipants: Map<string, string>,
/**
* List of breakout rooms that were created.
*/
breakoutRooms: Array,
/**
* The redux dispatch function.
*/
dispatch: Function,
/**
* List of participants waiting in lobby.
*/
lobbyParticipants: Array,
/**
* Participants search string.
*/
@@ -180,6 +190,8 @@ class MeetingParticipantList extends PureComponent<Props> {
_participantsCount,
_showInviteButton,
_sortedRemoteParticipants,
breakoutRooms,
lobbyParticipants,
t
} = this.props;
const title = _currentRoom?.name
@@ -192,12 +204,19 @@ class MeetingParticipantList extends PureComponent<Props> {
// Regarding the fact that we have 3 sections, we apply
// a certain height percentage for every section in order for all to fit
// inside the participants pane container
// If there are only meeting participants available,
// we take the full container height
const onlyMeetingParticipants
= breakoutRooms?.length === 0 && lobbyParticipants.length === 0;
const containerStyle
= _participantsCount > 3 && styles.meetingListContainer;
= onlyMeetingParticipants
? styles.meetingListFullContainer : styles.meetingListContainer;
const finalContainerStyle
= _participantsCount > 3 && containerStyle;
return (
<CollapsibleList
containerStyle = { containerStyle }
containerStyle = { finalContainerStyle }
title = { title } >
{
_showInviteButton

View File

@@ -19,6 +19,10 @@ import {
getCurrentRoomId,
isInBreakoutRoom
} from '../../../breakout-rooms/functions';
import {
getKnockingParticipants,
getLobbyEnabled
} from '../../../lobby/functions';
import MuteEveryoneDialog
from '../../../video-menu/components/native/MuteEveryoneDialog';
import {
@@ -63,11 +67,15 @@ const ParticipantsPane = () => {
&& participantsCount > 2 && rooms.length > 1;
const addBreakoutRoom
= _isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator;
const lobbyEnabled = useSelector(getLobbyEnabled);
const lobbyParticipants = useSelector(getKnockingParticipants);
return (
<JitsiScreen style = { styles.participantsPaneContainer }>
<LobbyParticipantList />
<MeetingParticipantList
breakoutRooms = { _isBreakoutRoomsSupported && rooms }
lobbyParticipants = { lobbyEnabled && lobbyParticipants }
searchString = { searchString }
setSearchString = { setSearchString } />
{

View File

@@ -196,7 +196,7 @@ export default {
},
lobbyListContent: {
height: '20%'
height: '16%'
},
lobbyListDescription: {
@@ -217,7 +217,11 @@ export default {
},
meetingListContainer: {
height: '60%'
height: '56%'
},
meetingListFullContainer: {
height: '82%'
},
meetingListDescription: {

View File

@@ -379,10 +379,7 @@ class StartRecordingDialogContent extends Component<Props> {
<Container>
<Container
className = 'recording-header recording-header-line'
style = { [
styles.headerIntegrations,
_dialogStyles.topBorderContainer
] }>
style = { styles.headerIntegrations }>
<Container
className = 'recording-icon-container'>
<Image

View File

@@ -9,6 +9,7 @@ import HeaderNavigationButton
from '../../../../mobile/navigation/components/HeaderNavigationButton';
import { goBack } from
'../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { RECORDING_TYPES } from '../../../constants';
import AbstractStartRecordingDialog, {
type Props,
mapStateToProps
@@ -43,30 +44,38 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
* @returns {void}
*/
componentDidMount() {
const {
_fileRecordingsServiceEnabled,
_isDropboxEnabled,
navigation,
t
} = this.props;
super.componentDidMount();
const {
isTokenValid,
isValidating
} = this.state;
// disable start button id recording service is shown only, when
// validating dropbox token, if that is not enabled we either always
// show the start button or if just dropbox is enabled start is available
// when there is token
const isStartDisabled
= _fileRecordingsServiceEnabled ? isValidating
: _isDropboxEnabled ? !isTokenValid : false;
const { navigation, t } = this.props;
navigation.setOptions({
headerRight: () => (
<HeaderNavigationButton
disabled = { isStartDisabled }
disabled = { this.isStartRecordingDisabled() }
label = { t('dialog.start') }
onPress = { this._onStartPress }
twoActions = { true } />
)
});
}
/**
* Implements React's {@link Component#componentDidUpdate()}. Invoked
* immediately after this component is updated.
*
* @inheritdoc
* @returns {void}
*/
componentDidUpdate(prevProps) {
super.componentDidUpdate(prevProps);
const { navigation, t } = this.props;
navigation.setOptions({
// eslint-disable-next-line react/no-multi-comp
headerRight: () => (
<HeaderNavigationButton
disabled = { this.isStartRecordingDisabled() }
label = { t('dialog.start') }
onPress = { this._onStartPress }
twoActions = { true } />
@@ -85,6 +94,30 @@ class StartRecordingDialog extends AbstractStartRecordingDialog<Props> {
this._onSubmit() && goBack();
}
isStartRecordingDisabled: () => boolean;
/**
* Disables start recording button.
*
* @returns {boolean}
*/
isStartRecordingDisabled() {
const { isTokenValid, selectedRecordingService } = this.state;
// Start button is disabled if recording service is only shown;
// When validating dropbox token, if that is not enabled, we either always
// show the start button or, if just dropbox is enabled, start button
// is available when there is token.
if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) {
return false;
} else if (selectedRecordingService === RECORDING_TYPES.DROPBOX) {
return !isTokenValid;
}
return true;
}
/**
* Implements React's {@link Component#render()}.
*

View File

@@ -7,6 +7,7 @@ import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux';
import { toggleScreenshotCaptureSummary } from '../../../../screenshot-capture';
import { isScreenshotCaptureEnabled } from '../../../../screenshot-capture/functions';
import { RECORDING_TYPES } from '../../../constants';
import AbstractStartRecordingDialog, {
mapStateToProps as abstractMapStateToProps
} from '../AbstractStartRecordingDialog';
@@ -19,6 +20,30 @@ import StartRecordingDialogContent from '../StartRecordingDialogContent';
* @augments Component
*/
class StartRecordingDialog extends AbstractStartRecordingDialog {
isStartRecordingDisabled: () => boolean;
/**
* Disables start recording button.
*
* @returns {boolean}
*/
isStartRecordingDisabled() {
const { isTokenValid, selectedRecordingService } = this.state;
// Start button is disabled if recording service is only shown;
// When validating dropbox token, if that is not enabled, we either always
// show the start button or, if just dropbox is enabled, start button
// is available when there is token.
if (selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) {
return false;
} else if (selectedRecordingService === RECORDING_TYPES.DROPBOX) {
return !isTokenValid;
}
return true;
}
/**
* Implements React's {@link Component#render()}.
*
@@ -33,19 +58,14 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
spaceLeft,
userName
} = this.state;
const { _fileRecordingsServiceEnabled, _fileRecordingsServiceSharingEnabled, _isDropboxEnabled } = this.props;
// disable ok button id recording service is shown only, when
// validating dropbox token, if that is not enabled we either always
// show the ok button or if just dropbox is enabled ok is available
// when there is token
const isOkDisabled
= _fileRecordingsServiceEnabled ? isValidating
: _isDropboxEnabled ? !isTokenValid : false;
const {
_fileRecordingsServiceEnabled,
_fileRecordingsServiceSharingEnabled
} = this.props;
return (
<Dialog
okDisabled = { isOkDisabled }
okDisabled = { this.isStartRecordingDisabled() }
okKey = 'dialog.startRecording'
onSubmit = { this._onSubmit }
titleKey = 'dialog.startRecording'