diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 6844d453a89b..d07a44f7ee70 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -7487,6 +7487,10 @@ "message": "Unmute", "description": "Aria label for unmuting stories" }, + "StoryViewer__views-off": { + "message": "Views off", + "description": "When the user has read receipts turned off" + }, "StoryDetailsModal__sent-time": { "message": "Sent $time$", "description": "Sent timestamp", @@ -7511,6 +7515,10 @@ "message": "Copy timestamp", "description": "Context menu item to help debugging" }, + "StoryViewsNRepliesModal__read-receipts-off": { + "message": "Enable read receipts to see who's viewed your stories. Open the Signal app on your mobile device and navigate to Settings > Privacy.", + "description": "Instructions on how to enable read receipts" + }, "StoryViewsNRepliesModal__no-replies": { "message": "No replies yet", "description": "Placeholder text for when there are no replies" diff --git a/stylesheets/components/StoryViewsNRepliesModal.scss b/stylesheets/components/StoryViewsNRepliesModal.scss index 7d7d70c3c46f..99f6bd0fc182 100644 --- a/stylesheets/components/StoryViewsNRepliesModal.scss +++ b/stylesheets/components/StoryViewsNRepliesModal.scss @@ -208,6 +208,11 @@ &__copy-icon { @include color-svg('../images/icons/v2/copy-outline-24.svg', $color-white); } + + &__read-receipts-off { + color: $color-gray-25; + margin: 160px 16px; + } } .Tabs.StoryViewsNRepliesModal__tabs { diff --git a/ts/components/StoryViewer.stories.tsx b/ts/components/StoryViewer.stories.tsx index 468bade01589..cbb894996e71 100644 --- a/ts/components/StoryViewer.stories.tsx +++ b/ts/components/StoryViewer.stories.tsx @@ -31,6 +31,9 @@ export default { hasAllStoriesMuted: { defaultValue: false, }, + hasReadReceiptSetting: { + defaultValue: true, + }, i18n: { defaultValue: i18n, }, @@ -168,3 +171,35 @@ export const YourStory = Template.bind({}); }; YourStory.storyName = 'Your story'; } + +export const ReadReceiptsOff = Template.bind({}); +{ + const storyView = getFakeStoryView( + '/fixtures/nathan-anderson-316188-unsplash.jpg' + ); + ReadReceiptsOff.args = { + hasReadReceiptSetting: false, + story: { + ...storyView, + sender: { + ...storyView.sender, + isMe: true, + }, + sendState: [ + { + recipient: getDefaultConversation(), + status: SendStatus.Viewed, + }, + { + recipient: getDefaultConversation(), + status: SendStatus.Delivered, + }, + { + recipient: getDefaultConversation(), + status: SendStatus.Pending, + }, + ], + }, + }; +} +ReadReceiptsOff.storyName = 'Read receipts turned off'; diff --git a/ts/components/StoryViewer.tsx b/ts/components/StoryViewer.tsx index 8dfe6e0c38e5..aef896565c5b 100644 --- a/ts/components/StoryViewer.tsx +++ b/ts/components/StoryViewer.tsx @@ -59,6 +59,7 @@ export type PropsType = { >; hasActiveCall?: boolean; hasAllStoriesMuted: boolean; + hasReadReceiptSetting: boolean; i18n: LocalizerType; loadStoryReplies: (conversationId: string, messageId: string) => unknown; markStoryRead: (mId: string) => unknown; @@ -107,6 +108,7 @@ export const StoryViewer = ({ group, hasActiveCall, hasAllStoriesMuted, + hasReadReceiptSetting, i18n, loadStoryReplies, markStoryRead, @@ -661,7 +663,11 @@ export const StoryViewer = ({ <> {sendState || replyCount > 0 ? ( + {sendState && !hasReadReceiptSetting && !replyCount && ( + <>{i18n('StoryViewer__views-off')} + )} {sendState && + hasReadReceiptSetting && (viewCount === 1 ? ( setHasStoryViewsNRepliesModal(false)} diff --git a/ts/components/StoryViewsNRepliesModal.stories.tsx b/ts/components/StoryViewsNRepliesModal.stories.tsx index fdf568ad24d8..823cbaf703b3 100644 --- a/ts/components/StoryViewsNRepliesModal.stories.tsx +++ b/ts/components/StoryViewsNRepliesModal.stories.tsx @@ -28,6 +28,9 @@ export default { defaultValue: true, }, getPreferredBadge: { action: true }, + hasReadReceiptSetting: { + defaultValue: true, + }, i18n: { defaultValue: i18n, }, @@ -202,3 +205,23 @@ export const InAGroupCantReply = Template.bind({}); }; } InAGroupCantReply.storyName = "In a group (can't reply)"; + +export const ReadReceiptsTurnedOff = Template.bind({}); +ReadReceiptsTurnedOff.args = { + canReply: false, + hasReadReceiptSetting: false, + views: getViewsAndReplies().views, +}; +ReadReceiptsTurnedOff.storyName = 'Read receipts turned off'; + +export const GroupReadReceiptsOff = Template.bind({}); +{ + const { views, replies } = getViewsAndReplies(); + GroupReadReceiptsOff.args = { + hasReadReceiptSetting: false, + isGroupStory: true, + replies, + views, + }; +} +GroupReadReceiptsOff.storyName = 'Read receipts turned off (group)'; diff --git a/ts/components/StoryViewsNRepliesModal.tsx b/ts/components/StoryViewsNRepliesModal.tsx index b2d7fc2c1279..068df05c8253 100644 --- a/ts/components/StoryViewsNRepliesModal.tsx +++ b/ts/components/StoryViewsNRepliesModal.tsx @@ -87,6 +87,7 @@ export type PropsType = { authorTitle: string; canReply: boolean; getPreferredBadge: PreferredBadgeSelectorType; + hasReadReceiptSetting: boolean; i18n: LocalizerType; isGroupStory?: boolean; onClose: () => unknown; @@ -113,6 +114,7 @@ export const StoryViewsNRepliesModal = ({ authorTitle, canReply, getPreferredBadge, + hasReadReceiptSetting, i18n, isGroupStory, onClose, @@ -353,40 +355,52 @@ export const StoryViewsNRepliesModal = ({ ); } - const viewsElement = views.length ? ( -
- {views.map(view => ( -
-
- - - - + let viewsElement: JSX.Element | undefined; + if (!hasReadReceiptSetting) { + viewsElement = ( +
+ {i18n('StoryViewsNRepliesModal__read-receipts-off')} +
+ ); + } else if (views.length) { + viewsElement = ( +
+ {views.map(view => ( +
+
+ + + + +
+ {view.updatedAt && ( + + )}
- {view.updatedAt && ( - - )} -
- ))} -
- ) : undefined; + ))} +
+ ); + } const tabsElement = views.length && replies.length ? ( diff --git a/ts/state/selectors/items.ts b/ts/state/selectors/items.ts index 85cbda8a14fe..c8a444e80bcc 100644 --- a/ts/state/selectors/items.ts +++ b/ts/state/selectors/items.ts @@ -130,3 +130,8 @@ export const getHasSetMyStoriesPrivacy = createSelector( getItems, (state: ItemsStateType): boolean => Boolean(state.hasSetMyStoriesPrivacy) ); + +export const getHasReadReceiptSetting = createSelector( + getItems, + (state: ItemsStateType): boolean => Boolean(state['read-receipt-setting']) +); diff --git a/ts/state/smart/StoryViewer.tsx b/ts/state/smart/StoryViewer.tsx index 015f1c9b24b2..d975ca5b1c80 100644 --- a/ts/state/smart/StoryViewer.tsx +++ b/ts/state/smart/StoryViewer.tsx @@ -14,6 +14,7 @@ import { getConversationSelector } from '../selectors/conversations'; import { getEmojiSkinTone, getHasAllStoriesMuted, + getHasReadReceiptSetting, getPreferredReactionEmoji, } from '../selectors/items'; import { getIntl } from '../selectors/user'; @@ -76,6 +77,9 @@ export function SmartStoryViewer(): JSX.Element | null { ); const hasActiveCall = useSelector(isInFullScreenCall); + const hasReadReceiptSetting = useSelector( + getHasReadReceiptSetting + ); return (