stories: muted by default, muted on app blur

This commit is contained in:
Jamie Kyle 2022-11-09 20:24:42 -08:00 committed by GitHub
parent cd1a1a00a2
commit 9f85db3fd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 88 additions and 60 deletions

View file

@ -417,6 +417,12 @@ export async function startApp(): Promise<void> {
}
});
window.SignalContext.activeWindowService.registerForChange(isActive => {
if (!isActive) {
window.reduxActions.stories.setHasAllStoriesUnmuted(false);
}
});
let resolveOnAppView: (() => void) | undefined;
const onAppView = new Promise<void>(resolve => {
resolveOnAppView = resolve;

View file

@ -68,6 +68,7 @@ export default {
},
toggleHasAllStoriesMuted: { action: true },
viewStory: { action: true },
isWindowActive: { defaultValue: true },
},
args: {
currentIndex: 0,

View file

@ -66,7 +66,7 @@ export type PropsType = {
| 'left'
>;
hasActiveCall?: boolean;
hasAllStoriesMuted: boolean;
hasAllStoriesUnmuted: boolean;
hasViewReceiptSetting: boolean;
i18n: LocalizerType;
isSignalConversation?: boolean;
@ -95,8 +95,9 @@ export type PropsType = {
skinTone?: number;
story: StoryViewType;
storyViewMode: StoryViewModeType;
toggleHasAllStoriesMuted: () => unknown;
setHasAllStoriesUnmuted: (isUnmuted: boolean) => unknown;
viewStory: ViewStoryActionCreatorType;
isWindowActive: boolean;
deleteGroupStoryReply: (id: string) => void;
deleteGroupStoryReplyForEveryone: (id: string) => void;
};
@ -119,7 +120,7 @@ export const StoryViewer = ({
getPreferredBadge,
group,
hasActiveCall,
hasAllStoriesMuted,
hasAllStoriesUnmuted,
hasViewReceiptSetting,
i18n,
isSignalConversation,
@ -143,8 +144,9 @@ export const StoryViewer = ({
skinTone,
story,
storyViewMode,
toggleHasAllStoriesMuted,
setHasAllStoriesUnmuted,
viewStory,
isWindowActive,
deleteGroupStoryReply,
deleteGroupStoryReplyForEveryone,
}: PropsType): JSX.Element => {
@ -305,6 +307,12 @@ export const StoryViewer = ({
const [pauseStory, setPauseStory] = useState(false);
useEffect(() => {
if (!isWindowActive) {
setPauseStory(true);
}
}, [isWindowActive]);
const shouldPauseViewing =
Boolean(confirmDeleteStory) ||
currentViewTarget != null ||
@ -436,19 +444,19 @@ export const StoryViewer = ({
const replyCount = replies.length;
const viewCount = views.length;
const canMuteStory = isVideoAttachment(attachment);
const isStoryMuted = hasAllStoriesMuted || !canMuteStory;
const hasAudio = isVideoAttachment(attachment);
const isStoryMuted = !hasAllStoriesUnmuted || !hasAudio;
let muteClassName: string;
let muteAriaLabel: string;
if (canMuteStory) {
muteAriaLabel = hasAllStoriesMuted
? i18n('StoryViewer__unmute')
: i18n('StoryViewer__mute');
if (hasAudio) {
muteAriaLabel = hasAllStoriesUnmuted
? i18n('StoryViewer__mute')
: i18n('StoryViewer__unmute');
muteClassName = hasAllStoriesMuted
? 'StoryViewer__unmute'
: 'StoryViewer__mute';
muteClassName = hasAllStoriesUnmuted
? 'StoryViewer__mute'
: 'StoryViewer__unmute';
} else {
muteAriaLabel = i18n('Stories__toast--hasNoSound');
muteClassName = 'StoryViewer__soundless';
@ -686,8 +694,8 @@ export const StoryViewer = ({
aria-label={muteAriaLabel}
className={muteClassName}
onClick={
canMuteStory
? toggleHasAllStoriesMuted
hasAudio
? () => setHasAllStoriesUnmuted(!hasAllStoriesUnmuted)
: () => showToast(ToastType.StoryMuted)
}
type="button"

View file

@ -91,7 +91,6 @@ export const actions = {
removeItem,
removeItemExternal,
resetItems,
toggleHasAllStoriesMuted,
};
export const useActions = (): typeof actions => useBoundActions(actions);
@ -112,19 +111,6 @@ function onSetSkinTone(tone: number): ItemPutAction {
return putItem('skinTone', tone);
}
function toggleHasAllStoriesMuted(): ThunkAction<
void,
RootStateType,
unknown,
ItemPutAction
> {
return (dispatch, getState) => {
const hasAllStoriesMuted = Boolean(getState().items.hasAllStoriesMuted);
dispatch(putItem('hasAllStoriesMuted', !hasAllStoriesMuted));
};
}
function putItemExternal(key: string, value: unknown): ItemPutExternalAction {
return {
type: 'items/PUT_EXTERNAL',

View file

@ -106,21 +106,22 @@ export type AddStoryData =
// State
export type StoriesStateType = {
readonly lastOpenedAtTimestamp: number | undefined;
readonly openedAtTimestamp: number | undefined;
readonly replyState?: {
export type StoriesStateType = Readonly<{
lastOpenedAtTimestamp: number | undefined;
openedAtTimestamp: number | undefined;
replyState?: Readonly<{
messageId: string;
replies: Array<MessageAttributesType>;
};
readonly selectedStoryData?: SelectedStoryDataType;
readonly addStoryData: AddStoryData;
readonly sendStoryModalData?: {
untrustedUuids: Array<string>;
verifiedUuids: Array<string>;
};
readonly stories: Array<StoryDataType>;
};
}>;
selectedStoryData?: SelectedStoryDataType;
addStoryData: AddStoryData;
sendStoryModalData?: Readonly<{
untrustedUuids: ReadonlyArray<string>;
verifiedUuids: ReadonlyArray<string>;
}>;
stories: ReadonlyArray<StoryDataType>;
hasAllStoriesUnmuted: boolean;
}>;
// Actions
@ -138,6 +139,7 @@ const STORY_REPLY_DELETED = 'stories/STORY_REPLY_DELETED';
const REMOVE_ALL_STORIES = 'stories/REMOVE_ALL_STORIES';
const SET_ADD_STORY_DATA = 'stories/SET_ADD_STORY_DATA';
const SET_STORY_SENDING = 'stories/SET_STORY_SENDING';
const SET_HAS_ALL_STORIES_UNMUTED = 'stories/SET_HAS_ALL_STORIES_UNMUTED';
type DOEStoryActionType = {
type: typeof DOE_STORY;
@ -211,6 +213,11 @@ type SetStorySendingType = {
payload: boolean;
};
type SetHasAllStoriesUnmutedType = {
type: typeof SET_HAS_ALL_STORIES_UNMUTED;
payload: boolean;
};
export type StoriesActionType =
| DOEStoryActionType
| ListMembersVerified
@ -227,7 +234,8 @@ export type StoriesActionType =
| StoryReplyDeletedActionType
| RemoveAllStoriesActionType
| SetAddStoryDataType
| SetStorySendingType;
| SetStorySendingType
| SetHasAllStoriesUnmutedType;
// Action Creators
@ -1235,6 +1243,15 @@ function setStorySending(sending: boolean): SetStorySendingType {
};
}
function setHasAllStoriesUnmuted(
isUnmuted: boolean
): SetHasAllStoriesUnmutedType {
return {
type: SET_HAS_ALL_STORIES_UNMUTED,
payload: isUnmuted,
};
}
function setStoriesDisabled(
value: boolean
): ThunkAction<void, RootStateType, unknown, never> {
@ -1262,6 +1279,7 @@ export const actions = {
deleteGroupStoryReplyForEveryone,
setAddStoryData,
setStoriesDisabled,
setHasAllStoriesUnmuted,
setStorySending,
};
@ -1277,6 +1295,7 @@ export function getEmptyState(
openedAtTimestamp: undefined,
addStoryData: undefined,
stories: [],
hasAllStoriesUnmuted: false,
...overrideState,
};
}
@ -1644,5 +1663,12 @@ export function reducer(
};
}
if (action.type === SET_HAS_ALL_STORIES_UNMUTED) {
return {
...state,
hasAllStoriesUnmuted: action.payload,
};
}
return state;
}

View file

@ -555,8 +555,8 @@ export const getNonGroupStories = createSelector(
export const selectMostRecentActiveStoryTimestampByGroupOrDistributionList =
createSelector(
(state: StateType): Array<StoryDataType> => state.stories.stories,
(stories: Array<StoryDataType>): Record<string, number> => {
(state: StateType): ReadonlyArray<StoryDataType> => state.stories.stories,
(stories: ReadonlyArray<StoryDataType>): Record<string, number> => {
return reduce<StoryDataType, Record<string, number>>(
stories,
(acc, story) => {

View file

@ -23,11 +23,6 @@ const DEFAULT_PREFERRED_LEFT_PANE_WIDTH = 320;
export const getItems = (state: StateType): ItemsStateType => state.items;
export const getHasAllStoriesMuted = createSelector(
getItems,
({ hasAllStoriesMuted }): boolean => Boolean(hasAllStoriesMuted)
);
export const getAreWeASubscriber = createSelector(
getItems,
({ areWeASubscriber }: Readonly<ItemsStateType>): boolean =>

View file

@ -541,3 +541,8 @@ export const getStoryByIdSelector = createSelector(
};
}
);
export const getHasAllStoriesUnmuted = createSelector(
getStoriesState,
({ hasAllStoriesUnmuted }): boolean => hasAllStoriesUnmuted
);

View file

@ -13,7 +13,6 @@ import { ToastType, useToastActions } from '../ducks/toast';
import { getConversationSelector } from '../selectors/conversations';
import {
getEmojiSkinTone,
getHasAllStoriesMuted,
getHasStoryViewReceiptSetting,
getPreferredReactionEmoji,
} from '../selectors/items';
@ -23,24 +22,28 @@ import {
getSelectedStoryData,
getStoryReplies,
getStoryByIdSelector,
getHasAllStoriesUnmuted,
} from '../selectors/stories';
import { isInFullScreenCall } from '../selectors/calling';
import { isSignalConversation } from '../../util/isSignalConversation';
import { renderEmojiPicker } from './renderEmojiPicker';
import { strictAssert } from '../../util/assert';
import { useActions as useEmojisActions } from '../ducks/emojis';
import { useActions as useItemsActions } from '../ducks/items';
import { useConversationsActions } from '../ducks/conversations';
import { useRecentEmojis } from '../selectors/emojis';
import { useActions as useItemsActions } from '../ducks/items';
import { useStoriesActions } from '../ducks/stories';
import { useIsWindowActive } from '../../hooks/useIsWindowActive';
export function SmartStoryViewer(): JSX.Element | null {
const storiesActions = useStoriesActions();
const { onSetSkinTone, toggleHasAllStoriesMuted } = useItemsActions();
const { onUseEmoji } = useEmojisActions();
const { showConversation, toggleHideStories } = useConversationsActions();
const { onSetSkinTone } = useItemsActions();
const { showToast } = useToastActions();
const isWindowActive = useIsWindowActive();
const i18n = useSelector<StateType, LocalizerType>(getIntl);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const preferredReactionEmoji = useSelector<StateType, Array<string>>(
@ -63,8 +66,8 @@ export function SmartStoryViewer(): JSX.Element | null {
const recentEmojis = useRecentEmojis();
const skinTone = useSelector<StateType, number>(getEmojiSkinTone);
const replyState = useSelector(getStoryReplies);
const hasAllStoriesMuted = useSelector<StateType, boolean>(
getHasAllStoriesMuted
const hasAllStoriesUnmuted = useSelector<StateType, boolean>(
getHasAllStoriesUnmuted
);
const hasActiveCall = useSelector(isInFullScreenCall);
@ -90,7 +93,7 @@ export function SmartStoryViewer(): JSX.Element | null {
getPreferredBadge={getPreferredBadge}
group={conversationStory.group}
hasActiveCall={hasActiveCall}
hasAllStoriesMuted={hasAllStoriesMuted}
hasAllStoriesUnmuted={hasAllStoriesUnmuted}
hasViewReceiptSetting={hasViewReceiptSetting}
i18n={i18n}
isSignalConversation={isSignalConversation({
@ -127,7 +130,7 @@ export function SmartStoryViewer(): JSX.Element | null {
skinTone={skinTone}
story={storyView}
storyViewMode={selectedStoryData.storyViewMode}
toggleHasAllStoriesMuted={toggleHasAllStoriesMuted}
isWindowActive={isWindowActive}
{...storiesActions}
/>
);

View file

@ -141,7 +141,6 @@ export type StorageAccessType = {
subscriberCurrencyCode: string;
displayBadgesOnProfile: boolean;
keepMutedChatsArchived: boolean;
hasAllStoriesMuted: boolean;
// Deprecated
senderCertificateWithUuid: never;

View file

@ -17,7 +17,6 @@ export const STORAGE_UI_KEYS: ReadonlyArray<keyof StorageAccessType> = [
'call-system-notification',
'customColors',
'defaultConversationColor',
'hasAllStoriesMuted',
'hide-menu-bar',
'incoming-call-notification',
'notification-draw-attention',

View file

@ -10,7 +10,7 @@ import { onStoryRecipientUpdate } from './onStoryRecipientUpdate';
import { sendDeleteForEveryoneMessage } from './sendDeleteForEveryoneMessage';
export async function deleteStoryForEveryone(
stories: Array<StoryDataType>,
stories: ReadonlyArray<StoryDataType>,
story: StoryDataType
): Promise<void> {
if (!story.sendStateByConversationId) {