diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3b6d14f14c58..e9aa3ecf7c21 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -4495,21 +4495,21 @@ "message": "Playback time of audio attachment", "description": "Aria label for audio attachment's playback time slider" }, - "MessageAudio--playbackRate1x": { - "message": "1x", - "description": "Button in the voice note message widget that shows the current playback rate of 1x (regular speed) and allows the user to toggle to the next rate" + "MessageAudio--playbackRate1": { + "message": "1", + "description": "Button in the voice note message widget that shows the current playback rate of 1x (regular speed) and allows the user to toggle to the next rate. Don't include the 'x'." }, - "MessageAudio--playbackRate1p5x": { - "message": "1.5x", - "description": "Button in the voice note message widget that shows the current playback rate of 1.5x (%50 faster) and allows the user to toggle to the next rate" + "MessageAudio--playbackRate1p5": { + "message": "1.5", + "description": "Button in the voice note message widget that shows the current playback rate of 1.5x (%50 faster) and allows the user to toggle to the next rate. Don't include the 'x'." }, - "MessageAudio--playbackRate2x": { - "message": "2x", - "description": "Button in the voice note message widget that shows the current playback rate of 2x (double speed) and allows the user to toggle to the next rate" + "MessageAudio--playbackRate2": { + "message": "2", + "description": "Button in the voice note message widget that shows the current playback rate of 2x (double speed) and allows the user to toggle to the next rate. Don't include the 'x'." }, - "MessageAudio--playbackRatep5x": { - "message": ".5x", - "description": "Button in the voice note message widget that shows the current playback rate of .5x (half speed) and allows the user to toggle to the next rate" + "MessageAudio--playbackRatep5": { + "message": ".5", + "description": "Button in the voice note message widget that shows the current playback rate of .5x (half speed) and allows the user to toggle to the next rate. Don't include the 'x'." }, "emptyInboxMessage": { "message": "Click the $composeIcon$ above and search for your contacts or groups to message.", diff --git a/images/icons/v2/x-8.svg b/images/icons/v2/x-8.svg new file mode 100644 index 000000000000..b5e893ea93fd --- /dev/null +++ b/images/icons/v2/x-8.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/stylesheets/components/MessageAudio.scss b/stylesheets/components/MessageAudio.scss index 9a9f50a3b9e4..bd1549b22178 100644 --- a/stylesheets/components/MessageAudio.scss +++ b/stylesheets/components/MessageAudio.scss @@ -54,6 +54,7 @@ $audio-attachment-button-margin-small: 4px; align-items: center; justify-content: flex-start; transition: width 100ms ease-out; + width: 14px; &:before { content: ''; @@ -61,17 +62,10 @@ $audio-attachment-button-margin-small: 4px; width: 6px; height: 6px; border-radius: 100%; - transition: background 100ms ease-out; } - &--unplayed { - width: 14px; - } - &--played { - width: 0px; - } .module-message__audio-attachment--incoming & { - &--unplayed:before { + &:before { @include light-theme { background: $color-gray-60; } @@ -85,9 +79,6 @@ $audio-attachment-button-margin-small: 4px; background: $color-white-alpha-80; } } - &--played:before { - background: transparent; - } } .module-message__audio-attachment__playback-rate-button { @@ -120,9 +111,33 @@ $audio-attachment-button-margin-small: 4px; color: $color-white-alpha-80; background: $color-white-alpha-20; } + + &::after { + content: ''; + display: inline-block; + width: 8px; + height: 8px; + margin-left: 2px; + + @mixin x-icon($color) { + @include color-svg('../images/icons/v2/x-8.svg', $color, false); + } + + .module-message__audio-attachment--incoming & { + @include light-theme { + @include x-icon($color-gray-60); + } + @include dark-theme { + @include x-icon($color-gray-25); + } + } + .module-message__audio-attachment--outgoing & { + @include x-icon($color-white-alpha-80); + } + } } -.module-message__audio-attachment__button, +.module-message__audio-attachment__play-button, .module-message__audio-attachment__spinner { @include button-reset; @@ -195,7 +210,7 @@ $audio-attachment-button-margin-small: 4px; outline: 0; } -.module-message__audio-attachment__button, +.module-message__audio-attachment__play-button, .module-message__audio-attachment__playback-rate-button, .module-message__audio-attachment__spinner, .module-message__audio-attachment__waveform { diff --git a/ts/components/conversation/Message.stories.tsx b/ts/components/conversation/Message.stories.tsx index a848c4bca497..3e9f8e117ba4 100644 --- a/ts/components/conversation/Message.stories.tsx +++ b/ts/components/conversation/Message.stories.tsx @@ -163,7 +163,10 @@ const MessageAudioContainer: React.FC = ({ audio.play(); setPlaying(true); } - audio.currentTime = audio.duration * position; + + if (!Number.isNaN(audio.duration)) { + audio.currentTime = audio.duration * position; + } if (!Number.isNaN(audio.currentTime)) { setCurrentTime(audio.currentTime); } diff --git a/ts/components/conversation/MessageAudio.tsx b/ts/components/conversation/MessageAudio.tsx index 3adec475f021..f46cabed3438 100644 --- a/ts/components/conversation/MessageAudio.tsx +++ b/ts/components/conversation/MessageAudio.tsx @@ -4,6 +4,7 @@ import React, { useRef, useEffect, useState } from 'react'; import classNames from 'classnames'; import { noop } from 'lodash'; +import { animated, useSpring } from '@react-spring/web'; import type { LocalizerType } from '../../types/Util'; import type { AttachmentType } from '../../types/Attachment'; @@ -35,7 +36,6 @@ export type OwnProps = Readonly<{ status?: MessageStatusType; textPending?: boolean; timestamp: number; - buttonRef: React.RefObject; kickOffAttachmentDownload(): void; onCorrupted(): void; onFirstPlayed(): void; @@ -59,11 +59,13 @@ export type Props = OwnProps & DispatchProps; type ButtonProps = { i18n: LocalizerType; - buttonRef: React.RefObject; - - mod: string; + variant: 'play' | 'playback-rate'; + mod?: string; label: string; + visible?: boolean; onClick: () => void; + onMouseDown?: () => void; + onMouseUp?: () => void; }; enum State { @@ -89,6 +91,15 @@ const BIG_INCREMENT = 5; const PLAYBACK_RATES = [1, 1.5, 2, 0.5]; +const SPRING_DEFAULTS = { + mass: 0.5, + tension: 350, + friction: 20, + velocity: 0.01, +}; + +const DOT_DIV_WIDTH = 14; + // Utils const timeToText = (time: number): string => { @@ -107,8 +118,28 @@ const timeToText = (time: number): string => { return hours ? `${hours}:${minutes}:${seconds}` : `${minutes}:${seconds}`; }; +/** + * Handles animations, key events, and stoping event propagation + * for play button and playback rate button + */ const Button: React.FC = props => { - const { i18n, buttonRef, mod, label, onClick } = props; + const { + i18n, + variant, + mod, + label, + children, + onClick, + visible = true, + } = props; + const [isDown, setIsDown] = useState(false); + + const animProps = useSpring({ + ...SPRING_DEFAULTS, + from: isDown ? { scale: 1 } : { scale: 0 }, + to: isDown ? { scale: 1.3 } : { scale: visible ? 1 : 0 }, + }); + // Clicking button toggle playback const onButtonClick = (event: React.MouseEvent) => { event.stopPropagation(); @@ -129,17 +160,59 @@ const Button: React.FC = props => { }; return ( - + + ); +}; + +const PlayedDot = ({ + played, + onHide, +}: { + played: boolean; + onHide: () => void; +}) => { + const start = played ? 1 : 0; + const end = played ? 0 : 1; + + const [animProps] = useSpring( + { + ...SPRING_DEFAULTS, + from: { scale: start, opacity: start, width: start }, + to: { scale: end, opacity: end, width: end * DOT_DIV_WIDTH }, + onRest: () => { + if (played) { + onHide(); + } + }, + }, + [played] + ); + + return ( +