Skip to content

Commit

Permalink
stories: muted by default, muted on app blur
Browse files Browse the repository at this point in the history
  • Loading branch information
jamiebuilds-signal committed Nov 10, 2022
1 parent cd1a1a0 commit 9f85db3
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 61 deletions.
6 changes: 6 additions & 0 deletions ts/background.ts
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions ts/components/StoryViewer.stories.tsx
Expand Up @@ -68,6 +68,7 @@ export default {
},
toggleHasAllStoriesMuted: { action: true },
viewStory: { action: true },
isWindowActive: { defaultValue: true },
},
args: {
currentIndex: 0,
Expand Down
40 changes: 24 additions & 16 deletions ts/components/StoryViewer.tsx
Expand Up @@ -66,7 +66,7 @@ export type PropsType = {
| 'left'
>;
hasActiveCall?: boolean;
hasAllStoriesMuted: boolean;
hasAllStoriesUnmuted: boolean;
hasViewReceiptSetting: boolean;
i18n: LocalizerType;
isSignalConversation?: boolean;
Expand Down Expand Up @@ -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;
};
Expand All @@ -119,7 +120,7 @@ export const StoryViewer = ({
getPreferredBadge,
group,
hasActiveCall,
hasAllStoriesMuted,
hasAllStoriesUnmuted,
hasViewReceiptSetting,
i18n,
isSignalConversation,
Expand All @@ -143,8 +144,9 @@ export const StoryViewer = ({
skinTone,
story,
storyViewMode,
toggleHasAllStoriesMuted,
setHasAllStoriesUnmuted,
viewStory,
isWindowActive,
deleteGroupStoryReply,
deleteGroupStoryReplyForEveryone,
}: PropsType): JSX.Element => {
Expand Down Expand Up @@ -305,6 +307,12 @@ export const StoryViewer = ({

const [pauseStory, setPauseStory] = useState(false);

useEffect(() => {
if (!isWindowActive) {
setPauseStory(true);
}
}, [isWindowActive]);

const shouldPauseViewing =
Boolean(confirmDeleteStory) ||
currentViewTarget != null ||
Expand Down Expand Up @@ -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');

muteClassName = hasAllStoriesMuted
? 'StoryViewer__unmute'
: 'StoryViewer__mute';
if (hasAudio) {
muteAriaLabel = hasAllStoriesUnmuted
? i18n('StoryViewer__mute')
: i18n('StoryViewer__unmute');

muteClassName = hasAllStoriesUnmuted
? 'StoryViewer__mute'
: 'StoryViewer__unmute';
} else {
muteAriaLabel = i18n('Stories__toast--hasNoSound');
muteClassName = 'StoryViewer__soundless';
Expand Down Expand Up @@ -686,8 +694,8 @@ export const StoryViewer = ({
aria-label={muteAriaLabel}
className={muteClassName}
onClick={
canMuteStory
? toggleHasAllStoriesMuted
hasAudio
? () => setHasAllStoriesUnmuted(!hasAllStoriesUnmuted)
: () => showToast(ToastType.StoryMuted)
}
type="button"
Expand Down
14 changes: 0 additions & 14 deletions ts/state/ducks/items.ts
Expand Up @@ -91,7 +91,6 @@ export const actions = {
removeItem,
removeItemExternal,
resetItems,
toggleHasAllStoriesMuted,
};

export const useActions = (): typeof actions => useBoundActions(actions);
Expand All @@ -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',
Expand Down
54 changes: 40 additions & 14 deletions ts/state/ducks/stories.ts
Expand Up @@ -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

Expand All @@ -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;
Expand Down Expand Up @@ -211,6 +213,11 @@ type SetStorySendingType = {
payload: boolean;
};

type SetHasAllStoriesUnmutedType = {
type: typeof SET_HAS_ALL_STORIES_UNMUTED;
payload: boolean;
};

export type StoriesActionType =
| DOEStoryActionType
| ListMembersVerified
Expand All @@ -227,7 +234,8 @@ export type StoriesActionType =
| StoryReplyDeletedActionType
| RemoveAllStoriesActionType
| SetAddStoryDataType
| SetStorySendingType;
| SetStorySendingType
| SetHasAllStoriesUnmutedType;

// Action Creators

Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -1262,6 +1279,7 @@ export const actions = {
deleteGroupStoryReplyForEveryone,
setAddStoryData,
setStoriesDisabled,
setHasAllStoriesUnmuted,
setStorySending,
};

Expand All @@ -1277,6 +1295,7 @@ export function getEmptyState(
openedAtTimestamp: undefined,
addStoryData: undefined,
stories: [],
hasAllStoriesUnmuted: false,
...overrideState,
};
}
Expand Down Expand Up @@ -1644,5 +1663,12 @@ export function reducer(
};
}

if (action.type === SET_HAS_ALL_STORIES_UNMUTED) {
return {
...state,
hasAllStoriesUnmuted: action.payload,
};
}

return state;
}
4 changes: 2 additions & 2 deletions ts/state/selectors/conversations.ts
Expand Up @@ -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) => {
Expand Down
5 changes: 0 additions & 5 deletions ts/state/selectors/items.ts
Expand Up @@ -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 =>
Expand Down
5 changes: 5 additions & 0 deletions ts/state/selectors/stories.ts
Expand Up @@ -541,3 +541,8 @@ export const getStoryByIdSelector = createSelector(
};
}
);

export const getHasAllStoriesUnmuted = createSelector(
getStoriesState,
({ hasAllStoriesUnmuted }): boolean => hasAllStoriesUnmuted
);
17 changes: 10 additions & 7 deletions ts/state/smart/StoryViewer.tsx
Expand Up @@ -13,7 +13,6 @@ import { ToastType, useToastActions } from '../ducks/toast';
import { getConversationSelector } from '../selectors/conversations';
import {
getEmojiSkinTone,
getHasAllStoriesMuted,
getHasStoryViewReceiptSetting,
getPreferredReactionEmoji,
} from '../selectors/items';
Expand All @@ -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>>(
Expand All @@ -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);
Expand All @@ -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({
Expand Down Expand Up @@ -127,7 +130,7 @@ export function SmartStoryViewer(): JSX.Element | null {
skinTone={skinTone}
story={storyView}
storyViewMode={selectedStoryData.storyViewMode}
toggleHasAllStoriesMuted={toggleHasAllStoriesMuted}
isWindowActive={isWindowActive}
{...storiesActions}
/>
);
Expand Down
1 change: 0 additions & 1 deletion ts/types/Storage.d.ts
Expand Up @@ -141,7 +141,6 @@ export type StorageAccessType = {
subscriberCurrencyCode: string;
displayBadgesOnProfile: boolean;
keepMutedChatsArchived: boolean;
hasAllStoriesMuted: boolean;

// Deprecated
senderCertificateWithUuid: never;
Expand Down
1 change: 0 additions & 1 deletion ts/types/StorageUIKeys.ts
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion ts/util/deleteStoryForEveryone.ts
Expand Up @@ -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) {
Expand Down

0 comments on commit 9f85db3

Please sign in to comment.