Story settings modal copy and design changes

This commit is contained in:
Jamie Kyle 2022-10-26 16:17:39 -07:00 committed by GitHub
parent 7f0a66847b
commit 5ccfabeb0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 295 additions and 93 deletions

View file

@ -5388,7 +5388,7 @@
"description": "Label of the button on the bottom of the media editor that trigger the add-caption dialog"
},
"MyStories__title": {
"message": "My Stories",
"message": "My Story",
"description": "Title for the my stories list"
},
"MyStories__story": {
@ -5460,7 +5460,7 @@
"description": "Title for the stories list"
},
"Stories__mine": {
"message": "My Stories",
"message": "My Story",
"description": "Label for your stories"
},
"Stories__add": {
@ -5515,6 +5515,10 @@
"message": "Story settings",
"description": "Title for the story settings modal"
},
"icu:StoriesSettings__description": {
"messageformat": "Stories automatically disapper after 24 hours. Choose who can view your story or create new stories with specific viewers or groups.",
"description": "Description for story settings modal"
},
"StoriesSettings__new-list": {
"message": "New custom story",
"description": "Label to create a new custom distribution list"
@ -5600,7 +5604,7 @@
"description": "Description of button StoriesSettings__mine__all--label"
},
"StoriesSettings__mine__exclude--label": {
"message": "All Signal connections except...",
"message": "All except...",
"description": "Input label to create a block list"
},
"StoriesSettings__mine__exclude--description": {
@ -5704,8 +5708,8 @@
"description": "Modal title when choosing groups"
},
"SendStoryModal__my-stories-privacy": {
"message": "My stories privacy",
"description": "Modal title for setting privacy for My Stories"
"message": "My Story privacy",
"description": "Modal title for setting privacy for My Story"
},
"SendStoryModal__privacy-disclaimer": {
"message": "Choose which Signal connections can view your story. You can always change this in privacy settings. $learnMore$",

View file

@ -74,7 +74,7 @@
@mixin font-subtitle {
@include font-family;
font-size: 11px;
font-size: 12px;
line-height: 16px;
letter-spacing: 0;
}

View file

@ -74,9 +74,9 @@
@include font-body-1;
align-items: center;
display: flex;
height: 52px;
justify-content: space-between;
width: 100%;
padding: 8px 0;
&--no-pointer {
cursor: inherit;
@ -144,9 +144,9 @@
}
&__divider {
border-color: $color-gray-65;
border-style: solid;
width: 100%;
border: 0 solid $color-gray-65;
border-top-width: 1px;
}
&__input__container {
@ -165,12 +165,51 @@
margin-top: 32px;
}
&__description {
@include font-subtitle;
color: $color-gray-25;
margin-top: 0px;
margin-bottom: 16px;
}
&__listHeader {
display: flex;
align-items: center;
}
&__listHeader__title {
flex: 1;
@include font-body-1-bold;
margin-right: 8px;
}
&__listHeader__button {
@include button-reset;
@include button-secondary;
@include rounded-corners;
padding: 4px 10px;
@include font-family;
font-size: 12px;
display: flex;
align-items: center;
&::before {
content: '';
display: inline-block;
width: 12px;
height: 12px;
flex-shrink: 0;
margin-right: 6px;
@include color-svg('../images/icons/v2/plus-24.svg', $color-white);
}
}
&__delete-list {
@include button-reset;
align-items: center;
color: $color-accent-red;
display: flex;
height: 52px;
padding: 8px 0;
width: 100%;
&::before {
@ -186,7 +225,22 @@
}
&__checkbox {
margin: 18px 0;
margin: 14px 0;
}
&__checkbox-container {
flex: 1;
display: flex;
align-items: center;
}
&__checkbox-label {
flex: 1;
margin-right: 8px;
}
&__checkbox-description {
color: $color-gray-25;
}
&__conversation-list {
@ -204,4 +258,15 @@
color: $color-gray-05;
}
}
&__stories-off-container {
display: flex;
gap: 16;
align-items: center;
}
&__stories-off-text {
flex: 1;
font-size: 12px;
}
}

View file

@ -12,6 +12,7 @@ export type PropsType = {
id: string;
checkboxNode: JSX.Element;
labelNode: JSX.Element;
checked?: boolean;
}) => JSX.Element;
description?: string;
disabled?: boolean;
@ -65,7 +66,7 @@ export const Checkbox = ({
<div className={getClassName('')}>
<div className={getClassName('__container')}>
{children ? (
children({ id, checkboxNode, labelNode })
children({ id, checkboxNode, labelNode, checked })
) : (
<>
{checkboxNode}

View file

@ -113,7 +113,7 @@ function getKeyForMyStoryType(list: StoryDistributionListWithMembersDataType) {
return 'StoriesSettings__mine__all--label';
}
function getListViewers(
export function getListViewers(
list: StoryDistributionListWithMembersDataType,
i18n: LocalizerType,
signalConnections: Array<ConversationType>

View file

@ -7,7 +7,10 @@ import React from 'react';
import type { PropsType } from './StoriesSettingsModal';
import enMessages from '../../_locales/en/messages.json';
import { StoriesSettingsModal } from './StoriesSettingsModal';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import {
getDefaultConversation,
getDefaultGroup,
} from '../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../util/setupI18n';
import {
getMyStories,
@ -23,9 +26,15 @@ export default {
candidateConversations: {
defaultValue: Array.from(Array(100), () => getDefaultConversation()),
},
signalConnections: {
defaultValue: Array.from(Array(42), getDefaultConversation),
},
distributionLists: {
defaultValue: [],
},
groupStories: {
defaultValue: Array.from(Array(2), getDefaultGroup),
},
getPreferredBadge: { action: true },
hideStoriesSettings: { action: true },
i18n: {
@ -43,6 +52,7 @@ export default {
onViewersUpdated: { action: true },
setMyStoriesToAllSignalConnections: { action: true },
toggleSignalConnectionsModal: { action: true },
setStoriesDisabled: { action: true },
},
} as Meta;

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { MeasuredComponentProps } from 'react-measure';
import type { ReactNode } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import Measure from 'react-measure';
import { noop } from 'lodash';
@ -36,10 +37,12 @@ import {
asyncShouldNeverBeCalled,
} from '../util/shouldNeverBeCalled';
import { useConfirmDiscard } from '../hooks/useConfirmDiscard';
import { getListViewers } from './SendStoryModal';
export type PropsType = {
candidateConversations: Array<ConversationType>;
distributionLists: Array<StoryDistributionListWithMembersDataType>;
signalConnections: Array<ConversationType>;
getPreferredBadge: PreferredBadgeSelectorType;
hideStoriesSettings: () => unknown;
i18n: LocalizerType;
@ -62,6 +65,8 @@ export type PropsType = {
setMyStoriesToAllSignalConnections: () => unknown;
storyViewReceiptsEnabled: boolean;
toggleSignalConnectionsModal: () => unknown;
toggleStoriesView: () => void;
setStoriesDisabled: (value: boolean) => void;
};
export enum Page {
@ -89,9 +94,65 @@ const modalCommonProps: Pick<ModalPropsType, 'hasXButton' | 'moduleClassName'> =
moduleClassName: 'StoriesSettingsModal__modal',
};
type DistributionListItemProps = {
i18n: LocalizerType;
distributionList: StoryDistributionListWithMembersDataType;
me: ConversationType;
signalConnections: Array<ConversationType>;
onSelectItemToEdit(id: UUIDStringType): void;
};
function DistributionListItem({
i18n,
distributionList,
me,
signalConnections,
onSelectItemToEdit,
}: DistributionListItemProps) {
return (
<button
className="StoriesSettingsModal__list"
onClick={() => {
onSelectItemToEdit(distributionList.id);
}}
type="button"
>
<span className="StoriesSettingsModal__list__left">
{distributionList.id === MY_STORIES_ID ? (
<Avatar
acceptedMessageRequest={me.acceptedMessageRequest}
avatarPath={me.avatarPath}
badge={undefined}
color={me.color}
conversationType={me.type}
i18n={i18n}
isMe
sharedGroupNames={me.sharedGroupNames}
size={AvatarSize.THIRTY_SIX}
title={me.title}
/>
) : (
<span className="StoriesSettingsModal__list__avatar--private" />
)}
<span className="StoriesSettingsModal__list__title">
<StoryDistributionListName
i18n={i18n}
id={distributionList.id}
name={distributionList.name}
/>
</span>
</span>
<span className="StoriesSettingsModal__list__viewers">
{getListViewers(distributionList, i18n, signalConnections)}
</span>
</button>
);
}
export const StoriesSettingsModal = ({
candidateConversations,
distributionLists,
signalConnections,
getPreferredBadge,
hideStoriesSettings,
i18n,
@ -105,6 +166,8 @@ export const StoriesSettingsModal = ({
setMyStoriesToAllSignalConnections,
storyViewReceiptsEnabled,
toggleSignalConnectionsModal,
toggleStoriesView,
setStoriesDisabled,
}: PropsType): JSX.Element => {
const [confirmDiscardModal, confirmDiscardIf] = useConfirmDiscard(i18n);
@ -200,10 +263,6 @@ export const StoriesSettingsModal = ({
/>
);
} else {
const privateStories = distributionLists.filter(
list => list.id !== MY_STORIES_ID
);
modal = onClose => (
<ModalPage
modalName="StoriesSettingsModal__list"
@ -212,72 +271,36 @@ export const StoriesSettingsModal = ({
title={i18n('StoriesSettings__title')}
{...modalCommonProps}
>
<button
className="StoriesSettingsModal__list"
onClick={() => {
setListToEditId(MY_STORIES_ID);
}}
type="button"
>
<span className="StoriesSettingsModal__list__left">
<Avatar
acceptedMessageRequest={me.acceptedMessageRequest}
avatarPath={me.avatarPath}
badge={getPreferredBadge(me.badges)}
color={me.color}
conversationType={me.type}
i18n={i18n}
isMe
sharedGroupNames={me.sharedGroupNames}
size={AvatarSize.THIRTY_SIX}
theme={ThemeType.dark}
title={me.title}
/>
<span className="StoriesSettingsModal__list__title">
{i18n('Stories__mine')}
</span>
</span>
<p className="StoriesSettingsModal__description">
{i18n('icu:StoriesSettings__description')}
</p>
<span className="StoriesSettingsModal__list__viewers" />
</button>
<button
className="StoriesSettingsModal__list"
onClick={() => {
setPage(Page.ChooseViewers);
}}
type="button"
>
<span className="StoriesSettingsModal__list__left">
<span className="StoriesSettingsModal__list__avatar--new" />
<span className="StoriesSettingsModal__list__title">
{i18n('StoriesSettings__new-list')}
</span>
</span>
</button>
{privateStories.map(list => (
<div className="StoriesSettingsModal__listHeader">
<h2 className="StoriesSettingsModal__listHeader__title">
{i18n('Stories__mine')}
</h2>
<button
className="StoriesSettingsModal__list"
key={list.id}
onClick={() => {
setListToEditId(list.id);
}}
type="button"
className="StoriesSettingsModal__listHeader__button"
onClick={() => {
setPage(Page.ChooseViewers);
}}
>
<span className="StoriesSettingsModal__list__left">
<span className="StoriesSettingsModal__list__avatar--custom" />
<span className="StoriesSettingsModal__list__title">
{list.name}
</span>
</span>
<span className="StoriesSettingsModal__list__viewers">
{i18n('icu:StoriesSettings__viewers', {
count: list.members.length,
})}
</span>
{i18n('StoriesSettings__new-list')}
</button>
))}
</div>
{distributionLists.map(distributionList => {
return (
<DistributionListItem
i18n={i18n}
me={me}
distributionList={distributionList}
signalConnections={signalConnections}
onSelectItemToEdit={setListToEditId}
/>
);
})}
<hr className="StoriesSettingsModal__divider" />
@ -290,6 +313,22 @@ export const StoriesSettingsModal = ({
name="view-receipts"
onChange={noop}
/>
<div className="StoriesSettingsModal__stories-off-container">
<p className="StoriesSettingsModal__stories-off-text">
{i18n('Preferences__turn-stories-off--body')}
</p>
<Button
className="Preferences__stories-off"
onClick={async () => {
setStoriesDisabled(true);
toggleStoriesView();
onClose();
}}
>
{i18n('Preferences__turn-stories-off')}
</Button>
</div>
</ModalPage>
);
}
@ -555,6 +594,30 @@ export const DistributionListSettingsModal = ({
);
};
type CheckboxRenderProps = {
checkboxNode: ReactNode;
labelNode: ReactNode;
descriptionNode: ReactNode;
};
function CheckboxRender({
checkboxNode,
labelNode,
descriptionNode,
}: CheckboxRenderProps) {
return (
<>
{checkboxNode}
<div className="StoriesSettingsModal__checkbox-container">
<div className="StoriesSettingsModal__checkbox-label">{labelNode}</div>
<div className="StoriesSettingsModal__checkbox-description">
{descriptionNode}
</div>
</div>
</>
);
}
type EditMyStoriesPrivacyPropsType = {
hasDisclaimerAbove?: boolean;
i18n: LocalizerType;
@ -605,7 +668,6 @@ export const EditMyStoriesPrivacy = ({
<Checkbox
checked={!myStories.members.length}
description={i18n('StoriesSettings__mine__all--description')}
isRadio
label={i18n('StoriesSettings__mine__all--label')}
moduleClassName="StoriesSettingsModal__checkbox"
@ -613,13 +675,28 @@ export const EditMyStoriesPrivacy = ({
onChange={() => {
setMyStoriesToAllSignalConnections();
}}
/>
>
{({ checkboxNode, labelNode, checked }) => {
return (
<CheckboxRender
checkboxNode={checkboxNode}
labelNode={labelNode}
descriptionNode={
checked && (
<>
{i18n('icu:StoriesSettings__viewers', {
count: myStories.members.length,
})}
</>
)
}
/>
);
}}
</Checkbox>
<Checkbox
checked={myStories.isBlockList && myStories.members.length > 0}
description={i18n('StoriesSettings__mine__exclude--description', [
myStories.isBlockList ? String(myStories.members.length) : '0',
])}
isRadio
label={i18n('StoriesSettings__mine__exclude--label')}
moduleClassName="StoriesSettingsModal__checkbox"
@ -631,17 +708,28 @@ export const EditMyStoriesPrivacy = ({
}
onClickExclude();
}}
/>
>
{({ checkboxNode, labelNode, checked }) => {
return (
<CheckboxRender
checkboxNode={checkboxNode}
labelNode={labelNode}
descriptionNode={
checked && (
<>
{i18n('icu:StoriesSettings__viewers', {
count: myStories.members.length,
})}
</>
)
}
/>
);
}}
</Checkbox>
<Checkbox
checked={!myStories.isBlockList && myStories.members.length > 0}
description={
!myStories.isBlockList && myStories.members.length
? i18n('StoriesSettings__mine__only--description--people', [
String(myStories.members.length),
])
: i18n('StoriesSettings__mine__only--description')
}
isRadio
label={i18n('StoriesSettings__mine__only--label')}
moduleClassName="StoriesSettingsModal__checkbox"
@ -653,7 +741,25 @@ export const EditMyStoriesPrivacy = ({
}
onClickOnlyShareWith();
}}
/>
>
{({ checkboxNode, labelNode, checked }) => {
return (
<CheckboxRender
checkboxNode={checkboxNode}
labelNode={labelNode}
descriptionNode={
checked && (
<>
{i18n('icu:StoriesSettings__viewers', {
count: myStories.members.length,
})}
</>
)
}
/>
);
}}
</Checkbox>
{!hasDisclaimerAbove && disclaimerElement}
</>

View file

@ -1130,6 +1130,14 @@ const viewStory: ViewStoryActionCreatorType = (
};
};
function setStoriesDisabled(
value: boolean
): ThunkAction<void, RootStateType, unknown, never> {
return async () => {
await window.Events.setHasStoriesDisabled(value);
};
}
export const actions = {
deleteStoryForEveryone,
loadStoryReplies,
@ -1145,6 +1153,7 @@ export const actions = {
verifyStoryListMembers,
viewUserStories,
viewStory,
setStoriesDisabled,
};
export const useStoriesActions = (): typeof actions => useBoundActions(actions);

View file

@ -8,6 +8,7 @@ import type { LocalizerType } from '../../types/Util';
import type { StateType } from '../reducer';
import { StoriesSettingsModal } from '../../components/StoriesSettingsModal';
import {
getAllSignalConnections,
getCandidateContactsForNewGroup,
getMe,
} from '../selectors/conversations';
@ -17,8 +18,10 @@ import { getPreferredBadgeSelector } from '../selectors/badges';
import { getHasStoryViewReceiptSetting } from '../selectors/items';
import { useGlobalModalActions } from '../ducks/globalModals';
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
import { useStoriesActions } from '../ducks/stories';
export function SmartStoriesSettingsModal(): JSX.Element | null {
const { toggleStoriesView, setStoriesDisabled } = useStoriesActions();
const { hideStoriesSettings, toggleSignalConnectionsModal } =
useGlobalModalActions();
const {
@ -30,6 +33,7 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
setMyStoriesToAllSignalConnections,
updateStoryViewers,
} = useStoryDistributionListsActions();
const signalConnections = useSelector(getAllSignalConnections);
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
const storyViewReceiptsEnabled = useSelector(getHasStoryViewReceiptSetting);
@ -43,6 +47,7 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
<StoriesSettingsModal
candidateConversations={candidateConversations}
distributionLists={distributionLists}
signalConnections={signalConnections}
hideStoriesSettings={hideStoriesSettings}
getPreferredBadge={getPreferredBadge}
i18n={i18n}
@ -56,6 +61,8 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
setMyStoriesToAllSignalConnections={setMyStoriesToAllSignalConnections}
storyViewReceiptsEnabled={storyViewReceiptsEnabled}
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
toggleStoriesView={toggleStoriesView}
setStoriesDisabled={setStoriesDisabled}
/>
);
}