Notification for failed story sends

This commit is contained in:
Josh Perez 2023-02-07 14:33:04 -05:00 committed by GitHub
parent 4c2f169783
commit e11f961d7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 67 additions and 3 deletions

View file

@ -6315,6 +6315,14 @@
"message": "Add a link for viewers of your story", "message": "Add a link for viewers of your story",
"description": "Empty state for the link preview" "description": "Empty state for the link preview"
}, },
"icu:Stories__failed-send--full": {
"messageformat": "Story failed to send",
"description": "Notification text whenever a story fails to send"
},
"icu:Stories__failed-send--partial": {
"messageformat": "Story couldn't be sent to all recipients",
"description": "Notification text whenever a story partially fails to send"
},
"TextAttachment__placeholder": { "TextAttachment__placeholder": {
"message": "Add text", "message": "Add text",
"description": "Placeholder for the add text input" "description": "Placeholder for the add text input"

View file

@ -28,6 +28,7 @@ export type PropsType = {
profileName?: string; profileName?: string;
theme: ThemeType; theme: ThemeType;
title: string; title: string;
hasFailedStorySends?: boolean;
unreadStoriesCount: number; unreadStoriesCount: number;
showArchivedConversations: () => void; showArchivedConversations: () => void;
@ -149,6 +150,7 @@ export class MainHeader extends React.Component<PropsType, StateType> {
avatarPath, avatarPath,
badge, badge,
color, color,
hasFailedStorySends,
hasPendingUpdate, hasPendingUpdate,
i18n, i18n,
name, name,
@ -251,7 +253,10 @@ export class MainHeader extends React.Component<PropsType, StateType> {
title={i18n('stories')} title={i18n('stories')}
type="button" type="button"
> >
{unreadStoriesCount ? ( {hasFailedStorySends && (
<span className="module-main-header__stories-badge">!</span>
)}
{!hasFailedStorySends && unreadStoriesCount ? (
<span className="module-main-header__stories-badge"> <span className="module-main-header__stories-badge">
{unreadStoriesCount} {unreadStoriesCount}
</span> </span>

View file

@ -440,6 +440,8 @@ export async function sendStory(
const oldSendStateByConversationId = const oldSendStateByConversationId =
message.get('sendStateByConversationId') || {}; message.get('sendStateByConversationId') || {};
let hasFailedSends = false;
const newSendStateByConversationId = Object.keys( const newSendStateByConversationId = Object.keys(
oldSendStateByConversationId oldSendStateByConversationId
).reduce((acc, conversationId) => { ).reduce((acc, conversationId) => {
@ -481,6 +483,8 @@ export async function sendStory(
}; };
} }
hasFailedSends = true;
return { return {
...acc, ...acc,
[conversationId]: sendStateReducer(oldSendState, { [conversationId]: sendStateReducer(oldSendState, {
@ -490,6 +494,10 @@ export async function sendStory(
}; };
}, {} as SendStateByConversationId); }, {} as SendStateByConversationId);
if (hasFailedSends) {
message.notifyStorySendFailed();
}
if (isEqual(oldSendStateByConversationId, newSendStateByConversationId)) { if (isEqual(oldSendStateByConversationId, newSendStateByConversationId)) {
return; return;
} }

View file

@ -1467,6 +1467,26 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}) })
) )
); );
this.notifyStorySendFailed();
}
public notifyStorySendFailed(): void {
if (!isStory(this.attributes)) {
return;
}
notificationService.add({
conversationId: this.get('conversationId'),
storyId: this.id,
messageId: this.id,
senderTitle:
this.getConversation()?.getTitle() ?? window.i18n('Stories__mine'),
message: this.hasSuccessfulDelivery()
? window.i18n('icu:Stories__failed-send--partial')
: window.i18n('icu:Stories__failed-send--full'),
isExpiringMessage: false,
});
} }
removeOutgoingErrors(incomingIdentifier: string): CustomError { removeOutgoingErrors(incomingIdentifier: string): CustomError {
@ -1619,6 +1639,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
updatedAt: Date.now(), updatedAt: Date.now(),
} }
); );
this.notifyStorySendFailed();
} }
} }

View file

@ -23,6 +23,7 @@ import * as log from '../../logging/log';
import { SIGNAL_ACI } from '../../types/SignalConversation'; import { SIGNAL_ACI } from '../../types/SignalConversation';
import dataInterface from '../../sql/Client'; import dataInterface from '../../sql/Client';
import { ReadStatus } from '../../messages/MessageReadStatus'; import { ReadStatus } from '../../messages/MessageReadStatus';
import { SendStatus } from '../../messages/MessageSendState';
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog'; import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories'; import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
import { assertDev, strictAssert } from '../../util/assert'; import { assertDev, strictAssert } from '../../util/assert';
@ -987,6 +988,10 @@ const viewStory: ViewStoryActionCreatorType = (
// Go directly to the storyId selected // Go directly to the storyId selected
if (!viewDirection) { if (!viewDirection) {
const hasFailedSend = Object.values(
story.sendStateByConversationId || {}
).some(({ status }) => status === SendStatus.Failed);
dispatch({ dispatch({
type: VIEW_STORY, type: VIEW_STORY,
payload: { payload: {
@ -995,7 +1000,7 @@ const viewStory: ViewStoryActionCreatorType = (
numStories, numStories,
storyViewMode, storyViewMode,
unviewedStoryConversationIdsSorted, unviewedStoryConversationIdsSorted,
viewTarget, viewTarget: hasFailedSend ? undefined : viewTarget,
}, },
}); });
return; return;

View file

@ -576,3 +576,16 @@ export const getHasAllStoriesUnmuted = createSelector(
getStoriesState, getStoriesState,
({ hasAllStoriesUnmuted }): boolean => hasAllStoriesUnmuted ({ hasAllStoriesUnmuted }): boolean => hasAllStoriesUnmuted
); );
export const getHasAnyFailedStorySends = createSelector(
getStoriesState,
({ lastOpenedAtTimestamp, stories }): boolean => {
return stories.some(
story =>
story.timestamp > (lastOpenedAtTimestamp || 0) &&
Object.values(story.sendStateByConversationId || {}).some(
({ status }) => status === SendStatus.Failed
)
);
}
);

View file

@ -17,7 +17,10 @@ import {
} from '../selectors/user'; } from '../selectors/user';
import { getMe } from '../selectors/conversations'; import { getMe } from '../selectors/conversations';
import { getStoriesEnabled } from '../selectors/items'; import { getStoriesEnabled } from '../selectors/items';
import { getStoriesNotificationCount } from '../selectors/stories'; import {
getStoriesNotificationCount,
getHasAnyFailedStorySends,
} from '../selectors/stories';
const mapStateToProps = (state: StateType) => { const mapStateToProps = (state: StateType) => {
const me = getMe(state); const me = getMe(state);
@ -32,6 +35,7 @@ const mapStateToProps = (state: StateType) => {
badge: getPreferredBadgeSelector(state)(me.badges), badge: getPreferredBadgeSelector(state)(me.badges),
theme: getTheme(state), theme: getTheme(state),
i18n: getIntl(state), i18n: getIntl(state),
hasFailedStorySends: getHasAnyFailedStorySends(state),
unreadStoriesCount: getStoriesNotificationCount(state), unreadStoriesCount: getStoriesNotificationCount(state),
}; };
}; };