2022-03-04 21:14:52 +00:00
|
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { createSelector } from 'reselect';
|
|
|
|
import { pick } from 'lodash';
|
|
|
|
|
2022-04-15 00:08:46 +00:00
|
|
|
import type { GetConversationByIdType } from './conversations';
|
2022-03-04 21:14:52 +00:00
|
|
|
import type {
|
|
|
|
ConversationStoryType,
|
|
|
|
StoryViewType,
|
|
|
|
} from '../../components/StoryListItem';
|
2022-04-15 00:08:46 +00:00
|
|
|
import type { ReplyStateType } from '../../types/Stories';
|
2022-03-04 21:14:52 +00:00
|
|
|
import type { StateType } from '../reducer';
|
2022-04-15 00:08:46 +00:00
|
|
|
import type { StoryDataType, StoriesStateType } from '../ducks/stories';
|
2022-03-04 21:14:52 +00:00
|
|
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
2022-04-15 00:08:46 +00:00
|
|
|
import { canReply } from './message';
|
|
|
|
import {
|
|
|
|
getContactNameColorSelector,
|
|
|
|
getConversationSelector,
|
|
|
|
getMe,
|
|
|
|
} from './conversations';
|
|
|
|
import { getUserConversationId } from './user';
|
2022-03-04 21:14:52 +00:00
|
|
|
|
|
|
|
export const getStoriesState = (state: StateType): StoriesStateType =>
|
|
|
|
state.stories;
|
|
|
|
|
|
|
|
export const shouldShowStoriesView = createSelector(
|
|
|
|
getStoriesState,
|
|
|
|
({ isShowingStoriesView }): boolean => isShowingStoriesView
|
|
|
|
);
|
|
|
|
|
2022-04-08 15:40:15 +00:00
|
|
|
function getNewestStory(x: ConversationStoryType): StoryViewType {
|
|
|
|
return x.stories[x.stories.length - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
function sortByRecencyAndUnread(
|
|
|
|
a: ConversationStoryType,
|
|
|
|
b: ConversationStoryType
|
|
|
|
): number {
|
|
|
|
const storyA = getNewestStory(a);
|
|
|
|
const storyB = getNewestStory(b);
|
|
|
|
|
|
|
|
if (storyA.isUnread && storyB.isUnread) {
|
|
|
|
return storyA.timestamp > storyB.timestamp ? -1 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storyB.isUnread) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storyA.isUnread) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return storyA.timestamp > storyB.timestamp ? -1 : 1;
|
|
|
|
}
|
|
|
|
|
2022-04-15 00:08:46 +00:00
|
|
|
function getConversationStory(
|
|
|
|
conversationSelector: GetConversationByIdType,
|
|
|
|
story: StoryDataType,
|
|
|
|
ourConversationId?: string
|
|
|
|
): ConversationStoryType {
|
|
|
|
const sender = pick(conversationSelector(story.sourceUuid || story.source), [
|
|
|
|
'acceptedMessageRequest',
|
|
|
|
'avatarPath',
|
|
|
|
'color',
|
|
|
|
'firstName',
|
|
|
|
'hideStory',
|
|
|
|
'id',
|
|
|
|
'isMe',
|
|
|
|
'name',
|
|
|
|
'profileName',
|
|
|
|
'sharedGroupNames',
|
|
|
|
'title',
|
|
|
|
]);
|
|
|
|
|
|
|
|
const conversation = pick(conversationSelector(story.conversationId), [
|
|
|
|
'acceptedMessageRequest',
|
|
|
|
'avatarPath',
|
|
|
|
'color',
|
|
|
|
'id',
|
|
|
|
'name',
|
|
|
|
'profileName',
|
|
|
|
'sharedGroupNames',
|
|
|
|
'title',
|
|
|
|
]);
|
|
|
|
|
|
|
|
const { attachment, timestamp } = pick(story, ['attachment', 'timestamp']);
|
|
|
|
|
|
|
|
const storyView: StoryViewType = {
|
|
|
|
attachment,
|
|
|
|
canReply: canReply(story, ourConversationId, conversationSelector),
|
|
|
|
isUnread: story.readStatus === ReadStatus.Unread,
|
|
|
|
messageId: story.messageId,
|
|
|
|
selectedReaction: story.selectedReaction,
|
|
|
|
sender,
|
|
|
|
timestamp,
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
conversationId: conversation.id,
|
|
|
|
group: conversation.id !== sender.id ? conversation : undefined,
|
|
|
|
isHidden: Boolean(sender.hideStory),
|
|
|
|
stories: [storyView],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export type GetStoriesByConversationIdType = (
|
|
|
|
conversationId: string
|
|
|
|
) => ConversationStoryType;
|
|
|
|
export const getStoriesSelector = createSelector(
|
|
|
|
getConversationSelector,
|
|
|
|
getUserConversationId,
|
|
|
|
getStoriesState,
|
|
|
|
(
|
|
|
|
conversationSelector,
|
|
|
|
ourConversationId,
|
|
|
|
{ stories }: Readonly<StoriesStateType>
|
|
|
|
): GetStoriesByConversationIdType => {
|
|
|
|
return conversationId => {
|
|
|
|
const conversationStoryAcc: ConversationStoryType = {
|
|
|
|
conversationId,
|
|
|
|
stories: [],
|
|
|
|
};
|
|
|
|
|
|
|
|
return stories.reduce((acc, story) => {
|
|
|
|
if (story.conversationId !== conversationId) {
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
|
|
|
const conversationStory = getConversationStory(
|
|
|
|
conversationSelector,
|
|
|
|
story,
|
|
|
|
ourConversationId
|
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
...conversationStory,
|
|
|
|
stories: [...acc.stories, ...conversationStory.stories],
|
|
|
|
};
|
|
|
|
}, conversationStoryAcc);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
export const getStoryReplies = createSelector(
|
|
|
|
getConversationSelector,
|
|
|
|
getContactNameColorSelector,
|
|
|
|
getMe,
|
|
|
|
getStoriesState,
|
|
|
|
(
|
|
|
|
conversationSelector,
|
|
|
|
contactNameColorSelector,
|
|
|
|
me,
|
|
|
|
{ replyState }: Readonly<StoriesStateType>
|
|
|
|
): ReplyStateType | undefined => {
|
|
|
|
if (!replyState) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
messageId: replyState.messageId,
|
|
|
|
replies: replyState.replies.map(reply => {
|
|
|
|
const conversation =
|
|
|
|
reply.type === 'outgoing'
|
|
|
|
? me
|
|
|
|
: conversationSelector(reply.sourceUuid || reply.source);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...pick(conversation, [
|
|
|
|
'acceptedMessageRequest',
|
|
|
|
'avatarPath',
|
|
|
|
'color',
|
|
|
|
'isMe',
|
|
|
|
'name',
|
|
|
|
'profileName',
|
|
|
|
'sharedGroupNames',
|
|
|
|
'title',
|
|
|
|
]),
|
|
|
|
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
|
|
|
contactNameColor: contactNameColorSelector(
|
|
|
|
reply.conversationId,
|
|
|
|
conversation.id
|
|
|
|
),
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2022-03-04 21:14:52 +00:00
|
|
|
export const getStories = createSelector(
|
|
|
|
getConversationSelector,
|
2022-04-15 00:08:46 +00:00
|
|
|
getUserConversationId,
|
2022-03-04 21:14:52 +00:00
|
|
|
getStoriesState,
|
2022-03-29 01:10:08 +00:00
|
|
|
shouldShowStoriesView,
|
2022-03-04 21:14:52 +00:00
|
|
|
(
|
|
|
|
conversationSelector,
|
2022-04-15 00:08:46 +00:00
|
|
|
ourConversationId,
|
2022-03-29 01:10:08 +00:00
|
|
|
{ stories }: Readonly<StoriesStateType>,
|
|
|
|
isShowingStoriesView
|
2022-03-04 21:14:52 +00:00
|
|
|
): {
|
|
|
|
hiddenStories: Array<ConversationStoryType>;
|
|
|
|
stories: Array<ConversationStoryType>;
|
|
|
|
} => {
|
2022-03-29 01:10:08 +00:00
|
|
|
if (!isShowingStoriesView) {
|
|
|
|
return {
|
|
|
|
hiddenStories: [],
|
|
|
|
stories: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-03-04 21:14:52 +00:00
|
|
|
const storiesById = new Map<string, ConversationStoryType>();
|
|
|
|
const hiddenStoriesById = new Map<string, ConversationStoryType>();
|
|
|
|
|
|
|
|
stories.forEach(story => {
|
2022-04-15 00:08:46 +00:00
|
|
|
const conversationStory = getConversationStory(
|
|
|
|
conversationSelector,
|
|
|
|
story,
|
|
|
|
ourConversationId
|
2022-03-04 21:14:52 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let storiesMap: Map<string, ConversationStoryType>;
|
2022-04-15 00:08:46 +00:00
|
|
|
if (conversationStory.isHidden) {
|
2022-03-04 21:14:52 +00:00
|
|
|
storiesMap = hiddenStoriesById;
|
|
|
|
} else {
|
|
|
|
storiesMap = storiesById;
|
|
|
|
}
|
|
|
|
|
2022-04-15 00:08:46 +00:00
|
|
|
const existingConversationStory = storiesMap.get(
|
|
|
|
conversationStory.conversationId
|
|
|
|
) || { stories: [] };
|
2022-03-04 21:14:52 +00:00
|
|
|
|
2022-04-15 00:08:46 +00:00
|
|
|
storiesMap.set(conversationStory.conversationId, {
|
|
|
|
...existingConversationStory,
|
2022-03-04 21:14:52 +00:00
|
|
|
...conversationStory,
|
2022-04-15 00:08:46 +00:00
|
|
|
stories: [
|
|
|
|
...existingConversationStory.stories,
|
|
|
|
...conversationStory.stories,
|
|
|
|
],
|
2022-03-04 21:14:52 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
2022-04-08 15:40:15 +00:00
|
|
|
hiddenStories: Array.from(hiddenStoriesById.values()).sort(
|
|
|
|
sortByRecencyAndUnread
|
|
|
|
),
|
|
|
|
stories: Array.from(storiesById.values()).sort(sortByRecencyAndUnread),
|
2022-03-04 21:14:52 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|