Voice Notes mini-player: Show with no conversation, fix spacing
This commit is contained in:
parent
9015837b2e
commit
75d5e81013
10 changed files with 159 additions and 87 deletions
|
@ -24,6 +24,8 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
margin-top: calc(52px + var(--title-bar-drag-area-height));
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background-color: $color-gray-02;
|
background-color: $color-gray-02;
|
||||||
|
@ -32,6 +34,11 @@
|
||||||
background-color: $color-gray-75;
|
background-color: $color-gray-75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--flow {
|
||||||
|
margin-top: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
&__playback-button {
|
&__playback-button {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ export type PropsType = {
|
||||||
renderConversationView: () => JSX.Element;
|
renderConversationView: () => JSX.Element;
|
||||||
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
||||||
renderLeftPane: () => JSX.Element;
|
renderLeftPane: () => JSX.Element;
|
||||||
|
renderMiniPlayer: (options: { shouldFlow: boolean }) => JSX.Element;
|
||||||
scrollToMessage: (conversationId: string, messageId: string) => unknown;
|
scrollToMessage: (conversationId: string, messageId: string) => unknown;
|
||||||
selectedConversationId?: string;
|
selectedConversationId?: string;
|
||||||
selectedMessage?: string;
|
selectedMessage?: string;
|
||||||
|
@ -42,6 +43,7 @@ export function Inbox({
|
||||||
renderConversationView,
|
renderConversationView,
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
renderLeftPane,
|
renderLeftPane,
|
||||||
|
renderMiniPlayer,
|
||||||
scrollToMessage,
|
scrollToMessage,
|
||||||
selectedConversationId,
|
selectedConversationId,
|
||||||
selectedMessage,
|
selectedMessage,
|
||||||
|
@ -219,6 +221,7 @@ export function Inbox({
|
||||||
)}
|
)}
|
||||||
{!prevConversationId && (
|
{!prevConversationId && (
|
||||||
<div className="no-conversation-open">
|
<div className="no-conversation-open">
|
||||||
|
{renderMiniPlayer({ shouldFlow: false })}
|
||||||
<div className="module-splash-screen__logo module-img--128 module-logo-blue" />
|
<div className="module-splash-screen__logo module-img--128 module-logo-blue" />
|
||||||
<h3>{i18n('welcomeToSignal')}</h3>
|
<h3>{i18n('welcomeToSignal')}</h3>
|
||||||
<p className="whats-new-placeholder">
|
<p className="whats-new-placeholder">
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import classnames from 'classnames';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { durationToPlaybackText } from '../util/durationToPlaybackText';
|
import { durationToPlaybackText } from '../util/durationToPlaybackText';
|
||||||
|
@ -22,6 +23,8 @@ export type Props = Readonly<{
|
||||||
duration: number | undefined;
|
duration: number | undefined;
|
||||||
playbackRate: number;
|
playbackRate: number;
|
||||||
state: PlayerState;
|
state: PlayerState;
|
||||||
|
// if false or not provided, position:absolute. Otherwise, it's position: relative
|
||||||
|
shouldFlow?: boolean;
|
||||||
onPlay: () => void;
|
onPlay: () => void;
|
||||||
onPause: () => void;
|
onPause: () => void;
|
||||||
onPlaybackRate: (rate: number) => void;
|
onPlaybackRate: (rate: number) => void;
|
||||||
|
@ -35,6 +38,7 @@ export function MiniPlayer({
|
||||||
currentTime,
|
currentTime,
|
||||||
duration,
|
duration,
|
||||||
playbackRate,
|
playbackRate,
|
||||||
|
shouldFlow,
|
||||||
onPlay,
|
onPlay,
|
||||||
onPause,
|
onPause,
|
||||||
onPlaybackRate,
|
onPlaybackRate,
|
||||||
|
@ -79,7 +83,12 @@ export function MiniPlayer({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="MiniPlayer">
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'MiniPlayer',
|
||||||
|
shouldFlow ? 'MiniPlayer--flow' : null
|
||||||
|
)}
|
||||||
|
>
|
||||||
<PlaybackButton
|
<PlaybackButton
|
||||||
context="incoming"
|
context="incoming"
|
||||||
variant="mini"
|
variant="mini"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SmartMiniPlayer } from '../../state/smart/MiniPlayer';
|
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -87,7 +86,6 @@ export function ConversationView({
|
||||||
{renderConversationHeader()}
|
{renderConversationHeader()}
|
||||||
</div>
|
</div>
|
||||||
<div className="ConversationView__pane main panel">
|
<div className="ConversationView__pane main panel">
|
||||||
<SmartMiniPlayer />
|
|
||||||
<div className="ConversationView__timeline--container">
|
<div className="ConversationView__timeline--container">
|
||||||
<div aria-live="polite" className="ConversationView__timeline">
|
<div aria-live="polite" className="ConversationView__timeline">
|
||||||
{renderTimeline()}
|
{renderTimeline()}
|
||||||
|
|
|
@ -335,9 +335,6 @@ const renderItem = ({
|
||||||
getPreferredBadge={() => undefined}
|
getPreferredBadge={() => undefined}
|
||||||
id=""
|
id=""
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
renderEmojiPicker={() => <div />}
|
|
||||||
renderReactionPicker={() => <div />}
|
|
||||||
item={items[messageId]}
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
interactionMode="keyboard"
|
interactionMode="keyboard"
|
||||||
isNextItemCallingNotification={false}
|
isNextItemCallingNotification={false}
|
||||||
|
@ -345,11 +342,14 @@ const renderItem = ({
|
||||||
containerElementRef={containerElementRef}
|
containerElementRef={containerElementRef}
|
||||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||||
conversationId=""
|
conversationId=""
|
||||||
|
item={items[messageId]}
|
||||||
|
renderAudioAttachment={() => <div>*AudioAttachment*</div>}
|
||||||
renderContact={() => '*ContactName*'}
|
renderContact={() => '*ContactName*'}
|
||||||
|
renderEmojiPicker={() => <div />}
|
||||||
|
renderReactionPicker={() => <div />}
|
||||||
renderUniversalTimerNotification={() => (
|
renderUniversalTimerNotification={() => (
|
||||||
<div>*UniversalTimerNotification*</div>
|
<div>*UniversalTimerNotification*</div>
|
||||||
)}
|
)}
|
||||||
renderAudioAttachment={() => <div>*AudioAttachment*</div>}
|
|
||||||
shouldCollapseAbove={false}
|
shouldCollapseAbove={false}
|
||||||
shouldCollapseBelow={false}
|
shouldCollapseBelow={false}
|
||||||
shouldHideMetadata={false}
|
shouldHideMetadata={false}
|
||||||
|
@ -436,6 +436,9 @@ const renderTypingBubble = () => (
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const renderMiniPlayer = () => (
|
||||||
|
<div>If active, this is where smart mini player would be</div>
|
||||||
|
);
|
||||||
|
|
||||||
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
discardMessages: action('discardMessages'),
|
discardMessages: action('discardMessages'),
|
||||||
|
@ -455,6 +458,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
messageChangeCounter: 0,
|
messageChangeCounter: 0,
|
||||||
scrollToIndex: overrideProps.scrollToIndex,
|
scrollToIndex: overrideProps.scrollToIndex,
|
||||||
scrollToIndexCounter: 0,
|
scrollToIndexCounter: 0,
|
||||||
|
shouldShowMiniPlayer: Boolean(overrideProps.shouldShowMiniPlayer),
|
||||||
totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0),
|
totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0),
|
||||||
oldestUnseenIndex:
|
oldestUnseenIndex:
|
||||||
number('oldestUnseenIndex', overrideProps.oldestUnseenIndex || 0) ||
|
number('oldestUnseenIndex', overrideProps.oldestUnseenIndex || 0) ||
|
||||||
|
@ -466,6 +470,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
renderItem,
|
renderItem,
|
||||||
renderHeroRow,
|
renderHeroRow,
|
||||||
|
renderMiniPlayer,
|
||||||
renderTypingBubble,
|
renderTypingBubble,
|
||||||
renderContactSpoofingReviewDialog,
|
renderContactSpoofingReviewDialog,
|
||||||
isSomeoneTyping: overrideProps.isSomeoneTyping || false,
|
isSomeoneTyping: overrideProps.isSomeoneTyping || false,
|
||||||
|
@ -620,3 +625,12 @@ export function WithSameNameInGroupConversationWarning(): JSX.Element {
|
||||||
WithSameNameInGroupConversationWarning.story = {
|
WithSameNameInGroupConversationWarning.story = {
|
||||||
name: 'With "same name in group conversation" warning',
|
name: 'With "same name in group conversation" warning',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function WithJustMiniPlayer(): JSX.Element {
|
||||||
|
const props = useProps({
|
||||||
|
shouldShowMiniPlayer: true,
|
||||||
|
items: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Timeline {...props} />;
|
||||||
|
}
|
||||||
|
|
|
@ -100,8 +100,9 @@ type PropsHousekeepingType = {
|
||||||
isSomeoneTyping: boolean;
|
isSomeoneTyping: boolean;
|
||||||
unreadCount?: number;
|
unreadCount?: number;
|
||||||
|
|
||||||
selectedMessageId?: string;
|
|
||||||
invitedContactsForNewlyCreatedGroup: Array<ConversationType>;
|
invitedContactsForNewlyCreatedGroup: Array<ConversationType>;
|
||||||
|
selectedMessageId?: string;
|
||||||
|
shouldShowMiniPlayer: boolean;
|
||||||
|
|
||||||
warning?: WarningType;
|
warning?: WarningType;
|
||||||
contactSpoofingReview?: ContactSpoofingReviewPropType;
|
contactSpoofingReview?: ContactSpoofingReviewPropType;
|
||||||
|
@ -120,6 +121,10 @@ type PropsHousekeepingType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
|
|
||||||
|
renderContactSpoofingReviewDialog: (
|
||||||
|
props: SmartContactSpoofingReviewDialogPropsType
|
||||||
|
) => JSX.Element;
|
||||||
|
renderHeroRow: (id: string) => JSX.Element;
|
||||||
renderItem: (props: {
|
renderItem: (props: {
|
||||||
containerElementRef: RefObject<HTMLElement>;
|
containerElementRef: RefObject<HTMLElement>;
|
||||||
containerWidthBreakpoint: WidthBreakpoint;
|
containerWidthBreakpoint: WidthBreakpoint;
|
||||||
|
@ -130,11 +135,8 @@ type PropsHousekeepingType = {
|
||||||
previousMessageId: undefined | string;
|
previousMessageId: undefined | string;
|
||||||
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
|
unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
|
||||||
}) => JSX.Element;
|
}) => JSX.Element;
|
||||||
renderHeroRow: (id: string) => JSX.Element;
|
renderMiniPlayer: (options: { shouldFlow: boolean }) => JSX.Element;
|
||||||
renderTypingBubble: (id: string) => JSX.Element;
|
renderTypingBubble: (id: string) => JSX.Element;
|
||||||
renderContactSpoofingReviewDialog: (
|
|
||||||
props: SmartContactSpoofingReviewDialogPropsType
|
|
||||||
) => JSX.Element;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropsActionsType = {
|
export type PropsActionsType = {
|
||||||
|
@ -758,9 +760,11 @@ export class Timeline extends React.Component<
|
||||||
renderContactSpoofingReviewDialog,
|
renderContactSpoofingReviewDialog,
|
||||||
renderHeroRow,
|
renderHeroRow,
|
||||||
renderItem,
|
renderItem,
|
||||||
|
renderMiniPlayer,
|
||||||
renderTypingBubble,
|
renderTypingBubble,
|
||||||
reviewGroupMemberNameCollision,
|
reviewGroupMemberNameCollision,
|
||||||
reviewMessageRequestNameCollision,
|
reviewMessageRequestNameCollision,
|
||||||
|
shouldShowMiniPlayer,
|
||||||
theme,
|
theme,
|
||||||
totalUnseen,
|
totalUnseen,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
|
@ -890,72 +894,74 @@ export class Timeline extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
const warning = Timeline.getWarning(this.props, this.state);
|
const warning = Timeline.getWarning(this.props, this.state);
|
||||||
let timelineWarning: ReactNode;
|
let headerElements: ReactNode;
|
||||||
if (warning) {
|
if (warning || shouldShowMiniPlayer) {
|
||||||
let text: ReactChild;
|
let text: ReactChild | undefined;
|
||||||
let onClose: () => void;
|
let onClose: () => void;
|
||||||
switch (warning.type) {
|
if (warning) {
|
||||||
case ContactSpoofingType.DirectConversationWithSameTitle:
|
switch (warning.type) {
|
||||||
text = (
|
case ContactSpoofingType.DirectConversationWithSameTitle:
|
||||||
<Intl
|
text = (
|
||||||
i18n={i18n}
|
<Intl
|
||||||
id="ContactSpoofing__same-name"
|
i18n={i18n}
|
||||||
components={{
|
id="ContactSpoofing__same-name"
|
||||||
link: (
|
components={{
|
||||||
<TimelineWarning.Link
|
link: (
|
||||||
onClick={() => {
|
<TimelineWarning.Link
|
||||||
reviewMessageRequestNameCollision({
|
onClick={() => {
|
||||||
safeConversationId: warning.safeConversation.id,
|
reviewMessageRequestNameCollision({
|
||||||
});
|
safeConversationId: warning.safeConversation.id,
|
||||||
}}
|
});
|
||||||
>
|
}}
|
||||||
{i18n('ContactSpoofing__same-name__link')}
|
>
|
||||||
</TimelineWarning.Link>
|
{i18n('ContactSpoofing__same-name__link')}
|
||||||
),
|
</TimelineWarning.Link>
|
||||||
}}
|
),
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
onClose = () => {
|
);
|
||||||
this.setState({
|
onClose = () => {
|
||||||
hasDismissedDirectContactSpoofingWarning: true,
|
this.setState({
|
||||||
});
|
hasDismissedDirectContactSpoofingWarning: true,
|
||||||
};
|
});
|
||||||
break;
|
};
|
||||||
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
break;
|
||||||
const { groupNameCollisions } = warning;
|
case ContactSpoofingType.MultipleGroupMembersWithSameTitle: {
|
||||||
text = (
|
const { groupNameCollisions } = warning;
|
||||||
<Intl
|
text = (
|
||||||
i18n={i18n}
|
<Intl
|
||||||
id="ContactSpoofing__same-name-in-group"
|
i18n={i18n}
|
||||||
components={{
|
id="ContactSpoofing__same-name-in-group"
|
||||||
count: Object.values(groupNameCollisions)
|
components={{
|
||||||
.reduce(
|
count: Object.values(groupNameCollisions)
|
||||||
(result, conversations) => result + conversations.length,
|
.reduce(
|
||||||
0
|
(result, conversations) => result + conversations.length,
|
||||||
)
|
0
|
||||||
.toString(),
|
)
|
||||||
link: (
|
.toString(),
|
||||||
<TimelineWarning.Link
|
link: (
|
||||||
onClick={() => {
|
<TimelineWarning.Link
|
||||||
reviewGroupMemberNameCollision(id);
|
onClick={() => {
|
||||||
}}
|
reviewGroupMemberNameCollision(id);
|
||||||
>
|
}}
|
||||||
{i18n('ContactSpoofing__same-name-in-group__link')}
|
>
|
||||||
</TimelineWarning.Link>
|
{i18n('ContactSpoofing__same-name-in-group__link')}
|
||||||
),
|
</TimelineWarning.Link>
|
||||||
}}
|
),
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
onClose = () => {
|
);
|
||||||
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
|
onClose = () => {
|
||||||
};
|
acknowledgeGroupMemberNameCollisions(id, groupNameCollisions);
|
||||||
break;
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw missingCaseError(warning);
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
throw missingCaseError(warning);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timelineWarning = (
|
headerElements = (
|
||||||
<Measure
|
<Measure
|
||||||
bounds
|
bounds
|
||||||
onResize={({ bounds }) => {
|
onResize={({ bounds }) => {
|
||||||
|
@ -968,12 +974,15 @@ export class Timeline extends React.Component<
|
||||||
>
|
>
|
||||||
{({ measureRef }) => (
|
{({ measureRef }) => (
|
||||||
<TimelineWarnings ref={measureRef}>
|
<TimelineWarnings ref={measureRef}>
|
||||||
<TimelineWarning i18n={i18n} onClose={onClose}>
|
{renderMiniPlayer({ shouldFlow: true })}
|
||||||
<TimelineWarning.IconContainer>
|
{text && (
|
||||||
<TimelineWarning.GenericIcon />
|
<TimelineWarning i18n={i18n} onClose={onClose}>
|
||||||
</TimelineWarning.IconContainer>
|
<TimelineWarning.IconContainer>
|
||||||
<TimelineWarning.Text>{text}</TimelineWarning.Text>
|
<TimelineWarning.GenericIcon />
|
||||||
</TimelineWarning>
|
</TimelineWarning.IconContainer>
|
||||||
|
<TimelineWarning.Text>{text}</TimelineWarning.Text>
|
||||||
|
</TimelineWarning>
|
||||||
|
)}
|
||||||
</TimelineWarnings>
|
</TimelineWarnings>
|
||||||
)}
|
)}
|
||||||
</Measure>
|
</Measure>
|
||||||
|
@ -1044,7 +1053,7 @@ export class Timeline extends React.Component<
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
ref={measureRef}
|
ref={measureRef}
|
||||||
>
|
>
|
||||||
{timelineWarning}
|
{headerElements}
|
||||||
|
|
||||||
{floatingHeader}
|
{floatingHeader}
|
||||||
|
|
||||||
|
|
|
@ -190,6 +190,7 @@ const {
|
||||||
} = window.Signal.Data;
|
} = window.Signal.Data;
|
||||||
|
|
||||||
const FIVE_MINUTES = MINUTE * 5;
|
const FIVE_MINUTES = MINUTE * 5;
|
||||||
|
const FETCH_TIMEOUT = SECOND * 30;
|
||||||
|
|
||||||
const JOB_REPORTING_THRESHOLD_MS = 25;
|
const JOB_REPORTING_THRESHOLD_MS = 25;
|
||||||
const SEND_REPORTING_THRESHOLD_MS = 25;
|
const SEND_REPORTING_THRESHOLD_MS = 25;
|
||||||
|
@ -1366,10 +1367,12 @@ export class ConversationModel extends window.Backbone
|
||||||
e164,
|
e164,
|
||||||
reason: 'ConversationModel.onNewMessage',
|
reason: 'ConversationModel.onNewMessage',
|
||||||
});
|
});
|
||||||
const typingToken = `${source?.id}.${sourceDevice}`;
|
if (source) {
|
||||||
|
const typingToken = `${source.id}.${sourceDevice}`;
|
||||||
|
|
||||||
// Clear typing indicator for a given contact if we receive a message from them
|
// Clear typing indicator for a given contact if we receive a message from them
|
||||||
this.clearContactTypingTimer(typingToken);
|
this.clearContactTypingTimer(typingToken);
|
||||||
|
}
|
||||||
|
|
||||||
// If it's a group story reply or a story message, we don't want to update
|
// If it's a group story reply or a story message, we don't want to update
|
||||||
// the last message or add new messages to redux.
|
// the last message or add new messages to redux.
|
||||||
|
@ -1451,10 +1454,18 @@ export class ConversationModel extends window.Backbone
|
||||||
resolvePromise = resolve;
|
resolvePromise = resolve;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
const finish = () => {
|
const finish = () => {
|
||||||
resolvePromise();
|
resolvePromise();
|
||||||
|
clearTimeout(timeout);
|
||||||
this.inProgressFetch = undefined;
|
this.inProgressFetch = undefined;
|
||||||
};
|
};
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
log.warn(
|
||||||
|
`setInProgressFetch(${this.idForLogging()}): Calling finish manually after timeout`
|
||||||
|
);
|
||||||
|
finish();
|
||||||
|
}, FETCH_TIMEOUT);
|
||||||
|
|
||||||
return finish;
|
return finish;
|
||||||
}
|
}
|
||||||
|
@ -5542,7 +5553,7 @@ export class ConversationModel extends window.Backbone
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const typingToken = `${senderId}.${senderDevice}`;
|
const typingToken = `${sender.id}.${senderDevice}`;
|
||||||
|
|
||||||
this.contactTypingTimers = this.contactTypingTimers || {};
|
this.contactTypingTimers = this.contactTypingTimers || {};
|
||||||
const record = this.contactTypingTimers[typingToken];
|
const record = this.contactTypingTimers[typingToken];
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { SmartLeftPane } from './LeftPane';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { getIsCustomizingPreferredReactions } from '../selectors/preferredReactions';
|
import { getIsCustomizingPreferredReactions } from '../selectors/preferredReactions';
|
||||||
|
import { SmartMiniPlayer } from './MiniPlayer';
|
||||||
|
|
||||||
function renderConversationView() {
|
function renderConversationView() {
|
||||||
return <SmartConversationView />;
|
return <SmartConversationView />;
|
||||||
|
@ -23,6 +24,10 @@ function renderCustomizingPreferredReactionsModal() {
|
||||||
return <SmartCustomizingPreferredReactionsModal />;
|
return <SmartCustomizingPreferredReactionsModal />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderMiniPlayer(options: { shouldFlow: boolean }) {
|
||||||
|
return <SmartMiniPlayer {...options} />;
|
||||||
|
}
|
||||||
|
|
||||||
function renderLeftPane() {
|
function renderLeftPane() {
|
||||||
return <SmartLeftPane />;
|
return <SmartLeftPane />;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +64,7 @@ export function SmartInbox(): JSX.Element {
|
||||||
renderCustomizingPreferredReactionsModal
|
renderCustomizingPreferredReactionsModal
|
||||||
}
|
}
|
||||||
renderLeftPane={renderLeftPane}
|
renderLeftPane={renderLeftPane}
|
||||||
|
renderMiniPlayer={renderMiniPlayer}
|
||||||
scrollToMessage={scrollToMessage}
|
scrollToMessage={scrollToMessage}
|
||||||
selectedConversationId={selectedConversationId}
|
selectedConversationId={selectedConversationId}
|
||||||
selectedMessage={selectedMessage}
|
selectedMessage={selectedMessage}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { MiniPlayer, PlayerState } from '../../components/MiniPlayer';
|
import { MiniPlayer, PlayerState } from '../../components/MiniPlayer';
|
||||||
|
import type { Props as DumbProps } from '../../components/MiniPlayer';
|
||||||
import {
|
import {
|
||||||
AudioPlayerContent,
|
AudioPlayerContent,
|
||||||
useAudioPlayerActions,
|
useAudioPlayerActions,
|
||||||
|
@ -14,13 +15,15 @@ import {
|
||||||
} from '../selectors/audioPlayer';
|
} from '../selectors/audioPlayer';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
|
||||||
|
type Props = Pick<DumbProps, 'shouldFlow'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wires the dispatch props and shows/hides the MiniPlayer
|
* Wires the dispatch props and shows/hides the MiniPlayer
|
||||||
*
|
*
|
||||||
* It also triggers side-effecting actions (actual playback) in response to changes in
|
* It also triggers side-effecting actions (actual playback) in response to changes in
|
||||||
* the state
|
* the state
|
||||||
*/
|
*/
|
||||||
export function SmartMiniPlayer(): JSX.Element | null {
|
export function SmartMiniPlayer({ shouldFlow }: Props): JSX.Element | null {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const active = useSelector(selectAudioPlayerActive);
|
const active = useSelector(selectAudioPlayerActive);
|
||||||
const getVoiceNoteTitle = useSelector(selectVoiceNoteTitle);
|
const getVoiceNoteTitle = useSelector(selectVoiceNoteTitle);
|
||||||
|
@ -56,6 +59,7 @@ export function SmartMiniPlayer(): JSX.Element | null {
|
||||||
onPause={handlePause}
|
onPause={handlePause}
|
||||||
onPlaybackRate={setPlaybackRate}
|
onPlaybackRate={setPlaybackRate}
|
||||||
onClose={unloadMessageAudio}
|
onClose={unloadMessageAudio}
|
||||||
|
shouldFlow={shouldFlow}
|
||||||
state={state}
|
state={state}
|
||||||
currentTime={active.currentTime}
|
currentTime={active.currentTime}
|
||||||
duration={active.duration}
|
duration={active.duration}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
getInvitedContactsForNewlyCreatedGroup,
|
getInvitedContactsForNewlyCreatedGroup,
|
||||||
getSelectedMessage,
|
getSelectedMessage,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
|
import { selectAudioPlayerActive } from '../selectors/audioPlayer';
|
||||||
|
|
||||||
import { SmartTimelineItem } from './TimelineItem';
|
import { SmartTimelineItem } from './TimelineItem';
|
||||||
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog';
|
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog';
|
||||||
|
@ -46,6 +47,7 @@ import { ContactSpoofingType } from '../../util/contactSpoofing';
|
||||||
import type { UnreadIndicatorPlacement } from '../../util/timelineUtil';
|
import type { UnreadIndicatorPlacement } from '../../util/timelineUtil';
|
||||||
import type { WidthBreakpoint } from '../../components/_util';
|
import type { WidthBreakpoint } from '../../components/_util';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
|
import { SmartMiniPlayer } from './MiniPlayer';
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -93,6 +95,9 @@ function renderContactSpoofingReviewDialog(
|
||||||
function renderHeroRow(id: string): JSX.Element {
|
function renderHeroRow(id: string): JSX.Element {
|
||||||
return <SmartHeroRow id={id} />;
|
return <SmartHeroRow id={id} />;
|
||||||
}
|
}
|
||||||
|
function renderMiniPlayer(options: { shouldFlow: boolean }): JSX.Element {
|
||||||
|
return <SmartMiniPlayer {...options} />;
|
||||||
|
}
|
||||||
function renderTypingBubble(id: string): JSX.Element {
|
function renderTypingBubble(id: string): JSX.Element {
|
||||||
return <SmartTypingBubble id={id} />;
|
return <SmartTypingBubble id={id} />;
|
||||||
}
|
}
|
||||||
|
@ -227,6 +232,8 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
const getTimestampForMessage = (messageId: string): undefined | number =>
|
const getTimestampForMessage = (messageId: string): undefined | number =>
|
||||||
getMessages(state)[messageId]?.timestamp;
|
getMessages(state)[messageId]?.timestamp;
|
||||||
|
|
||||||
|
const shouldShowMiniPlayer = Boolean(selectAudioPlayerActive(state));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
...pick(conversation, ['unreadCount', 'isGroupV1AndDisabled']),
|
...pick(conversation, ['unreadCount', 'isGroupV1AndDisabled']),
|
||||||
|
@ -237,9 +244,11 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
),
|
),
|
||||||
isSomeoneTyping: Boolean(conversation.typingContactId),
|
isSomeoneTyping: Boolean(conversation.typingContactId),
|
||||||
...conversationMessages,
|
...conversationMessages,
|
||||||
|
|
||||||
invitedContactsForNewlyCreatedGroup:
|
invitedContactsForNewlyCreatedGroup:
|
||||||
getInvitedContactsForNewlyCreatedGroup(state),
|
getInvitedContactsForNewlyCreatedGroup(state),
|
||||||
selectedMessageId: selectedMessage ? selectedMessage.id : undefined,
|
selectedMessageId: selectedMessage ? selectedMessage.id : undefined,
|
||||||
|
shouldShowMiniPlayer,
|
||||||
|
|
||||||
warning: getWarning(conversation, state),
|
warning: getWarning(conversation, state),
|
||||||
contactSpoofingReview: getContactSpoofingReview(id, state),
|
contactSpoofingReview: getContactSpoofingReview(id, state),
|
||||||
|
@ -248,9 +257,11 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
theme: getTheme(state),
|
theme: getTheme(state),
|
||||||
renderItem,
|
|
||||||
renderContactSpoofingReviewDialog,
|
renderContactSpoofingReviewDialog,
|
||||||
renderHeroRow,
|
renderHeroRow,
|
||||||
|
renderItem,
|
||||||
|
renderMiniPlayer,
|
||||||
renderTypingBubble,
|
renderTypingBubble,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue