Manual download of attachments with no blurHash

This commit is contained in:
Josh Perez 2021-02-11 20:50:11 -05:00 committed by GitHub
parent ed786898fb
commit 34285054f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 117 additions and 32 deletions

View file

@ -5021,11 +5021,17 @@ button.module-conversation-details__action-button {
align-items: center; align-items: center;
display: flex; display: flex;
justify-content: center; justify-content: center;
background-color: $color-gray-75;
border-radius: 48px; border-radius: 48px;
height: 48px; height: 48px;
width: 48px; width: 48px;
@include light-theme {
background-color: $color-gray-65;
}
@include dark-theme {
background-color: $color-gray-75;
}
&:after { &:after {
content: ''; content: '';
height: 17px; height: 17px;

View file

@ -326,7 +326,7 @@ type WhatIsThis = import('./window.d').WhatIsThis;
window.Events = { window.Events = {
getDeviceName: () => window.textsecure.storage.user.getDeviceName(), getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
getThemeSetting: () => getThemeSetting: (): 'light' | 'dark' | 'system' =>
window.storage.get( window.storage.get(
'theme-setting', 'theme-setting',
window.platform === 'darwin' ? 'system' : 'light' window.platform === 'darwin' ? 'system' : 'light'
@ -751,6 +751,7 @@ type WhatIsThis = import('./window.d').WhatIsThis;
platform: window.platform, platform: window.platform,
i18n: window.i18n, i18n: window.i18n,
interactionMode: window.getInteractionMode(), interactionMode: window.getInteractionMode(),
theme: window.Events.getThemeSetting(),
}, },
}; };
@ -2162,6 +2163,11 @@ type WhatIsThis = import('./window.d').WhatIsThis;
if (view) { if (view) {
view.applyTheme(); view.applyTheme();
} }
const theme = window.Events.getThemeSetting();
window.reduxActions.user.userChanged({
theme: theme === 'system' ? window.systemTheme : theme,
});
} }
const FIVE_MINUTES = 5 * 60 * 1000; const FIVE_MINUTES = 5 * 60 * 1000;

View file

@ -10,6 +10,7 @@ import { storiesOf } from '@storybook/react';
import { pngUrl } from '../../storybook/Fixtures'; import { pngUrl } from '../../storybook/Fixtures';
import { Image, Props } from './Image'; import { Image, Props } from './Image';
import { IMAGE_PNG } from '../../types/MIME'; import { IMAGE_PNG } from '../../types/MIME';
import { ThemeType } from '../../types/Util';
import { setup as setupI18n } from '../../../js/modules/i18n'; import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json'; import enMessages from '../../../_locales/en/messages.json';
@ -56,6 +57,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
), ),
softCorners: boolean('softCorners', overrideProps.softCorners || false), softCorners: boolean('softCorners', overrideProps.softCorners || false),
tabIndex: number('tabIndex', overrideProps.tabIndex || 0), tabIndex: number('tabIndex', overrideProps.tabIndex || 0),
theme: text('theme', overrideProps.theme || 'light') as ThemeType,
url: text('url', overrideProps.url || pngUrl), url: text('url', overrideProps.url || pngUrl),
width: number('width', overrideProps.width || 100), width: number('width', overrideProps.width || 100),
}); });
@ -191,6 +193,32 @@ story.add('Blurhash', () => {
return <Image {...props} />; return <Image {...props} />;
}); });
story.add('undefined blurHash (light)', () => {
const defaultProps = createProps();
const props = {
...defaultProps,
blurHash: undefined,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
url: undefined as any,
theme: ThemeType.light,
};
return <Image {...props} />;
});
story.add('undefined blurHash (dark)', () => {
const defaultProps = createProps();
const props = {
...defaultProps,
blurHash: undefined,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
url: undefined as any,
theme: ThemeType.dark,
};
return <Image {...props} />;
});
story.add('Missing Image', () => { story.add('Missing Image', () => {
const defaultProps = createProps(); const defaultProps = createProps();
const props = { const props = {

View file

@ -6,7 +6,7 @@ import classNames from 'classnames';
import { Blurhash } from 'react-blurhash'; import { Blurhash } from 'react-blurhash';
import { Spinner } from '../Spinner'; import { Spinner } from '../Spinner';
import { LocalizerType } from '../../types/Util'; import { LocalizerType, ThemeType } from '../../types/Util';
import { AttachmentType, hasNotDownloaded } from '../../types/Attachment'; import { AttachmentType, hasNotDownloaded } from '../../types/Attachment';
export type Props = { export type Props = {
@ -37,6 +37,7 @@ export type Props = {
blurHash?: string; blurHash?: string;
i18n: LocalizerType; i18n: LocalizerType;
theme?: ThemeType;
onClick?: (attachment: AttachmentType) => void; onClick?: (attachment: AttachmentType) => void;
onClickClose?: (attachment: AttachmentType) => void; onClickClose?: (attachment: AttachmentType) => void;
onError?: () => void; onError?: () => void;
@ -44,10 +45,10 @@ export type Props = {
export class Image extends React.Component<Props> { export class Image extends React.Component<Props> {
private canClick() { private canClick() {
const { onClick, attachment, blurHash, url } = this.props; const { onClick, attachment } = this.props;
const { pending } = attachment || { pending: true }; const { pending } = attachment || { pending: true };
return Boolean(onClick && !pending && (url || blurHash)); return Boolean(onClick && !pending);
} }
public handleClick = (event: React.MouseEvent): void => { public handleClick = (event: React.MouseEvent): void => {
@ -150,6 +151,7 @@ export class Image extends React.Component<Props> {
smallCurveTopLeft, smallCurveTopLeft,
softCorners, softCorners,
tabIndex, tabIndex,
theme,
url, url,
width = 0, width = 0,
} = this.props; } = this.props;
@ -158,6 +160,12 @@ export class Image extends React.Component<Props> {
const canClick = this.canClick(); const canClick = this.canClick();
const imgNotDownloaded = hasNotDownloaded(attachment); const imgNotDownloaded = hasNotDownloaded(attachment);
const defaulBlurHash =
theme === ThemeType.dark
? 'L05OQnoffQofoffQfQfQfQfQfQfQ'
: 'L1Q]+w-;fQ-;~qfQfQfQfQfQfQfQ';
const resolvedBlurHash = blurHash || defaulBlurHash;
const overlayClassName = classNames('module-image__border-overlay', { const overlayClassName = classNames('module-image__border-overlay', {
'module-image__border-overlay--with-border': !noBorder, 'module-image__border-overlay--with-border': !noBorder,
'module-image__border-overlay--with-click-handler': canClick, 'module-image__border-overlay--with-click-handler': canClick,
@ -210,9 +218,9 @@ export class Image extends React.Component<Props> {
width={width} width={width}
src={url} src={url}
/> />
) : blurHash ? ( ) : resolvedBlurHash ? (
<Blurhash <Blurhash
hash={blurHash} hash={resolvedBlurHash}
width={width} width={width}
height={height} height={height}
style={{ display: 'block' }} style={{ display: 'block' }}

View file

@ -16,7 +16,7 @@ import {
import { Image } from './Image'; import { Image } from './Image';
import { LocalizerType } from '../../types/Util'; import { LocalizerType, ThemeType } from '../../types/Util';
export type Props = { export type Props = {
attachments: Array<AttachmentType>; attachments: Array<AttachmentType>;
@ -28,6 +28,7 @@ export type Props = {
tabIndex?: number; tabIndex?: number;
i18n: LocalizerType; i18n: LocalizerType;
theme?: ThemeType;
onError: () => void; onError: () => void;
onClick?: (attachment: AttachmentType) => void; onClick?: (attachment: AttachmentType) => void;
@ -42,6 +43,7 @@ export const ImageGrid = ({
onError, onError,
onClick, onClick,
tabIndex, tabIndex,
theme,
withContentAbove, withContentAbove,
withContentBelow, withContentBelow,
}: Props): JSX.Element | null => { }: Props): JSX.Element | null => {
@ -75,6 +77,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[0], i18n)} alt={getAlt(attachments[0], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[0].blurHash} blurHash={attachments[0].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={isSticker} noBorder={isSticker}
@ -102,6 +105,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[0], i18n)} alt={getAlt(attachments[0], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
attachment={attachments[0]} attachment={attachments[0]}
blurHash={attachments[0].blurHash} blurHash={attachments[0].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
@ -118,6 +122,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[1], i18n)} alt={getAlt(attachments[1], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[1].blurHash} blurHash={attachments[1].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={false} noBorder={false}
@ -141,6 +146,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[0], i18n)} alt={getAlt(attachments[0], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[0].blurHash} blurHash={attachments[0].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={false} noBorder={false}
@ -158,6 +164,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[1], i18n)} alt={getAlt(attachments[1], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[1].blurHash} blurHash={attachments[1].blurHash}
curveTopRight={curveTopRight} curveTopRight={curveTopRight}
height={99} height={99}
@ -171,6 +178,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[2], i18n)} alt={getAlt(attachments[2], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[2].blurHash} blurHash={attachments[2].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={false} noBorder={false}
@ -196,6 +204,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[0], i18n)} alt={getAlt(attachments[0], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[0].blurHash} blurHash={attachments[0].blurHash}
curveTopLeft={curveTopLeft} curveTopLeft={curveTopLeft}
noBorder={false} noBorder={false}
@ -210,6 +219,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[1], i18n)} alt={getAlt(attachments[1], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[1].blurHash} blurHash={attachments[1].blurHash}
curveTopRight={curveTopRight} curveTopRight={curveTopRight}
playIconOverlay={isVideoAttachment(attachments[1])} playIconOverlay={isVideoAttachment(attachments[1])}
@ -226,6 +236,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[2], i18n)} alt={getAlt(attachments[2], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[2].blurHash} blurHash={attachments[2].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={false} noBorder={false}
@ -241,6 +252,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[3], i18n)} alt={getAlt(attachments[3], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[3].blurHash} blurHash={attachments[3].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={false} noBorder={false}
@ -271,6 +283,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[0], i18n)} alt={getAlt(attachments[0], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[0].blurHash} blurHash={attachments[0].blurHash}
curveTopLeft={curveTopLeft} curveTopLeft={curveTopLeft}
attachment={attachments[0]} attachment={attachments[0]}
@ -284,6 +297,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[1], i18n)} alt={getAlt(attachments[1], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[1].blurHash} blurHash={attachments[1].blurHash}
curveTopRight={curveTopRight} curveTopRight={curveTopRight}
playIconOverlay={isVideoAttachment(attachments[1])} playIconOverlay={isVideoAttachment(attachments[1])}
@ -299,6 +313,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[2], i18n)} alt={getAlt(attachments[2], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[2].blurHash} blurHash={attachments[2].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={isSticker} noBorder={isSticker}
@ -314,6 +329,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[3], i18n)} alt={getAlt(attachments[3], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[3].blurHash} blurHash={attachments[3].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={isSticker} noBorder={isSticker}
@ -328,6 +344,7 @@ export const ImageGrid = ({
<Image <Image
alt={getAlt(attachments[4], i18n)} alt={getAlt(attachments[4], i18n)}
i18n={i18n} i18n={i18n}
theme={theme}
blurHash={attachments[4].blurHash} blurHash={attachments[4].blurHash}
bottomOverlay={withBottomOverlay} bottomOverlay={withBottomOverlay}
noBorder={isSticker} noBorder={isSticker}

View file

@ -36,7 +36,6 @@ import {
getImageDimensions, getImageDimensions,
hasImage, hasImage,
hasNotDownloaded, hasNotDownloaded,
hasVideoBlurHash,
hasVideoScreenshot, hasVideoScreenshot,
isAudio, isAudio,
isImage, isImage,
@ -47,7 +46,7 @@ import { ContactType } from '../../types/Contact';
import { getIncrement } from '../../util/timer'; import { getIncrement } from '../../util/timer';
import { isFileDangerous } from '../../util/isFileDangerous'; import { isFileDangerous } from '../../util/isFileDangerous';
import { BodyRangesType, LocalizerType } from '../../types/Util'; import { BodyRangesType, LocalizerType, ThemeType } from '../../types/Util';
import { ColorType } from '../../types/Colors'; import { ColorType } from '../../types/Colors';
import { createRefMerger } from '../_util'; import { createRefMerger } from '../_util';
import { emojiToData } from '../emoji/lib'; import { emojiToData } from '../emoji/lib';
@ -141,6 +140,7 @@ export type PropsData = {
export type PropsHousekeeping = { export type PropsHousekeeping = {
i18n: LocalizerType; i18n: LocalizerType;
theme?: ThemeType;
disableMenu?: boolean; disableMenu?: boolean;
disableScroll?: boolean; disableScroll?: boolean;
collapseMetadata?: boolean; collapseMetadata?: boolean;
@ -675,7 +675,9 @@ export class Message extends React.PureComponent<Props, State> {
showVisualAttachment, showVisualAttachment,
isSticker, isSticker,
text, text,
theme,
} = this.props; } = this.props;
const { imageBroken } = this.state; const { imageBroken } = this.state;
if (!attachments || !attachments[0]) { if (!attachments || !attachments[0]) {
@ -693,9 +695,7 @@ export class Message extends React.PureComponent<Props, State> {
if ( if (
displayImage && displayImage &&
!imageBroken && !imageBroken &&
((isImage(attachments) && hasImage(attachments)) || (isImage(attachments) || isVideo(attachments))
(isVideo(attachments) &&
(hasVideoBlurHash(attachments) || hasVideoScreenshot(attachments))))
) { ) {
const prefix = isSticker ? 'sticker' : 'attachment'; const prefix = isSticker ? 'sticker' : 'attachment';
const bottomOverlay = !isSticker && !collapseMetadata; const bottomOverlay = !isSticker && !collapseMetadata;
@ -725,6 +725,7 @@ export class Message extends React.PureComponent<Props, State> {
stickerSize={STICKER_SIZE} stickerSize={STICKER_SIZE}
bottomOverlay={bottomOverlay} bottomOverlay={bottomOverlay}
i18n={i18n} i18n={i18n}
theme={theme}
onError={this.handleImageError} onError={this.handleImageError}
tabIndex={tabIndex} tabIndex={tabIndex}
onClick={attachment => { onClick={attachment => {
@ -783,7 +784,11 @@ export class Message extends React.PureComponent<Props, State> {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if (!firstAttachment.url) { if (hasNotDownloaded(firstAttachment)) {
kickOffAttachmentDownload({
attachment: firstAttachment,
messageId: id,
});
return; return;
} }
@ -841,6 +846,7 @@ export class Message extends React.PureComponent<Props, State> {
openLink, openLink,
previews, previews,
quote, quote,
theme,
} = this.props; } = this.props;
// Attachments take precedence over Link Previews // Attachments take precedence over Link Previews
@ -885,6 +891,7 @@ export class Message extends React.PureComponent<Props, State> {
withContentBelow withContentBelow
onError={this.handleImageError} onError={this.handleImageError}
i18n={i18n} i18n={i18n}
theme={theme}
/> />
) : null} ) : null}
<div className="module-message__link-preview__content"> <div className="module-message__link-preview__content">
@ -1546,12 +1553,7 @@ export class Message extends React.PureComponent<Props, State> {
if (attachments && attachments.length) { if (attachments && attachments.length) {
const displayImage = canDisplayImage(attachments); const displayImage = canDisplayImage(attachments);
return ( return displayImage && (isImage(attachments) || isVideo(attachments));
displayImage &&
((isImage(attachments) && hasImage(attachments)) ||
(isVideo(attachments) &&
(hasVideoBlurHash(attachments) || hasVideoScreenshot(attachments))))
);
} }
if (previews && previews.length) { if (previews && previews.length) {
@ -2012,8 +2014,7 @@ export class Message extends React.PureComponent<Props, State> {
!isAttachmentPending && !isAttachmentPending &&
canDisplayImage(attachments) && canDisplayImage(attachments) &&
((isImage(attachments) && hasImage(attachments)) || ((isImage(attachments) && hasImage(attachments)) ||
(isVideo(attachments) && (isVideo(attachments) && hasVideoScreenshot(attachments)))
(hasVideoBlurHash(attachments) || hasVideoScreenshot(attachments))))
) { ) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React from 'react';
import { LocalizerType } from '../../types/Util'; import { LocalizerType, ThemeType } from '../../types/Util';
import { import {
Message, Message,
@ -126,6 +126,7 @@ type PropsLocalType = {
selectMessage: (messageId: string, conversationId: string) => unknown; selectMessage: (messageId: string, conversationId: string) => unknown;
renderContact: SmartContactRendererType; renderContact: SmartContactRendererType;
i18n: LocalizerType; i18n: LocalizerType;
theme?: ThemeType;
}; };
type PropsActionsType = MessageActionsType & type PropsActionsType = MessageActionsType &
@ -145,6 +146,7 @@ export class TimelineItem extends React.PureComponent<PropsType> {
isSelected, isSelected,
item, item,
i18n, i18n,
theme,
messageSizeChanged, messageSizeChanged,
renderContact, renderContact,
returnToActiveCall, returnToActiveCall,
@ -159,7 +161,9 @@ export class TimelineItem extends React.PureComponent<PropsType> {
} }
if (item.type === 'message') { if (item.type === 'message') {
return <Message {...this.props} {...item.data} i18n={i18n} />; return (
<Message {...this.props} {...item.data} i18n={i18n} theme={theme} />
);
} }
let notification; let notification;

View file

@ -4,7 +4,7 @@
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
import { NoopActionType } from './noop'; import { NoopActionType } from './noop';
import { LocalizerType } from '../../types/Util'; import { LocalizerType, ThemeType } from '../../types/Util';
// State // State
@ -19,6 +19,7 @@ export type UserStateType = {
regionCode: string; regionCode: string;
i18n: LocalizerType; i18n: LocalizerType;
interactionMode: 'mouse' | 'keyboard'; interactionMode: 'mouse' | 'keyboard';
theme: ThemeType;
}; };
// Actions // Actions
@ -31,6 +32,7 @@ type UserChangedActionType = {
ourNumber?: string; ourNumber?: string;
regionCode?: string; regionCode?: string;
interactionMode?: 'mouse' | 'keyboard'; interactionMode?: 'mouse' | 'keyboard';
theme?: ThemeType;
}; };
}; };
@ -45,10 +47,11 @@ export const actions = {
function userChanged(attributes: { function userChanged(attributes: {
interactionMode?: 'mouse' | 'keyboard'; interactionMode?: 'mouse' | 'keyboard';
ourConversationId: string; ourConversationId?: string;
ourNumber: string; ourNumber?: string;
ourUuid: string; ourUuid?: string;
regionCode: string; regionCode?: string;
theme?: ThemeType;
}): UserChangedActionType { }): UserChangedActionType {
return { return {
type: 'USER_CHANGED', type: 'USER_CHANGED',
@ -78,6 +81,7 @@ export function getEmptyState(): UserStateType {
regionCode: 'missing', regionCode: 'missing',
platform: 'missing', platform: 'missing',
interactionMode: 'mouse', interactionMode: 'mouse',
theme: ThemeType.light,
i18n: () => 'missing', i18n: () => 'missing',
}; };
} }

View file

@ -3,7 +3,7 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { LocalizerType } from '../../types/Util'; import { LocalizerType, ThemeType } from '../../types/Util';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { UserStateType } from '../ducks/user'; import { UserStateType } from '../ducks/user';
@ -59,3 +59,8 @@ export const getTempPath = createSelector(
getUser, getUser,
(state: UserStateType): string => state.tempPath (state: UserStateType): string => state.tempPath
); );
export const getTheme = createSelector(
getUser,
(state: UserStateType): ThemeType => state.theme
);

View file

@ -8,7 +8,7 @@ import { mapDispatchToProps } from '../actions';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { TimelineItem } from '../../components/conversation/TimelineItem'; import { TimelineItem } from '../../components/conversation/TimelineItem';
import { getIntl } from '../selectors/user'; import { getIntl, getTheme } from '../selectors/user';
import { import {
getMessageSelector, getMessageSelector,
getSelectedMessage, getSelectedMessage,
@ -47,6 +47,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
isSelected, isSelected,
renderContact, renderContact,
i18n: getIntl(state), i18n: getIntl(state),
theme: getTheme(state),
}; };
}; };

View file

@ -170,7 +170,7 @@ export function isVideoAttachment(
} }
export function hasNotDownloaded(attachment?: AttachmentType): boolean { export function hasNotDownloaded(attachment?: AttachmentType): boolean {
return Boolean(attachment && !attachment.url && attachment.blurHash); return Boolean(attachment && !attachment.url);
} }
export function hasVideoBlurHash(attachments?: Array<AttachmentType>): boolean { export function hasVideoBlurHash(attachments?: Array<AttachmentType>): boolean {

View file

@ -24,3 +24,8 @@ export type LocalizerType = (
key: string, key: string,
values?: Array<string | null> | ReplacementValuesType values?: Array<string | null> | ReplacementValuesType
) => string; ) => string;
export enum ThemeType {
'light' = 'light',
'dark' = 'dark',
}