Fix voice note drafts when switching chats
This commit is contained in:
parent
a25c2036b5
commit
11b2563a3d
7 changed files with 39 additions and 42 deletions
|
@ -45,12 +45,12 @@ export function Default(): JSX.Element {
|
||||||
{active && (
|
{active && (
|
||||||
<CompositionRecording
|
<CompositionRecording
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
conversationId="convo-id"
|
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSend={handleSend}
|
onSend={handleSend}
|
||||||
errorRecording={_ => action('error')()}
|
errorRecording={_ => action('error')()}
|
||||||
addAttachment={action('addAttachment')}
|
addAttachment={action('addAttachment')}
|
||||||
completeRecording={action('completeRecording')}
|
completeRecording={action('completeRecording')}
|
||||||
|
saveDraftRecordingIfNeeded={action('saveDraftRecordingIfNeeded')}
|
||||||
showToast={action('showToast')}
|
showToast={action('showToast')}
|
||||||
hideToast={action('hideToast')}
|
hideToast={action('hideToast')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||||
import { usePrevious } from '../hooks/usePrevious';
|
|
||||||
import type { HideToastAction, ShowToastAction } from '../state/ducks/toast';
|
import type { HideToastAction, ShowToastAction } from '../state/ducks/toast';
|
||||||
import type { InMemoryAttachmentDraftType } from '../types/Attachment';
|
import type { InMemoryAttachmentDraftType } from '../types/Attachment';
|
||||||
import { ErrorDialogAudioRecorderType } from '../types/AudioRecorder';
|
import { ErrorDialogAudioRecorderType } from '../types/AudioRecorder';
|
||||||
|
@ -18,7 +17,6 @@ import { RecordingComposer } from './RecordingComposer';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
conversationId: string;
|
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onSend: () => void;
|
onSend: () => void;
|
||||||
errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
|
errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
|
||||||
|
@ -31,47 +29,30 @@ export type Props = {
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
onRecordingComplete: (rec: InMemoryAttachmentDraftType) => unknown
|
onRecordingComplete: (rec: InMemoryAttachmentDraftType) => unknown
|
||||||
) => unknown;
|
) => unknown;
|
||||||
|
saveDraftRecordingIfNeeded: () => void;
|
||||||
showToast: ShowToastAction;
|
showToast: ShowToastAction;
|
||||||
hideToast: HideToastAction;
|
hideToast: HideToastAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CompositionRecording({
|
export function CompositionRecording({
|
||||||
i18n,
|
i18n,
|
||||||
conversationId,
|
|
||||||
onCancel,
|
onCancel,
|
||||||
onSend,
|
onSend,
|
||||||
errorRecording,
|
errorRecording,
|
||||||
errorDialogAudioRecorderType,
|
errorDialogAudioRecorderType,
|
||||||
addAttachment,
|
saveDraftRecordingIfNeeded,
|
||||||
completeRecording,
|
|
||||||
showToast,
|
showToast,
|
||||||
hideToast,
|
hideToast,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
useEscapeHandling(onCancel);
|
useEscapeHandling(onCancel);
|
||||||
|
|
||||||
// when interrupted (blur, switching convos)
|
|
||||||
// stop recording and save draft
|
|
||||||
const handleRecordingInterruption = useCallback(() => {
|
|
||||||
completeRecording(conversationId, attachment => {
|
|
||||||
addAttachment(conversationId, attachment);
|
|
||||||
});
|
|
||||||
}, [conversationId, completeRecording, addAttachment]);
|
|
||||||
|
|
||||||
// switched to another app
|
// switched to another app
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('blur', handleRecordingInterruption);
|
window.addEventListener('blur', saveDraftRecordingIfNeeded);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('blur', handleRecordingInterruption);
|
window.removeEventListener('blur', saveDraftRecordingIfNeeded);
|
||||||
};
|
};
|
||||||
}, [handleRecordingInterruption]);
|
}, [saveDraftRecordingIfNeeded]);
|
||||||
|
|
||||||
// switched conversations
|
|
||||||
const previousConversationId = usePrevious(conversationId, conversationId);
|
|
||||||
useEffect(() => {
|
|
||||||
if (previousConversationId !== conversationId) {
|
|
||||||
handleRecordingInterruption();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const toast: AnyToast = { toastType: ToastType.VoiceNoteLimit };
|
const toast: AnyToast = { toastType: ToastType.VoiceNoteLimit };
|
||||||
|
|
|
@ -65,6 +65,10 @@ type AudioPlayerActionType = ReadonlyDeep<
|
||||||
| StartRecordingAction
|
| StartRecordingAction
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export function getIsRecording(audioRecorder: AudioRecorderStateType): boolean {
|
||||||
|
return audioRecorder.recordingState === RecordingState.Recording;
|
||||||
|
}
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
|
|
@ -96,6 +96,7 @@ import {
|
||||||
} from '../../types/CallDisposition';
|
} from '../../types/CallDisposition';
|
||||||
import type { CallHistoryAdd } from './callHistory';
|
import type { CallHistoryAdd } from './callHistory';
|
||||||
import { addCallHistory } from './callHistory';
|
import { addCallHistory } from './callHistory';
|
||||||
|
import { saveDraftRecordingIfNeeded } from './composer';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -926,6 +927,8 @@ function acceptCall(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveDraftRecordingIfNeeded()(dispatch, getState, undefined);
|
||||||
|
|
||||||
switch (call.callMode) {
|
switch (call.callMode) {
|
||||||
case CallMode.Direct:
|
case CallMode.Direct:
|
||||||
await calling.acceptDirectCall(conversationId, asVideoCall);
|
await calling.acceptDirectCall(conversationId, asVideoCall);
|
||||||
|
|
|
@ -33,8 +33,7 @@ import {
|
||||||
} from './linkPreviews';
|
} from './linkPreviews';
|
||||||
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||||
import type { AciString } from '../../types/ServiceId';
|
import type { AciString } from '../../types/ServiceId';
|
||||||
import { completeRecording } from './audioRecorder';
|
import { completeRecording, getIsRecording } from './audioRecorder';
|
||||||
import { RecordingState } from '../../types/AudioRecorder';
|
|
||||||
import { SHOW_TOAST } from './toast';
|
import { SHOW_TOAST } from './toast';
|
||||||
import type { AnyToast } from '../../types/Toast';
|
import type { AnyToast } from '../../types/Toast';
|
||||||
import { ToastType } from '../../types/Toast';
|
import { ToastType } from '../../types/Toast';
|
||||||
|
@ -243,6 +242,7 @@ export const actions = {
|
||||||
removeAttachment,
|
removeAttachment,
|
||||||
replaceAttachments,
|
replaceAttachments,
|
||||||
resetComposer,
|
resetComposer,
|
||||||
|
saveDraftRecordingIfNeeded,
|
||||||
scrollToQuotedMessage,
|
scrollToQuotedMessage,
|
||||||
sendEditedMessage,
|
sendEditedMessage,
|
||||||
sendMultiMediaMessage,
|
sendMultiMediaMessage,
|
||||||
|
@ -363,23 +363,33 @@ function scrollToQuotedMessage({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleLeaveConversation(
|
export function saveDraftRecordingIfNeeded(): ThunkAction<
|
||||||
conversationId: string
|
void,
|
||||||
): ThunkAction<void, RootStateType, unknown, never> {
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
never
|
||||||
|
> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { audioRecorder } = getState();
|
const { conversations, audioRecorder } = getState();
|
||||||
|
const { selectedConversationId: conversationId } = conversations;
|
||||||
|
|
||||||
if (audioRecorder.recordingState !== RecordingState.Recording) {
|
if (!getIsRecording(audioRecorder) || !conversationId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save draft of voice note
|
|
||||||
dispatch(
|
dispatch(
|
||||||
completeRecording(conversationId, attachment => {
|
completeRecording(conversationId, attachment => {
|
||||||
dispatch(
|
dispatch(
|
||||||
addPendingAttachment(conversationId, { ...attachment, pending: true })
|
addPendingAttachment(conversationId, { ...attachment, pending: true })
|
||||||
);
|
);
|
||||||
dispatch(addAttachment(conversationId, attachment));
|
dispatch(addAttachment(conversationId, attachment));
|
||||||
|
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('saveDraftRecordingIfNeeded: No conversation found');
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(conversation.updateLastMessage());
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1014,11 +1024,9 @@ function processAttachments({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = getState();
|
const { audioRecorder } = getState();
|
||||||
const isRecording =
|
|
||||||
state.audioRecorder.recordingState === RecordingState.Recording;
|
|
||||||
|
|
||||||
if (hasLinkPreviewLoaded() || isRecording) {
|
if (hasLinkPreviewLoaded() || getIsRecording(audioRecorder)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,7 @@ import {
|
||||||
setComposerFocus,
|
setComposerFocus,
|
||||||
setQuoteByMessageId,
|
setQuoteByMessageId,
|
||||||
resetComposer,
|
resetComposer,
|
||||||
handleLeaveConversation,
|
saveDraftRecordingIfNeeded,
|
||||||
} from './composer';
|
} from './composer';
|
||||||
import { ReceiptType } from '../../types/Receipt';
|
import { ReceiptType } from '../../types/Receipt';
|
||||||
import { Sound, SoundType } from '../../util/Sound';
|
import { Sound, SoundType } from '../../util/Sound';
|
||||||
|
@ -4254,7 +4254,7 @@ function showConversation({
|
||||||
|
|
||||||
// notify composer in case we need to stop recording a voice note
|
// notify composer in case we need to stop recording a voice note
|
||||||
if (conversations.selectedConversationId) {
|
if (conversations.selectedConversationId) {
|
||||||
dispatch(handleLeaveConversation(conversations.selectedConversationId));
|
saveDraftRecordingIfNeeded()(dispatch, getState, undefined);
|
||||||
dispatch(
|
dispatch(
|
||||||
onConversationClosed(
|
onConversationClosed(
|
||||||
conversations.selectedConversationId,
|
conversations.selectedConversationId,
|
||||||
|
|
|
@ -23,7 +23,8 @@ export const SmartCompositionRecording = memo(
|
||||||
const { errorRecording, cancelRecording, completeRecording } =
|
const { errorRecording, cancelRecording, completeRecording } =
|
||||||
useAudioRecorderActions();
|
useAudioRecorderActions();
|
||||||
|
|
||||||
const { sendMultiMediaMessage, addAttachment } = useComposerActions();
|
const { sendMultiMediaMessage, addAttachment, saveDraftRecordingIfNeeded } =
|
||||||
|
useComposerActions();
|
||||||
const { hideToast, showToast } = useToastActions();
|
const { hideToast, showToast } = useToastActions();
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
|
@ -53,12 +54,12 @@ export const SmartCompositionRecording = memo(
|
||||||
return (
|
return (
|
||||||
<CompositionRecording
|
<CompositionRecording
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
conversationId={selectedConversationId}
|
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSend={handleSend}
|
onSend={handleSend}
|
||||||
errorRecording={errorRecording}
|
errorRecording={errorRecording}
|
||||||
addAttachment={addAttachment}
|
addAttachment={addAttachment}
|
||||||
completeRecording={completeRecording}
|
completeRecording={completeRecording}
|
||||||
|
saveDraftRecordingIfNeeded={saveDraftRecordingIfNeeded}
|
||||||
showToast={showToast}
|
showToast={showToast}
|
||||||
hideToast={hideToast}
|
hideToast={hideToast}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue