mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-17 23:34:14 +01:00
Send View Once Messages
This commit is contained in:
@@ -17,9 +17,10 @@ import type {
|
||||
InMemoryAttachmentDraftType,
|
||||
} from '../../types/Attachment.std.js';
|
||||
import {
|
||||
isVideoAttachment,
|
||||
isImageAttachment,
|
||||
isVideoAttachment,
|
||||
} from '../../util/Attachment.std.js';
|
||||
import { isViewOnceEligible } from '../../util/viewOnceEligibility.std.js';
|
||||
import { DataReader, DataWriter } from '../../sql/Client.preload.js';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions.std.js';
|
||||
import type { DraftBodyRanges } from '../../types/BodyRange.std.js';
|
||||
@@ -119,6 +120,7 @@ type ComposerStateByConversationType = {
|
||||
attachments: ReadonlyArray<AttachmentDraftType>;
|
||||
focusCounter: number;
|
||||
disabledCounter: number;
|
||||
isViewOnce: boolean;
|
||||
linkPreviewLoading: boolean;
|
||||
linkPreviewResult?: LinkPreviewForUIType;
|
||||
messageCompositionId: string;
|
||||
@@ -144,6 +146,7 @@ function getEmptyComposerState(): ComposerStateByConversationType {
|
||||
attachments: [],
|
||||
focusCounter: 0,
|
||||
disabledCounter: 0,
|
||||
isViewOnce: false,
|
||||
linkPreviewLoading: false,
|
||||
messageCompositionId: generateUuid(),
|
||||
sendCounter: 0,
|
||||
@@ -166,6 +169,7 @@ const RESET_COMPOSER = 'composer/RESET_COMPOSER';
|
||||
export const SET_FOCUS = 'composer/SET_FOCUS';
|
||||
const SET_HIGH_QUALITY_SETTING = 'composer/SET_HIGH_QUALITY_SETTING';
|
||||
const SET_QUOTED_MESSAGE = 'composer/SET_QUOTED_MESSAGE';
|
||||
const SET_VIEW_ONCE = 'composer/SET_VIEW_ONCE';
|
||||
const UPDATE_COMPOSER_DISABLED = 'composer/UPDATE_COMPOSER_DISABLED';
|
||||
|
||||
type AddPendingAttachmentActionType = ReadonlyDeep<{
|
||||
@@ -231,6 +235,14 @@ export type SetQuotedMessageActionType = {
|
||||
};
|
||||
};
|
||||
|
||||
export type SetViewOnceActionType = ReadonlyDeep<{
|
||||
type: typeof SET_VIEW_ONCE;
|
||||
payload: {
|
||||
conversationId: string;
|
||||
value: boolean;
|
||||
};
|
||||
}>;
|
||||
|
||||
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
||||
type ComposerActionType =
|
||||
| AddLinkPreviewActionType
|
||||
@@ -244,7 +256,8 @@ type ComposerActionType =
|
||||
| UpdateComposerDisabledActionType
|
||||
| SetFocusActionType
|
||||
| SetHighQualitySettingActionType
|
||||
| SetQuotedMessageActionType;
|
||||
| SetQuotedMessageActionType
|
||||
| SetViewOnceActionType;
|
||||
|
||||
// Action Creators
|
||||
|
||||
@@ -275,6 +288,7 @@ export const actions = {
|
||||
setMediaQualitySetting,
|
||||
setQuoteByMessageId,
|
||||
setQuotedMessage,
|
||||
setViewOnce,
|
||||
updateComposerDisabled,
|
||||
};
|
||||
|
||||
@@ -925,6 +939,7 @@ export function setQuoteByMessageId(
|
||||
quote,
|
||||
})
|
||||
);
|
||||
dispatch(disableViewOnceIfIneligible(conversationId));
|
||||
|
||||
dispatch(setComposerFocus(conversation.id));
|
||||
};
|
||||
@@ -1377,7 +1392,12 @@ function removeAttachment(
|
||||
export function replaceAttachments(
|
||||
conversationId: string,
|
||||
attachments: ReadonlyArray<AttachmentDraftType>
|
||||
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
|
||||
): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
ReplaceAttachmentsActionType | SetViewOnceActionType | ShowToastActionType
|
||||
> {
|
||||
return (dispatch, getState) => {
|
||||
// If the call came from a conversation we are no longer in we do not
|
||||
// update the state.
|
||||
@@ -1397,6 +1417,7 @@ export function replaceAttachments(
|
||||
},
|
||||
});
|
||||
dispatch(setComposerFocus(conversationId));
|
||||
dispatch(disableViewOnceIfIneligible(conversationId));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1565,6 +1586,95 @@ function setQuotedMessage(
|
||||
};
|
||||
}
|
||||
|
||||
export function setViewOnce({
|
||||
conversationId,
|
||||
value,
|
||||
toastNotify,
|
||||
}: {
|
||||
conversationId: string;
|
||||
value: boolean;
|
||||
toastNotify: boolean;
|
||||
}): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
SetViewOnceActionType | ShowToastActionType
|
||||
> {
|
||||
return async (dispatch, getState) => {
|
||||
const composerState = getComposerStateForConversation(
|
||||
getState().composer,
|
||||
conversationId
|
||||
);
|
||||
const nextValue =
|
||||
value &&
|
||||
isViewOnceEligible(
|
||||
composerState.attachments,
|
||||
Boolean(composerState.quotedMessage)
|
||||
);
|
||||
|
||||
if (composerState.isViewOnce !== nextValue) {
|
||||
dispatch({
|
||||
type: SET_VIEW_ONCE,
|
||||
payload: {
|
||||
conversationId,
|
||||
value: nextValue,
|
||||
},
|
||||
});
|
||||
|
||||
if (toastNotify) {
|
||||
dispatch(
|
||||
showToast({
|
||||
toastType: nextValue
|
||||
? ToastType.ViewOnceEnabled
|
||||
: ToastType.ViewOnceDisabled,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const conversation = window.ConversationController.get(conversationId);
|
||||
if (conversation && conversation.get('draftIsViewOnce') !== nextValue) {
|
||||
conversation.set({
|
||||
draftIsViewOnce: nextValue,
|
||||
draftChanged: true,
|
||||
});
|
||||
await DataWriter.updateConversation(conversation.attributes);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function disableViewOnceIfIneligible(
|
||||
conversationId: string,
|
||||
toastNotify = true
|
||||
): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
SetViewOnceActionType | ShowToastActionType
|
||||
> {
|
||||
return (dispatch, getState) => {
|
||||
const composerState = getComposerStateForConversation(
|
||||
getState().composer,
|
||||
conversationId
|
||||
);
|
||||
if (
|
||||
composerState.isViewOnce &&
|
||||
!isViewOnceEligible(
|
||||
composerState.attachments,
|
||||
Boolean(composerState.quotedMessage)
|
||||
)
|
||||
) {
|
||||
dispatch(
|
||||
setViewOnce({
|
||||
conversationId,
|
||||
value: false,
|
||||
toastNotify,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Reducer
|
||||
|
||||
export function getEmptyState(): ComposerStateType {
|
||||
@@ -1702,6 +1812,12 @@ export function reducer(
|
||||
}));
|
||||
}
|
||||
|
||||
if (action.type === SET_VIEW_ONCE) {
|
||||
return updateComposerState(state, action, () => ({
|
||||
isViewOnce: action.payload.value,
|
||||
}));
|
||||
}
|
||||
|
||||
if (action.type === UPDATE_COMPOSER_DISABLED) {
|
||||
return updateComposerState(state, action, oldState => ({
|
||||
disabledCounter:
|
||||
|
||||
@@ -167,6 +167,7 @@ import type {
|
||||
ResetComposerActionType,
|
||||
SetFocusActionType,
|
||||
SetQuotedMessageActionType,
|
||||
SetViewOnceActionType,
|
||||
} from './composer.preload.js';
|
||||
import {
|
||||
SET_FOCUS,
|
||||
@@ -175,6 +176,7 @@ import {
|
||||
setQuoteByMessageId,
|
||||
resetComposer,
|
||||
saveDraftRecordingIfNeeded,
|
||||
setViewOnce,
|
||||
} from './composer.preload.js';
|
||||
import { ReceiptType } from '../../types/Receipt.std.js';
|
||||
import { Sound, SoundType } from '../../util/Sound.std.js';
|
||||
@@ -4883,6 +4885,7 @@ function onConversationOpened(
|
||||
| ResetComposerActionType
|
||||
| SetFocusActionType
|
||||
| SetQuotedMessageActionType
|
||||
| SetViewOnceActionType
|
||||
> {
|
||||
return async dispatch => {
|
||||
const promises: Array<Promise<void>> = [];
|
||||
@@ -4974,6 +4977,13 @@ function onConversationOpened(
|
||||
)
|
||||
);
|
||||
dispatch(resetComposer(conversationId));
|
||||
dispatch(
|
||||
setViewOnce({
|
||||
conversationId,
|
||||
value: conversation.get('draftIsViewOnce') ?? false,
|
||||
toastNotify: false,
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
if (window.SignalCI) {
|
||||
|
||||
@@ -25,3 +25,10 @@ export const getQuotedMessageSelector = createSelector(
|
||||
(conversationId: string): QuotedMessageForComposerType | undefined =>
|
||||
composerStateForConversationIdSelector(conversationId).quotedMessage
|
||||
);
|
||||
|
||||
export const getViewOnceSelector = createSelector(
|
||||
getComposerStateForConversationIdSelector,
|
||||
composerStateForConversationIdSelector =>
|
||||
(conversationId: string): boolean =>
|
||||
composerStateForConversationIdSelector(conversationId).isViewOnce
|
||||
);
|
||||
|
||||
@@ -112,6 +112,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
attachments: draftAttachments,
|
||||
focusCounter,
|
||||
disabledCounter,
|
||||
isViewOnce,
|
||||
linkPreviewLoading,
|
||||
linkPreviewResult,
|
||||
messageCompositionId,
|
||||
@@ -202,6 +203,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
processAttachments,
|
||||
setMediaQualitySetting,
|
||||
setQuoteByMessageId,
|
||||
setViewOnce,
|
||||
cancelJoinRequest,
|
||||
sendStickerMessage,
|
||||
sendEditedMessage,
|
||||
@@ -301,6 +303,9 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||
quotedMessageAuthorAci={quotedMessage?.quote?.authorAci ?? null}
|
||||
quotedMessageSentAt={quotedMessage?.quote?.id ?? null}
|
||||
setQuoteByMessageId={setQuoteByMessageId}
|
||||
// View Once
|
||||
isViewOnce={isViewOnce}
|
||||
setViewOnce={setViewOnce}
|
||||
// Fun Picker
|
||||
emojiSkinToneDefault={emojiSkinToneDefault}
|
||||
onSelectEmoji={onUseEmoji}
|
||||
|
||||
Reference in New Issue
Block a user