Voice Notes mini-player: Show with no conversation, fix spacing

This commit is contained in:
Scott Nonnenberg 2023-03-20 11:03:21 -07:00 committed by GitHub
parent 9015837b2e
commit 75d5e81013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 159 additions and 87 deletions

View file

@ -25,6 +25,7 @@ export type PropsType = {
renderConversationView: () => JSX.Element;
renderCustomizingPreferredReactionsModal: () => JSX.Element;
renderLeftPane: () => JSX.Element;
renderMiniPlayer: (options: { shouldFlow: boolean }) => JSX.Element;
scrollToMessage: (conversationId: string, messageId: string) => unknown;
selectedConversationId?: string;
selectedMessage?: string;
@ -42,6 +43,7 @@ export function Inbox({
renderConversationView,
renderCustomizingPreferredReactionsModal,
renderLeftPane,
renderMiniPlayer,
scrollToMessage,
selectedConversationId,
selectedMessage,
@ -219,6 +221,7 @@ export function Inbox({
)}
{!prevConversationId && (
<div className="no-conversation-open">
{renderMiniPlayer({ shouldFlow: false })}
<div className="module-splash-screen__logo module-img--128 module-logo-blue" />
<h3>{i18n('welcomeToSignal')}</h3>
<p className="whats-new-placeholder">

View file

@ -1,6 +1,7 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import classnames from 'classnames';
import React, { useCallback } from 'react';
import type { LocalizerType } from '../types/Util';
import { durationToPlaybackText } from '../util/durationToPlaybackText';
@ -22,6 +23,8 @@ export type Props = Readonly<{
duration: number | undefined;
playbackRate: number;
state: PlayerState;
// if false or not provided, position:absolute. Otherwise, it's position: relative
shouldFlow?: boolean;
onPlay: () => void;
onPause: () => void;
onPlaybackRate: (rate: number) => void;
@ -35,6 +38,7 @@ export function MiniPlayer({
currentTime,
duration,
playbackRate,
shouldFlow,
onPlay,
onPause,
onPlaybackRate,
@ -79,7 +83,12 @@ export function MiniPlayer({
}
return (
<div className="MiniPlayer">
<div
className={classnames(
'MiniPlayer',
shouldFlow ? 'MiniPlayer--flow' : null
)}
>
<PlaybackButton
context="incoming"
variant="mini"

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { SmartMiniPlayer } from '../../state/smart/MiniPlayer';
export type PropsType = {
conversationId: string;
@ -87,7 +86,6 @@ export function ConversationView({
{renderConversationHeader()}
</div>
<div className="ConversationView__pane main panel">
<SmartMiniPlayer />
<div className="ConversationView__timeline--container">
<div aria-live="polite" className="ConversationView__timeline">
{renderTimeline()}

View file

@ -335,9 +335,6 @@ const renderItem = ({
getPreferredBadge={() => undefined}
id=""
isSelected={false}
renderEmojiPicker={() => <div />}
renderReactionPicker={() => <div />}
item={items[messageId]}
i18n={i18n}
interactionMode="keyboard"
isNextItemCallingNotification={false}
@ -345,11 +342,14 @@ const renderItem = ({
containerElementRef={containerElementRef}
containerWidthBreakpoint={containerWidthBreakpoint}
conversationId=""
item={items[messageId]}
renderAudioAttachment={() => <div>*AudioAttachment*</div>}
renderContact={() => '*ContactName*'}
renderEmojiPicker={() => <div />}
renderReactionPicker={() => <div />}
renderUniversalTimerNotification={() => (
<div>*UniversalTimerNotification*</div>
)}
renderAudioAttachment={() => <div>*AudioAttachment*</div>}
shouldCollapseAbove={false}
shouldCollapseBelow={false}
shouldHideMetadata={false}
@ -436,6 +436,9 @@ const renderTypingBubble = () => (
sharedGroupNames={[]}
/>
);
const renderMiniPlayer = () => (
<div>If active, this is where smart mini player would be</div>
);
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
discardMessages: action('discardMessages'),
@ -455,6 +458,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
messageChangeCounter: 0,
scrollToIndex: overrideProps.scrollToIndex,
scrollToIndexCounter: 0,
shouldShowMiniPlayer: Boolean(overrideProps.shouldShowMiniPlayer),
totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0),
oldestUnseenIndex:
number('oldestUnseenIndex', overrideProps.oldestUnseenIndex || 0) ||
@ -466,6 +470,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
id: uuid(),
renderItem,
renderHeroRow,
renderMiniPlayer,
renderTypingBubble,
renderContactSpoofingReviewDialog,
isSomeoneTyping: overrideProps.isSomeoneTyping || false,
@ -620,3 +625,12 @@ export function WithSameNameInGroupConversationWarning(): JSX.Element {
WithSameNameInGroupConversationWarning.story = {
name: 'With "same name in group conversation" warning',
};
export function WithJustMiniPlayer(): JSX.Element {
const props = useProps({
shouldShowMiniPlayer: true,
items: [],
});
return <Timeline {...props} />;
}

View file

@ -100,8 +100,9 @@ type PropsHousekeepingType = {
isSomeoneTyping: boolean;
unreadCount?: number;
selectedMessageId?: string;
invitedContactsForNewlyCreatedGroup: Array<ConversationType>;
selectedMessageId?: string;
shouldShowMiniPlayer: boolean;
warning?: WarningType;
contactSpoofingReview?: ContactSpoofingReviewPropType;
@ -120,6 +121,10 @@ type PropsHousekeepingType = {
i18n: LocalizerType;
theme: ThemeType;
renderContactSpoofingReviewDialog: (
props: SmartContactSpoofingReviewDialogPropsType
) => JSX.Element;
renderHeroRow: (id: string) => JSX.Element;
renderItem: (props: {
containerElementRef: RefObject<HTMLElement>;
containerWidthBreakpoint: WidthBreakpoint;
@ -130,11 +135,8 @@ type PropsHousekeepingType = {
previousMessageId: undefined | string;
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
}) => JSX.Element;
renderHeroRow: (id: string) => JSX.Element;
renderMiniPlayer: (options: { shouldFlow: boolean }) => JSX.Element;
renderTypingBubble: (id: string) => JSX.Element;
renderContactSpoofingReviewDialog: (
props: SmartContactSpoofingReviewDialogPropsType
) => JSX.Element;
};
export type PropsActionsType = {
@ -758,9 +760,11 @@ export class Timeline extends React.Component<
renderContactSpoofingReviewDialog,
renderHeroRow,
renderItem,
renderMiniPlayer,
renderTypingBubble,
reviewGroupMemberNameCollision,
reviewMessageRequestNameCollision,
shouldShowMiniPlayer,
theme,
totalUnseen,
unreadCount,
@ -890,72 +894,74 @@ export class Timeline extends React.Component<
}
const warning = Timeline.getWarning(this.props, this.state);
let timelineWarning: ReactNode;
if (warning) {
let text: ReactChild;
let headerElements: ReactNode;
if (warning || shouldShowMiniPlayer) {
let text: ReactChild | undefined;
let onClose: () => void;
switch (warning.type) {
case ContactSpoofingType.DirectConversationWithSameTitle:
text = (
<Intl
i18n={i18n}
id="ContactSpoofing__same-name"
components={{
link: (
<TimelineWarning.Link
onClick={() => {
reviewMessageRequestNameCollision({
safeConversationId: warning.safeConversation.id,
});
}}
>
{i18n('ContactSpoofing__same-name__link')}
</TimelineWarning.Link>
),
}}
/>
);
onClose = () => {
this.setState({
hasDismissedDirectContactSpoofingWarning: true,
});
};
break;
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
const { groupNameCollisions } = warning;
text = (
<Intl
i18n={i18n}
id="ContactSpoofing__same-name-in-group"
components={{
count: Object.values(groupNameCollisions)
.reduce(
(result, conversations) => result + conversations.length,
0
)
.toString(),
link: (
<TimelineWarning.Link
onClick={() => {
reviewGroupMemberNameCollision(id);
}}
>
{i18n('ContactSpoofing__same-name-in-group__link')}
</TimelineWarning.Link>
),
}}
/>
);
onClose = () => {
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
};
break;
if (warning) {
switch (warning.type) {
case ContactSpoofingType.DirectConversationWithSameTitle:
text = (
<Intl
i18n={i18n}
id="ContactSpoofing__same-name"
components={{
link: (
<TimelineWarning.Link
onClick={() => {
reviewMessageRequestNameCollision({
safeConversationId: warning.safeConversation.id,
});
}}
>
{i18n('ContactSpoofing__same-name__link')}
</TimelineWarning.Link>
),
}}
/>
);
onClose = () => {
this.setState({
hasDismissedDirectContactSpoofingWarning: true,
});
};
break;
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
const { groupNameCollisions } = warning;
text = (
<Intl
i18n={i18n}
id="ContactSpoofing__same-name-in-group"
components={{
count: Object.values(groupNameCollisions)
.reduce(
(result, conversations) => result + conversations.length,
0
)
.toString(),
link: (
<TimelineWarning.Link
onClick={() => {
reviewGroupMemberNameCollision(id);
}}
>
{i18n('ContactSpoofing__same-name-in-group__link')}
</TimelineWarning.Link>
),
}}
/>
);
onClose = () => {
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
};
break;
}
default:
throw missingCaseError(warning);
}
default:
throw missingCaseError(warning);
}
timelineWarning = (
headerElements = (
<Measure
bounds
onResize={({ bounds }) => {
@ -968,12 +974,15 @@ export class Timeline extends React.Component<
>
{({ measureRef }) => (
<TimelineWarnings ref={measureRef}>
<TimelineWarning i18n={i18n} onClose={onClose}>
<TimelineWarning.IconContainer>
<TimelineWarning.GenericIcon />
</TimelineWarning.IconContainer>
<TimelineWarning.Text>{text}</TimelineWarning.Text>
</TimelineWarning>
{renderMiniPlayer({ shouldFlow: true })}
{text && (
<TimelineWarning i18n={i18n} onClose={onClose}>
<TimelineWarning.IconContainer>
<TimelineWarning.GenericIcon />
</TimelineWarning.IconContainer>
<TimelineWarning.Text>{text}</TimelineWarning.Text>
</TimelineWarning>
)}
</TimelineWarnings>
)}
</Measure>
@ -1044,7 +1053,7 @@ export class Timeline extends React.Component<
onKeyDown={this.handleKeyDown}
ref={measureRef}
>
{timelineWarning}
{headerElements}
{floatingHeader}