add(highlights): mobile flow (#11168)

* add(highlight): mobile initial flow

* fix(hightlight): get meeting fqn on mobile

* fix(dynamic-branding): extract fqn on mobile

* fix(highlights): remove local fqn extraction and grounp dispatches in batch

* fix(dynamic-branding): check if state is defined in extract fqn
This commit is contained in:
Gabriel Borlea 2022-03-18 16:16:56 +02:00 committed by GitHub
parent b8e12e581f
commit 85c505a29d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 248 additions and 22 deletions

View File

@ -865,6 +865,7 @@
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"fileSharingdescription": "Share the recording link with the meeting participants",
"highlight": "Highlight",
"highlightMoment": "Highlight moment",
"highlightMomentDisabled": "You can highlight moments when the recording starts",
"highlightMomentSuccess": "Moment highlighted",

View File

@ -377,6 +377,13 @@ export const typography = {
letterSpacing: 0
},
bodyLongRegularLarge: {
fontSize: 16,
lineHeight: 26,
fontWeight: font.weightRegular,
letterSpacing: 0
},
bodyLongBold: {
fontSize: 14,
lineHeight: 24,

View File

@ -1,10 +1,13 @@
// @flow
import React from 'react';
import React, { useCallback } from 'react';
import { TouchableOpacity } from 'react-native';
import { useDispatch } from 'react-redux';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { RecordingLabel } from '../../../recording';
import { openHighlightDialog } from '../../../recording/actions.native';
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
import {
@ -23,22 +26,32 @@ type Props = {
createOnPress: Function
}
const AlwaysOnLabels = ({ createOnPress }: Props) => (<>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_RECORDING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_STREAMING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_RAISED_HANDS_COUNT) } >
<RaisedHandsCountLabel />
</TouchableOpacity>
</>);
const AlwaysOnLabels = ({ createOnPress }: Props) => {
const dispatch = useDispatch();
const openHighlightDialogCallback = useCallback(() => dispatch(openHighlightDialog()), [ dispatch ]);
return (<>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_RECORDING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_STREAMING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { openHighlightDialogCallback }>
<HighlightButton />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_RAISED_HANDS_COUNT) } >
<RaisedHandsCountLabel />
</TouchableOpacity>
</>);
};
export default AlwaysOnLabels;

View File

@ -6,11 +6,21 @@ import { loadConfig } from '../base/lib-jitsi-meet';
* Extracts the fqn part from a path, where fqn represents
* tenant/roomName.
*
* @param {string} path - The URL path.
* @param {Object} state - A redux state.
* @returns {string}
*/
export function extractFqnFromPath() {
const parts = window.location.pathname.split('/');
export function extractFqnFromPath(state?: Object) {
let pathname;
if (window.location.pathname) {
pathname = window.location.pathname;
} else if (state && state['features/base/connection']) {
pathname = state['features/base/connection'].locationURL.pathname;
} else {
return '';
}
const parts = pathname.split('/');
const len = parts.length;
return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : parts[1];

View File

@ -1,10 +1,24 @@
// @flow
import { openDialog } from '../base/dialog';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import HighlightDialog from './components/Recording/native/HighlightDialog';
export * from './actions.any';
/**
* Opens the highlight dialog.
*
* @returns {Function}
*/
export function openHighlightDialog() {
return (dispatch: Function) => {
dispatch(openDialog(HighlightDialog));
};
}
/**
* Signals that a started recording notification should be shown on the
* screen for a given period.

View File

@ -0,0 +1,63 @@
// @flow
import React from 'react';
import type { Dispatch } from 'redux';
import { translate } from '../../../../base/i18n';
import { IconHighlight } from '../../../../base/icons';
import { Label } from '../../../../base/label';
import { connect } from '../../../../base/redux';
import BaseTheme from '../../../../base/ui/components/BaseTheme';
import AbstractHighlightButton, {
_abstractMapStateToProps,
type Props as AbstractProps
} from '../AbstractHighlightButton';
import styles from '../styles.native';
type Props = AbstractProps & {
_disabled: boolean,
/**
* Flag controlling visibility of the component.
*/
_visible: boolean,
dispatch: Dispatch<any>
};
/**
* React {@code Component} responsible for displaying an action that
* allows users to highlight a meeting moment.
*/
export class HighlightButton extends AbstractHighlightButton<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
_disabled,
_visible,
t
} = this.props;
if (!_visible || _disabled) {
return null;
}
return (
<Label
icon = { IconHighlight }
iconColor = { BaseTheme.palette.field01 }
id = 'highlightMeetingLabel'
style = { styles.highlightButton }
text = { t('recording.highlight') }
textStyle = { styles.highlightButtonText } />
);
}
}
export default translate(connect(_abstractMapStateToProps)(HighlightButton));

View File

@ -0,0 +1,52 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Text, View } from 'react-native';
import { Button } from 'react-native-paper';
import { useDispatch, batch } from 'react-redux';
import { hideDialog, BottomSheet } from '../../../../base/dialog';
import { highlightMeetingMoment } from '../../../actions.any';
import styles from '../styles.native';
const HighlightDialog = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const closeDialog = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const highlightMoment = useCallback(() => {
batch(() => {
dispatch(highlightMeetingMoment());
dispatch(hideDialog());
});
}, [ dispatch ]);
return (
<BottomSheet onCancel = { closeDialog }>
<View style = { styles.highlightDialog }>
<Text style = { styles.highlightDialogHeading }>{ `${t('recording.highlightMoment')}?` }</Text>
<Text style = { styles.highlightDialogText }>
{ t('recording.highlightMomentSucessDescription') }
</Text>
<View style = { styles.highlightDialogButtonsContainer } >
<Button
accessibilityLabel = { t('dialog.Cancel') }
children = { t('dialog.Cancel') }
labelStyle = { styles.highlightDialogCancelLabel }
mode = 'contained'
onPress = { closeDialog }
style = { styles.highlightDialogCancelButton } />
<View style = { styles.highlightDialogButtonsSpace } />
<Button
accessibilityLabel = { t('recording.highlight') }
children = { t('recording.highlight') }
labelStyle = { styles.highlightDialogHighlighLabel }
mode = 'contained'
onPress = { highlightMoment }
style = { styles.highlightDialogHighlightButton } />
</View>
</View>
</BottomSheet>
);
};
export default HighlightDialog;

View File

@ -1,5 +1,6 @@
// @flow
export { default as HighlightButton } from './HighlightButton';
export { default as RecordButton } from './RecordButton';
export { default as StartRecordingDialog } from './StartRecordingDialog';
export { default as StopRecordingDialog } from './StopRecordingDialog';

View File

@ -39,6 +39,17 @@ const title = {
paddingLeft: BoxModel.padding
};
const baseHighlightDialogButton = {
borderRadius: BaseTheme.shape.borderRadius,
height: BaseTheme.spacing[7],
flex: 1
};
const baseHighlightDialogLabel = {
...BaseTheme.typography.bodyShortBoldLarge,
textTransform: 'none'
};
export default {
/**
* Container for the StartRecordingDialog screen.
@ -58,7 +69,61 @@ export default {
startRecordingLabel: {
color: BaseTheme.palette.text01,
marginRight: 12
},
highlightButton: {
backgroundColor: BaseTheme.palette.section01,
flexDirection: 'row',
alignItems: 'center',
marginLeft: BaseTheme.spacing[0],
marginBottom: BaseTheme.spacing[0],
marginRight: BaseTheme.spacing[1]
},
highlightButtonText: {
color: BaseTheme.palette.field01,
paddingLeft: BaseTheme.spacing[2],
...BaseTheme.typography.labelBold
},
highlightDialog: {
paddingLeft: BaseTheme.spacing[3],
paddingRight: BaseTheme.spacing[3],
paddingTop: BaseTheme.spacing[4],
paddingBottom: BaseTheme.spacing[7]
},
highlightDialogHeading: {
...BaseTheme.typography.heading5,
color: BaseTheme.palette.text01,
marginBottom: BaseTheme.spacing[3]
},
highlightDialogText: {
...BaseTheme.typography.bodyLongRegularLarge,
color: BaseTheme.palette.text01,
marginBottom: BaseTheme.spacing[5]
},
highlightDialogButtonsContainer: {
display: 'flex',
flexDirection: 'row'
},
highlightDialogCancelButton: {
...baseHighlightDialogButton,
backgroundColor: BaseTheme.palette.section01
},
highlightDialogHighlightButton: {
...baseHighlightDialogButton,
backgroundColor: BaseTheme.palette.action01
},
highlightDialogCancelLabel: {
...baseHighlightDialogLabel,
color: BaseTheme.palette.field01
},
highlightDialogHighlighLabel: {
...baseHighlightDialogLabel,
color: BaseTheme.palette.text01
},
highlightDialogButtonsSpace: {
width: 16,
height: '100%'
}
};
/**

View File

@ -154,7 +154,7 @@ export async function sendMeetingHighlight(state: Object) {
};
const reqBody = {
meetingFqn: extractFqnFromPath(),
meetingFqn: extractFqnFromPath(state),
sessionId: conference.sessionId,
submitted: Date.now(),
participantId: localParticipant.jwtId,