UI for when read receipts are turned off

This commit is contained in:
Josh Perez 2022-08-31 12:11:14 -04:00 committed by GitHub
parent 7632f31cf2
commit 39143015c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 33 deletions

View file

@ -7487,6 +7487,10 @@
"message": "Unmute", "message": "Unmute",
"description": "Aria label for unmuting stories" "description": "Aria label for unmuting stories"
}, },
"StoryViewer__views-off": {
"message": "Views off",
"description": "When the user has read receipts turned off"
},
"StoryDetailsModal__sent-time": { "StoryDetailsModal__sent-time": {
"message": "Sent $time$", "message": "Sent $time$",
"description": "Sent timestamp", "description": "Sent timestamp",
@ -7511,6 +7515,10 @@
"message": "Copy timestamp", "message": "Copy timestamp",
"description": "Context menu item to help debugging" "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": { "StoryViewsNRepliesModal__no-replies": {
"message": "No replies yet", "message": "No replies yet",
"description": "Placeholder text for when there are no replies" "description": "Placeholder text for when there are no replies"

View file

@ -208,6 +208,11 @@
&__copy-icon { &__copy-icon {
@include color-svg('../images/icons/v2/copy-outline-24.svg', $color-white); @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 { .Tabs.StoryViewsNRepliesModal__tabs {

View file

@ -31,6 +31,9 @@ export default {
hasAllStoriesMuted: { hasAllStoriesMuted: {
defaultValue: false, defaultValue: false,
}, },
hasReadReceiptSetting: {
defaultValue: true,
},
i18n: { i18n: {
defaultValue: i18n, defaultValue: i18n,
}, },
@ -168,3 +171,35 @@ export const YourStory = Template.bind({});
}; };
YourStory.storyName = 'Your story'; 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';

View file

@ -59,6 +59,7 @@ export type PropsType = {
>; >;
hasActiveCall?: boolean; hasActiveCall?: boolean;
hasAllStoriesMuted: boolean; hasAllStoriesMuted: boolean;
hasReadReceiptSetting: boolean;
i18n: LocalizerType; i18n: LocalizerType;
loadStoryReplies: (conversationId: string, messageId: string) => unknown; loadStoryReplies: (conversationId: string, messageId: string) => unknown;
markStoryRead: (mId: string) => unknown; markStoryRead: (mId: string) => unknown;
@ -107,6 +108,7 @@ export const StoryViewer = ({
group, group,
hasActiveCall, hasActiveCall,
hasAllStoriesMuted, hasAllStoriesMuted,
hasReadReceiptSetting,
i18n, i18n,
loadStoryReplies, loadStoryReplies,
markStoryRead, markStoryRead,
@ -661,7 +663,11 @@ export const StoryViewer = ({
<> <>
{sendState || replyCount > 0 ? ( {sendState || replyCount > 0 ? (
<span className="StoryViewer__reply__chevron"> <span className="StoryViewer__reply__chevron">
{sendState && !hasReadReceiptSetting && !replyCount && (
<>{i18n('StoryViewer__views-off')}</>
)}
{sendState && {sendState &&
hasReadReceiptSetting &&
(viewCount === 1 ? ( (viewCount === 1 ? (
<Intl <Intl
i18n={i18n} i18n={i18n}
@ -749,6 +755,7 @@ export const StoryViewer = ({
authorTitle={firstName || title} authorTitle={firstName || title}
canReply={Boolean(canReply)} canReply={Boolean(canReply)}
getPreferredBadge={getPreferredBadge} getPreferredBadge={getPreferredBadge}
hasReadReceiptSetting={hasReadReceiptSetting}
i18n={i18n} i18n={i18n}
isGroupStory={isGroupStory} isGroupStory={isGroupStory}
onClose={() => setHasStoryViewsNRepliesModal(false)} onClose={() => setHasStoryViewsNRepliesModal(false)}

View file

@ -28,6 +28,9 @@ export default {
defaultValue: true, defaultValue: true,
}, },
getPreferredBadge: { action: true }, getPreferredBadge: { action: true },
hasReadReceiptSetting: {
defaultValue: true,
},
i18n: { i18n: {
defaultValue: i18n, defaultValue: i18n,
}, },
@ -202,3 +205,23 @@ export const InAGroupCantReply = Template.bind({});
}; };
} }
InAGroupCantReply.storyName = "In a group (can't reply)"; 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)';

View file

@ -87,6 +87,7 @@ export type PropsType = {
authorTitle: string; authorTitle: string;
canReply: boolean; canReply: boolean;
getPreferredBadge: PreferredBadgeSelectorType; getPreferredBadge: PreferredBadgeSelectorType;
hasReadReceiptSetting: boolean;
i18n: LocalizerType; i18n: LocalizerType;
isGroupStory?: boolean; isGroupStory?: boolean;
onClose: () => unknown; onClose: () => unknown;
@ -113,6 +114,7 @@ export const StoryViewsNRepliesModal = ({
authorTitle, authorTitle,
canReply, canReply,
getPreferredBadge, getPreferredBadge,
hasReadReceiptSetting,
i18n, i18n,
isGroupStory, isGroupStory,
onClose, onClose,
@ -353,40 +355,52 @@ export const StoryViewsNRepliesModal = ({
); );
} }
const viewsElement = views.length ? ( let viewsElement: JSX.Element | undefined;
<div className="StoryViewsNRepliesModal__views"> if (!hasReadReceiptSetting) {
{views.map(view => ( viewsElement = (
<div className="StoryViewsNRepliesModal__view" key={view.recipient.id}> <div className="StoryViewsNRepliesModal__read-receipts-off">
<div> {i18n('StoryViewsNRepliesModal__read-receipts-off')}
<Avatar </div>
acceptedMessageRequest={view.recipient.acceptedMessageRequest} );
avatarPath={view.recipient.avatarPath} } else if (views.length) {
badge={undefined} viewsElement = (
color={getAvatarColor(view.recipient.color)} <div className="StoryViewsNRepliesModal__views">
conversationType="direct" {views.map(view => (
i18n={i18n} <div
isMe={Boolean(view.recipient.isMe)} className="StoryViewsNRepliesModal__view"
name={view.recipient.name} key={view.recipient.id}
profileName={view.recipient.profileName} >
sharedGroupNames={view.recipient.sharedGroupNames || []} <div>
size={AvatarSize.TWENTY_EIGHT} <Avatar
title={view.recipient.title} acceptedMessageRequest={view.recipient.acceptedMessageRequest}
/> avatarPath={view.recipient.avatarPath}
<span className="StoryViewsNRepliesModal__view--name"> badge={undefined}
<ContactName title={view.recipient.title} /> color={getAvatarColor(view.recipient.color)}
</span> conversationType="direct"
i18n={i18n}
isMe={Boolean(view.recipient.isMe)}
name={view.recipient.name}
profileName={view.recipient.profileName}
sharedGroupNames={view.recipient.sharedGroupNames || []}
size={AvatarSize.TWENTY_EIGHT}
title={view.recipient.title}
/>
<span className="StoryViewsNRepliesModal__view--name">
<ContactName title={view.recipient.title} />
</span>
</div>
{view.updatedAt && (
<MessageTimestamp
i18n={i18n}
module="StoryViewsNRepliesModal__view--timestamp"
timestamp={view.updatedAt}
/>
)}
</div> </div>
{view.updatedAt && ( ))}
<MessageTimestamp </div>
i18n={i18n} );
module="StoryViewsNRepliesModal__view--timestamp" }
timestamp={view.updatedAt}
/>
)}
</div>
))}
</div>
) : undefined;
const tabsElement = const tabsElement =
views.length && replies.length ? ( views.length && replies.length ? (

View file

@ -130,3 +130,8 @@ export const getHasSetMyStoriesPrivacy = createSelector(
getItems, getItems,
(state: ItemsStateType): boolean => Boolean(state.hasSetMyStoriesPrivacy) (state: ItemsStateType): boolean => Boolean(state.hasSetMyStoriesPrivacy)
); );
export const getHasReadReceiptSetting = createSelector(
getItems,
(state: ItemsStateType): boolean => Boolean(state['read-receipt-setting'])
);

View file

@ -14,6 +14,7 @@ import { getConversationSelector } from '../selectors/conversations';
import { import {
getEmojiSkinTone, getEmojiSkinTone,
getHasAllStoriesMuted, getHasAllStoriesMuted,
getHasReadReceiptSetting,
getPreferredReactionEmoji, getPreferredReactionEmoji,
} from '../selectors/items'; } from '../selectors/items';
import { getIntl } from '../selectors/user'; import { getIntl } from '../selectors/user';
@ -76,6 +77,9 @@ export function SmartStoryViewer(): JSX.Element | null {
); );
const hasActiveCall = useSelector(isInFullScreenCall); const hasActiveCall = useSelector(isInFullScreenCall);
const hasReadReceiptSetting = useSelector<StateType, boolean>(
getHasReadReceiptSetting
);
return ( return (
<StoryViewer <StoryViewer
@ -84,6 +88,7 @@ export function SmartStoryViewer(): JSX.Element | null {
group={conversationStory.group} group={conversationStory.group}
hasActiveCall={hasActiveCall} hasActiveCall={hasActiveCall}
hasAllStoriesMuted={hasAllStoriesMuted} hasAllStoriesMuted={hasAllStoriesMuted}
hasReadReceiptSetting={hasReadReceiptSetting}
i18n={i18n} i18n={i18n}
numStories={selectedStoryData.numStories} numStories={selectedStoryData.numStories}
onHideStory={toggleHideStories} onHideStory={toggleHideStories}