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:
parent
b8e12e581f
commit
85c505a29d
|
@ -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",
|
||||
|
|
|
@ -377,6 +377,13 @@ export const typography = {
|
|||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyLongRegularLarge: {
|
||||
fontSize: 16,
|
||||
lineHeight: 26,
|
||||
fontWeight: font.weightRegular,
|
||||
letterSpacing: 0
|
||||
},
|
||||
|
||||
bodyLongBold: {
|
||||
fontSize: 14,
|
||||
lineHeight: 24,
|
||||
|
|
|
@ -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,7 +26,11 @@ type Props = {
|
|||
createOnPress: Function
|
||||
}
|
||||
|
||||
const AlwaysOnLabels = ({ createOnPress }: Props) => (<>
|
||||
const AlwaysOnLabels = ({ createOnPress }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const openHighlightDialogCallback = useCallback(() => dispatch(openHighlightDialog()), [ dispatch ]);
|
||||
|
||||
return (<>
|
||||
<TouchableOpacity
|
||||
hitSlop = { LabelHitSlop }
|
||||
onPress = { createOnPress(LABEL_ID_RECORDING) } >
|
||||
|
@ -34,11 +41,17 @@ const AlwaysOnLabels = ({ createOnPress }: Props) => (<>
|
|||
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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
|
@ -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%'
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue