1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2026-01-03 21:42:32 +03:00

Add Voice Broadcast labs setting and composer button (#9279)

* Add Voice Broadcast labs setting and composer button

* Implement strict typing

* Extend MessageComposer-test

* Extend tests

* Revert some strict type fixex

* Convert FEATURES to enum; change case

* Use fake timers in MessageComposer-test
This commit is contained in:
Michael Weimann
2022-09-16 11:10:33 +02:00
committed by GitHub
parent 4a23630e06
commit a0c35d088a
13 changed files with 469 additions and 45 deletions

View File

@@ -103,7 +103,7 @@ export class ModalManager {
}
public createDialog<T extends any[]>(
Element: React.ComponentType,
Element: React.ComponentType<any>,
...rest: ParametersWithoutFirst<ModalManager["createDialogAsync"]>
) {
return this.createDialogAsync<T>(Promise.resolve(Element), ...rest);

View File

@@ -28,7 +28,7 @@ import LocationShareMenu from './LocationShareMenu';
interface IProps {
roomId: string;
sender: RoomMember;
menuPosition: AboveLeftOf;
menuPosition?: AboveLeftOf;
relation?: IEventRelation;
}

View File

@@ -52,6 +52,7 @@ import MessageComposerButtons from './MessageComposerButtons';
import { ButtonEvent } from '../elements/AccessibleButton';
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom';
import { Features } from '../../../settings/Settings';
let instanceCount = 0;
@@ -89,10 +90,11 @@ interface IState {
isStickerPickerOpen: boolean;
showStickersButton: boolean;
showPollsButton: boolean;
showVoiceBroadcastButton: boolean;
}
export default class MessageComposer extends React.Component<IProps, IState> {
private dispatcherRef: string;
private dispatcherRef?: string;
private messageComposerInput = createRef<SendMessageComposerClass>();
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
private ref: React.RefObject<HTMLDivElement> = createRef();
@@ -114,17 +116,19 @@ export default class MessageComposer extends React.Component<IProps, IState> {
this.state = {
isComposerEmpty: true,
haveRecording: false,
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
recordingTimeLeftSeconds: undefined, // when set to a number, shows a toast
isMenuOpen: false,
isStickerPickerOpen: false,
showStickersButton: SettingsStore.getValue("MessageComposerInput.showStickersButton"),
showPollsButton: SettingsStore.getValue("MessageComposerInput.showPollsButton"),
showVoiceBroadcastButton: SettingsStore.getValue(Features.VoiceBroadcast),
};
this.instanceId = instanceCount++;
SettingsStore.monitorSetting("MessageComposerInput.showStickersButton", null);
SettingsStore.monitorSetting("MessageComposerInput.showPollsButton", null);
SettingsStore.monitorSetting(Features.VoiceBroadcast, null);
}
private get voiceRecording(): Optional<VoiceRecording> {
@@ -153,7 +157,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
public componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
this.waitForOwnMember();
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current!);
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
this.updateRecordingState(); // grab any cached recordings
}
@@ -199,13 +203,19 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
break;
}
case Features.VoiceBroadcast: {
if (this.state.showVoiceBroadcastButton !== settingUpdatedPayload.newValue) {
this.setState({ showVoiceBroadcastButton: !!settingUpdatedPayload.newValue });
}
break;
}
}
}
}
};
private waitForOwnMember() {
// if we have the member already, do that
// If we have the member already, do that
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
if (me) {
this.setState({ me });
@@ -242,6 +252,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
const viaServers = [this.context.tombstone.getSender().split(':').slice(1).join(':')];
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
highlighted: true,
@@ -426,8 +437,8 @@ export default class MessageComposer extends React.Component<IProps, IState> {
}
let recordingTooltip;
const secondsLeft = Math.round(this.state.recordingTimeLeftSeconds);
if (secondsLeft) {
if (this.state.recordingTimeLeftSeconds) {
const secondsLeft = Math.round(this.state.recordingTimeLeftSeconds);
recordingTooltip = <Tooltip
label={_t("%(seconds)ss left", { seconds: secondsLeft })}
alignment={Alignment.Top}
@@ -484,6 +495,14 @@ export default class MessageComposer extends React.Component<IProps, IState> {
showPollsButton={this.state.showPollsButton}
showStickersButton={this.showStickersButton}
toggleButtonMenu={this.toggleButtonMenu}
showVoiceBroadcastButton={this.state.showVoiceBroadcastButton}
onStartVoiceBroadcastClick={() => {
// Sends a voice message. To be replaced by voice broadcast during development.
this.voiceRecordingButton.current?.onRecordStartEndClick();
if (this.context.narrow) {
this.toggleButtonMenu();
}
}}
/> }
{ showSendButton && (
<SendButton

View File

@@ -45,7 +45,7 @@ interface IProps {
haveRecording: boolean;
isMenuOpen: boolean;
isStickerPickerOpen: boolean;
menuPosition: AboveLeftOf;
menuPosition?: AboveLeftOf;
onRecordStartEndClick: () => void;
relation?: IEventRelation;
setStickerPickerOpen: (isStickerPickerOpen: boolean) => void;
@@ -53,6 +53,8 @@ interface IProps {
showPollsButton: boolean;
showStickersButton: boolean;
toggleButtonMenu: () => void;
showVoiceBroadcastButton: boolean;
onStartVoiceBroadcastClick: () => void;
}
type OverflowMenuCloser = () => void;
@@ -76,7 +78,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
uploadButton(), // props passed via UploadButtonContext
showStickersButton(props),
voiceRecordingButton(props, narrow),
props.showPollsButton && pollButton(room, props.relation),
startVoiceBroadcastButton(props),
props.showPollsButton ? pollButton(room, props.relation) : null,
showLocationButton(props, room, roomId, matrixClient),
];
} else {
@@ -87,7 +90,8 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
moreButtons = [
showStickersButton(props),
voiceRecordingButton(props, narrow),
props.showPollsButton && pollButton(room, props.relation),
startVoiceBroadcastButton(props),
props.showPollsButton ? pollButton(room, props.relation) : null,
showLocationButton(props, room, roomId, matrixClient),
];
}
@@ -265,7 +269,7 @@ const UploadButton = () => {
/>;
};
function showStickersButton(props: IProps): ReactElement {
function showStickersButton(props: IProps): ReactElement | null {
return (
props.showStickersButton
? <CollapsibleButton
@@ -280,7 +284,21 @@ function showStickersButton(props: IProps): ReactElement {
);
}
function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement {
const startVoiceBroadcastButton: React.FC<IProps> = (props: IProps): ReactElement | null => {
return (
props.showVoiceBroadcastButton
? <CollapsibleButton
key="start_voice_broadcast"
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_voiceBroadcast"
onClick={props.onStartVoiceBroadcastClick}
title={_t("Voice broadcast")}
/>
: null
);
};
function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement | null {
// XXX: recording UI does not work well in narrow mode, so hide for now
return (
narrow
@@ -312,7 +330,7 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
this.context?.(); // close overflow menu
const canSend = this.props.room.currentState.maySendEvent(
M_POLL_START.name,
MatrixClientPeg.get().getUserId(),
MatrixClientPeg.get().getUserId()!,
);
if (!canSend) {
Modal.createDialog(
@@ -362,14 +380,16 @@ function showLocationButton(
room: Room,
roomId: string,
matrixClient: MatrixClient,
): ReactElement {
): ReactElement | null {
const sender = room.getMember(matrixClient.getUserId()!);
return (
props.showLocationButton
props.showLocationButton && sender
? <LocationButton
key="location"
roomId={roomId}
relation={props.relation}
sender={room.getMember(matrixClient.getUserId())}
sender={sender}
menuPosition={props.menuPosition}
/>
: null

View File

@@ -34,13 +34,13 @@ function cancelQuoting(context: TimelineRenderingType) {
interface IProps {
permalinkCreator: RoomPermalinkCreator;
replyToEvent: MatrixEvent;
replyToEvent?: MatrixEvent;
}
export default class ReplyPreview extends React.Component<IProps> {
public static contextType = RoomContext;
public render(): JSX.Element {
public render(): JSX.Element | null {
if (!this.props.replyToEvent) return null;
return <div className="mx_ReplyPreview">

View File

@@ -911,6 +911,7 @@
"Sliding Sync mode (under active development, cannot be disabled)": "Sliding Sync mode (under active development, cannot be disabled)",
"Live Location Sharing (temporary implementation: locations persist in room history)": "Live Location Sharing (temporary implementation: locations persist in room history)",
"Favourite Messages (under active development)": "Favourite Messages (under active development)",
"Voice broadcast (under active development)": "Voice broadcast (under active development)",
"Use new session manager (under active development)": "Use new session manager (under active development)",
"Font size": "Font size",
"Use custom size": "Use custom size",
@@ -1813,6 +1814,7 @@
"Emoji": "Emoji",
"Hide stickers": "Hide stickers",
"Sticker": "Sticker",
"Voice broadcast": "Voice broadcast",
"Voice Message": "Voice Message",
"You do not have permission to start polls in this room.": "You do not have permission to start polls in this room.",
"Poll": "Poll",

View File

@@ -101,6 +101,10 @@ export enum LabGroup {
Developer,
}
export enum Features {
VoiceBroadcast = "feature_voice_broadcast",
}
export const labGroupNames: Record<LabGroup, string> = {
[LabGroup.Messaging]: _td("Messaging"),
[LabGroup.Profile]: _td("Profile"),
@@ -435,6 +439,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
displayName: _td("Favourite Messages (under active development)"),
default: false,
},
[Features.VoiceBroadcast]: {
isFeature: true,
labsGroup: LabGroup.Messaging,
supportedLevels: LEVELS_FEATURE,
displayName: _td("Voice broadcast (under active development)"),
default: false,
},
"feature_new_device_manager": {
isFeature: true,
labsGroup: LabGroup.Experimental,