Show story rings around avatars in send story modal
This commit is contained in:
parent
78e3120d1a
commit
d2322de4a3
8 changed files with 71 additions and 45 deletions
|
@ -8,6 +8,7 @@ import { SearchInput } from './SearchInput';
|
||||||
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
|
import type { ConversationWithStoriesType } from '../state/selectors/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { PropsType as StoriesSettingsModalPropsType } from './StoriesSettingsModal';
|
import type { PropsType as StoriesSettingsModalPropsType } from './StoriesSettingsModal';
|
||||||
|
@ -46,7 +47,7 @@ export type PropsType = {
|
||||||
distributionLists: Array<StoryDistributionListWithMembersDataType>;
|
distributionLists: Array<StoryDistributionListWithMembersDataType>;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
groupConversations: Array<ConversationType>;
|
groupConversations: Array<ConversationType>;
|
||||||
groupStories: Array<ConversationType>;
|
groupStories: Array<ConversationWithStoriesType>;
|
||||||
hasFirstStoryPostExperience: boolean;
|
hasFirstStoryPostExperience: boolean;
|
||||||
ourConversationId: string | undefined;
|
ourConversationId: string | undefined;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
@ -712,6 +713,7 @@ export function SendStoryModal({
|
||||||
isMe
|
isMe
|
||||||
sharedGroupNames={me.sharedGroupNames}
|
sharedGroupNames={me.sharedGroupNames}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
storyRing={undefined}
|
||||||
title={me.title}
|
title={me.title}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -759,7 +761,7 @@ export function SendStoryModal({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderGroup = (group: ConversationType) => {
|
const renderGroup = (group: ConversationWithStoriesType) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedGroupIds.has(group.id)}
|
checked={selectedGroupIds.has(group.id)}
|
||||||
|
@ -826,6 +828,7 @@ export function SendStoryModal({
|
||||||
isMe={false}
|
isMe={false}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
size={AvatarSize.THIRTY_TWO}
|
size={AvatarSize.THIRTY_TWO}
|
||||||
|
storyRing={group.hasStories}
|
||||||
title={group.title}
|
title={group.title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Measure from 'react-measure';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
|
import type { ConversationWithStoriesType } from '../state/selectors/conversations';
|
||||||
import type { LocalizerType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||||
import type { Row } from './ConversationList';
|
import type { Row } from './ConversationList';
|
||||||
|
@ -43,7 +44,7 @@ import { strictAssert } from '../util/assert';
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
candidateConversations: Array<ConversationType>;
|
candidateConversations: Array<ConversationType>;
|
||||||
distributionLists: Array<StoryDistributionListWithMembersDataType>;
|
distributionLists: Array<StoryDistributionListWithMembersDataType>;
|
||||||
groupStories: Array<ConversationType>;
|
groupStories: Array<ConversationWithStoriesType>;
|
||||||
signalConnections: Array<ConversationType>;
|
signalConnections: Array<ConversationType>;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
getPreferredBadge: PreferredBadgeSelectorType;
|
||||||
hideStoriesSettings: () => unknown;
|
hideStoriesSettings: () => unknown;
|
||||||
|
|
|
@ -58,6 +58,12 @@ import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
import { reduce } from '../../util/iterables';
|
import { reduce } from '../../util/iterables';
|
||||||
import { getConversationTitleForPanelType } from '../../util/getConversationTitleForPanelType';
|
import { getConversationTitleForPanelType } from '../../util/getConversationTitleForPanelType';
|
||||||
import type { PanelRenderType } from '../../types/Panels';
|
import type { PanelRenderType } from '../../types/Panels';
|
||||||
|
import type { HasStories } from '../../types/Stories';
|
||||||
|
import { getHasStoriesSelector } from './stories2';
|
||||||
|
|
||||||
|
export type ConversationWithStoriesType = ConversationType & {
|
||||||
|
hasStories?: HasStories;
|
||||||
|
};
|
||||||
|
|
||||||
let placeholderContact: ConversationType;
|
let placeholderContact: ConversationType;
|
||||||
export const getPlaceholderContact = (): ConversationType => {
|
export const getPlaceholderContact = (): ConversationType => {
|
||||||
|
@ -575,15 +581,22 @@ export const selectMostRecentActiveStoryTimestampByGroupOrDistributionList =
|
||||||
export const getGroupStories = createSelector(
|
export const getGroupStories = createSelector(
|
||||||
getConversationLookup,
|
getConversationLookup,
|
||||||
getConversationIdsWithStories,
|
getConversationIdsWithStories,
|
||||||
|
getHasStoriesSelector,
|
||||||
(
|
(
|
||||||
conversationLookup: ConversationLookupType,
|
conversationLookup: ConversationLookupType,
|
||||||
conversationIdsWithStories: Set<string>
|
conversationIdsWithStories: Set<string>,
|
||||||
): Array<ConversationType> => {
|
hasStoriesSelector
|
||||||
return Object.values(conversationLookup).filter(
|
): Array<ConversationWithStoriesType> => {
|
||||||
conversation =>
|
return Object.values(conversationLookup)
|
||||||
isGroupInStoryMode(conversation, conversationIdsWithStories) &&
|
.filter(
|
||||||
!conversation.left
|
conversation =>
|
||||||
);
|
isGroupInStoryMode(conversation, conversationIdsWithStories) &&
|
||||||
|
!conversation.left
|
||||||
|
)
|
||||||
|
.map(conversation => ({
|
||||||
|
...conversation,
|
||||||
|
hasStories: hasStoriesSelector(conversation.id),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,7 @@ import type {
|
||||||
StoriesStateType,
|
StoriesStateType,
|
||||||
AddStoryData,
|
AddStoryData,
|
||||||
} from '../ducks/stories';
|
} from '../ducks/stories';
|
||||||
import {
|
import { MY_STORY_ID, ResolvedSendStatus } from '../../types/Stories';
|
||||||
HasStories,
|
|
||||||
MY_STORY_ID,
|
|
||||||
ResolvedSendStatus,
|
|
||||||
} from '../../types/Stories';
|
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { SendStatus } from '../../messages/MessageSendState';
|
import { SendStatus } from '../../messages/MessageSendState';
|
||||||
import { canReply } from './message';
|
import { canReply } from './message';
|
||||||
|
@ -38,7 +34,6 @@ import {
|
||||||
} from './conversations';
|
} from './conversations';
|
||||||
import { getUserConversationId } from './user';
|
import { getUserConversationId } from './user';
|
||||||
import { getDistributionListSelector } from './storyDistributionLists';
|
import { getDistributionListSelector } from './storyDistributionLists';
|
||||||
import { getStoriesEnabled } from './items';
|
|
||||||
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
||||||
import { getMessageIdForLogging } from '../../util/idForLogging';
|
import { getMessageIdForLogging } from '../../util/idForLogging';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
|
@ -499,32 +494,6 @@ export const getStoriesNotificationCount = createSelector(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getHasStoriesSelector = createSelector(
|
|
||||||
getStoriesEnabled,
|
|
||||||
getStoriesState,
|
|
||||||
(isEnabled, { stories }) =>
|
|
||||||
(conversationId?: string): HasStories | undefined => {
|
|
||||||
if (!isEnabled || !conversationId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const conversationStories = stories.filter(
|
|
||||||
story => story.conversationId === conversationId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!conversationStories.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return conversationStories.some(
|
|
||||||
story =>
|
|
||||||
story.readStatus === ReadStatus.Unread && !story.deletedForEveryone
|
|
||||||
)
|
|
||||||
? HasStories.Unread
|
|
||||||
: HasStories.Read;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getStoryByIdSelector = createSelector(
|
export const getStoryByIdSelector = createSelector(
|
||||||
getStoriesState,
|
getStoriesState,
|
||||||
getUserConversationId,
|
getUserConversationId,
|
||||||
|
|
40
ts/state/selectors/stories2.ts
Normal file
40
ts/state/selectors/stories2.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
|
import { HasStories } from '../../types/Stories';
|
||||||
|
import { getStoriesEnabled } from './items';
|
||||||
|
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import type { StoriesStateType } from '../ducks/stories';
|
||||||
|
|
||||||
|
const getStoriesState = (state: StateType): StoriesStateType => state.stories;
|
||||||
|
|
||||||
|
// This exists solely to avoid circular import dependencies since it is required
|
||||||
|
// by the conversations selector.
|
||||||
|
export const getHasStoriesSelector = createSelector(
|
||||||
|
getStoriesEnabled,
|
||||||
|
getStoriesState,
|
||||||
|
(isEnabled, { stories }) =>
|
||||||
|
(conversationId?: string): HasStories | undefined => {
|
||||||
|
if (!isEnabled || !conversationId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationStories = stories.filter(
|
||||||
|
story => story.conversationId === conversationId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!conversationStories.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversationStories.some(
|
||||||
|
story =>
|
||||||
|
story.readStatus === ReadStatus.Unread && !story.deletedForEveryone
|
||||||
|
)
|
||||||
|
? HasStories.Unread
|
||||||
|
: HasStories.Read;
|
||||||
|
}
|
||||||
|
);
|
|
@ -11,7 +11,7 @@ import { getAreWeASubscriber } from '../selectors/items';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getBadgesSelector } from '../selectors/badges';
|
import { getBadgesSelector } from '../selectors/badges';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getHasStoriesSelector } from '../selectors/stories';
|
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType): PropsDataType => {
|
const mapStateToProps = (state: StateType): PropsDataType => {
|
||||||
const { contactId, conversationId } =
|
const { contactId, conversationId } =
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import { CallMode } from '../../types/Calling';
|
import { CallMode } from '../../types/Calling';
|
||||||
import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling';
|
import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling';
|
||||||
import { getConversationCallMode } from '../ducks/conversations';
|
import { getConversationCallMode } from '../ducks/conversations';
|
||||||
import { getHasStoriesSelector } from '../selectors/stories';
|
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||||
import { getOwn } from '../../util/getOwn';
|
import { getOwn } from '../../util/getOwn';
|
||||||
import { getUserACI, getIntl, getTheme } from '../selectors/user';
|
import { getUserACI, getIntl, getTheme } from '../selectors/user';
|
||||||
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
|
import { isConversationSMSOnly } from '../../util/isConversationSMSOnly';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { ConversationHero } from '../../components/conversation/ConversationHero
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getHasStoriesSelector } from '../selectors/stories';
|
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue