Follow up gifts and stories collections (#6216)

This commit is contained in:
Alexander Zinchuk 2025-09-19 14:35:05 +02:00
parent 3146f58abc
commit d3b4c57aab
13 changed files with 61 additions and 43 deletions

View File

@ -23,14 +23,14 @@ type OwnProps = {
items: TabItem[];
selectedItemId?: string;
className?: string;
animationLevel?: AnimationLevel;
animationLevel: AnimationLevel;
onItemSelect?: (itemId: string) => void;
};
const AnimatedTabList = ({
items,
selectedItemId,
animationLevel = 1,
animationLevel,
onItemSelect,
className,
}: OwnProps) => {

View File

@ -106,7 +106,7 @@
}
.content {
transition: transform 0.3s;
transition: transform var(--layer-transition);
&.showContentPanel {
transform: translateY(3rem);
padding-bottom: 3.5rem !important;
@ -230,7 +230,7 @@
right: 0;
left: 0;
transition: transform 0.3s, opacity 0.3s;
transition: transform var(--layer-transition), opacity 0.2s ease;
&.hiddenPanel {
transform: translateY(-100%);

View File

@ -14,6 +14,7 @@ import type {
ApiUser,
ApiUserStatus,
} from '../../api/types';
import type { ProfileCollectionKey } from '../../global/selectors/payments';
import type { TabState } from '../../global/types';
import type { AnimationLevel, ProfileState, ProfileTabType, SharedMediaType, ThemeKey, ThreadId } from '../../types';
import type { RegularLangKey } from '../../types/language';
@ -21,6 +22,7 @@ import { MAIN_THREAD_ID } from '../../api/types';
import { AudioOrigin, MediaViewerOrigin, NewChatMembersProgress } from '../../types';
import { MEMBERS_SLICE, PROFILE_SENSITIVE_AREA, SHARED_MEDIA_SLICE, SLIDE_TRANSITION_DURATION } from '../../config';
import { selectActiveGiftsCollectionId } from '../../global/selectors/payments';
const CONTENT_PANEL_SHOW_DELAY = 300;
import {
@ -57,6 +59,7 @@ import {
import { selectPremiumLimit } from '../../global/selectors/limits';
import { selectMessageDownloadableMedia } from '../../global/selectors/media';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import { selectActiveStoriesCollectionId } from '../../global/selectors/stories';
import { areDeepEqual } from '../../util/areDeepEqual';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName';
@ -147,8 +150,8 @@ type StateProps = {
pinnedStoryIds?: number[];
archiveStoryIds?: number[];
storyByIds?: Record<number, ApiTypeStory>;
selectedStoryAlbumId?: number;
activeCollectionId?: number;
selectedStoryAlbumId: ProfileCollectionKey;
activeCollectionId: ProfileCollectionKey;
giftsFilter?: any;
chatsById: Record<string, ApiChat>;
usersById: Record<string, ApiUser>;
@ -613,10 +616,12 @@ const Profile: FC<OwnProps & StateProps> = ({
if (isFirstTab) {
renderingDelay = !isRightColumnShown ? HIDDEN_RENDER_DELAY : 0;
// @optimization Used to delay first render of secondary tabs while animating
} else if (!viewportIds && !botPreviewMedia) {
} else if ((!viewportIds && !botPreviewMedia) || (!gifts?.length && resultType === 'gifts')) {
renderingDelay = SLIDE_TRANSITION_DURATION;
}
const canRenderContent = useAsyncRendering([chatId, threadId, resultType, renderingActiveTab], renderingDelay);
const canRenderContent = useAsyncRendering([chatId, threadId, resultType,
renderingActiveTab, activeCollectionId, selectedStoryAlbumId], renderingDelay);
function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined {
return memberId === currentUserId || !canDeleteMembers ? undefined : [{
@ -986,10 +991,10 @@ const Profile: FC<OwnProps & StateProps> = ({
const shouldUseTransitionForContent = resultType === 'stories' || resultType === 'gifts';
const contentTransitionKey = (() => {
if (resultType === 'stories') {
return selectedStoryAlbumId || 0;
return selectedStoryAlbumId === 'all' ? 0 : selectedStoryAlbumId;
}
if (resultType === 'gifts') {
return activeCollectionId || 0;
return activeCollectionId === 'all' ? 0 : activeCollectionId;
}
return 0;
})();
@ -1178,8 +1183,9 @@ export default memo(withGlobal<OwnProps>(
&& !isSavedDialog;
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
const tabState = selectTabState(global);
const { selectedStoryAlbumId, nextProfileTab, forceScrollProfileTab, savedGifts } = tabState;
const storyIds = selectedStoryAlbumId
const { nextProfileTab, forceScrollProfileTab, savedGifts } = tabState;
const selectedStoryAlbumId = selectActiveStoriesCollectionId(global);
const storyIds = selectedStoryAlbumId !== 'all'
? peerStories?.idsByAlbumId?.[selectedStoryAlbumId]?.ids
: peerStories?.profileIds;
const pinnedStoryIds = peerStories?.pinnedIds;
@ -1187,8 +1193,8 @@ export default memo(withGlobal<OwnProps>(
const archiveStoryIds = peerStories?.archiveIds;
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
const activeCollectionId = savedGifts.activeCollectionByPeerId[chatId];
const peerGifts = savedGifts.collectionsByPeerId[chatId]?.[activeCollectionId || 'all'];
const activeCollectionId = selectActiveGiftsCollectionId(global, chatId);
const peerGifts = savedGifts.collectionsByPeerId[chatId]?.[activeCollectionId];
const storyAlbums = global.stories.albumsByPeerId?.[chatId];
const giftCollections = global.starGiftCollections?.byPeerId?.[chatId];

View File

@ -2,10 +2,11 @@ import { memo, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiStarGiftCollection } from '../../../api/types';
import type { ProfileCollectionKey } from '../../../global/selectors/payments';
import type { AnimationLevel } from '../../../types';
import type { TabItem } from '../../common/AnimatedTabList';
import { selectActiveCollectionId } from '../../../global/selectors';
import { selectActiveGiftsCollectionId } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import buildClassName from '../../../util/buildClassName';
@ -15,7 +16,6 @@ import useLastCallback from '../../../hooks/useLastCallback';
import AnimatedTabList from '../../common/AnimatedTabList';
import styles from './StarGiftCollectionList.module.scss';
type OwnProps = {
peerId: string;
className?: string;
@ -23,8 +23,8 @@ type OwnProps = {
type StateProps = {
collections?: ApiStarGiftCollection[];
activeCollectionId?: number;
animationLevel?: AnimationLevel;
activeCollectionId: ProfileCollectionKey;
animationLevel: AnimationLevel;
};
const StarGiftCollectionList = ({
@ -37,7 +37,7 @@ const StarGiftCollectionList = ({
const { updateSelectedGiftCollection, resetSelectedGiftCollection } = getActions();
const lang = useLang();
const handleItemSelect = useLastCallback((itemId?: string) => {
const handleItemSelect = useLastCallback((itemId: string) => {
if (itemId === 'all') {
resetSelectedGiftCollection({ peerId });
} else {
@ -79,7 +79,7 @@ export default memo(withGlobal<OwnProps>(
(global, { peerId }): StateProps => {
const { starGiftCollections } = global;
const collections = starGiftCollections?.byPeerId?.[peerId];
const activeCollectionId = selectActiveCollectionId(global, peerId);
const activeCollectionId = selectActiveGiftsCollectionId(global, peerId);
return {
collections,

View File

@ -2,11 +2,12 @@ import { memo, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiStoryAlbum } from '../../../api/types';
import type { ProfileCollectionKey } from '../../../global/selectors/payments';
import type { AnimationLevel } from '../../../types';
import type { TabItem } from '../../common/AnimatedTabList';
import { selectTabState } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { selectActiveStoriesCollectionId } from '../../../global/selectors/stories';
import buildClassName from '../../../util/buildClassName';
import useLang from '../../../hooks/useLang';
@ -23,8 +24,8 @@ type OwnProps = {
type StateProps = {
albums?: ApiStoryAlbum[];
selectedAlbumId?: number;
animationLevel?: AnimationLevel;
selectedAlbumId: ProfileCollectionKey;
animationLevel: AnimationLevel;
};
const StoryAlbumList = ({
@ -77,9 +78,8 @@ const StoryAlbumList = ({
export default memo(withGlobal<OwnProps>(
(global, { peerId }): StateProps => {
const { stories } = global;
const tabState = selectTabState(global);
const albums = stories?.albumsByPeerId?.[peerId];
const selectedAlbumId = tabState.selectedStoryAlbumId;
const selectedAlbumId = selectActiveStoriesCollectionId(global);
return {
albums,

View File

@ -25,7 +25,7 @@ import {
} from '../../reducers';
import { updateTabState } from '../../reducers/tabs';
import {
selectActiveCollectionId,
selectActiveGiftsCollectionId,
selectGiftProfileFilter,
selectPeer,
selectPeerSavedGifts,
@ -303,18 +303,18 @@ addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise
if (!shouldRefresh && currentGifts && !localNextOffset) return; // Already loaded all
const fetchingFilter = selectGiftProfileFilter(global, peerId, tabId);
const fetchingCollectionId = selectActiveCollectionId(global, peerId, tabId);
const fetchingCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
const result = await callApi('fetchSavedStarGifts', {
peer,
offset: !shouldRefresh ? localNextOffset : '',
filter: fetchingFilter,
collectionId: fetchingCollectionId ? Number(fetchingCollectionId) : undefined,
collectionId: fetchingCollectionId === 'all' ? undefined : fetchingCollectionId,
});
global = getGlobal();
const currentFilter = selectGiftProfileFilter(global, peerId, tabId);
const currentCollectionId = selectActiveCollectionId(global, peerId, tabId);
const currentCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
if (!result || currentCollectionId !== fetchingCollectionId || currentFilter !== fetchingFilter) {
return;
@ -400,7 +400,7 @@ addActionHandler('changeGiftVisibility', async (global, actions, payload): Promi
const requestInputGift = getRequestInputSavedStarGift(global, gift);
if (!requestInputGift) return;
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
const activeCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
const oldGifts = selectTabState(global, tabId).savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId];
if (oldGifts?.gifts?.length) {
const newGifts = oldGifts.gifts.map((g) => {

View File

@ -31,6 +31,7 @@ import {
selectPeer, selectPeerStories, selectPeerStory,
selectPinnedStories, selectTabState,
} from '../../selectors';
import { selectActiveStoriesCollectionId } from '../../selectors/stories';
const INFINITE_LOOP_MARKER = 100;
@ -308,9 +309,8 @@ addActionHandler('loadPeerProfileStories', async (global, actions, payload): Pro
return;
}
const tabState = selectTabState(global, tabId);
const selectedAlbumId = tabState.selectedStoryAlbumId;
if (selectedAlbumId) {
const selectedAlbumId = selectActiveStoriesCollectionId(global, tabId);
if (selectedAlbumId !== 'all') {
let albumData = peerStories?.idsByAlbumId?.[selectedAlbumId];
if (albumData?.isFullyLoaded) {
return;

View File

@ -2,7 +2,7 @@ import type { ApiSavedGifts } from '../../../api/types';
import type { ActionReturnType } from '../../types';
import { DEFAULT_GIFT_PROFILE_FILTER_OPTIONS } from '../../../config';
import { selectActiveCollectionId } from '../../../global/selectors';
import { selectActiveGiftsCollectionId } from '../../../global/selectors';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { addActionHandler, setGlobal } from '../../index';
import {
@ -102,7 +102,7 @@ addActionHandler('updateGiftProfileFilter', (global, actions, payload): ActionRe
};
}
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
const activeCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
global = updateTabState(global, {
savedGifts: {
@ -126,7 +126,7 @@ addActionHandler('resetGiftProfileFilter', (global, actions, payload): ActionRet
const { peerId, tabId = getCurrentTabId() } = payload || {};
const tabState = selectTabState(global, tabId);
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
const activeCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
global = updateTabState(global, {
savedGifts: {

View File

@ -15,7 +15,7 @@ import { getCurrentTabId } from '../../util/establishMultitabRole';
import { omit, omitUndefined, unique } from '../../util/iteratees';
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import { getSavedGiftKey } from '../helpers/stars';
import { selectActiveCollectionId } from '../selectors';
import { selectActiveGiftsCollectionId } from '../selectors';
import { selectTabState } from '../selectors';
import { updateTabState } from './tabs';
@ -346,7 +346,7 @@ export function replacePeerSavedGifts<T extends GlobalState>(
keyCounts.set(id, count + 1);
});
const activeCollectionId = selectActiveCollectionId(global, peerId, tabId) || 'all';
const activeCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
return updateTabState(global, {
savedGifts: {

View File

@ -10,6 +10,8 @@ import { selectChat } from './chats';
import { selectTabState } from './tabs';
import { selectUser } from './users';
export type ProfileCollectionKey = number | 'all';
export function selectPaymentInputInvoice<T extends GlobalState>(
global: T,
...[tabId = getCurrentTabId()]: TabArgs<T>
@ -97,10 +99,10 @@ export function selectIsGiftProfileFilterDefault<T extends GlobalState>(
return arePropsShallowEqual(selectTabState(global, tabId).savedGifts.filter, DEFAULT_GIFT_PROFILE_FILTER_OPTIONS);
}
export function selectActiveCollectionId<T extends GlobalState>(
export function selectActiveGiftsCollectionId<T extends GlobalState>(
global: T,
peerId: string,
...[tabId = getCurrentTabId()]: TabArgs<T>
) {
return selectTabState(global, tabId).savedGifts.activeCollectionByPeerId?.[peerId];
): ProfileCollectionKey {
return selectTabState(global, tabId).savedGifts.activeCollectionByPeerId?.[peerId] || 'all';
}

View File

@ -6,6 +6,7 @@ import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import { isChatAdmin, isDeletedUser } from '../helpers';
import { selectChat, selectChatFullInfo } from './chats';
import { selectActiveGiftsCollectionId } from './payments';
import { selectTabState } from './tabs';
import { selectBot, selectUser, selectUserFullInfo } from './users';
@ -34,7 +35,7 @@ export function selectPeerSavedGifts<T extends GlobalState>(
...[tabId = getCurrentTabId()]: TabArgs<T>
): ApiSavedGifts | undefined {
const tabState = selectTabState(global, tabId);
const activeCollectionId = tabState.savedGifts.activeCollectionByPeerId[peerId] || 'all';
const activeCollectionId = selectActiveGiftsCollectionId(global, peerId, tabId);
return tabState.savedGifts.collectionsByPeerId[peerId]?.[activeCollectionId];
}

View File

@ -1,5 +1,6 @@
import type { ApiPeerStories, ApiTypeStory } from '../../api/types';
import type { GlobalState, TabArgs } from '../types';
import type { ProfileCollectionKey } from './payments';
import { getCurrentTabId } from '../../util/establishMultitabRole';
import { selectPeer } from './peers';
@ -150,3 +151,10 @@ function getPeerStoryIdsForViewer<T extends GlobalState>(
? storyIds.slice(lastReadIndex + 1)
: undefined;
}
export function selectActiveStoriesCollectionId<T extends GlobalState>(
global: T,
...[tabId = getCurrentTabId()]: TabArgs<T>
): ProfileCollectionKey {
return selectTabState(global, tabId).selectedStoryAlbumId || 'all';
}

View File

@ -94,6 +94,7 @@ import type {
import type { WebApp, WebAppModalStateType } from '../../types/webapp';
import type { SearchResultKey } from '../../util/keys/searchResultKey';
import type { RegularLangFnParameters } from '../../util/localization';
import type { ProfileCollectionKey } from '../selectors/payments';
import type { CallbackAction } from './actions';
export type TabState = {
@ -220,7 +221,7 @@ export type TabState = {
};
savedGifts: {
collectionsByPeerId: Record<string, Record<number | 'all', ApiSavedGifts>>;
collectionsByPeerId: Record<string, Record<ProfileCollectionKey, ApiSavedGifts>>;
activeCollectionByPeerId: Record<string, number | undefined>;
filter: GiftProfileFilterOptions;
};