Populate blurHash when sending stories

This commit is contained in:
Fedor Indutny 2022-11-16 13:41:38 -08:00 committed by GitHub
parent 6be69a7ba8
commit 9bad2301fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 12 deletions

View file

@ -60,6 +60,8 @@ const useProps = (overrideProps: Partial<Props> = {}): Props => ({
quotedMessageProps: overrideProps.quotedMessageProps, quotedMessageProps: overrideProps.quotedMessageProps,
onClickQuotedMessage: action('onClickQuotedMessage'), onClickQuotedMessage: action('onClickQuotedMessage'),
setQuotedMessage: action('setQuotedMessage'), setQuotedMessage: action('setQuotedMessage'),
// MediaEditor
imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
// MediaQualitySelector // MediaQualitySelector
onSelectMediaQuality: action('onSelectMediaQuality'), onSelectMediaQuality: action('onSelectMediaQuality'),
shouldSendHighQualityAttachments: Boolean( shouldSendHighQualityAttachments: Boolean(

View file

@ -13,6 +13,7 @@ import type {
import type { ErrorDialogAudioRecorderType } from '../state/ducks/audioRecorder'; import type { ErrorDialogAudioRecorderType } from '../state/ducks/audioRecorder';
import { RecordingState } from '../state/ducks/audioRecorder'; import { RecordingState } from '../state/ducks/audioRecorder';
import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachmentsProcessing'; import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachmentsProcessing';
import type { imageToBlurHash } from '../util/imageToBlurHash';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
import type { import type {
Props as EmojiButtonProps, Props as EmojiButtonProps,
@ -56,7 +57,6 @@ import {
useKeyboardShortcuts, useKeyboardShortcuts,
} from '../hooks/useKeyboardShortcuts'; } from '../hooks/useKeyboardShortcuts';
import { MediaEditor } from './MediaEditor'; import { MediaEditor } from './MediaEditor';
import { IMAGE_PNG } from '../types/MIME';
import { isImageTypeSupported } from '../util/GoogleChrome'; import { isImageTypeSupported } from '../util/GoogleChrome';
import * as KeyboardLayout from '../services/keyboardLayout'; import * as KeyboardLayout from '../services/keyboardLayout';
@ -98,6 +98,7 @@ export type OwnProps = Readonly<{
groupAdmins: Array<ConversationType>; groupAdmins: Array<ConversationType>;
groupVersion?: 1 | 2; groupVersion?: 1 | 2;
i18n: LocalizerType; i18n: LocalizerType;
imageToBlurHash: typeof imageToBlurHash;
isFetchingUUID?: boolean; isFetchingUUID?: boolean;
isGroupV1AndDisabled?: boolean; isGroupV1AndDisabled?: boolean;
isMissingMandatoryProfileSharing?: boolean; isMissingMandatoryProfileSharing?: boolean;
@ -174,6 +175,7 @@ export const CompositionArea = ({
conversationId, conversationId,
i18n, i18n,
onSendMessage, onSendMessage,
imageToBlurHash,
processAttachments, processAttachments,
removeAttachment, removeAttachment,
theme, theme,
@ -594,12 +596,14 @@ export const CompositionArea = ({
<MediaEditor <MediaEditor
i18n={i18n} i18n={i18n}
imageSrc={attachmentToEdit.url} imageSrc={attachmentToEdit.url}
imageToBlurHash={imageToBlurHash}
isSending={false} isSending={false}
onClose={() => setAttachmentToEdit(undefined)} onClose={() => setAttachmentToEdit(undefined)}
onDone={data => { onDone={({ data, contentType, blurHash }) => {
const newAttachment = { const newAttachment = {
...attachmentToEdit, ...attachmentToEdit,
contentType: IMAGE_PNG, contentType,
blurHash,
data, data,
size: data.byteLength, size: data.byteLength,
}; };

View file

@ -28,6 +28,7 @@ const getDefaultProps = (): PropsType => ({
onClose: action('onClose'), onClose: action('onClose'),
onDone: action('onDone'), onDone: action('onDone'),
isSending: false, isSending: false,
imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
// StickerButtonProps // StickerButtonProps
installedPacks, installedPacks,

View file

@ -10,6 +10,8 @@ import { get, has, noop } from 'lodash';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import { ThemeType } from '../types/Util'; import { ThemeType } from '../types/Util';
import type { MIMEType } from '../types/MIME';
import { IMAGE_PNG } from '../types/MIME';
import type { Props as StickerButtonProps } from './stickers/StickerButton'; import type { Props as StickerButtonProps } from './stickers/StickerButton';
import type { ImageStateType } from '../mediaEditor/ImageStateType'; import type { ImageStateType } from '../mediaEditor/ImageStateType';
@ -20,6 +22,7 @@ import { Slider } from './Slider';
import { StickerButton } from './stickers/StickerButton'; import { StickerButton } from './stickers/StickerButton';
import { Theme } from '../util/theme'; import { Theme } from '../util/theme';
import { canvasToBytes } from '../util/canvasToBytes'; import { canvasToBytes } from '../util/canvasToBytes';
import type { imageToBlurHash } from '../util/imageToBlurHash';
import { useFabricHistory } from '../mediaEditor/useFabricHistory'; import { useFabricHistory } from '../mediaEditor/useFabricHistory';
import { usePortal } from '../hooks/usePortal'; import { usePortal } from '../hooks/usePortal';
import { useUniqueId } from '../hooks/useUniqueId'; import { useUniqueId } from '../hooks/useUniqueId';
@ -41,13 +44,21 @@ import { AddNewLines } from './conversation/AddNewLines';
import { useConfirmDiscard } from '../hooks/useConfirmDiscard'; import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
import { Spinner } from './Spinner'; import { Spinner } from './Spinner';
export type MediaEditorResultType = Readonly<{
data: Uint8Array;
contentType: MIMEType;
blurHash: string;
caption?: string;
}>;
export type PropsType = { export type PropsType = {
doneButtonLabel?: string; doneButtonLabel?: string;
i18n: LocalizerType; i18n: LocalizerType;
imageSrc: string; imageSrc: string;
isSending: boolean; isSending: boolean;
imageToBlurHash: typeof imageToBlurHash;
onClose: () => unknown; onClose: () => unknown;
onDone: (data: Uint8Array, caption?: string | undefined) => unknown; onDone: (result: MediaEditorResultType) => unknown;
} & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> & } & Pick<StickerButtonProps, 'installedPacks' | 'recentStickers'> &
( (
| { | {
@ -1115,6 +1126,7 @@ export const MediaEditor = ({
setIsSaving(true); setIsSaving(true);
let data: Uint8Array; let data: Uint8Array;
let blurHash: string;
try { try {
const renderFabricCanvas = await cloneFabricCanvas( const renderFabricCanvas = await cloneFabricCanvas(
fabricCanvas fabricCanvas
@ -1151,6 +1163,12 @@ export const MediaEditor = ({
const renderedCanvas = renderFabricCanvas.toCanvasElement(); const renderedCanvas = renderFabricCanvas.toCanvasElement();
data = await canvasToBytes(renderedCanvas); data = await canvasToBytes(renderedCanvas);
const blob = new Blob([data], {
type: IMAGE_PNG,
});
blurHash = await props.imageToBlurHash(blob);
} catch (err) { } catch (err) {
onTryClose(); onTryClose();
throw err; throw err;
@ -1158,7 +1176,12 @@ export const MediaEditor = ({
setIsSaving(false); setIsSaving(false);
} }
onDone(data, caption !== '' ? caption : undefined); onDone({
contentType: IMAGE_PNG,
data,
caption: caption !== '' ? caption : undefined,
blurHash,
});
}} }}
theme={Theme.Dark} theme={Theme.Dark}
variant={ButtonVariant.Primary} variant={ButtonVariant.Primary}

View file

@ -37,6 +37,7 @@ export default {
defaultValue: false, defaultValue: false,
}, },
i18n: { defaultValue: i18n }, i18n: { defaultValue: i18n },
imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
installedPacks: { installedPacks: {
defaultValue: [], defaultValue: [],
}, },

View file

@ -14,8 +14,9 @@ import type { LocalizerType } from '../types/Util';
import type { Props as StickerButtonProps } from './stickers/StickerButton'; import type { Props as StickerButtonProps } from './stickers/StickerButton';
import type { PropsType as SendStoryModalPropsType } from './SendStoryModal'; import type { PropsType as SendStoryModalPropsType } from './SendStoryModal';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID';
import type { imageToBlurHash } from '../util/imageToBlurHash';
import { IMAGE_JPEG, TEXT_ATTACHMENT } from '../types/MIME'; import { TEXT_ATTACHMENT } from '../types/MIME';
import { isVideoAttachment } from '../types/Attachment'; import { isVideoAttachment } from '../types/Attachment';
import { SendStoryModal } from './SendStoryModal'; import { SendStoryModal } from './SendStoryModal';
@ -38,6 +39,7 @@ export type PropsType = {
conversationIds: Array<string>, conversationIds: Array<string>,
attachment: AttachmentType attachment: AttachmentType
) => unknown; ) => unknown;
imageToBlurHash: typeof imageToBlurHash;
processAttachment: ( processAttachment: (
file: File file: File
) => Promise<void | InMemoryAttachmentDraftType>; ) => Promise<void | InMemoryAttachmentDraftType>;
@ -80,6 +82,7 @@ export const StoryCreator = ({
groupStories, groupStories,
hasFirstStoryPostExperience, hasFirstStoryPostExperience,
i18n, i18n,
imageToBlurHash,
installedPacks, installedPacks,
isSending, isSending,
linkPreview, linkPreview,
@ -107,6 +110,7 @@ export const StoryCreator = ({
const [draftAttachment, setDraftAttachment] = useState< const [draftAttachment, setDraftAttachment] = useState<
AttachmentType | undefined AttachmentType | undefined
>(); >();
const [isReadyToSend, setIsReadyToSend] = useState(false);
const [attachmentUrl, setAttachmentUrl] = useState<string | undefined>(); const [attachmentUrl, setAttachmentUrl] = useState<string | undefined>();
useEffect(() => { useEffect(() => {
@ -123,11 +127,16 @@ export const StoryCreator = ({
return; return;
} }
setDraftAttachment(attachment);
if (isVideoAttachment(attachment)) { if (isVideoAttachment(attachment)) {
setDraftAttachment(attachment); setAttachmentUrl(undefined);
setIsReadyToSend(true);
} else if (attachment && has(attachment, 'data')) { } else if (attachment && has(attachment, 'data')) {
url = URL.createObjectURL(new Blob([get(attachment, 'data')])); url = URL.createObjectURL(new Blob([get(attachment, 'data')]));
setAttachmentUrl(url); setAttachmentUrl(url);
// Needs editing in MediaEditor
setIsReadyToSend(false);
} }
} }
@ -142,12 +151,17 @@ export const StoryCreator = ({
}, [file, processAttachment]); }, [file, processAttachment]);
useEffect(() => { useEffect(() => {
sendStoryModalOpenStateChanged(Boolean(draftAttachment)); if (draftAttachment === undefined) {
sendStoryModalOpenStateChanged(false);
setIsReadyToSend(false);
} else {
sendStoryModalOpenStateChanged(true);
}
}, [draftAttachment, sendStoryModalOpenStateChanged]); }, [draftAttachment, sendStoryModalOpenStateChanged]);
return ( return (
<> <>
{draftAttachment && ( {draftAttachment && isReadyToSend && (
<SendStoryModal <SendStoryModal
draftAttachment={draftAttachment} draftAttachment={draftAttachment}
candidateConversations={candidateConversations} candidateConversations={candidateConversations}
@ -182,7 +196,7 @@ export const StoryCreator = ({
toggleSignalConnectionsModal={toggleSignalConnectionsModal} toggleSignalConnectionsModal={toggleSignalConnectionsModal}
/> />
)} )}
{attachmentUrl && ( {draftAttachment && !isReadyToSend && attachmentUrl && (
<MediaEditor <MediaEditor
doneButtonLabel={i18n('next2')} doneButtonLabel={i18n('next2')}
i18n={i18n} i18n={i18n}
@ -192,13 +206,17 @@ export const StoryCreator = ({
onClose={onClose} onClose={onClose}
supportsCaption supportsCaption
renderCompositionTextArea={renderCompositionTextArea} renderCompositionTextArea={renderCompositionTextArea}
onDone={(data, caption) => { imageToBlurHash={imageToBlurHash}
onDone={({ contentType, data, blurHash, caption }) => {
setDraftAttachment({ setDraftAttachment({
contentType: IMAGE_JPEG, ...draftAttachment,
contentType,
data, data,
size: data.byteLength, size: data.byteLength,
blurHash,
caption, caption,
}); });
setIsReadyToSend(true);
}} }}
recentStickers={recentStickers} recentStickers={recentStickers}
/> />
@ -216,6 +234,7 @@ export const StoryCreator = ({
textAttachment, textAttachment,
size: textAttachment.text?.length || 0, size: textAttachment.text?.length || 0,
}); });
setIsReadyToSend(true);
}} }}
/> />
)} )}

View file

@ -9,6 +9,7 @@ import { CompositionArea } from '../../components/CompositionArea';
import type { StateType } from '../reducer'; import type { StateType } from '../reducer';
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly'; import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
import { dropNull } from '../../util/dropNull'; import { dropNull } from '../../util/dropNull';
import { imageToBlurHash } from '../../util/imageToBlurHash';
import { getPreferredBadgeSelector } from '../selectors/badges'; import { getPreferredBadgeSelector } from '../selectors/badges';
import { selectRecentEmojis } from '../selectors/emojis'; import { selectRecentEmojis } from '../selectors/emojis';
@ -89,6 +90,8 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
recordingState: state.audioRecorder.recordingState, recordingState: state.audioRecorder.recordingState,
// AttachmentsList // AttachmentsList
draftAttachments, draftAttachments,
// MediaEditor
imageToBlurHash,
// MediaQualitySelector // MediaQualitySelector
shouldSendHighQualityAttachments, shouldSendHighQualityAttachments,
// StagedLinkPreview // StagedLinkPreview

View file

@ -26,6 +26,7 @@ import { getHasSetMyStoriesPrivacy } from '../selectors/items';
import { getLinkPreview } from '../selectors/linkPreviews'; import { getLinkPreview } from '../selectors/linkPreviews';
import { getPreferredBadgeSelector } from '../selectors/badges'; import { getPreferredBadgeSelector } from '../selectors/badges';
import { processAttachment } from '../../util/processAttachment'; import { processAttachment } from '../../util/processAttachment';
import { imageToBlurHash } from '../../util/imageToBlurHash';
import { useConversationsActions } from '../ducks/conversations'; import { useConversationsActions } from '../ducks/conversations';
import { useGlobalModalActions } from '../ducks/globalModals'; import { useGlobalModalActions } from '../ducks/globalModals';
import { useLinkPreviewActions } from '../ducks/linkPreviews'; import { useLinkPreviewActions } from '../ducks/linkPreviews';
@ -91,6 +92,7 @@ export function SmartStoryCreator(): JSX.Element | null {
groupStories={groupStories} groupStories={groupStories}
hasFirstStoryPostExperience={!hasSetMyStoriesPrivacy} hasFirstStoryPostExperience={!hasSetMyStoriesPrivacy}
i18n={i18n} i18n={i18n}
imageToBlurHash={imageToBlurHash}
installedPacks={installedPacks} installedPacks={installedPacks}
isSending={isSending} isSending={isSending}
linkPreview={linkPreviewForSource(LinkPreviewSourceType.StoryCreator)} linkPreview={linkPreviewForSource(LinkPreviewSourceType.StoryCreator)}