mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-02-14 23:18:54 +00:00
Add view-once media support for pinned messages bar
This commit is contained in:
@@ -1726,6 +1726,10 @@
|
||||
"messageformat": "Sticker",
|
||||
"description": "Conversation > With pinned message(s) > Pinned messages bar > Message Preview > Symbol Accessibility Label (Sticker)"
|
||||
},
|
||||
"icu:PinnedMessagesBar__MessagePreview__SymbolLabel--ViewOnceMedia": {
|
||||
"messageformat": "View-once media",
|
||||
"description": "Conversation > With pinned message(s) > Pinned messages bar > Message Preview > Symbol Accessibility Label (View-once media)"
|
||||
},
|
||||
"icu:PinnedMessagesBar__MessagePreview__Text--Photo": {
|
||||
"messageformat": "Photo",
|
||||
"description": "Conversation > With pinned message(s) > Pinned messages bar > Message Preview > Symbol Accessibility Label (Photo)"
|
||||
@@ -1750,6 +1754,10 @@
|
||||
"messageformat": "Sticker",
|
||||
"description": "Conversation > With pinned message(s) > Pinned messages bar > Message Preview > Text (Sticker)"
|
||||
},
|
||||
"icu:PinnedMessagesBar__MessagePreview__Text--ViewOnceMedia": {
|
||||
"messageformat": "View-once media",
|
||||
"description": "Conversation > With pinned message(s) > Pinned messages bar > Message Preview > Text (View-once media)"
|
||||
},
|
||||
"icu:PinnedMessagesPanel__Title": {
|
||||
"messageformat": "Pinned messages",
|
||||
"description": "Conversation > Pinned messages panel (view all) > Title"
|
||||
|
||||
@@ -16,6 +16,26 @@ export default {
|
||||
title: 'Components/PinnedMessages/PinnedMessagesBar',
|
||||
} satisfies Meta;
|
||||
|
||||
type MockPinMessageProps = Partial<
|
||||
Omit<PinMessage, 'id' | 'sentAtTimestamp' | 'receivedAtCounter'>
|
||||
>;
|
||||
|
||||
function mockPinMessage(id: number, props: MockPinMessageProps): PinMessage {
|
||||
return {
|
||||
id: `message-${id}`,
|
||||
sentAtTimestamp: id,
|
||||
receivedAtCounter: id,
|
||||
text: null,
|
||||
attachment: null,
|
||||
contact: null,
|
||||
payment: false,
|
||||
poll: null,
|
||||
sticker: false,
|
||||
viewOnceMedia: false,
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
const PIN_1: Pin = {
|
||||
id: 1 as PinnedMessageId,
|
||||
sender: {
|
||||
@@ -23,14 +43,11 @@ const PIN_1: Pin = {
|
||||
title: 'Jamie',
|
||||
isMe: true,
|
||||
},
|
||||
message: {
|
||||
id: 'message-1',
|
||||
sentAtTimestamp: 1,
|
||||
receivedAtCounter: 1,
|
||||
message: mockPinMessage(1, {
|
||||
poll: {
|
||||
question: 'What should we get for lunch?',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const PIN_2: Pin = {
|
||||
@@ -40,10 +57,7 @@ const PIN_2: Pin = {
|
||||
title: 'Tyler',
|
||||
isMe: false,
|
||||
},
|
||||
message: {
|
||||
id: 'message-2',
|
||||
sentAtTimestamp: 2,
|
||||
receivedAtCounter: 2,
|
||||
message: mockPinMessage(2, {
|
||||
text: {
|
||||
body: 'We found a cute pottery store close to Inokashira Park that we’re going to check out on Saturday. Anyone want to meet at the south exit at Kichijoji station at 1pm? Too early?',
|
||||
bodyRanges: [
|
||||
@@ -51,7 +65,7 @@ const PIN_2: Pin = {
|
||||
{ start: 39, length: 15, style: BodyRange.Style.SPOILER },
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const PIN_3: Pin = {
|
||||
@@ -61,10 +75,7 @@ const PIN_3: Pin = {
|
||||
title: 'Adrian',
|
||||
isMe: false,
|
||||
},
|
||||
message: {
|
||||
id: 'message-3',
|
||||
sentAtTimestamp: 3,
|
||||
receivedAtCounter: 3,
|
||||
message: mockPinMessage(3, {
|
||||
text: {
|
||||
body: 'Photo',
|
||||
bodyRanges: [],
|
||||
@@ -73,7 +84,7 @@ const PIN_3: Pin = {
|
||||
type: 'image',
|
||||
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
function Template(props: {
|
||||
@@ -113,10 +124,7 @@ export function Default(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function Variant(props: {
|
||||
title: string;
|
||||
message: Omit<PinMessage, 'id' | 'sentAtTimestamp' | 'receivedAtCounter'>;
|
||||
}) {
|
||||
function Variant(props: { title: string; message: MockPinMessageProps }) {
|
||||
const pin: Pin = {
|
||||
id: 1 as PinnedMessageId,
|
||||
sender: {
|
||||
@@ -124,12 +132,7 @@ function Variant(props: {
|
||||
title: props.title,
|
||||
isMe: true,
|
||||
},
|
||||
message: {
|
||||
id: 'message-1',
|
||||
sentAtTimestamp: 1,
|
||||
receivedAtCounter: 1,
|
||||
...props.message,
|
||||
},
|
||||
message: mockPinMessage(1, props.message),
|
||||
};
|
||||
return <Template defaultCurrent={pin.id} pins={[pin]} />;
|
||||
}
|
||||
@@ -182,6 +185,7 @@ export function Variants(): React.JSX.Element {
|
||||
<Variant title="Sticker" message={{ sticker: true }} />
|
||||
<Variant title="Contact" message={{ contact: { name: 'Tyler' } }} />
|
||||
<Variant title="Payment" message={{ payment: true }} />
|
||||
<Variant title="View-Once Media" message={{ viewOnceMedia: true }} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,12 +45,13 @@ export type PinMessage = Readonly<{
|
||||
id: string;
|
||||
sentAtTimestamp: number;
|
||||
receivedAtCounter: number;
|
||||
text?: PinMessageText | null;
|
||||
attachment?: PinMessageAttachment | null;
|
||||
contact?: PinMessageContact | null;
|
||||
payment?: boolean;
|
||||
poll?: PinMessagePoll | null;
|
||||
sticker?: boolean;
|
||||
text: PinMessageText | null;
|
||||
attachment: PinMessageAttachment | null;
|
||||
contact: PinMessageContact | null;
|
||||
payment: boolean;
|
||||
poll: PinMessagePoll | null;
|
||||
sticker: boolean;
|
||||
viewOnceMedia: boolean;
|
||||
}>;
|
||||
|
||||
export type PinSender = Readonly<{
|
||||
@@ -336,6 +337,10 @@ const Content = forwardRef(function Content(
|
||||
});
|
||||
|
||||
function getThumbnailUrl(message: PinMessage): string | null {
|
||||
// Never render a thumbnail if its view-once media
|
||||
if (message.viewOnceMedia) {
|
||||
return null;
|
||||
}
|
||||
if (message.attachment == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -363,122 +368,116 @@ type PreviewIcon = Readonly<{
|
||||
label: string;
|
||||
}>;
|
||||
|
||||
function getMessagePreviewIcon(
|
||||
i18n: LocalizerType,
|
||||
message: PinMessage
|
||||
): PreviewIcon | null {
|
||||
type Preview = Readonly<{
|
||||
icon: PreviewIcon | null;
|
||||
text: ReactNode | null;
|
||||
}>;
|
||||
|
||||
function getMessagePreview(i18n: LocalizerType, message: PinMessage): Preview {
|
||||
// Note: The order of this function matters, once something higher-up assigns
|
||||
// `icon` or `text` nothing else should override it.
|
||||
let icon: PreviewIcon | null = null;
|
||||
let text: ReactNode | null = null;
|
||||
|
||||
// 1. View-once media is highest priority, no other icons or text should be shown
|
||||
if (message.viewOnceMedia) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--ViewOnceMedia');
|
||||
icon ??= {
|
||||
symbol: 'viewonce',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--ViewOnceMedia'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// 2. The message body should take priority over other generic text labels
|
||||
if (message.text != null) {
|
||||
text ??= <MessageTextPreview i18n={i18n} text={message.text} />;
|
||||
}
|
||||
|
||||
// 3. And everything else...
|
||||
if (message.attachment != null) {
|
||||
if (message.attachment.type === 'voiceMessage') {
|
||||
return {
|
||||
if (message.attachment.type === 'image') {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Photo');
|
||||
} else if (message.attachment.type === 'video') {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Video');
|
||||
} else if (message.attachment.type === 'voiceMessage') {
|
||||
text ??= i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__Text--VoiceMessage'
|
||||
);
|
||||
icon ??= {
|
||||
symbol: 'audio',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--VoiceMessage'
|
||||
),
|
||||
};
|
||||
}
|
||||
if (message.attachment.type === 'gif') {
|
||||
return {
|
||||
} else if (message.attachment.type === 'gif') {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Gif');
|
||||
icon ??= {
|
||||
symbol: 'gif',
|
||||
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Gif'),
|
||||
};
|
||||
}
|
||||
if (message.attachment.type === 'file') {
|
||||
return {
|
||||
} else if (message.attachment.type === 'file') {
|
||||
text ??= <UserText text={message.attachment.name ?? ''} />;
|
||||
icon ??= {
|
||||
symbol: 'file',
|
||||
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--File'),
|
||||
};
|
||||
} else {
|
||||
throw missingCaseError(message.attachment);
|
||||
}
|
||||
}
|
||||
if (message.contact != null) {
|
||||
return {
|
||||
} else if (message.contact != null) {
|
||||
const name = message.contact.name ?? i18n('icu:unknownContact');
|
||||
text ??= <UserText text={name} />;
|
||||
icon ??= {
|
||||
symbol: 'person-circle',
|
||||
label: message.contact.name ?? i18n('icu:unknownContact'),
|
||||
label: name,
|
||||
};
|
||||
}
|
||||
if (message.payment) {
|
||||
return {
|
||||
} else if (message.payment) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Payment');
|
||||
icon ??= {
|
||||
symbol: 'creditcard',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Payment'
|
||||
),
|
||||
};
|
||||
}
|
||||
if (message.poll != null) {
|
||||
return {
|
||||
} else if (message.poll != null) {
|
||||
text ??= <UserText text={message.poll.question} />;
|
||||
icon ??= {
|
||||
symbol: 'poll',
|
||||
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Poll'),
|
||||
};
|
||||
}
|
||||
if (message.sticker) {
|
||||
return {
|
||||
} else if (message.sticker) {
|
||||
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Sticker');
|
||||
icon ??= {
|
||||
symbol: 'sticker',
|
||||
label: i18n(
|
||||
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Sticker'
|
||||
),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getMessagePreviewText(
|
||||
i18n: LocalizerType,
|
||||
message: PinMessage
|
||||
): ReactNode {
|
||||
if (message.text != null) {
|
||||
return <MessageTextPreview i18n={i18n} text={message.text} />;
|
||||
}
|
||||
if (message.attachment != null) {
|
||||
if (message.attachment.type === 'image') {
|
||||
return i18n('icu:PinnedMessagesBar__MessagePreview__Text--Photo');
|
||||
}
|
||||
if (message.attachment.type === 'video') {
|
||||
return i18n('icu:PinnedMessagesBar__MessagePreview__Text--Video');
|
||||
}
|
||||
if (message.attachment.type === 'voiceMessage') {
|
||||
return i18n('icu:PinnedMessagesBar__MessagePreview__Text--VoiceMessage');
|
||||
}
|
||||
if (message.attachment.type === 'gif') {
|
||||
return i18n('icu:PinnedMessagesBar__MessagePreview__Text--Gif');
|
||||
}
|
||||
if (message.attachment.type === 'file') {
|
||||
return <UserText text={message.attachment.name ?? ''} />;
|
||||
}
|
||||
throw missingCaseError(message.attachment);
|
||||
}
|
||||
if (message.contact?.name != null) {
|
||||
return <UserText text={message.contact.name} />;
|
||||
}
|
||||
if (message.payment != null) {
|
||||
return i18n('icu:PinnedMessagesBar__MessagePreview__Text--Payment');
|
||||
}
|
||||
if (message.poll != null) {
|
||||
return <UserText text={message.poll.question} />;
|
||||
}
|
||||
if (message.sticker != null) {
|
||||
return i18n('icu:PinnedMessagesBar__MessagePreview__Text--Sticker');
|
||||
}
|
||||
return null;
|
||||
return { icon, text };
|
||||
}
|
||||
|
||||
function MessagePreview(props: { i18n: LocalizerType; message: PinMessage }) {
|
||||
const { i18n, message } = props;
|
||||
|
||||
const icon = useMemo(() => {
|
||||
return getMessagePreviewIcon(i18n, message);
|
||||
}, [i18n, message]);
|
||||
|
||||
const text = useMemo(() => {
|
||||
return getMessagePreviewText(i18n, message);
|
||||
const preview = useMemo(() => {
|
||||
return getMessagePreview(i18n, message);
|
||||
}, [i18n, message]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{icon != null && (
|
||||
{preview.icon != null && (
|
||||
<>
|
||||
<AxoSymbol.InlineGlyph symbol={icon.symbol} label={null} />{' '}
|
||||
<AxoSymbol.InlineGlyph
|
||||
symbol={preview.icon.symbol}
|
||||
label={null}
|
||||
/>{' '}
|
||||
</>
|
||||
)}
|
||||
{text}
|
||||
{preview.text}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,6 +101,10 @@ function isPinMessageSticker(props: MessagePropsType): boolean {
|
||||
return props.isSticker ?? false;
|
||||
}
|
||||
|
||||
function isPinMessageViewOnceMedia(props: MessagePropsType): boolean {
|
||||
return props.isTapToView ?? false;
|
||||
}
|
||||
|
||||
function getPinMessage(
|
||||
props: MessagePropsType,
|
||||
sentAtTimestamp: number,
|
||||
@@ -116,6 +120,7 @@ function getPinMessage(
|
||||
payment: isPinMessagePayment(props),
|
||||
poll: getPinMessagePoll(props),
|
||||
sticker: isPinMessageSticker(props),
|
||||
viewOnceMedia: isPinMessageViewOnceMedia(props),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user