Auto-select a newly created distribution list or group when sending story

This commit is contained in:
Alvaro 2022-12-09 10:35:34 -07:00 committed by GitHub
parent 81e4564687
commit 2db14e8d6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 94 additions and 26 deletions

View file

@ -56,7 +56,7 @@ export type PropsType = {
onDistributionListCreated: ( onDistributionListCreated: (
name: string, name: string,
viewerUuids: Array<UUIDStringType> viewerUuids: Array<UUIDStringType>
) => unknown; ) => Promise<UUIDStringType>;
onSelectedStoryList: (options: { onSelectedStoryList: (options: {
conversationId: string; conversationId: string;
distributionId: string | undefined; distributionId: string | undefined;
@ -67,7 +67,7 @@ export type PropsType = {
conversationIds: Array<string> conversationIds: Array<string>
) => unknown; ) => unknown;
signalConnections: Array<ConversationType>; signalConnections: Array<ConversationType>;
toggleGroupsForStorySend: (cids: Array<string>) => unknown; toggleGroupsForStorySend: (cids: Array<string>) => Promise<void>;
mostRecentActiveStoryTimestampByGroupOrDistributionList: Record< mostRecentActiveStoryTimestampByGroupOrDistributionList: Record<
string, string,
number number
@ -419,9 +419,17 @@ export function SendStoryModal({
candidateConversations={candidateConversations} candidateConversations={candidateConversations}
getPreferredBadge={getPreferredBadge} getPreferredBadge={getPreferredBadge}
i18n={i18n} i18n={i18n}
onCreateList={(name, uuids) => { onCreateList={async (name, uuids) => {
const newDistributionListId = await onDistributionListCreated(
name,
uuids
);
setSelectedContacts([]); setSelectedContacts([]);
onDistributionListCreated(name, uuids); setSelectedListIds(
listIds => new Set([...listIds, newDistributionListId])
);
setPage(Page.SendStory); setPage(Page.SendStory);
}} }}
onViewersUpdated={uuids => { onViewersUpdated={uuids => {
@ -480,9 +488,10 @@ export function SendStoryModal({
aria-label={i18n('ok')} aria-label={i18n('ok')}
className="SendStoryModal__ok" className="SendStoryModal__ok"
disabled={!chosenGroupIds.size} disabled={!chosenGroupIds.size}
onClick={() => { onClick={async () => {
toggleGroupsForStorySend(Array.from(chosenGroupIds)); await toggleGroupsForStorySend(Array.from(chosenGroupIds));
setChosenGroupIds(new Set()); setChosenGroupIds(new Set());
setSelectedGroupIds(chosenGroupIds);
setPage(Page.SendStory); setPage(Page.SendStory);
}} }}
type="button" type="button"

View file

@ -54,7 +54,7 @@ export type PropsType = {
onDistributionListCreated: ( onDistributionListCreated: (
name: string, name: string,
viewerUuids: Array<UUIDStringType> viewerUuids: Array<UUIDStringType>
) => unknown; ) => Promise<string>;
onHideMyStoriesFrom: (viewerUuids: Array<UUIDStringType>) => unknown; onHideMyStoriesFrom: (viewerUuids: Array<UUIDStringType>) => unknown;
onRemoveMembers: (listId: string, uuids: Array<UUIDStringType>) => unknown; onRemoveMembers: (listId: string, uuids: Array<UUIDStringType>) => unknown;
onRepliesNReactionsChanged: ( onRepliesNReactionsChanged: (

View file

@ -2,16 +2,45 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ActionCreatorsMapObject } from 'redux'; import type { ActionCreatorsMapObject } from 'redux';
import type { ThunkAction } from 'redux-thunk';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useMemo } from 'react'; import { useMemo } from 'react';
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Type-level function from an action creator (which may be ThunkAction creator) to a
* bound action creator.
*
* binding a thunk action creator changes it from:
* (params) => ThunkAction<R, ...>
* to:
* (params) => R
*
* a regular action creator's type is unchanged
*/
type BoundActionCreator<A> = A extends (
...params: infer P
) => ThunkAction<infer R, any, any, any>
? (...params: P) => R
: A;
export type BoundActionCreatorsMapObject<T extends ActionCreatorsMapObject> = {
[Property in keyof T]: BoundActionCreator<T[Property]>;
};
export const useBoundActions = <T extends ActionCreatorsMapObject>( export const useBoundActions = <T extends ActionCreatorsMapObject>(
actions: T actions: T
): T => { ): BoundActionCreatorsMapObject<T> => {
const dispatch = useDispatch(); const dispatch = useDispatch();
return useMemo(() => { return useMemo(() => {
return bindActionCreators(actions, dispatch); // bindActionCreators from redux has the wrong type when using thunk actions
// so we cast to the correct type
return bindActionCreators(
actions,
dispatch
) as any as BoundActionCreatorsMapObject<T>;
}, [actions, dispatch]); }, [actions, dispatch]);
}; };

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { ThunkAction } from 'redux-thunk'; import type { ThunkAction } from 'redux-thunk';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import { Sound } from '../../util/Sound'; import { Sound } from '../../util/Sound';
import * as Errors from '../../types/errors'; import * as Errors from '../../types/errors';
@ -106,7 +107,8 @@ export const actions = {
setIsPlaying, setIsPlaying,
}; };
export const useActions = (): typeof actions => useBoundActions(actions); export const useActions = (): BoundActionCreatorsMapObject<typeof actions> =>
useBoundActions(actions);
function setCurrentTime(value: number): CurrentTimeUpdated { function setCurrentTime(value: number): CurrentTimeUpdated {
globalMessageAudio.currentTime = value; globalMessageAudio.currentTime = value;

View file

@ -10,6 +10,7 @@ import type { StateType as RootStateType } from '../reducer';
import { fileToBytes } from '../../util/fileToBytes'; import { fileToBytes } from '../../util/fileToBytes';
import { recorder } from '../../services/audioRecorder'; import { recorder } from '../../services/audioRecorder';
import { stringToMIMEType } from '../../types/MIME'; import { stringToMIMEType } from '../../types/MIME';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
export enum ErrorDialogAudioRecorderType { export enum ErrorDialogAudioRecorderType {
@ -76,7 +77,8 @@ export const actions = {
startRecording, startRecording,
}; };
export const useActions = (): typeof actions => useBoundActions(actions); export const useActions = (): BoundActionCreatorsMapObject<typeof actions> =>
useBoundActions(actions);
function startRecording(): ThunkAction< function startRecording(): ThunkAction<
void, void,

View file

@ -83,6 +83,7 @@ import {
SelectedMessageSource, SelectedMessageSource,
} from './conversationsEnums'; } from './conversationsEnums';
import { markViewed as messageUpdaterMarkViewed } from '../../services/MessageUpdater'; import { markViewed as messageUpdaterMarkViewed } from '../../services/MessageUpdater';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import type { NoopActionType } from './noop'; import type { NoopActionType } from './noop';
@ -935,8 +936,9 @@ export const actions = {
verifyConversationsStoppingSend, verifyConversationsStoppingSend,
}; };
export const useConversationsActions = (): typeof actions => export const useConversationsActions = (): BoundActionCreatorsMapObject<
useBoundActions(actions); typeof actions
> => useBoundActions(actions);
function filterAvatarData( function filterAvatarData(
avatars: ReadonlyArray<AvatarDataType>, avatars: ReadonlyArray<AvatarDataType>,
@ -2621,7 +2623,7 @@ function addMemberToGroup(
function toggleGroupsForStorySend( function toggleGroupsForStorySend(
conversationIds: Array<string> conversationIds: Array<string>
): ThunkAction<void, RootStateType, unknown, NoopActionType> { ): ThunkAction<Promise<void>, RootStateType, unknown, NoopActionType> {
return async dispatch => { return async dispatch => {
await Promise.all( await Promise.all(
conversationIds.map(async conversationId => { conversationIds.map(async conversationId => {

View file

@ -5,6 +5,7 @@ import { take, uniq } from 'lodash';
import type { ThunkAction } from 'redux-thunk'; import type { ThunkAction } from 'redux-thunk';
import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker'; import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker';
import dataInterface from '../../sql/Client'; import dataInterface from '../../sql/Client';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
const { updateEmojiUsage } = dataInterface; const { updateEmojiUsage } = dataInterface;
@ -31,7 +32,8 @@ export const actions = {
useEmoji, useEmoji,
}; };
export const useActions = (): typeof actions => useBoundActions(actions); export const useActions = (): BoundActionCreatorsMapObject<typeof actions> =>
useBoundActions(actions);
function onUseEmoji({ function onUseEmoji({
shortName, shortName,

View file

@ -12,6 +12,7 @@ import type { UUIDStringType } from '../../types/UUID';
import * as SingleServePromise from '../../services/singleServePromise'; import * as SingleServePromise from '../../services/singleServePromise';
import { getMessageById } from '../../messages/getMessageById'; import { getMessageById } from '../../messages/getMessageById';
import { getMessagePropsSelector } from '../selectors/message'; import { getMessagePropsSelector } from '../selectors/message';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper'; import { longRunningTaskWrapper } from '../../util/longRunningTaskWrapper';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import { isGroupV1 } from '../../util/whatTypeOfConversation'; import { isGroupV1 } from '../../util/whatTypeOfConversation';
@ -215,8 +216,9 @@ export const actions = {
closeGV2MigrationDialog, closeGV2MigrationDialog,
}; };
export const useGlobalModalActions = (): typeof actions => export const useGlobalModalActions = (): BoundActionCreatorsMapObject<
useBoundActions(actions); typeof actions
> => useBoundActions(actions);
function hideContactModal(): HideContactModalActionType { function hideContactModal(): HideContactModalActionType {
return { return {

View file

@ -6,6 +6,7 @@ import { v4 as getGuid } from 'uuid';
import type { ThunkAction } from 'redux-thunk'; import type { ThunkAction } from 'redux-thunk';
import type { StateType as RootStateType } from '../reducer'; import type { StateType as RootStateType } from '../reducer';
import * as storageShim from '../../shims/storage'; import * as storageShim from '../../shims/storage';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import type { import type {
ConversationColorType, ConversationColorType,
@ -93,7 +94,8 @@ export const actions = {
resetItems, resetItems,
}; };
export const useActions = (): typeof actions => useBoundActions(actions); export const useActions = (): BoundActionCreatorsMapObject<typeof actions> =>
useBoundActions(actions);
function putItem<K extends keyof StorageAccessType>( function putItem<K extends keyof StorageAccessType>(
key: K, key: K,

View file

@ -12,6 +12,7 @@ import type {
} from '../../types/LinkPreview'; } from '../../types/LinkPreview';
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation'; import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
import { maybeGrabLinkPreview } from '../../services/LinkPreview'; import { maybeGrabLinkPreview } from '../../services/LinkPreview';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
// State // State
@ -84,8 +85,9 @@ export const actions = {
removeLinkPreview, removeLinkPreview,
}; };
export const useLinkPreviewActions = (): typeof actions => export const useLinkPreviewActions = (): BoundActionCreatorsMapObject<
useBoundActions(actions); typeof actions
> => useBoundActions(actions);
// Reducer // Reducer

View file

@ -6,6 +6,7 @@ import { omit } from 'lodash';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import * as Errors from '../../types/errors'; import * as Errors from '../../types/errors';
import { replaceIndex } from '../../util/replaceIndex'; import { replaceIndex } from '../../util/replaceIndex';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import type { StateType as RootStateType } from '../reducer'; import type { StateType as RootStateType } from '../reducer';
import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants'; import { DEFAULT_PREFERRED_REACTION_EMOJI_SHORT_NAMES } from '../../reactions/constants';
@ -97,7 +98,8 @@ export const actions = {
selectDraftEmojiToBeReplaced, selectDraftEmojiToBeReplaced,
}; };
export const useActions = (): typeof actions => useBoundActions(actions); export const useActions = (): BoundActionCreatorsMapObject<typeof actions> =>
useBoundActions(actions);
function cancelCustomizePreferredReactionsModal(): CancelCustomizePreferredReactionsModalActionType { function cancelCustomizePreferredReactionsModal(): CancelCustomizePreferredReactionsModalActionType {
return { type: CANCEL_CUSTOMIZE_PREFERRED_REACTIONS_MODAL }; return { type: CANCEL_CUSTOMIZE_PREFERRED_REACTIONS_MODAL };

View file

@ -51,6 +51,7 @@ import { isGroup } from '../../util/whatTypeOfConversation';
import { isNotNil } from '../../util/isNotNil'; import { isNotNil } from '../../util/isNotNil';
import { isStory } from '../../messages/helpers'; import { isStory } from '../../messages/helpers';
import { sendStoryMessage as doSendStoryMessage } from '../../util/sendStoryMessage'; import { sendStoryMessage as doSendStoryMessage } from '../../util/sendStoryMessage';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import { verifyStoryListMembers as doVerifyStoryListMembers } from '../../util/verifyStoryListMembers'; import { verifyStoryListMembers as doVerifyStoryListMembers } from '../../util/verifyStoryListMembers';
import { viewSyncJobQueue } from '../../jobs/viewSyncJobQueue'; import { viewSyncJobQueue } from '../../jobs/viewSyncJobQueue';
@ -1317,7 +1318,9 @@ export const actions = {
setStorySending, setStorySending,
}; };
export const useStoriesActions = (): typeof actions => useBoundActions(actions); export const useStoriesActions = (): BoundActionCreatorsMapObject<
typeof actions
> => useBoundActions(actions);
// Reducer // Reducer

View file

@ -14,6 +14,7 @@ import { UUID } from '../../types/UUID';
import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone'; import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone';
import { replaceIndex } from '../../util/replaceIndex'; import { replaceIndex } from '../../util/replaceIndex';
import { storageServiceUploadJob } from '../../services/storage'; import { storageServiceUploadJob } from '../../services/storage';
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
// State // State
@ -156,7 +157,12 @@ function createDistributionList(
memberUuids: Array<UUIDStringType>, memberUuids: Array<UUIDStringType>,
storageServiceDistributionListRecord?: StoryDistributionWithMembersType, storageServiceDistributionListRecord?: StoryDistributionWithMembersType,
shouldSave = true shouldSave = true
): ThunkAction<void, RootStateType, null, CreateListActionType> { ): ThunkAction<
Promise<UUIDStringType>,
RootStateType,
string,
CreateListActionType
> {
return async dispatch => { return async dispatch => {
const storyDistribution: StoryDistributionWithMembersType = { const storyDistribution: StoryDistributionWithMembersType = {
allowsReplies: true, allowsReplies: true,
@ -188,6 +194,8 @@ function createDistributionList(
name: storyDistribution.name, name: storyDistribution.name,
}, },
}); });
return storyDistribution.id;
}; };
} }
@ -483,8 +491,8 @@ export const actions = {
updateStoryViewers, updateStoryViewers,
}; };
export const useStoryDistributionListsActions = (): typeof actions => export const useStoryDistributionListsActions =
useBoundActions(actions); (): BoundActionCreatorsMapObject<typeof actions> => useBoundActions(actions);
// Reducer // Reducer

View file

@ -1,6 +1,7 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
import { useBoundActions } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions';
import type { ReplacementValuesType } from '../../types/Util'; import type { ReplacementValuesType } from '../../types/Util';
@ -94,7 +95,9 @@ export const actions = {
showToast, showToast,
}; };
export const useToastActions = (): typeof actions => useBoundActions(actions); export const useToastActions = (): BoundActionCreatorsMapObject<
typeof actions
> => useBoundActions(actions);
// Reducer // Reducer