Sticker Picker: Support clearing recent, allow viewing set from context menu (#1852)

This commit is contained in:
Alexander Zinchuk 2022-05-16 13:34:20 +02:00
parent efa4c59ae0
commit ccaa82bec1
21 changed files with 341 additions and 52 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<T> = {
clickArg: T;
noContextMenu?: boolean;
isSavedMessages?: boolean;
canViewSet?: boolean;
observeIntersection: ObserveFn;
onClick?: (arg: OwnProps<T>['clickArg'], isSilent?: boolean, shouldSchedule?: boolean) => void;
onFaveClick?: (sticker: ApiSticker) => void;
onUnfaveClick?: (sticker: ApiSticker) => void;
onRemoveRecentClick?: (sticker: ApiSticker) => void;
};
const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult | undefined = undefined>({
@ -49,11 +52,14 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
clickArg,
noContextMenu,
isSavedMessages,
canViewSet,
observeIntersection,
onClick,
onFaveClick,
onUnfaveClick,
onRemoveRecentClick,
}: OwnProps<T>) => {
const { openStickerSet } = getActions();
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const lang = useLang();
@ -140,12 +146,16 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
handleBeforeContextMenu(e);
};
const handleUnfaveClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
const handleRemoveClick = useCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
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 = <T extends number | ApiSticker | ApiBotInlineMediaResult |
onClick?.(clickArg, undefined, true);
}, [clickArg, onClick]);
const handleOpenSet = useCallback(() => {
openStickerSet({ sticker });
}, [openStickerSet, sticker]);
const shouldShowCloseButton = !IS_TOUCH_ENV && onRemoveRecentClick;
const fullClassName = buildClassName(
'StickerButton',
onClick && 'interactive',
@ -207,12 +223,12 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
onLoad={markLoaded}
/>
)}
{!IS_TOUCH_ENV && onUnfaveClick && (
{shouldShowCloseButton && (
<Button
className="sticker-unfave-button"
className="sticker-remove-button"
color="dark"
round
onClick={handleUnfaveClick}
onClick={handleRemoveClick}
>
<i className="icon-close" />
</Button>
@ -244,6 +260,16 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
<MenuItem onClick={handleSendScheduled} icon="calendar">
{lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
</MenuItem>
{canViewSet && (
<MenuItem onClick={handleOpenSet} icon="stickers">
{lang('ViewPackPreview')}
</MenuItem>
)}
{onRemoveRecentClick && (
<MenuItem icon="delete" onClick={handleContextRemoveRecent}>
{lang('DeleteFromRecent')}
</MenuItem>
)}
</Menu>
)}
</div>

View File

@ -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<OwnProps> = ({
id={`emoji-category-${index}`}
className="symbol-set"
>
<div className="symbol-set-header">
<p className="symbol-set-name" dir="auto">
{lang(category.id === 'recent' ? 'RecentStickers' : `Emoji${index}`)}
{lang(category.id === RECENT_SYMBOL_SET_ID ? 'RecentStickers' : `Emoji${index}`)}
</p>
</div>
<div
className={buildClassName('symbol-set-container', transitionClassNames)}
style={`height: ${height}px;`}

View File

@ -5,7 +5,7 @@ import { withGlobal } from '../../../global';
import { GlobalState } from '../../../global/types';
import { MENU_TRANSITION_DURATION } from '../../../config';
import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config';
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import {
@ -126,7 +126,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
const themeCategories = [...categories];
if (recentEmojis?.length) {
themeCategories.unshift({
id: 'recent',
id: RECENT_SYMBOL_SET_ID,
name: lang('RecentStickers'),
emojis: recentEmojis,
});

View File

@ -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<OwnProps & StateProps> = ({
addRecentSticker,
unfaveSticker,
faveSticker,
removeRecentSticker,
} = getActions();
// eslint-disable-next-line no-null/no-null
@ -116,19 +119,28 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
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<OwnProps & StateProps> = ({
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 (
<Button
key={stickerSet.id}
className={buttonClassName}
ariaLabel={stickerSet.title}
round
faded={stickerSet.id === 'recent' || stickerSet.id === 'favorite'}
faded={stickerSet.id === RECENT_SYMBOL_SET_ID || stickerSet.id === FAVORITE_SYMBOL_SET_ID}
color="translucent"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => selectStickerSet(index)}
>
{stickerSet.id === 'recent' ? (
{stickerSet.id === RECENT_SYMBOL_SET_ID ? (
<i className="icon-recent" />
) : stickerSet.id === 'favorite' ? (
) : stickerSet.id === FAVORITE_SYMBOL_SET_ID ? (
<i className="icon-favorite" />
) : stickerSet.isLottie ? (
<StickerSetCoverAnimated
@ -281,6 +300,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
onStickerSelect={handleStickerSelect}
onStickerUnfave={handleStickerUnfave}
onStickerFave={handleStickerFave}
onStickerRemoveRecent={handleRemoveRecentSticker}
favoriteStickers={favoriteStickers}
isSavedMessages={isSavedMessages}
/>

View File

@ -1,19 +1,23 @@
import React, {
FC, memo, useMemo, useRef,
FC, memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import { ApiSticker } from '../../../api/types';
import { StickerSetOrRecent } from '../../../types';
import { ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
import { STICKER_SIZE_PICKER } from '../../../config';
import { FAVORITE_SYMBOL_SET_ID, RECENT_SYMBOL_SET_ID, STICKER_SIZE_PICKER } from '../../../config';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
import windowSize from '../../../util/windowSize';
import buildClassName from '../../../util/buildClassName';
import useLang from '../../../hooks/useLang';
import useFlag from '../../../hooks/useFlag';
import useMediaTransition from '../../../hooks/useMediaTransition';
import StickerButton from '../../common/StickerButton';
import ConfirmDialog from '../../ui/ConfirmDialog';
type OwnProps = {
stickerSet: StickerSetOrRecent;
@ -26,6 +30,7 @@ type OwnProps = {
onStickerSelect: (sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => void;
onStickerUnfave: (sticker: ApiSticker) => void;
onStickerFave: (sticker: ApiSticker) => void;
onStickerRemoveRecent: (sticker: ApiSticker) => void;
};
const STICKERS_PER_ROW_ON_DESKTOP = 5;
@ -43,14 +48,23 @@ const StickerSet: FC<OwnProps> = ({
onStickerSelect,
onStickerUnfave,
onStickerFave,
onStickerRemoveRecent,
}) => {
const { clearRecentStickers } = getActions();
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
const lang = useLang();
useOnIntersect(ref, observeIntersection);
const transitionClassNames = useMediaTransition(shouldRender);
const handleClearRecent = useCallback(() => {
clearRecentStickers();
closeConfirmModal();
}, [clearRecentStickers, closeConfirmModal]);
const stickersPerRow = IS_SINGLE_COLUMN_LAYOUT
? Math.floor((windowSize.get().width - MOBILE_CONTAINER_PADDING) / (STICKER_SIZE_PICKER + STICKER_MARGIN))
: STICKERS_PER_ROW_ON_DESKTOP;
@ -60,6 +74,8 @@ const StickerSet: FC<OwnProps> = ({
favoriteStickers ? new Set(favoriteStickers.map(({ id }) => id)) : undefined
), [favoriteStickers]);
const isRecent = stickerSet.id === RECENT_SYMBOL_SET_ID;
return (
<div
ref={ref}
@ -67,7 +83,12 @@ const StickerSet: FC<OwnProps> = ({
id={`sticker-set-${index}`}
className="symbol-set"
>
<div className="symbol-set-header">
<p className="symbol-set-name">{stickerSet.title}</p>
{isRecent && (
<i className="symbol-set-remove icon-close" onClick={openConfirmModal} />
)}
</div>
<div
className={buildClassName('symbol-set-container', transitionClassNames)}
style={`height: ${height}px;`}
@ -81,12 +102,25 @@ const StickerSet: FC<OwnProps> = ({
noAnimate={!loadAndPlay}
onClick={onStickerSelect}
clickArg={sticker}
onUnfaveClick={favoriteStickerIdsSet?.has(sticker.id) ? onStickerUnfave : undefined}
onUnfaveClick={stickerSet.id === FAVORITE_SYMBOL_SET_ID && favoriteStickerIdsSet?.has(sticker.id)
? onStickerUnfave : undefined}
onFaveClick={!favoriteStickerIdsSet?.has(sticker.id) ? onStickerFave : undefined}
onRemoveRecentClick={isRecent ? onStickerRemoveRecent : undefined}
isSavedMessages={isSavedMessages}
canViewSet
/>
))}
</div>
{isRecent && (
<ConfirmDialog
text={lang('ClearRecentEmoji')}
isOpen={isConfirmModalOpen}
onClose={closeConfirmModal}
confirmHandler={handleClearRecent}
confirmIsDestructive
/>
)}
</div>
);
};

View File

@ -82,6 +82,7 @@ const StickerTooltip: FC<OwnProps & StateProps> = ({
onClick={onStickerSelect}
clickArg={sticker}
isSavedMessages={isSavedMessages}
canViewSet
/>
))
) : shouldRender ? (

View File

@ -147,18 +147,29 @@
.symbol-set {
margin-bottom: 1rem;
&-header {
display: flex;
align-items: center;
color: rgba(var(--color-text-secondary-rgb), 0.75);
}
&-name {
font-size: 1rem;
line-height: 1.6875rem;
font-weight: 500;
margin: 0;
padding-left: 0.5rem;
color: rgba(var(--color-text-secondary-rgb), 0.75);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: initial;
unicode-bidi: plaintext;
flex-grow: 1;
}
&-remove {
font-size: 1rem;
cursor: pointer;
}
&-container {

View File

@ -36,6 +36,7 @@ const StickerResult: FC<OwnProps> = ({
onClick={onClick}
clickArg={inlineResult}
isSavedMessages={isSavedMessages}
canViewSet
/>
);
};

View File

@ -53,7 +53,7 @@ const ConfirmDialog: FC<OwnProps> = ({
return (
<Modal
className="confirm"
title={title}
title={title || lang('Telegram')}
header={header}
isOpen={isOpen}
onClose={onClose}

View File

@ -139,6 +139,8 @@ export const STICKER_SIZE_JOIN_REQUESTS = 140;
export const STICKER_SIZE_INVITES = 140;
export const RECENT_STICKERS_LIMIT = 20;
export const NO_STICKER_SET_ID = 'NO_STICKER_SET';
export const RECENT_SYMBOL_SET_ID = 'recent';
export const FAVORITE_SYMBOL_SET_ID = 'favorite';
export const BASE_EMOJI_KEYWORD_LANG = 'en';

View File

@ -1,9 +1,11 @@
import { addActionHandler, getGlobal, setGlobal } from '../../index';
import {
addActionHandler, getActions, getGlobal, setGlobal,
} from '../../index';
import { ApiSticker } from '../../../api/types';
import { LangCode } from '../../../types';
import { callApi } from '../../../api/gramjs';
import { pause, throttle } from '../../../util/schedulers';
import { onTickEnd, pause, throttle } from '../../../util/schedulers';
import {
updateStickerSets,
updateStickerSet,
@ -14,6 +16,7 @@ import {
} from '../../reducers';
import searchWords from '../../../util/searchWords';
import { selectStickerSet } from '../../selectors';
import { getTranslation } from '../../../util/langProvider';
const ADDED_SETS_THROTTLE = 200;
const ADDED_SETS_THROTTLE_CHUNK = 10;
@ -34,7 +37,7 @@ addActionHandler('loadAddedStickers', async (global, actions) => {
for (let i = 0; i < addedSetIds.length; i++) {
const id = addedSetIds[i];
if (cached[id].stickers) {
if (cached[id]?.stickers) {
continue; // Already loaded
}
actions.loadStickers({ stickerSetId: id });
@ -89,13 +92,19 @@ addActionHandler('loadStickers', (global, actions, payload) => {
if (!stickerSetAccessHash && !stickerSetShortName) {
const stickerSet = selectStickerSet(global, stickerSetId);
if (!stickerSet) {
if (global.openedStickerSetShortName === stickerSetShortName) {
setGlobal({
...global,
openedStickerSetShortName: undefined,
});
}
return;
}
stickerSetAccessHash = stickerSet.accessHash;
}
void loadStickers(stickerSetId, stickerSetAccessHash, stickerSetShortName);
void loadStickers(stickerSetId, stickerSetAccessHash!, stickerSetShortName);
});
addActionHandler('loadAnimatedEmojis', () => {
@ -147,6 +156,33 @@ addActionHandler('unfaveSticker', (global, actions, payload) => {
}
});
addActionHandler('removeRecentSticker', async (global, action, payload) => {
const { sticker } = payload!;
const result = await callApi('removeRecentSticker', { sticker });
if (!result) return;
loadRecentStickers();
});
addActionHandler('clearRecentStickers', async (global) => {
const result = await callApi('clearRecentStickers');
if (!result) return;
global = getGlobal();
setGlobal({
...global,
stickers: {
...global.stickers,
recent: {
stickers: [],
},
},
});
});
addActionHandler('toggleStickerSet', (global, actions, payload) => {
const { stickerSetId } = payload!;
const stickerSet = selectStickerSet(global, stickerSetId);
@ -284,14 +320,25 @@ async function loadStickers(stickerSetId: string, accessHash: string, stickerSet
'fetchStickers',
{ stickerSetShortName, stickerSetId, accessHash },
);
let global = getGlobal();
if (!stickerSet) {
onTickEnd(() => {
getActions().showNotification({
message: getTranslation('StickerPack.ErrorNotFound'),
});
});
if (global.openedStickerSetShortName === stickerSetShortName) {
setGlobal({
...global,
openedStickerSetShortName: undefined,
});
}
return;
}
const { set, stickers, packs } = stickerSet;
let global = getGlobal();
global = updateStickerSet(global, set.id, { ...set, stickers, packs });
const currentEmoji = global.stickers.forEmoji.emoji;
@ -396,13 +443,39 @@ addActionHandler('clearStickersForEmoji', (global) => {
});
addActionHandler('openStickerSetShortName', (global, actions, payload) => {
const { stickerSetShortName } = payload!;
const { stickerSetShortName } = payload;
return {
...global,
openedStickerSetShortName: stickerSetShortName,
};
});
addActionHandler('openStickerSet', async (global, actions, payload) => {
const { sticker } = payload;
if (!selectStickerSet(global, sticker.stickerSetId)) {
if (!sticker.stickerSetAccessHash) {
actions.showNotification({
message: getTranslation('StickerPack.ErrorNotFound'),
});
return;
}
await loadStickers(sticker.stickerSetId, sticker.stickerSetAccessHash);
}
global = getGlobal();
const set = selectStickerSet(global, sticker.stickerSetId);
if (!set?.shortName) {
return;
}
setGlobal({
...global,
openedStickerSetShortName: set.shortName,
});
});
async function searchStickers(query: string, hash?: string) {
const result = await callApi('searchStickers', { query, hash });

View File

@ -28,6 +28,22 @@ addActionHandler('apiUpdate', (global, actions, update) => {
actions.loadFavoriteStickers();
break;
case 'updateRecentStickers':
actions.loadRecentStickers();
break;
case 'updateStickerSets':
actions.loadStickerSets();
break;
case 'updateStickerSetsOrder':
actions.reorderStickerSets({ order: update.order });
break;
case 'updateSavedGifs':
actions.loadSavedGifs();
break;
case 'updatePrivacy':
global.settings.privacy[update.key as ApiPrivacyKey] = update.rules;
break;

View File

@ -161,7 +161,7 @@ addActionHandler('addRecentEmoji', (global, action, payload) => {
});
addActionHandler('addRecentSticker', (global, action, payload) => {
const { sticker } = payload!;
const { sticker } = payload;
const { recent } = global.stickers;
if (!recent) {
return {
@ -191,6 +191,19 @@ addActionHandler('addRecentSticker', (global, action, payload) => {
};
});
addActionHandler('reorderStickerSets', (global, action, payload) => {
const { order } = payload;
return {
...global,
stickers: {
...global.stickers,
added: {
setIds: order,
},
},
};
});
addActionHandler('showNotification', (global, actions, payload) => {
const notification = payload!;
notification.localId = generateIdFor({});

View File

@ -728,6 +728,39 @@ export interface ActionPayloads {
shouldSharePhoneNumber?: boolean;
};
// Stickers
addRecentSticker: {
sticker: ApiSticker;
};
removeRecentSticker: {
sticker: ApiSticker;
};
clearRecentStickers: {};
loadStickerSets: {};
loadAddedStickers: {};
loadRecentStickers: {};
loadFavoriteStickers: {};
loadFeaturedStickers: {};
reorderStickerSets: {
order: string[];
};
addNewStickerSet: {
stickerSet: ApiStickerSet;
};
openStickerSetShortName: {
stickerSetShortName?: string;
};
openStickerSet: {
sticker: ApiSticker;
};
// Bots
clickBotInlineButton: {
messageId: number;
@ -835,7 +868,7 @@ export type NonTypedActionNames = (
'init' | 'reset' | 'disconnect' | 'initApi' | 'sync' | 'saveSession' |
'showNotification' | 'dismissNotification' | 'showDialog' | 'dismissDialog' |
// ui
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'toggleLeftColumn' |
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' |
'setNewChatMembersDialogState' | 'disableHistoryAnimations' | 'setLeftColumnWidth' | 'resetLeftColumnWidth' |
'openSeenByModal' | 'closeSeenByModal' | 'closeReactorListModal' | 'openReactorListModal' |
@ -904,11 +937,9 @@ export type NonTypedActionNames = (
'loadContentSettings' | 'updateContentSettings' |
'loadCountryList' | 'ensureTimeFormat' | 'loadAppConfig' |
// stickers & GIFs
'loadStickerSets' | 'loadAddedStickers' | 'loadRecentStickers' | 'loadFavoriteStickers' | 'loadFeaturedStickers' |
'loadStickers' | 'setStickerSearchQuery' | 'loadSavedGifs' | 'saveGif' | 'setGifSearchQuery' | 'searchMoreGifs' |
'faveSticker' | 'unfaveSticker' | 'toggleStickerSet' | 'loadAnimatedEmojis' |
'setStickerSearchQuery' | 'loadSavedGifs' | 'saveGif' | 'setGifSearchQuery' | 'searchMoreGifs' |
'faveSticker' | 'unfaveSticker' | 'toggleStickerSet' | 'loadAnimatedEmojis' | 'loadStickers' |
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' | 'loadGreetingStickers' |
'openStickerSetShortName' |
// bots
'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
'resetInlineBot' | 'restartBot' | 'startBot' |

View File

@ -1095,6 +1095,8 @@ messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flag
messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers;
messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool;
messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = messages.RecentStickers;
messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
messages.getCommonChats#e40ca104 user_id:InputUser max_id:long limit:int = messages.Chats;
messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;

View File

@ -113,6 +113,8 @@
"messages.getFeaturedStickers",
"messages.readFeaturedStickers",
"messages.getRecentStickers",
"messages.saveRecentSticker",
"messages.clearRecentStickers",
"messages.getCommonChats",
"messages.getWebPage",
"messages.toggleDialogPin",