Show story rings around avatars in send story modal

This commit is contained in:
Josh Perez 2023-03-07 21:15:25 -05:00 committed by GitHub
parent 78e3120d1a
commit d2322de4a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 71 additions and 45 deletions

View file

@ -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}
/> />

View file

@ -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;

View file

@ -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),
}));
} }
); );

View file

@ -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,

View 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;
}
);

View file

@ -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 } =

View file

@ -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';

View file

@ -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 = {