feat(toolbox/native): custom overflow menu buttons (#14594)

* feat(toolbox/native): custom buttons for the OverflowMenu
pull/14618/head jitsi-meet_9430
Calinteodor 1 year ago committed by GitHub
parent b54cec8d77
commit 36671d7c4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java
  2. 7
      android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java
  3. 4
      ios/app/src/ViewController.m
  4. 7
      ios/sdk/src/JitsiMeetViewDelegate.h
  5. 11
      react/features/mobile/external-api/actionTypes.ts
  6. 21
      react/features/mobile/external-api/actions.ts
  7. 25
      react/features/mobile/external-api/middleware.ts
  8. 42
      react/features/toolbox/components/native/CustomOptionButton.tsx
  9. 47
      react/features/toolbox/components/native/OverflowMenu.tsx
  10. 5
      react/features/toolbox/components/native/styles.ts

@ -90,7 +90,8 @@ public class BroadcastEvent {
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"),
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED"),
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE"),
TRANSCRIPTION_CHUNK_RECEIVED("org.jitsi.meet.TRANSCRIPTION_CHUNK_RECEIVED");
TRANSCRIPTION_CHUNK_RECEIVED("org.jitsi.meet.TRANSCRIPTION_CHUNK_RECEIVED"),
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED("org.jitsi.meet.CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED");
private static final String CONFERENCE_BLURRED_NAME = "CONFERENCE_BLURRED";
private static final String CONFERENCE_FOCUSED_NAME = "CONFERENCE_FOCUSED";
@ -108,6 +109,7 @@ public class BroadcastEvent {
private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
private static final String READY_TO_CLOSE_NAME = "READY_TO_CLOSE";
private static final String TRANSCRIPTION_CHUNK_RECEIVED_NAME = "TRANSCRIPTION_CHUNK_RECEIVED";
private static final String CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME = "CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED";
private final String action;
@ -162,6 +164,8 @@ public class BroadcastEvent {
return READY_TO_CLOSE;
case TRANSCRIPTION_CHUNK_RECEIVED_NAME:
return TRANSCRIPTION_CHUNK_RECEIVED;
case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME:
return CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED;
}
return null;

@ -257,6 +257,10 @@ public class JitsiMeetActivity extends AppCompatActivity
// protected void onTranscriptionChunkReceived(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Transcription chunk received: " + extraData);
// }
// protected void onCustomOverflowMenuButtonPressed(HashMap<String, Object> extraData) {
// JitsiMeetLogger.i("Custom overflow menu button pressed: " + extraData);
// }
// Activity lifecycle methods
@ -344,6 +348,9 @@ public class JitsiMeetActivity extends AppCompatActivity
break;
// case TRANSCRIPTION_CHUNK_RECEIVED:
// onTranscriptionChunkReceived(event.getData());
// break;
// case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED:
// onCustomOverflowMenuButtonPressed(event.getData());
// break;
}
}

@ -88,6 +88,10 @@
[self _onJitsiMeetViewDelegateEvent:@"CONFERENCE_WILL_JOIN" withData:data];
}
// - (void)customOverflowMenuButtonPressed:(NSDictionary *)data {
// [self _onJitsiMeetViewDelegateEvent:@"CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED" withData:data];
// }
#if 0
- (void)enterPictureInPicture:(NSDictionary *)data {
[self _onJitsiMeetViewDelegateEvent:@"ENTER_PICTURE_IN_PICTURE" withData:data];

@ -123,4 +123,11 @@
*/
- (void)transcriptionChunkReceived:(NSDictionary *)data;
/**
* Called when the custom overflow menu button is pressed.
*
* The `data` dictionary contains a `id`, `text` key.
*/
- (void)customOverflowMenuButtonPressed:(NSDictionary *)data;
@end

@ -18,3 +18,14 @@ export const READY_TO_CLOSE = 'READY_TO_CLOSE';
*/
export const SCREEN_SHARE_PARTICIPANTS_UPDATED
= 'SCREEN_SHARE_PARTICIPANTS_UPDATED';
/**
* The type of (redux) action which signals that a custom button from the overflow menu was pressed.
*
* @returns {{
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
* id: string,
* text: string
* }}
*/
export const CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED = 'CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED';

@ -1,8 +1,10 @@
import {
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
READY_TO_CLOSE,
SCREEN_SHARE_PARTICIPANTS_UPDATED
} from './actionTypes';
/**
* Creates a (redux) action which signals that the SDK is ready to be closed.
*
@ -33,3 +35,22 @@ export function setParticipantsWithScreenShare(participantIds: Array<string>) {
participantIds
};
}
/**
* Creates a (redux) action which that a custom overflow menu button was pressed.
*
* @param {string} id - The id for the custom button.
* @param {string} text - The label for the custom button.
* @returns {{
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
* id: string,
* text: string
* }}
*/
export function customOverflowMenuButtonPressed(id: string, text: string) {
return {
type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
id,
text
};
}

@ -57,7 +57,10 @@ import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
// @ts-ignore
import { isExternalAPIAvailable } from '../react-native-sdk/functions';
import { READY_TO_CLOSE } from './actionTypes';
import {
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
READY_TO_CLOSE
} from './actionTypes';
import { setParticipantsWithScreenShare } from './actions';
import { participantToParticipantInfo, sendEvent } from './functions';
import logger from './logger';
@ -79,6 +82,12 @@ const CHAT_TOGGLED = 'CHAT_TOGGLED';
*/
const CONFERENCE_TERMINATED = 'CONFERENCE_TERMINATED';
/**
* Event which will be emitted on the native side to indicate that the custom overflow menu button was pressed.
*/
const CUSTOM_MENU_BUTTON_PRESSED = 'CUSTOM_MENU_BUTTON_PRESSED';
/**
* Event which will be emitted on the native side to indicate a message was received
* through the channel.
@ -185,6 +194,20 @@ externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
break;
}
case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED: {
const { id, text } = action;
sendEvent(
store,
CUSTOM_MENU_BUTTON_PRESSED,
{
id,
text
});
break;
}
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;

@ -0,0 +1,42 @@
import React from 'react';
import { Image } from 'react-native';
import { connect } from 'react-redux';
import { translate } from '../../../base/i18n/functions';
import AbstractButton, { IProps as AbstractButtonProps }
from '../../../base/toolbox/components/AbstractButton';
import styles from './styles';
interface IProps extends AbstractButtonProps {
icon: any;
id?: string;
text: string;
}
/**
* Component that renders a custom button.
*
* @returns {Component}
*/
class CustomOptionButton extends AbstractButton<IProps> {
iconSrc = this.props.icon;
id = this.props.id;
text = this.props.text;
/**
* Custom icon component.
*
* @returns {React.Component}
*/
icon = () => (
<Image
source = {{ uri: this.iconSrc }}
style = { styles.iconImageStyles } />
);
label = this.text;
}
export default translate(connect()(CustomOptionButton));

@ -11,6 +11,7 @@ import SettingsButton from '../../../base/settings/components/native/SettingsBut
import BreakoutRoomsButton
from '../../../breakout-rooms/components/native/BreakoutRoomsButton';
import SharedDocumentButton from '../../../etherpad/components/SharedDocumentButton.native';
import { customOverflowMenuButtonPressed } from '../../../mobile/external-api/actions';
import ReactionMenu from '../../../reactions/components/native/ReactionMenu';
import { shouldDisplayReactionsButtons } from '../../../reactions/functions.any';
import LiveStreamButton from '../../../recording/components/LiveStream/native/LiveStreamButton';
@ -27,6 +28,7 @@ import WhiteboardButton from '../../../whiteboard/components/native/WhiteboardBu
import { getMovableButtons } from '../../functions.native';
import AudioOnlyButton from './AudioOnlyButton';
import CustomOptionButton from './CustomOptionButton';
import LinkToSalesforceButton from './LinkToSalesforceButton';
import OpenCarmodeButton from './OpenCarmodeButton';
import RaiseHandButton from './RaiseHandButton';
@ -38,6 +40,11 @@ import ScreenSharingButton from './ScreenSharingButton';
*/
interface IProps {
/**
* Custom Toolbar buttons.
*/
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
/**
* True if breakout rooms feature is available, false otherwise.
*/
@ -145,6 +152,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
renderFooter = { _shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
? this._renderReactionMenu
: undefined }>
{ this._renderCustomOverflowMenuButtons(topButtonProps) }
<OpenCarmodeButton { ...topButtonProps } />
<AudioOnlyButton { ...buttonProps } />
{
@ -187,7 +195,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
/**
* Function to render the reaction menu as the footer of the bottom sheet.
*
* @returns {React$Element}
* @returns {React.ReactElement}
*/
_renderReactionMenu() {
return (
@ -196,6 +204,41 @@ class OverflowMenu extends PureComponent<IProps, IState> {
overflowMenu = { true } />
);
}
/**
* Function to render the custom buttons for the overflow menu.
*
* @param {Object} topButtonProps - Button properties.
* @returns {React.ReactElement}
*/
_renderCustomOverflowMenuButtons(topButtonProps: Object) {
const { _customToolbarButtons, dispatch } = this.props;
if (!_customToolbarButtons?.length) {
return;
}
return (
<>
{
_customToolbarButtons.map(({ id, text, icon, ...rest }) => (
<CustomOptionButton
{ ...rest }
{ ...topButtonProps }
/* eslint-disable react/jsx-no-bind */
handleClick = { () =>
dispatch(customOverflowMenuButtonPressed(id, text))
}
icon = { icon }
key = { id }
text = { text } />
))
}
<Divider style = { styles.divider as ViewStyle } />
</>
);
}
}
/**
@ -207,8 +250,10 @@ class OverflowMenu extends PureComponent<IProps, IState> {
*/
function _mapStateToProps(state: IReduxState) {
const { conference } = state['features/base/conference'];
const { customToolbarButtons } = state['features/base/config'];
return {
_customToolbarButtons: customToolbarButtons,
_isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(),
_isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state),

@ -103,6 +103,11 @@ const styles = {
marginLeft: 'auto',
marginRight: 'auto',
width: '100%'
},
iconImageStyles: {
height: BaseTheme.spacing[5],
width: BaseTheme.spacing[5]
}
};

Loading…
Cancel
Save