From ccaa82bec13390298090fd866b0d828b2296d9ab Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 16 May 2022 13:34:20 +0200 Subject: [PATCH] Sticker Picker: Support clearing recent, allow viewing set from context menu (#1852) --- src/api/gramjs/methods/index.ts | 1 + src/api/gramjs/methods/symbols.ts | 17 ++++ src/api/gramjs/updater.ts | 22 ++++- src/api/types/updates.ts | 22 ++++- src/components/common/StickerButton.scss | 5 +- src/components/common/StickerButton.tsx | 38 ++++++-- .../middle/composer/EmojiCategory.tsx | 10 ++- .../middle/composer/EmojiPicker.tsx | 4 +- .../middle/composer/StickerPicker.tsx | 50 +++++++---- src/components/middle/composer/StickerSet.tsx | 42 ++++++++- .../middle/composer/StickerTooltip.tsx | 1 + .../middle/composer/SymbolMenu.scss | 13 ++- .../composer/inlineResults/StickerResult.tsx | 1 + src/components/ui/ConfirmDialog.tsx | 2 +- src/config.ts | 2 + src/global/actions/api/symbols.ts | 87 +++++++++++++++++-- src/global/actions/apiUpdaters/misc.ts | 16 ++++ src/global/actions/ui/misc.ts | 15 +++- src/global/types.ts | 41 +++++++-- src/lib/gramjs/tl/apiTl.js | 2 + src/lib/gramjs/tl/static/api.json | 2 + 21 files changed, 341 insertions(+), 52 deletions(-) diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index 2019c2b4..9d67a06a 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -40,6 +40,7 @@ export { fetchStickerSets, fetchRecentStickers, fetchFavoriteStickers, fetchFeaturedStickers, faveSticker, fetchStickers, fetchSavedGifs, saveGif, searchStickers, installStickerSet, uninstallStickerSet, searchGifs, fetchAnimatedEmojis, fetchStickersForEmoji, fetchEmojiKeywords, fetchAnimatedEmojiEffects, + removeRecentSticker, clearRecentStickers, } from './symbols'; export { diff --git a/src/api/gramjs/methods/symbols.ts b/src/api/gramjs/methods/symbols.ts index bb9997d1..1ed7224e 100644 --- a/src/api/gramjs/methods/symbols.ts +++ b/src/api/gramjs/methods/symbols.ts @@ -94,6 +94,23 @@ export async function faveSticker({ } } +export function removeRecentSticker({ + sticker, +}: { + sticker: ApiSticker; +}) { + const request = new GramJs.messages.SaveRecentSticker({ + id: buildInputDocument(sticker), + unsave: true, + }); + + return invokeRequest(request); +} + +export function clearRecentStickers() { + return invokeRequest(new GramJs.messages.ClearRecentStickers()); +} + export async function fetchStickers( { stickerSetShortName, stickerSetId, accessHash }: { stickerSetShortName?: string; stickerSetId?: string; accessHash: string }, diff --git a/src/api/gramjs/updater.ts b/src/api/gramjs/updater.ts index 007986f7..293d3e9d 100644 --- a/src/api/gramjs/updater.ts +++ b/src/api/gramjs/updater.ts @@ -49,7 +49,7 @@ import { getGroupCallId, } from './apiBuilders/calls'; import { buildApiPeerId, getApiChatIdFromMtpPeer } from './apiBuilders/peers'; -import { buildApiEmojiInteraction } from './apiBuilders/symbols'; +import { buildApiEmojiInteraction, buildStickerSet } from './apiBuilders/symbols'; import { buildApiBotMenuButton } from './apiBuilders/bots'; type Update = ( @@ -381,8 +381,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { || originRequest instanceof GramJs.messages.SendMultiMedia || originRequest instanceof GramJs.messages.ForwardMessages ) && ( - update instanceof GramJs.UpdateMessageID - || update instanceof GramJs.UpdateShortSentMessage + update instanceof GramJs.UpdateMessageID || update instanceof GramJs.UpdateShortSentMessage )) { let randomId; if ('randomId' in update) { @@ -861,6 +860,23 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { onUpdate({ '@type': 'updateResetContactList' }); } else if (update instanceof GramJs.UpdateFavedStickers) { onUpdate({ '@type': 'updateFavoriteStickers' }); + } else if (update instanceof GramJs.UpdateRecentStickers) { + onUpdate({ '@type': 'updateRecentStickers' }); + } else if (update instanceof GramJs.UpdateStickerSets) { + onUpdate({ '@type': 'updateStickerSets' }); + } else if (update instanceof GramJs.UpdateStickerSetsOrder) { + onUpdate({ '@type': 'updateStickerSetsOrder', order: update.order.map((n) => n.toString()) }); + } else if (update instanceof GramJs.UpdateNewStickerSet) { + if (update.stickerset instanceof GramJs.messages.StickerSet) { + const stickerSet = buildStickerSet(update.stickerset.set); + onUpdate({ + '@type': 'updateStickerSet', + id: stickerSet.id, + stickerSet, + }); + } + } else if (update instanceof GramJs.UpdateSavedGifs) { + onUpdate({ '@type': 'updateSavedGifs' }); } else if (update instanceof GramJs.UpdateGroupCall) { onUpdate({ '@type': 'updateGroupCall', diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index e5fdda73..eaf0f3b3 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -355,12 +355,29 @@ export type ApiUpdateFavoriteStickers = { '@type': 'updateFavoriteStickers'; }; +export type ApiUpdateRecentStickers = { + '@type': 'updateRecentStickers'; +}; + +export type ApiUpdateStickerSets = { + '@type': 'updateStickerSets'; +}; + +export type ApiUpdateStickerSetsOrder = { + '@type': 'updateStickerSetsOrder'; + order: string[]; +}; + export type ApiUpdateStickerSet = { '@type': 'updateStickerSet'; id: string; stickerSet: Partial; }; +export type ApiUpdateSavedGifs = { + '@type': 'updateSavedGifs'; +}; + export type ApiUpdateTwoFaError = { '@type': 'updateTwoFaError'; message: string; @@ -511,8 +528,9 @@ export type ApiUpdate = ( ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateDeleteProfilePhotos | ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage | ApiUpdateError | ApiUpdateResetContacts | ApiUpdateStartEmojiInteraction | - ApiUpdateFavoriteStickers | ApiUpdateStickerSet | - ApiUpdateNewScheduledMessage | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | + ApiUpdateFavoriteStickers | ApiUpdateStickerSet | ApiUpdateStickerSets | ApiUpdateStickerSetsOrder | + ApiUpdateRecentStickers | ApiUpdateSavedGifs | ApiUpdateNewScheduledMessage | + ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent | ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy | diff --git a/src/components/common/StickerButton.scss b/src/components/common/StickerButton.scss index 1a8f468c..cc4b084c 100644 --- a/src/components/common/StickerButton.scss +++ b/src/components/common/StickerButton.scss @@ -15,7 +15,7 @@ &:hover { background-color: var(--color-interactive-element-hover); - .sticker-unfave-button { + .sticker-remove-button { opacity: 1; } } @@ -54,7 +54,7 @@ user-select: none; } - .sticker-unfave-button { + .sticker-remove-button { position: absolute; top: -0.5rem; right: -0.5rem; @@ -71,6 +71,7 @@ .sticker-context-menu { position: absolute; + cursor: default; .bubble { width: auto; diff --git a/src/components/common/StickerButton.tsx b/src/components/common/StickerButton.tsx index ccb7be8f..ffa50128 100644 --- a/src/components/common/StickerButton.tsx +++ b/src/components/common/StickerButton.tsx @@ -2,6 +2,7 @@ import { MouseEvent as ReactMouseEvent } from 'react'; import React, { memo, useCallback, useEffect, useRef, } from '../../lib/teact/teact'; +import { getActions } from '../../global'; import { ApiBotInlineMediaResult, ApiMediaFormat, ApiSticker } from '../../api/types'; @@ -34,10 +35,12 @@ type OwnProps = { clickArg: T; noContextMenu?: boolean; isSavedMessages?: boolean; + canViewSet?: boolean; observeIntersection: ObserveFn; onClick?: (arg: OwnProps['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void; onFaveClick?: (sticker: ApiSticker) => void; onUnfaveClick?: (sticker: ApiSticker) => void; + onRemoveRecentClick?: (sticker: ApiSticker) => void; }; const StickerButton = ({ @@ -49,11 +52,14 @@ const StickerButton = ) => { + const { openStickerSet } = getActions(); // eslint-disable-next-line no-null/no-null const ref = useRef(null); const lang = useLang(); @@ -140,12 +146,16 @@ const StickerButton = ) => { + const handleRemoveClick = useCallback((e: ReactMouseEvent) => { e.stopPropagation(); e.preventDefault(); - onUnfaveClick!(sticker); - }, [onUnfaveClick, sticker]); + onRemoveRecentClick!(sticker); + }, [onRemoveRecentClick, sticker]); + + const handleContextRemoveRecent = useCallback(() => { + onRemoveRecentClick!(sticker); + }, [onRemoveRecentClick, sticker]); const handleContextUnfave = useCallback(() => { onUnfaveClick!(sticker); @@ -163,6 +173,12 @@ const StickerButton = { + openStickerSet({ sticker }); + }, [openStickerSet, sticker]); + + const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick; + const fullClassName = buildClassName( 'StickerButton', onClick && 'interactive', @@ -207,12 +223,12 @@ const StickerButton = )} - {!IS_TOUCH_ENV && onUnfaveClick && ( + {shouldShowCloseButton && ( @@ -244,6 +260,16 @@ const StickerButton = {lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')} + {canViewSet && ( + + {lang('ViewPackPreview')} + + )} + {onRemoveRecentClick && ( + + {lang('DeleteFromRecent')} + + )} )} diff --git a/src/components/middle/composer/EmojiCategory.tsx b/src/components/middle/composer/EmojiCategory.tsx index 895ae866..dd9f0c5b 100644 --- a/src/components/middle/composer/EmojiCategory.tsx +++ b/src/components/middle/composer/EmojiCategory.tsx @@ -1,8 +1,10 @@ import React, { FC, memo, useRef } from '../../../lib/teact/teact'; +import { RECENT_SYMBOL_SET_ID } from '../../../config'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment'; import buildClassName from '../../../util/buildClassName'; import windowSize from '../../../util/windowSize'; + import { ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import useMediaTransition from '../../../hooks/useMediaTransition'; import useLang from '../../../hooks/useLang'; @@ -47,9 +49,11 @@ const EmojiCategory: FC = ({ id={`emoji-category-${index}`} className="symbol-set" > -

- {lang(category.id === 'recent' ? 'RecentStickers' : `Emoji${index}`)} -

+
+

+ {lang(category.id === RECENT_SYMBOL_SET_ID ? 'RecentStickers' : `Emoji${index}`)} +

+
= ({ const themeCategories = [...categories]; if (recentEmojis?.length) { themeCategories.unshift({ - id: 'recent', + id: RECENT_SYMBOL_SET_ID, name: lang('RecentStickers'), emojis: recentEmojis, }); diff --git a/src/components/middle/composer/StickerPicker.tsx b/src/components/middle/composer/StickerPicker.tsx index edc077f4..237c85af 100644 --- a/src/components/middle/composer/StickerPicker.tsx +++ b/src/components/middle/composer/StickerPicker.tsx @@ -6,7 +6,9 @@ import { getActions, withGlobal } from '../../../global'; import { ApiStickerSet, ApiSticker } from '../../../api/types'; import { StickerSetOrRecent } from '../../../types'; -import { SLIDE_TRANSITION_DURATION, STICKER_SIZE_PICKER_HEADER } from '../../../config'; +import { + FAVORITE_SYMBOL_SET_ID, RECENT_SYMBOL_SET_ID, SLIDE_TRANSITION_DURATION, STICKER_SIZE_PICKER_HEADER, +} from '../../../config'; import { IS_TOUCH_ENV } from '../../../util/environment'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import fastSmoothScroll from '../../../util/fastSmoothScroll'; @@ -72,6 +74,7 @@ const StickerPicker: FC = ({ addRecentSticker, unfaveSticker, faveSticker, + removeRecentSticker, } = getActions(); // eslint-disable-next-line no-null/no-null @@ -116,19 +119,28 @@ const StickerPicker: FC = ({ return MEMO_EMPTY_ARRAY; } - return [ - { - id: 'recent', - title: lang('RecentStickers'), - stickers: recentStickers, - count: recentStickers.length, - }, - { - id: 'favorite', + const defaultSets = []; + + if (favoriteStickers.length) { + defaultSets.push({ + id: FAVORITE_SYMBOL_SET_ID, title: lang('FavoriteStickers'), stickers: favoriteStickers, count: favoriteStickers.length, - }, + }); + } + + if (recentStickers.length) { + defaultSets.push({ + id: RECENT_SYMBOL_SET_ID, + title: lang('RecentStickers'), + stickers: recentStickers, + count: recentStickers.length, + }); + } + + return [ + ...defaultSets, ...addedSetIds.map((id) => stickerSetsById[id]).filter(Boolean), ]; }, [addedSetIds, lang, recentStickers, favoriteStickers, stickerSetsById]); @@ -186,6 +198,10 @@ const StickerPicker: FC = ({ sendMessageAction({ type: 'chooseSticker' }); }, [sendMessageAction]); + const handleRemoveRecentSticker = useCallback((sticker: ApiSticker) => { + removeRecentSticker({ sticker }); + }, [removeRecentSticker]); + const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION); function renderCover(stickerSet: StickerSetOrRecent, index: number) { @@ -195,21 +211,24 @@ const StickerPicker: FC = ({ index === activeSetIndex && 'activated', ); - if (stickerSet.id === 'recent' || stickerSet.id === 'favorite' || stickerSet.hasThumbnail || !firstSticker) { + if (stickerSet.id === RECENT_SYMBOL_SET_ID + || stickerSet.id === FAVORITE_SYMBOL_SET_ID + || stickerSet.hasThumbnail + || !firstSticker) { return (