[Backport staging] Update Voice Broadcast buffering style (#9660)

Co-authored-by: Michael Weimann <michaelw@matrix.org>
This commit is contained in:
ElementRobot 2022-12-01 20:34:57 +00:00 committed by GitHub
parent f4b6719a28
commit fcc49d0959
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 60 deletions

View file

@ -40,8 +40,9 @@ limitations under the License.
display: flex; display: flex;
gap: $spacing-4; gap: $spacing-4;
i { .mx_Spinner {
flex-shrink: 0; flex: 0 0 14px;
padding: 1px;
} }
span { span {

View file

@ -657,6 +657,7 @@
"Change input device": "Change input device", "Change input device": "Change input device",
"Live": "Live", "Live": "Live",
"Voice broadcast": "Voice broadcast", "Voice broadcast": "Voice broadcast",
"Buffering…": "Buffering…",
"Cannot reach homeserver": "Cannot reach homeserver", "Cannot reach homeserver": "Cannot reach homeserver",
"Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin", "Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin",
"Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured", "Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured",

View file

@ -25,6 +25,7 @@ import AccessibleButton from "../../../components/views/elements/AccessibleButto
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg"; import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
import Clock from "../../../components/views/audio_messages/Clock"; import Clock from "../../../components/views/audio_messages/Clock";
import { formatTimeLeft } from "../../../DateUtils"; import { formatTimeLeft } from "../../../DateUtils";
import Spinner from "../../../components/views/elements/Spinner";
interface VoiceBroadcastHeaderProps { interface VoiceBroadcastHeaderProps {
live?: VoiceBroadcastLiveness; live?: VoiceBroadcastLiveness;
@ -33,6 +34,7 @@ interface VoiceBroadcastHeaderProps {
room: Room; room: Room;
microphoneLabel?: string; microphoneLabel?: string;
showBroadcast?: boolean; showBroadcast?: boolean;
showBuffering?: boolean;
timeLeft?: number; timeLeft?: number;
showClose?: boolean; showClose?: boolean;
} }
@ -44,47 +46,55 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
room, room,
microphoneLabel, microphoneLabel,
showBroadcast = false, showBroadcast = false,
showBuffering = false,
showClose = false, showClose = false,
timeLeft, timeLeft,
}) => { }) => {
const broadcast = showBroadcast const broadcast = showBroadcast && (
? <div className="mx_VoiceBroadcastHeader_line"> <div className="mx_VoiceBroadcastHeader_line">
<LiveIcon className="mx_Icon mx_Icon_16" /> <LiveIcon className="mx_Icon mx_Icon_16" />
{ _t("Voice broadcast") } { _t("Voice broadcast") }
</div> </div>
: null; );
const liveBadge = live === "not-live" const liveBadge = live !== "not-live" && (
? null <LiveBadge grey={live === "grey"} />
: <LiveBadge grey={live === "grey"} />; );
const closeButton = showClose const closeButton = showClose && (
? <AccessibleButton onClick={onCloseClick}> <AccessibleButton onClick={onCloseClick}>
<XIcon className="mx_Icon mx_Icon_16" /> <XIcon className="mx_Icon mx_Icon_16" />
</AccessibleButton> </AccessibleButton>
: null; );
const timeLeftLine = timeLeft const timeLeftLine = timeLeft && (
? <div className="mx_VoiceBroadcastHeader_line"> <div className="mx_VoiceBroadcastHeader_line">
<TimerIcon className="mx_Icon mx_Icon_16" /> <TimerIcon className="mx_Icon mx_Icon_16" />
<Clock formatFn={formatTimeLeft} seconds={timeLeft} /> <Clock formatFn={formatTimeLeft} seconds={timeLeft} />
</div> </div>
: null; );
const buffering = showBuffering && (
<div className="mx_VoiceBroadcastHeader_line">
<Spinner w={14} h={14} />
{ _t("Buffering…") }
</div>
);
const microphoneLineClasses = classNames({ const microphoneLineClasses = classNames({
mx_VoiceBroadcastHeader_line: true, mx_VoiceBroadcastHeader_line: true,
["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick, ["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick,
}); });
const microphoneLine = microphoneLabel const microphoneLine = microphoneLabel && (
? <div <div
className={microphoneLineClasses} className={microphoneLineClasses}
onClick={onMicrophoneLineClick} onClick={onMicrophoneLineClick}
> >
<MicrophoneIcon className="mx_Icon mx_Icon_16" /> <MicrophoneIcon className="mx_Icon mx_Icon_16" />
<span>{ microphoneLabel }</span> <span>{ microphoneLabel }</span>
</div> </div>
: null; );
return <div className="mx_VoiceBroadcastHeader"> return <div className="mx_VoiceBroadcastHeader">
<RoomAvatar room={room} width={32} height={32} /> <RoomAvatar room={room} width={32} height={32} />
@ -95,6 +105,7 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
{ microphoneLine } { microphoneLine }
{ timeLeftLine } { timeLeftLine }
{ broadcast } { broadcast }
{ buffering }
</div> </div>
{ liveBadge } { liveBadge }
{ closeButton } { closeButton }

View file

@ -23,7 +23,6 @@ import {
VoiceBroadcastPlayback, VoiceBroadcastPlayback,
VoiceBroadcastPlaybackState, VoiceBroadcastPlaybackState,
} from "../.."; } from "../..";
import Spinner from "../../../components/views/elements/Spinner";
import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback"; import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback";
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg"; import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg"; import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
@ -55,40 +54,35 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
toggle, toggle,
} = useVoiceBroadcastPlayback(playback); } = useVoiceBroadcastPlayback(playback);
let control: React.ReactNode; let controlIcon: React.FC<React.SVGProps<SVGSVGElement>>;
let controlLabel: string;
let className = "";
if (playbackState === VoiceBroadcastPlaybackState.Buffering) { switch (playbackState) {
control = <Spinner />; case VoiceBroadcastPlaybackState.Stopped:
} else { controlIcon = PlayIcon;
let controlIcon: React.FC<React.SVGProps<SVGSVGElement>>; className = "mx_VoiceBroadcastControl-play";
let controlLabel: string; controlLabel = _t("play voice broadcast");
let className = ""; break;
case VoiceBroadcastPlaybackState.Paused:
switch (playbackState) { controlIcon = PlayIcon;
case VoiceBroadcastPlaybackState.Stopped: className = "mx_VoiceBroadcastControl-play";
controlIcon = PlayIcon; controlLabel = _t("resume voice broadcast");
className = "mx_VoiceBroadcastControl-play"; break;
controlLabel = _t("play voice broadcast"); case VoiceBroadcastPlaybackState.Buffering:
break; case VoiceBroadcastPlaybackState.Playing:
case VoiceBroadcastPlaybackState.Paused: controlIcon = PauseIcon;
controlIcon = PlayIcon; controlLabel = _t("pause voice broadcast");
className = "mx_VoiceBroadcastControl-play"; break;
controlLabel = _t("resume voice broadcast");
break;
case VoiceBroadcastPlaybackState.Playing:
controlIcon = PauseIcon;
controlLabel = _t("pause voice broadcast");
break;
}
control = <VoiceBroadcastControl
className={className}
label={controlLabel}
icon={controlIcon}
onClick={toggle}
/>;
} }
const control = <VoiceBroadcastControl
className={className}
label={controlLabel}
icon={controlIcon}
onClick={toggle}
/>;
let seekBackwardButton: ReactElement | null = null; let seekBackwardButton: ReactElement | null = null;
let seekForwardButton: ReactElement | null = null; let seekForwardButton: ReactElement | null = null;
@ -125,7 +119,8 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
live={liveness} live={liveness}
microphoneLabel={sender?.name} microphoneLabel={sender?.name}
room={room} room={room}
showBroadcast={true} showBroadcast={playbackState !== VoiceBroadcastPlaybackState.Buffering}
showBuffering={playbackState === VoiceBroadcastPlaybackState.Buffering}
/> />
<div className="mx_VoiceBroadcastBody_controls"> <div className="mx_VoiceBroadcastBody_controls">
{ seekBackwardButton } { seekBackwardButton }

View file

@ -27,6 +27,13 @@ import {
export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => { export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const room = client.getRoom(playback.infoEvent.getRoomId()); const room = client.getRoom(playback.infoEvent.getRoomId());
if (!room) {
throw new Error(
`Voice Broadcast room not found (event ${playback.infoEvent.getId()})`,
);
}
const playbackToggle = () => { const playbackToggle = () => {
playback.toggle(); playback.toggle();
}; };

View file

@ -35,12 +35,17 @@ describe("VoiceBroadcastHeader", () => {
const sender = new RoomMember(roomId, userId); const sender = new RoomMember(roomId, userId);
let container: Container; let container: Container;
const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast: boolean = undefined): RenderResult => { const renderHeader = (
live: VoiceBroadcastLiveness,
showBroadcast?: boolean,
buffering?: boolean,
): RenderResult => {
return render(<VoiceBroadcastHeader return render(<VoiceBroadcastHeader
live={live} live={live}
microphoneLabel={sender.name} microphoneLabel={sender.name}
room={room} room={room}
showBroadcast={showBroadcast} showBroadcast={showBroadcast}
showBuffering={buffering}
/>); />);
}; };
@ -51,6 +56,16 @@ describe("VoiceBroadcastHeader", () => {
}); });
describe("when rendering a live broadcast header with broadcast info", () => { describe("when rendering a live broadcast header with broadcast info", () => {
beforeEach(() => {
container = renderHeader("live", true, true).container;
});
it("should render the header with a red live badge", () => {
expect(container).toMatchSnapshot();
});
});
describe("when rendering a buffering live broadcast header with broadcast info", () => {
beforeEach(() => { beforeEach(() => {
container = renderHeader("live", true).container; container = renderHeader("live", true).container;
}); });

View file

@ -1,5 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VoiceBroadcastHeader when rendering a buffering live broadcast header with broadcast info should render the header with a red live badge 1`] = `
<div>
<div
class="mx_VoiceBroadcastHeader"
>
<div
data-testid="room-avatar"
>
room avatar:
!room:example.com
</div>
<div
class="mx_VoiceBroadcastHeader_content"
>
<div
class="mx_VoiceBroadcastHeader_room"
>
!room:example.com
</div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Icon mx_Icon_16"
/>
<span>
test user
</span>
</div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Icon mx_Icon_16"
/>
Voice broadcast
</div>
</div>
<div
class="mx_LiveBadge"
>
<div
class="mx_Icon mx_Icon_16"
/>
Live
</div>
</div>
</div>
`;
exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = ` exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = `
<div> <div>
<div <div
@ -87,6 +137,22 @@ exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadc
/> />
Voice broadcast Voice broadcast
</div> </div>
<div
class="mx_VoiceBroadcastHeader_line"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading..."
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 14px; height: 14px;"
/>
</div>
Buffering…
</div>
</div> </div>
<div <div
class="mx_LiveBadge" class="mx_LiveBadge"

View file

@ -242,9 +242,17 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
class="mx_VoiceBroadcastHeader_line" class="mx_VoiceBroadcastHeader_line"
> >
<div <div
class="mx_Icon mx_Icon_16" class="mx_Spinner"
/> >
Voice broadcast <div
aria-label="Loading..."
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 14px; height: 14px;"
/>
</div>
Buffering…
</div> </div>
</div> </div>
<div <div
@ -270,14 +278,13 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
/> />
</div> </div>
<div <div
class="mx_Spinner" aria-label="pause voice broadcast"
class="mx_AccessibleButton mx_VoiceBroadcastControl"
role="button"
tabindex="0"
> >
<div <div
aria-label="Loading..." class="mx_Icon mx_Icon_16"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/> />
</div> </div>
<div <div