From 6935d8d00835c51f004f069d8e8d4bb736dd3af8 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 22 Mar 2024 13:05:51 +0100 Subject: [PATCH] Stories: Fix color for close friends (#4370) --- src/components/common/Avatar.scss | 4 ++++ src/components/common/Avatar.tsx | 3 +++ src/components/common/AvatarStoryCircle.tsx | 25 ++++++++++++++++----- src/components/story/StoryToggler.tsx | 25 ++++++++++++++++++++- src/styles/_variables.scss | 6 +++-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/components/common/Avatar.scss b/src/components/common/Avatar.scss index 2033b4621..1e0229313 100644 --- a/src/components/common/Avatar.scss +++ b/src/components/common/Avatar.scss @@ -239,6 +239,10 @@ background-image: linear-gradient(215.87deg, var(--color-avatar-story-unread-from) -1.61%, var(--color-avatar-story-unread-to) 97.44%); } + &.has-unread-story.close-friend::before { + background-image: linear-gradient(215.87deg, var(--color-avatar-story-friend-unread-from) -1.61%, var(--color-avatar-story-friend-unread-to) 97.44%); + } + .poster { position: absolute; left: 0; diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index e4b4f4b18..b1879ef6b 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -59,6 +59,7 @@ type OwnProps = { forPremiumPromo?: boolean; withStoryGap?: boolean; withStorySolid?: boolean; + forceFriendStorySolid?: boolean; forceUnreadStorySolid?: boolean; storyViewerOrigin?: StoryViewerOrigin; storyViewerMode?: 'full' | 'single-peer' | 'disabled'; @@ -81,6 +82,7 @@ const Avatar: FC = ({ forPremiumPromo, withStoryGap, withStorySolid, + forceFriendStorySolid, forceUnreadStorySolid, storyViewerOrigin, storyViewerMode = 'single-peer', @@ -221,6 +223,7 @@ const Avatar: FC = ({ isRoundedRect && 'forum', ((withStory && peer?.hasStories) || forPremiumPromo) && 'with-story-circle', withStorySolid && peer?.hasStories && 'with-story-solid', + withStorySolid && forceFriendStorySolid && 'close-friend', withStorySolid && (peer?.hasUnreadStories || forceUnreadStorySolid) && 'has-unread-story', onClick && 'interactive', (!isSavedMessages && !imgBlobUrl) && 'no-photo', diff --git a/src/components/common/AvatarStoryCircle.tsx b/src/components/common/AvatarStoryCircle.tsx index 540bbc476..714e452bf 100644 --- a/src/components/common/AvatarStoryCircle.tsx +++ b/src/components/common/AvatarStoryCircle.tsx @@ -3,10 +3,11 @@ import React, { } from '../../lib/teact/teact'; import { withGlobal } from '../../global'; +import type { ApiTypeStory } from '../../api/types'; import type { ThemeKey } from '../../types'; import type { AvatarSize } from './Avatar'; -import { selectPeerStories, selectTheme, selectUser } from '../../global/selectors'; +import { selectPeerStories, selectTheme } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import { REM } from './helpers/mediaDimensions'; @@ -21,7 +22,7 @@ interface OwnProps { } interface StateProps { - isCloseFriend?: boolean; + peerStories?: Record; storyIds?: number[]; lastReadId?: number; appTheme: ThemeKey; @@ -58,7 +59,7 @@ const EXTRA_GAP_END = EXTRA_GAP_ANGLE + EXTRA_GAP_SIZE / 2; function AvatarStoryCircle({ size = 'large', className, - isCloseFriend, + peerStories, storyIds, lastReadId, withExtraGap, @@ -80,6 +81,21 @@ function AvatarStoryCircle({ }, { total: 0, read: 0 }); }, [lastReadId, storyIds]); + const isCloseFriend = useMemo(() => { + if (!peerStories || !storyIds?.length) { + return false; + } + + return storyIds.some((id) => { + const story = peerStories[id]; + if (!story || !('isForCloseFriends' in story)) { + return false; + } + const isRead = lastReadId && story.id <= lastReadId; + return story.isForCloseFriends && !isRead; + }); + }, [lastReadId, peerStories, storyIds]); + useLayoutEffect(() => { if (!ref.current) { return; @@ -113,12 +129,11 @@ function AvatarStoryCircle({ } export default memo(withGlobal((global, { peerId }): StateProps => { - const user = selectUser(global, peerId); const peerStories = selectPeerStories(global, peerId); const appTheme = selectTheme(global); return { - isCloseFriend: user?.isCloseFriend, + peerStories: peerStories?.byId, storyIds: peerStories?.orderedIds, lastReadId: peerStories?.lastReadId, appTheme, diff --git a/src/components/story/StoryToggler.tsx b/src/components/story/StoryToggler.tsx index 829fd5008..e80ae1b9f 100644 --- a/src/components/story/StoryToggler.tsx +++ b/src/components/story/StoryToggler.tsx @@ -2,6 +2,7 @@ import React, { memo, useEffect, useMemo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { ApiChat, ApiUser } from '../../api/types'; +import type { GlobalState } from '../../global/types'; import { ANIMATION_END_DELAY, PREVIEW_AVATAR_COUNT } from '../../config'; import { @@ -32,6 +33,7 @@ interface StateProps { withAnimation?: boolean; usersById: Record; chatsById: Record; + peerStories: GlobalState['stories']['byPeerId']; } const PRELOAD_PEERS = 5; @@ -46,6 +48,7 @@ function StoryToggler({ isForumPanelOpen, isArchived, withAnimation, + peerStories, }: OwnProps & StateProps) { const { toggleStoryRibbon } = getActions(); @@ -63,6 +66,24 @@ function StoryToggler({ .reverse(); }, [currentUserId, orderedPeerIds, usersById, chatsById]); + const closeFriends = useMemo(() => { + if (!peers?.length) return {}; + return peers.reduce((acc, peer) => { + const stories = peerStories[peer.id]; + if (!stories) return acc; + + const isCloseFriend = stories.orderedIds.some((id) => { + const story = stories.byId[id]; + if (!story || !('isForCloseFriends' in story)) return false; + const isRead = stories.lastReadId && story.id <= stories.lastReadId; + return story.isForCloseFriends && !isRead; + }); + + acc[peer.id] = isCloseFriend; + return acc; + }, {} as Record); + }, [peerStories, peers]); + const preloadPeerIds = useMemo(() => { return orderedPeerIds.slice(0, PRELOAD_PEERS); }, [orderedPeerIds]); @@ -104,6 +125,7 @@ function StoryToggler({ size="tiny" className={styles.avatar} withStorySolid + forceFriendStorySolid={closeFriends[peer.id]} /> ))} @@ -111,7 +133,7 @@ function StoryToggler({ } export default memo(withGlobal((global, { isArchived }): StateProps => { - const { orderedPeerIds: { archived, active } } = global.stories; + const { orderedPeerIds: { archived, active }, byPeerId } = global.stories; const { storyViewer: { isRibbonShown, isArchivedRibbonShown } } = selectTabState(global); const isForumPanelOpen = selectIsForumPanelOpen(global); const withAnimation = selectPerformanceSettingsValue(global, 'storyRibbonAnimations'); @@ -124,5 +146,6 @@ export default memo(withGlobal((global, { isArchived }): StateProps => withAnimation, usersById: global.users.byId, chatsById: global.chats.byId, + peerStories: byPeerId, }; })(StoryToggler)); diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index 81c0e70de..398c3e475 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -162,8 +162,10 @@ $color-message-story-mention-to: #74bcff; --color-selection-highlight: #{$color-selection}; --color-selection-highlight-emoji: rgba(#{toRGB($color-selection)}, 0.7); - --color-avatar-story-unread-from: #34c76f; - --color-avatar-story-unread-to: #3da1fd; + --color-avatar-story-unread-from: #34c578; + --color-avatar-story-unread-to: #3ca3f3; + --color-avatar-story-friend-unread-from: #c9eb38; + --color-avatar-story-friend-unread-to: #09c167; --color-default-shadow: #72727240; --color-light-shadow: #7272722b;