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; let resolveOnAppView: (() => void) | undefined;
const onAppView = new Promise<void>(resolve => { const onAppView = new Promise<void>(resolve => {
resolveOnAppView = resolve; resolveOnAppView = resolve;

View file

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

View file

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

View file

@ -91,7 +91,6 @@ export const actions = {
removeItem, removeItem,
removeItemExternal, removeItemExternal,
resetItems, resetItems,
toggleHasAllStoriesMuted,
}; };
export const useActions = (): typeof actions => useBoundActions(actions); export const useActions = (): typeof actions => useBoundActions(actions);
@ -112,19 +111,6 @@ function onSetSkinTone(tone: number): ItemPutAction {
return putItem('skinTone', tone); 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 { function putItemExternal(key: string, value: unknown): ItemPutExternalAction {
return { return {
type: 'items/PUT_EXTERNAL', type: 'items/PUT_EXTERNAL',

View file

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

View file

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

View file

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

View file

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

View file

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