diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 476c2ba6db..6af86609c4 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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$", diff --git a/stylesheets/_mixins.scss b/stylesheets/_mixins.scss index 7dc1dd579e..262830cc2f 100644 --- a/stylesheets/_mixins.scss +++ b/stylesheets/_mixins.scss @@ -74,7 +74,7 @@ @mixin font-subtitle { @include font-family; - font-size: 11px; + font-size: 12px; line-height: 16px; letter-spacing: 0; } diff --git a/stylesheets/components/StoriesSettingsModal.scss b/stylesheets/components/StoriesSettingsModal.scss index 351707592f..080bd58af1 100644 --- a/stylesheets/components/StoriesSettingsModal.scss +++ b/stylesheets/components/StoriesSettingsModal.scss @@ -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; + } } diff --git a/ts/components/Checkbox.tsx b/ts/components/Checkbox.tsx index 9059d3fa68..ccfa26fc1f 100644 --- a/ts/components/Checkbox.tsx +++ b/ts/components/Checkbox.tsx @@ -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 = ({
{children ? ( - children({ id, checkboxNode, labelNode }) + children({ id, checkboxNode, labelNode, checked }) ) : ( <> {checkboxNode} diff --git a/ts/components/SendStoryModal.tsx b/ts/components/SendStoryModal.tsx index ea3b3600b6..e8d6ead3c1 100644 --- a/ts/components/SendStoryModal.tsx +++ b/ts/components/SendStoryModal.tsx @@ -113,7 +113,7 @@ function getKeyForMyStoryType(list: StoryDistributionListWithMembersDataType) { return 'StoriesSettings__mine__all--label'; } -function getListViewers( +export function getListViewers( list: StoryDistributionListWithMembersDataType, i18n: LocalizerType, signalConnections: Array diff --git a/ts/components/StoriesSettingsModal.stories.tsx b/ts/components/StoriesSettingsModal.stories.tsx index 516336892c..8afd1eb1ba 100644 --- a/ts/components/StoriesSettingsModal.stories.tsx +++ b/ts/components/StoriesSettingsModal.stories.tsx @@ -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; diff --git a/ts/components/StoriesSettingsModal.tsx b/ts/components/StoriesSettingsModal.tsx index 806934413a..7a22f8d69d 100644 --- a/ts/components/StoriesSettingsModal.tsx +++ b/ts/components/StoriesSettingsModal.tsx @@ -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; distributionLists: Array; + signalConnections: Array; 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 = moduleClassName: 'StoriesSettingsModal__modal', }; +type DistributionListItemProps = { + i18n: LocalizerType; + distributionList: StoryDistributionListWithMembersDataType; + me: ConversationType; + signalConnections: Array; + onSelectItemToEdit(id: UUIDStringType): void; +}; + +function DistributionListItem({ + i18n, + distributionList, + me, + signalConnections, + onSelectItemToEdit, +}: DistributionListItemProps) { + return ( + + ); +} + 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 => ( - - - - {privateStories.map(list => ( +
+

+ {i18n('Stories__mine')} +

- ))} +
+ + {distributionLists.map(distributionList => { + return ( + + ); + })}
@@ -290,6 +313,22 @@ export const StoriesSettingsModal = ({ name="view-receipts" onChange={noop} /> + +
+

+ {i18n('Preferences__turn-stories-off--body')} +

+ +
); } @@ -555,6 +594,30 @@ export const DistributionListSettingsModal = ({ ); }; +type CheckboxRenderProps = { + checkboxNode: ReactNode; + labelNode: ReactNode; + descriptionNode: ReactNode; +}; + +function CheckboxRender({ + checkboxNode, + labelNode, + descriptionNode, +}: CheckboxRenderProps) { + return ( + <> + {checkboxNode} +
+
{labelNode}
+
+ {descriptionNode} +
+
+ + ); +} + type EditMyStoriesPrivacyPropsType = { hasDisclaimerAbove?: boolean; i18n: LocalizerType; @@ -605,7 +668,6 @@ export const EditMyStoriesPrivacy = ({ { setMyStoriesToAllSignalConnections(); }} - /> + > + {({ checkboxNode, labelNode, checked }) => { + return ( + + {i18n('icu:StoriesSettings__viewers', { + count: 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 ( + + {i18n('icu:StoriesSettings__viewers', { + count: 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 ( + + {i18n('icu:StoriesSettings__viewers', { + count: myStories.members.length, + })} + + ) + } + /> + ); + }} + {!hasDisclaimerAbove && disclaimerElement} diff --git a/ts/state/ducks/stories.ts b/ts/state/ducks/stories.ts index 85df7f9645..492a505629 100644 --- a/ts/state/ducks/stories.ts +++ b/ts/state/ducks/stories.ts @@ -1130,6 +1130,14 @@ const viewStory: ViewStoryActionCreatorType = ( }; }; +function setStoriesDisabled( + value: boolean +): ThunkAction { + 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); diff --git a/ts/state/smart/StoriesSettingsModal.tsx b/ts/state/smart/StoriesSettingsModal.tsx index 8a34a6678b..506f1e5c4c 100644 --- a/ts/state/smart/StoriesSettingsModal.tsx +++ b/ts/state/smart/StoriesSettingsModal.tsx @@ -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 { ); }