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",
"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": {
"message": "Add text",
"description": "Placeholder for the add text input"

View file

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

View file

@ -440,6 +440,8 @@ export async function sendStory(
const oldSendStateByConversationId =
message.get('sendStateByConversationId') || {};
let hasFailedSends = false;
const newSendStateByConversationId = Object.keys(
oldSendStateByConversationId
).reduce((acc, conversationId) => {
@ -481,6 +483,8 @@ export async function sendStory(
};
}
hasFailedSends = true;
return {
...acc,
[conversationId]: sendStateReducer(oldSendState, {
@ -490,6 +494,10 @@ export async function sendStory(
};
}, {} as SendStateByConversationId);
if (hasFailedSends) {
message.notifyStorySendFailed();
}
if (isEqual(oldSendStateByConversationId, newSendStateByConversationId)) {
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 {
@ -1619,6 +1639,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
updatedAt: Date.now(),
}
);
this.notifyStorySendFailed();
}
}

View file

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

View file

@ -576,3 +576,16 @@ export const getHasAllStoriesUnmuted = createSelector(
getStoriesState,
({ 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';
import { getMe } from '../selectors/conversations';
import { getStoriesEnabled } from '../selectors/items';
import { getStoriesNotificationCount } from '../selectors/stories';
import {
getStoriesNotificationCount,
getHasAnyFailedStorySends,
} from '../selectors/stories';
const mapStateToProps = (state: StateType) => {
const me = getMe(state);
@ -32,6 +35,7 @@ const mapStateToProps = (state: StateType) => {
badge: getPreferredBadgeSelector(state)(me.badges),
theme: getTheme(state),
i18n: getIntl(state),
hasFailedStorySends: getHasAnyFailedStorySends(state),
unreadStoriesCount: getStoriesNotificationCount(state),
};
};