Show "unplayed" dot on incoming audio messages

This commit is contained in:
Evan Hahn 2021-08-12 13:15:55 -05:00 committed by GitHub
parent 9fd191ae00
commit b0750e5f4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 812 additions and 175 deletions

View file

@ -20,6 +20,7 @@ import {
VIDEO_MP4,
stringToMIMEType,
} from '../../types/MIME';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { MessageAudio } from './MessageAudio';
import { computePeaks } from '../GlobalAudioContext';
import { setup as setupI18n } from '../../../js/modules/i18n';
@ -61,6 +62,7 @@ const MessageAudioContainer: React.FC<AudioAttachmentProps> = props => {
audio={audio}
computePeaks={computePeaks}
setActiveAudioID={(id, context) => setActive({ id, context })}
onFirstPlayed={action('onFirstPlayed')}
activeAudioID={active.id}
activeAudioContext={active.context}
/>
@ -120,12 +122,17 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
isTapToViewExpired: overrideProps.isTapToViewExpired,
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
onHeightChange: action('onHeightChange'),
openConversation: action('openConversation'),
openLink: action('openLink'),
previews: overrideProps.previews || [],
reactions: overrideProps.reactions,
reactToMessage: action('reactToMessage'),
readStatus:
overrideProps.readStatus === undefined
? ReadStatus.Read
: overrideProps.readStatus,
renderEmojiPicker,
renderAudioAttachment,
replyToMessage: action('replyToMessage'),
@ -866,33 +873,48 @@ story.add('Pending GIF', () => {
});
story.add('Audio', () => {
const props = createProps({
attachments: [
{
contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
},
],
status: 'sent',
});
const Wrapper = () => {
const [isPlayed, setIsPlayed] = React.useState(false);
return renderBothDirections(props);
});
const messageProps = createProps({
attachments: [
{
contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
},
],
...(isPlayed
? {
status: 'viewed',
readStatus: ReadStatus.Viewed,
}
: {
status: 'read',
readStatus: ReadStatus.Read,
}),
});
story.add('Audio (played)', () => {
const props = createProps({
attachments: [
{
contentType: AUDIO_MP3,
fileName: 'incompetech-com-Agnus-Dei-X.mp3',
url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
},
],
status: 'viewed',
});
return (
<>
<button
type="button"
onClick={() => {
setIsPlayed(old => !old);
}}
style={{
display: 'block',
marginBottom: '2em',
}}
>
Toggle played
</button>
{renderBothDirections(messageProps)}
</>
);
};
return renderBothDirections(props);
return <Wrapper />;
});
story.add('Long Audio', () => {

View file

@ -13,6 +13,7 @@ import {
ConversationTypeType,
InteractionModeType,
} from '../../state/ducks/conversations';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { Avatar } from '../Avatar';
import { Spinner } from '../Spinner';
import { MessageBody } from './MessageBody';
@ -110,6 +111,7 @@ export type AudioAttachmentProps = {
kickOffAttachmentDownload(): void;
onCorrupted(): void;
onFirstPlayed(): void;
};
export type PropsData = {
@ -167,6 +169,8 @@ export type PropsData = {
isTapToViewExpired?: boolean;
isTapToViewError?: boolean;
readStatus: ReadStatus;
expirationLength?: number;
expirationTimestamp?: number;
@ -225,6 +229,7 @@ export type PropsActions = {
attachment: AttachmentType;
messageId: string;
}) => void;
markViewed(messageId: string): void;
showVisualAttachment: (options: {
attachment: AttachmentType;
messageId: string;
@ -684,7 +689,9 @@ export class Message extends React.PureComponent<Props, State> {
isSticker,
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
markViewed,
quote,
readStatus,
reducedMotion,
renderAudioAttachment,
renderingContext,
@ -791,8 +798,7 @@ export class Message extends React.PureComponent<Props, State> {
played = status === 'viewed';
break;
case 'incoming':
// Not implemented yet. See DESKTOP-1855.
played = true;
played = readStatus === ReadStatus.Viewed;
break;
default:
window.log.error(missingCaseError(direction));
@ -831,6 +837,9 @@ export class Message extends React.PureComponent<Props, State> {
messageId: id,
});
},
onFirstPlayed() {
markViewed(id);
},
});
}
const { pending, fileName, fileSize, contentType } = firstAttachment;

View file

@ -37,6 +37,7 @@ export type Props = {
buttonRef: React.RefObject<HTMLButtonElement>;
kickOffAttachmentDownload(): void;
onCorrupted(): void;
onFirstPlayed(): void;
computePeaks(url: string, barCount: number): Promise<ComputePeaksResult>;
activeAudioID: string | undefined;
@ -163,6 +164,7 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
buttonRef,
kickOffAttachmentDownload,
onCorrupted,
onFirstPlayed,
audio,
computePeaks,
@ -365,6 +367,12 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
}
};
useEffect(() => {
if (!played && isPlaying) {
onFirstPlayed();
}
}, [played, isPlaying, onFirstPlayed]);
// Clicking waveform moves playback head position and starts playback.
const onWaveformClick = (event: React.MouseEvent) => {
event.preventDefault();

View file

@ -10,6 +10,7 @@ import { storiesOf } from '@storybook/react';
import { PropsData as MessageDataPropsType } from './Message';
import { MessageDetail, Props } from './MessageDetail';
import { SendStatus } from '../../messages/MessageSendState';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
@ -35,6 +36,7 @@ const defaultMessage: MessageDataPropsType = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
status: 'sent',
text: 'A message from Max',
timestamp: Date.now(),
@ -71,6 +73,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
openConversation: action('openConversation'),
openLink: action('openLink'),
reactToMessage: action('reactToMessage'),

View file

@ -69,6 +69,7 @@ export type Props = {
| 'interactionMode'
| 'kickOffAttachmentDownload'
| 'markAttachmentAsCorrupted'
| 'markViewed'
| 'openConversation'
| 'openLink'
| 'reactToMessage'
@ -269,6 +270,7 @@ export class MessageDetail extends React.Component<Props> {
interactionMode,
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
markViewed,
openConversation,
openLink,
reactToMessage,
@ -305,6 +307,7 @@ export class MessageDetail extends React.Component<Props> {
interactionMode={interactionMode}
kickOffAttachmentDownload={kickOffAttachmentDownload}
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
markViewed={markViewed}
onHeightChange={noop}
openConversation={openConversation}
openLink={openLink}

View file

@ -19,6 +19,7 @@ import {
stringToMIMEType,
} from '../../types/MIME';
import { Props, Quote } from './Quote';
import { ReadStatus } from '../../messages/MessageReadStatus';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
@ -56,11 +57,13 @@ const defaultMessageProps: MessagesProps = {
isMessageRequestAccepted: true,
kickOffAttachmentDownload: action('default--kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('default--markAttachmentAsCorrupted'),
markViewed: action('default--markViewed'),
onHeightChange: action('onHeightChange'),
openConversation: action('default--openConversation'),
openLink: action('default--openLink'),
previews: [],
reactToMessage: action('default--reactToMessage'),
readStatus: ReadStatus.Read,
renderEmojiPicker: () => <div />,
renderAudioAttachment: () => <div>*AudioAttachment*</div>,
replyToMessage: action('default--replyToMessage'),

View file

@ -20,6 +20,7 @@ import { LastSeenIndicator } from './LastSeenIndicator';
import { TimelineLoadingRow } from './TimelineLoadingRow';
import { TypingBubble } from './TypingBubble';
import { ContactSpoofingType } from '../../util/contactSpoofing';
import { ReadStatus } from '../../messages/MessageReadStatus';
const i18n = setupI18n('en', enMessages);
@ -51,6 +52,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
text: '🔥',
timestamp: Date.now(),
},
@ -70,6 +72,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
text: 'Hello there from the new world! http://somewhere.com',
timestamp: Date.now(),
},
@ -102,6 +105,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
text: 'Hello there from the new world!',
timestamp: Date.now(),
},
@ -200,6 +204,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
status: 'sent',
text: '🔥',
timestamp: Date.now(),
@ -220,6 +225,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
status: 'read',
text: 'Hello there from the new world! http://somewhere.com',
timestamp: Date.now(),
@ -240,6 +246,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
status: 'sent',
text: 'Hello there from the new world! 🔥',
timestamp: Date.now(),
@ -260,6 +267,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
status: 'sent',
text:
'Hello there from the new world! And this is multiple lines of text. Lines and lines and lines.',
@ -281,6 +289,7 @@ const items: Record<string, TimelineItemType> = {
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],
readStatus: ReadStatus.Read,
status: 'read',
text:
'Hello there from the new world! And this is multiple lines of text. Lines and lines and lines.',
@ -325,6 +334,7 @@ const actions = () => ({
showContactModal: action('showContactModal'),
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
showVisualAttachment: action('showVisualAttachment'),
downloadAttachment: action('downloadAttachment'),
displayTapToViewMessage: action('displayTapToViewMessage'),

View file

@ -1496,6 +1496,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
'loadNewerMessages',
'loadNewestMessages',
'markMessageRead',
'markViewed',
'onBlock',
'onBlockAndReportSpam',
'onDelete',

View file

@ -57,6 +57,7 @@ const getDefaultProps = () => ({
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
learnMoreAboutDeliveryIssue: action('learnMoreAboutDeliveryIssue'),
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
markViewed: action('markViewed'),
showMessageDetail: action('showMessageDetail'),
openConversation: action('openConversation'),
showContactDetail: action('showContactDetail'),