Profile: GIFs & Main Tab (#6336)

This commit is contained in:
zubiden 2025-10-11 19:07:54 +02:00 committed by Alexander Zinchuk
parent 1be16bf765
commit 5e679218e2
51 changed files with 603 additions and 330 deletions

View File

@ -5,7 +5,7 @@ module.exports = {
fontTypes: ['woff2', 'woff'], fontTypes: ['woff2', 'woff'],
assetTypes: ['scss', 'ts'], assetTypes: ['scss', 'ts'],
tag: '', tag: '',
// Use a custom Handlebars template normalize: true,
templates: { templates: {
scss: './dev/icons.scss.hbs', scss: './dev/icons.scss.hbs',
}, },

View File

@ -48,6 +48,8 @@ type AnyToVoidFunction = (...args: any[]) => void;
type BooleanToVoidFunction = (value: boolean) => void; type BooleanToVoidFunction = (value: boolean) => void;
type NoneToVoidFunction = () => void; type NoneToVoidFunction = () => void;
type StringAutocomplete<T> = T | (string & {});
type Complete<T> = { type Complete<T> = {
[P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>> ? T[P] : (T[P] | undefined); [P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>> ? T[P] : (T[P] | undefined);
}; };

View File

@ -29,14 +29,17 @@ import { getServerTimeOffset } from '../../../util/serverTime';
import { addPhotoToLocalDb, addUserToLocalDb } from '../helpers/localDb'; import { addPhotoToLocalDb, addUserToLocalDb } from '../helpers/localDb';
import { serializeBytes } from '../helpers/misc'; import { serializeBytes } from '../helpers/misc';
import { import {
buildApiBotVerification, buildApiFormattedText, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId, buildApiFormattedText, buildApiPhoto, buildApiUsernames,
} from './common'; } from './common';
import { omitVirtualClassFields } from './helpers'; import { omitVirtualClassFields } from './helpers';
import { buildApiPeerNotifySettings, buildApiRestrictionReasons } from './misc'; import { buildApiRestrictionReasons } from './misc';
import { import {
buildApiBotVerification,
buildApiEmojiStatus, buildApiEmojiStatus,
buildApiPeerColor, buildApiPeerColor,
buildApiPeerId, buildApiPeerId,
buildApiPeerNotifySettings,
buildAvatarPhotoId,
getApiChatIdFromMtpPeer, getApiChatIdFromMtpPeer,
isMtpPeerChat, isMtpPeerChat,
isMtpPeerUser, isMtpPeerUser,

View File

@ -3,7 +3,6 @@ import type { Entity } from '../../../lib/gramjs/types';
import { strippedPhotoToJpg } from '../../../lib/gramjs/Utils'; import { strippedPhotoToJpg } from '../../../lib/gramjs/Utils';
import type { import type {
ApiBotVerification,
ApiFormattedText, ApiFormattedText,
ApiMessageEntity, ApiMessageEntity,
ApiMessageEntityDefault, ApiMessageEntityDefault,
@ -293,19 +292,3 @@ export function buildApiMessageEntity(entity: GramJs.TypeMessageEntity): ApiMess
length, length,
}; };
} }
export function buildAvatarPhotoId(photo: GramJs.TypeUserProfilePhoto | GramJs.TypeChatPhoto) {
if ('photoId' in photo) {
return photo.photoId.toString();
}
return undefined;
}
export function buildApiBotVerification(botVerification: GramJs.BotVerification): ApiBotVerification {
return {
botId: buildApiPeerId(botVerification.botId, 'user'),
iconId: botVerification.icon.toString(),
description: botVerification.description,
};
}

View File

@ -7,9 +7,6 @@ import type {
ApiCountry, ApiCountry,
ApiLanguage, ApiLanguage,
ApiOldLangString, ApiOldLangString,
ApiPeerColors,
ApiPeerNotifySettings,
ApiPeerProfileColorSet,
ApiPrivacyKey, ApiPrivacyKey,
ApiRestrictionReason, ApiRestrictionReason,
ApiSession, ApiSession,
@ -20,9 +17,8 @@ import type {
LangPackStringValue, LangPackStringValue,
} from '../../types'; } from '../../types';
import { numberToHexColor } from '../../../util/colors';
import { import {
buildCollectionByCallback, omit, omitUndefined, pick, omit, omitUndefined, pick,
} from '../../../util/iteratees'; } from '../../../util/iteratees';
import { toJSNumber } from '../../../util/numbers'; import { toJSNumber } from '../../../util/numbers';
import { addUserToLocalDb } from '../helpers/localDb'; import { addUserToLocalDb } from '../helpers/localDb';
@ -111,23 +107,6 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und
return undefined; return undefined;
} }
export function buildApiPeerNotifySettings(
notifySettings: GramJs.TypePeerNotifySettings,
): ApiPeerNotifySettings {
const {
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = !(otherSound instanceof GramJs.NotificationSoundNone);
return {
hasSound,
isSilentPosting: silent,
mutedUntil: muteUntil,
shouldShowPreviews: showPreviews,
};
}
function buildApiCountry(country: GramJs.help.Country, code: GramJs.help.CountryCode) { function buildApiCountry(country: GramJs.help.Country, code: GramJs.help.CountryCode) {
const { const {
hidden, iso2, defaultName, name, hidden, iso2, defaultName, name,
@ -295,46 +274,6 @@ export function buildApiLanguage(lang: GramJs.TypeLangPackLanguage): ApiLanguage
}; };
} }
function buildApiPeerColorSet(colorSet: GramJs.help.PeerColorSet) {
return colorSet.colors.map((color) => numberToHexColor(color));
}
function buildApiPeerProfileColorSet(colorSet: GramJs.help.PeerColorProfileSet): ApiPeerProfileColorSet {
return {
paletteColors: colorSet.paletteColors.map((color) => numberToHexColor(color)),
bgColors: colorSet.bgColors.map((color) => numberToHexColor(color)),
storyColors: colorSet.storyColors.map((color) => numberToHexColor(color)),
};
}
export function buildApiPeerColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['general'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiPeerProfileColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['profile'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiTimezone(timezone: GramJs.TypeTimezone): ApiTimezone { export function buildApiTimezone(timezone: GramJs.TypeTimezone): ApiTimezone {
const { id, name, utcOffset } = timezone; const { id, name, utcOffset } = timezone;
return { return {

View File

@ -1,9 +1,18 @@
import { Api as GramJs } from '../../../lib/gramjs'; import { Api as GramJs } from '../../../lib/gramjs';
import type { ApiEmojiStatusType, ApiPeerColor } from '../../types'; import type {
ApiBotVerification,
ApiEmojiStatusType,
ApiPeerColor,
ApiPeerColors,
ApiPeerNotifySettings,
ApiPeerProfileColorSet,
ApiProfileTab,
} from '../../types';
import { CHANNEL_ID_BASE } from '../../../config'; import { CHANNEL_ID_BASE } from '../../../config';
import { numberToHexColor } from '../../../util/colors'; import { numberToHexColor } from '../../../util/colors';
import { buildCollectionByCallback } from '../../../util/iteratees';
type TypePeerOrInput = GramJs.TypePeer | GramJs.TypeInputPeer | GramJs.TypeInputUser | GramJs.TypeInputChannel; type TypePeerOrInput = GramJs.TypePeer | GramJs.TypeInputPeer | GramJs.TypeInputUser | GramJs.TypeInputChannel;
@ -49,6 +58,46 @@ export function buildApiPeerColor(peerColor: GramJs.TypePeerColor): ApiPeerColor
}; };
} }
function buildApiPeerColorSet(colorSet: GramJs.help.PeerColorSet) {
return colorSet.colors.map((color) => numberToHexColor(color));
}
function buildApiPeerProfileColorSet(colorSet: GramJs.help.PeerColorProfileSet): ApiPeerProfileColorSet {
return {
paletteColors: colorSet.paletteColors.map((color) => numberToHexColor(color)),
bgColors: colorSet.bgColors.map((color) => numberToHexColor(color)),
storyColors: colorSet.storyColors.map((color) => numberToHexColor(color)),
};
}
export function buildApiPeerColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['general'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorSet
? buildApiPeerColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiPeerProfileColors(wrapper: GramJs.help.TypePeerColors): ApiPeerColors['profile'] | undefined {
if (!(wrapper instanceof GramJs.help.PeerColors)) return undefined;
return buildCollectionByCallback(wrapper.colors, (color) => {
return [color.colorId, {
isHidden: color.hidden,
colors: color.colors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.colors) : undefined,
darkColors: color.darkColors instanceof GramJs.help.PeerColorProfileSet
? buildApiPeerProfileColorSet(color.darkColors) : undefined,
}];
});
}
export function buildApiEmojiStatus(mtpEmojiStatus: GramJs.TypeEmojiStatus): export function buildApiEmojiStatus(mtpEmojiStatus: GramJs.TypeEmojiStatus):
ApiEmojiStatusType | undefined { ApiEmojiStatusType | undefined {
if (mtpEmojiStatus instanceof GramJs.EmojiStatus) { if (mtpEmojiStatus instanceof GramJs.EmojiStatus) {
@ -77,3 +126,61 @@ ApiEmojiStatusType | undefined {
return undefined; return undefined;
} }
export function buildAvatarPhotoId(photo: GramJs.TypeUserProfilePhoto | GramJs.TypeChatPhoto) {
if ('photoId' in photo) {
return photo.photoId.toString();
}
return undefined;
}
export function buildApiBotVerification(botVerification: GramJs.BotVerification): ApiBotVerification {
return {
botId: buildApiPeerId(botVerification.botId, 'user'),
iconId: botVerification.icon.toString(),
description: botVerification.description,
};
}
export function buildApiPeerNotifySettings(
notifySettings: GramJs.TypePeerNotifySettings,
): ApiPeerNotifySettings {
const {
silent, muteUntil, showPreviews, otherSound,
} = notifySettings;
const hasSound = !(otherSound instanceof GramJs.NotificationSoundNone);
return {
hasSound,
isSilentPosting: silent,
mutedUntil: muteUntil,
shouldShowPreviews: showPreviews,
};
}
export function buildApiProfileTab(profileTab: GramJs.TypeProfileTab): ApiProfileTab {
switch (profileTab.className) {
case 'ProfileTabPosts':
return 'stories';
case 'ProfileTabGifts':
return 'gifts';
case 'ProfileTabMedia':
return 'media';
case 'ProfileTabFiles':
return 'documents';
case 'ProfileTabMusic':
return 'audio';
case 'ProfileTabVoice':
return 'voice';
case 'ProfileTabLinks':
return 'links';
case 'ProfileTabGifs':
return 'gif';
default: {
const _exhaustiveCheck: never = profileTab;
return _exhaustiveCheck;
}
}
}

View File

@ -16,9 +16,9 @@ import type {
TypeStatisticsGraph, TypeStatisticsGraph,
} from '../../types'; } from '../../types';
import { buildApiUsernames, buildAvatarPhotoId } from './common'; import { buildApiUsernames } from './common';
import { buildApiCurrencyAmount } from './payments'; import { buildApiCurrencyAmount } from './payments';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers'; import { buildApiPeerId, buildAvatarPhotoId, getApiChatIdFromMtpPeer } from './peers';
export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics { export function buildChannelStatistics(stats: GramJs.stats.BroadcastStats): ApiChannelStatistics {
return { return {

View File

@ -14,11 +14,18 @@ import { toJSNumber } from '../../../util/numbers';
import { buildApiBotInfo } from './bots'; import { buildApiBotInfo } from './bots';
import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business'; import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business';
import { import {
buildApiBotVerification, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId, buildApiPhoto, buildApiUsernames,
} from './common'; } from './common';
import { buildApiDisallowedGiftsSettings } from './gifts'; import { buildApiDisallowedGiftsSettings } from './gifts';
import { omitVirtualClassFields } from './helpers'; import { omitVirtualClassFields } from './helpers';
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers'; import {
buildApiBotVerification,
buildApiEmojiStatus,
buildApiPeerColor,
buildApiPeerId,
buildApiProfileTab,
buildAvatarPhotoId,
} from './peers';
export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo { export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo {
const { const {
@ -29,7 +36,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
contactRequirePremium, businessWorkHours, businessLocation, businessIntro, contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification, birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts, botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts,
starsRating, starsMyPendingRating, starsMyPendingRatingDate, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab,
}, },
users, users,
} = mtpUserFull; } = mtpUserFull;
@ -68,6 +75,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
hasScheduledMessages: hasScheduled, hasScheduledMessages: hasScheduled,
paidMessagesStars: toJSNumber(sendPaidMessagesStars), paidMessagesStars: toJSNumber(sendPaidMessagesStars),
settings: buildApiPeerSettings(settings), settings: buildApiPeerSettings(settings),
mainTab: mainTab && buildApiProfileTab(mainTab),
}; };
} }

View File

@ -23,6 +23,7 @@ import type {
ApiPoll, ApiPoll,
ApiPremiumGiftCodeOption, ApiPremiumGiftCodeOption,
ApiPrivacyKey, ApiPrivacyKey,
ApiProfileTab,
ApiReactionWithPaid, ApiReactionWithPaid,
ApiReportReason, ApiReportReason,
ApiRequestInputInvoice, ApiRequestInputInvoice,
@ -980,3 +981,28 @@ export function buildInputSavedStarGift(inputGift: ApiRequestInputSavedStarGift)
savedId: BigInt(inputGift.savedId), savedId: BigInt(inputGift.savedId),
}); });
} }
export function buildInputProfileTab(profileTab: ApiProfileTab) {
switch (profileTab) {
case 'stories':
return new GramJs.ProfileTabPosts();
case 'gifts':
return new GramJs.ProfileTabGifts();
case 'media':
return new GramJs.ProfileTabMedia();
case 'documents':
return new GramJs.ProfileTabFiles();
case 'audio':
return new GramJs.ProfileTabMusic();
case 'voice':
return new GramJs.ProfileTabVoice();
case 'links':
return new GramJs.ProfileTabLinks();
case 'gif':
return new GramJs.ProfileTabGifs();
default: {
const _exhaustiveCheck: never = profileTab;
return _exhaustiveCheck;
}
}
}

View File

@ -1,11 +1,17 @@
import { Api as GramJs } from '../../../lib/gramjs'; import { Api as GramJs } from '../../../lib/gramjs';
import type { import type {
ApiPeer, ApiPhoto, ApiReportReason, ApiPeer, ApiPhoto, ApiProfileTab, ApiReportReason,
} from '../../types'; } from '../../types';
import { buildApiChatLink } from '../apiBuilders/misc'; import { buildApiChatLink } from '../apiBuilders/misc';
import { buildInputPeer, buildInputPhoto, buildInputReportReason, DEFAULT_PRIMITIVES } from '../gramjsBuilders'; import {
buildInputPeer,
buildInputPhoto,
buildInputProfileTab,
buildInputReportReason,
DEFAULT_PRIMITIVES,
} from '../gramjsBuilders';
import { invokeRequest } from './client'; import { invokeRequest } from './client';
export async function reportPeer({ export async function reportPeer({
@ -124,3 +130,11 @@ export function setAccountTTL({ days }: { days: number }) {
shouldReturnTrue: true, shouldReturnTrue: true,
}); });
} }
export function setAccountMainProfileTab({ tab }: { tab: ApiProfileTab }) {
return invokeRequest(new GramJs.account.SetMainProfileTab({
tab: buildInputProfileTab(tab),
}), {
shouldReturnTrue: true,
});
}

View File

@ -17,6 +17,7 @@ import type {
ApiPeer, ApiPeer,
ApiPeerNotifySettings, ApiPeerNotifySettings,
ApiPhoto, ApiPhoto,
ApiProfileTab,
ApiTopic, ApiTopic,
ApiUser, ApiUser,
ApiUserStatus, ApiUserStatus,
@ -51,10 +52,15 @@ import {
buildChatMembers, buildChatMembers,
getPeerKey, getPeerKey,
} from '../apiBuilders/chats'; } from '../apiBuilders/chats';
import { buildApiBotVerification, buildApiPhoto } from '../apiBuilders/common'; import { buildApiPhoto } from '../apiBuilders/common';
import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages'; import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
import { buildApiPeerNotifySettings } from '../apiBuilders/misc'; import {
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; buildApiBotVerification,
buildApiPeerId,
buildApiPeerNotifySettings,
buildApiProfileTab,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import { buildStickerSet } from '../apiBuilders/symbols'; import { buildStickerSet } from '../apiBuilders/symbols';
import { buildApiPeerSettings, buildApiUser, buildApiUserStatuses } from '../apiBuilders/users'; import { buildApiPeerSettings, buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
import { import {
@ -66,6 +72,7 @@ import {
buildInputChatReactions, buildInputChatReactions,
buildInputPeer, buildInputPeer,
buildInputPhoto, buildInputPhoto,
buildInputProfileTab,
buildInputReplyTo, buildInputReplyTo,
buildInputSuggestedPost, buildInputSuggestedPost,
buildInputUser, buildInputUser,
@ -654,6 +661,7 @@ async function getFullChannelInfo(
stargiftsCount, stargiftsCount,
stargiftsAvailable, stargiftsAvailable,
paidMessagesAvailable, paidMessagesAvailable,
mainTab,
} = result.fullChat; } = result.fullChat;
if (chatPhoto) { if (chatPhoto) {
@ -753,6 +761,7 @@ async function getFullChannelInfo(
starGiftCount: stargiftsCount, starGiftCount: stargiftsCount,
areStarGiftsAvailable: Boolean(stargiftsAvailable), areStarGiftsAvailable: Boolean(stargiftsAvailable),
arePaidMessagesAvailable: paidMessagesAvailable, arePaidMessagesAvailable: paidMessagesAvailable,
mainTab: mainTab && buildApiProfileTab(mainTab),
}, },
chats, chats,
userStatusesById: statusesById, userStatusesById: statusesById,
@ -2092,3 +2101,12 @@ export function toggleAutoTranslation({
shouldReturnTrue: true, shouldReturnTrue: true,
}); });
} }
export function setChannelMainProfileTab({ chat, tab }: { chat: ApiChat; tab: ApiProfileTab }) {
return invokeRequest(new GramJs.channels.SetMainProfileTab({
channel: buildInputChannel(chat.id, chat.accessHash),
tab: buildInputProfileTab(tab),
}), {
shouldReturnTrue: true,
});
}

View File

@ -1494,6 +1494,9 @@ export async function searchMessagesInChat({
case 'profilePhoto': case 'profilePhoto':
filter = new GramJs.InputMessagesFilterChatPhotos(); filter = new GramJs.InputMessagesFilterChatPhotos();
break; break;
case 'gif':
filter = new GramJs.InputMessagesFilterGif();
break;
case 'text': case 'text':
default: { default: {
filter = new GramJs.InputMessagesFilterEmpty(); filter = new GramJs.InputMessagesFilterEmpty();

View File

@ -31,9 +31,6 @@ import {
buildApiConfig, buildApiConfig,
buildApiCountryList, buildApiCountryList,
buildApiLanguage, buildApiLanguage,
buildApiPeerColors,
buildApiPeerNotifySettings,
buildApiPeerProfileColors,
buildApiSession, buildApiSession,
buildApiTimezone, buildApiTimezone,
buildApiWallpaper, buildApiWallpaper,
@ -41,7 +38,12 @@ import {
buildLangStrings, buildLangStrings,
oldBuildLangPack, oldBuildLangPack,
} from '../apiBuilders/misc'; } from '../apiBuilders/misc';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import {
buildApiPeerColors,
buildApiPeerNotifySettings,
buildApiPeerProfileColors,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import { import {
buildDisallowedGiftsSettings, buildDisallowedGiftsSettings,
buildInputChannel, buildInputChannel,

View File

@ -49,12 +49,16 @@ import {
buildMessageDraft, buildMessageDraft,
} from '../apiBuilders/messages'; } from '../apiBuilders/messages';
import { import {
buildApiPeerNotifySettings,
buildLangStrings, buildLangStrings,
buildPrivacyKey, buildPrivacyKey,
} from '../apiBuilders/misc'; } from '../apiBuilders/misc';
import { buildApiCurrencyAmount } from '../apiBuilders/payments'; import { buildApiCurrencyAmount } from '../apiBuilders/payments';
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import {
buildApiEmojiStatus,
buildApiPeerId,
buildApiPeerNotifySettings,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import { import {
buildApiPaidReactionPrivacy, buildApiPaidReactionPrivacy,
buildApiReaction, buildApiReaction,

View File

@ -2,9 +2,17 @@ import type { ApiBotCommand } from './bots';
import type { import type {
ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiInputSuggestedPostInfo, ApiPhoto, ApiStickerSet, ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiInputSuggestedPostInfo, ApiPhoto, ApiStickerSet,
} from './messages'; } from './messages';
import type { ApiBotVerification, ApiChatInviteImporter, ApiPeerNotifySettings, ApiRestrictionReason } from './misc'; import type { ApiChatInviteImporter, ApiPeerNotifySettings, ApiRestrictionReason } from './misc';
import type { import type {
ApiEmojiStatusType, ApiFakeType, ApiUser, ApiUsername, ApiBotVerification,
ApiEmojiStatusType,
ApiFakeType,
ApiPeerColor,
ApiProfileTab,
ApiSendAsPeerId,
} from './peers';
import type {
ApiUser, ApiUsername,
} from './users'; } from './users';
type ApiChatType = ( type ApiChatType = (
@ -156,6 +164,7 @@ export interface ApiChatFullInfo {
boostsApplied?: number; boostsApplied?: number;
boostsToUnrestrict?: number; boostsToUnrestrict?: number;
botVerification?: ApiBotVerification; botVerification?: ApiBotVerification;
mainTab?: ApiProfileTab;
} }
export interface ApiChatMember { export interface ApiChatMember {
@ -235,23 +244,6 @@ export interface ApiChatFolder {
hasMyInvites?: true; hasMyInvites?: true;
} }
export interface ApiPeerSettings {
isAutoArchived?: boolean;
canReportSpam?: boolean;
canAddContact?: boolean;
canBlockContact?: boolean;
chargedPaidMessageStars?: number;
registrationMonth?: string;
phoneCountry?: string;
nameChangeDate?: number;
photoChangeDate?: number;
}
export interface ApiSendAsPeerId {
id: string;
isPremium?: boolean;
}
export interface ApiTopic { export interface ApiTopic {
id: number; id: number;
isClosed?: boolean; isClosed?: boolean;
@ -296,11 +288,6 @@ export interface ApiChatlistExportedInvite {
peerIds: string[]; peerIds: string[];
} }
export interface ApiPeerColor {
color?: number;
backgroundEmojiId?: string;
}
export interface ApiMissingInvitedUser { export interface ApiMissingInvitedUser {
id: string; id: string;
isRequiringPremiumToInvite?: boolean; isRequiringPremiumToInvite?: boolean;

View File

@ -12,3 +12,4 @@ export * from './statistics';
export * from './stories'; export * from './stories';
export * from './business'; export * from './business';
export * from './stars'; export * from './stars';
export * from './peers';

View File

@ -4,12 +4,12 @@ import type {
ApiBotInlineResult, ApiBotInlineResult,
ApiWebDocument, ApiWebDocument,
} from './bots'; } from './bots';
import type { ApiPeerColor } from './chats';
import type { ApiMessageAction } from './messageActions'; import type { ApiMessageAction } from './messageActions';
import type { ApiRestrictionReason } from './misc'; import type { ApiRestrictionReason } from './misc';
import type { import type {
ApiLabeledPrice, ApiLabeledPrice,
} from './payments'; } from './payments';
import type { ApiPeerColor } from './peers';
import type { ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars'; import type { ApiStarGiftUnique, ApiTypeCurrencyAmount } from './stars';
import type { import type {
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData, ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
@ -957,7 +957,8 @@ export type ApiTranscription = {
transcriptionId: string; transcriptionId: string;
}; };
export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'profilePhoto'; export type ApiMessageSearchType = 'text' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'gif'
| 'profilePhoto';
export type ApiGlobalMessageSearchType = 'text' | export type ApiGlobalMessageSearchType = 'text' |
'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'publicPosts'; 'channels' | 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'publicPosts';
export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels'; export type ApiMessageSearchContext = 'all' | 'users' | 'groups' | 'channels';

View File

@ -5,6 +5,7 @@ import type { IconName } from '../../types/icons';
import type { RegularLangFnParameters } from '../../util/localization'; import type { RegularLangFnParameters } from '../../util/localization';
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages'; import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
import type { ApiPremiumSection } from './payments'; import type { ApiPremiumSection } from './payments';
import type { ApiBotVerification } from './peers';
import type { ApiStarsSubscriptionPricing } from './stars'; import type { ApiStarsSubscriptionPricing } from './stars';
import type { ApiUser } from './users'; import type { ApiUser } from './users';
@ -287,26 +288,6 @@ export interface ApiConfig {
maxForwardedCount: number; maxForwardedCount: number;
} }
export type ApiPeerColorSet = string[];
export type ApiPeerProfileColorSet = {
paletteColors: string[];
bgColors: string[];
storyColors: string[];
};
export type ApiPeerColorOption<T extends ApiPeerColorSet | ApiPeerProfileColorSet> = {
isHidden?: true;
colors?: T;
darkColors?: T;
};
export interface ApiPeerColors {
general: Record<number, ApiPeerColorOption<ApiPeerColorSet>>;
generalHash?: number;
profile: Record<number, ApiPeerColorOption<ApiPeerProfileColorSet>>;
profileHash?: number;
}
export interface ApiTimezone { export interface ApiTimezone {
id: string; id: string;
name: string; name: string;
@ -352,21 +333,6 @@ export interface ApiCollectibleInfo {
url: string; url: string;
} }
export interface ApiPeerPhotos {
fallbackPhoto?: ApiPhoto;
personalPhoto?: ApiPhoto;
photos: ApiPhoto[];
count: number;
nextOffset?: number;
isLoading?: boolean;
}
export interface ApiBotVerification {
botId: string;
iconId: string;
description: string;
}
export type ApiLimitType = export type ApiLimitType =
| 'uploadMaxFileparts' | 'uploadMaxFileparts'
| 'stickersFaved' | 'stickersFaved'

84
src/api/types/peers.ts Normal file
View File

@ -0,0 +1,84 @@
import type { ApiPhoto } from './messages';
export interface ApiPeerPhotos {
fallbackPhoto?: ApiPhoto;
personalPhoto?: ApiPhoto;
photos: ApiPhoto[];
count: number;
nextOffset?: number;
isLoading?: boolean;
}
export type ApiFakeType = 'fake' | 'scam';
export interface ApiBotVerification {
botId: string;
iconId: string;
description: string;
}
export type ApiEmojiStatusType = ApiEmojiStatus | ApiEmojiStatusCollectible;
export interface ApiEmojiStatus {
type: 'regular';
documentId: string;
until?: number;
}
export interface ApiEmojiStatusCollectible {
type: 'collectible';
collectibleId: string;
documentId: string;
title: string;
slug: string;
patternDocumentId: string;
centerColor: string;
edgeColor: string;
patternColor: string;
textColor: string;
until?: number;
}
export interface ApiPeerSettings {
isAutoArchived?: boolean;
canReportSpam?: boolean;
canAddContact?: boolean;
canBlockContact?: boolean;
chargedPaidMessageStars?: number;
registrationMonth?: string;
phoneCountry?: string;
nameChangeDate?: number;
photoChangeDate?: number;
}
export interface ApiSendAsPeerId {
id: string;
isPremium?: boolean;
}
export interface ApiPeerColor {
color?: number;
backgroundEmojiId?: string;
}
export type ApiPeerColorSet = string[];
export type ApiPeerProfileColorSet = {
paletteColors: string[];
bgColors: string[];
storyColors: string[];
};
export type ApiPeerColorOption<T extends ApiPeerColorSet | ApiPeerProfileColorSet> = {
isHidden?: true;
colors?: T;
darkColors?: T;
};
export interface ApiPeerColors {
general: Record<number, ApiPeerColorOption<ApiPeerColorSet>>;
generalHash?: number;
profile: Record<number, ApiPeerColorOption<ApiPeerProfileColorSet>>;
profileHash?: number;
}
export type ApiProfileTab = 'stories' | 'gifts' | 'media' | 'documents' | 'audio' | 'voice' | 'links' | 'gif';

View File

@ -18,7 +18,6 @@ import type {
ApiChatFullInfo, ApiChatFullInfo,
ApiChatMember, ApiChatMember,
ApiDraft, ApiDraft,
ApiPeerSettings,
ApiTypingStatus, ApiTypingStatus,
} from './chats'; } from './chats';
import type { import type {
@ -43,11 +42,12 @@ import type {
ApiPeerNotifySettings, ApiPeerNotifySettings,
ApiSessionData, ApiSessionData,
} from './misc'; } from './misc';
import type { ApiEmojiStatusType, ApiPeerSettings } from './peers';
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings'; import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
import type { ApiTypeCurrencyAmount } from './stars'; import type { ApiTypeCurrencyAmount } from './stars';
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories'; import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
import type { import type {
ApiEmojiStatusType, ApiUser, ApiUserFullInfo, ApiUserStatus, ApiUser, ApiUserFullInfo, ApiUserStatus,
} from './users'; } from './users';
export type ApiUpdateReady = { export type ApiUpdateReady = {

View File

@ -1,9 +1,15 @@
import type { API_CHAT_TYPES } from '../../config'; import type { API_CHAT_TYPES } from '../../config';
import type { ApiBotInfo } from './bots'; import type { ApiBotInfo } from './bots';
import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business'; import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business';
import type { ApiPeerColor, ApiPeerSettings } from './chats';
import type { ApiDocument, ApiPhoto } from './messages'; import type { ApiDocument, ApiPhoto } from './messages';
import type { ApiBotVerification } from './misc'; import type {
ApiBotVerification,
ApiEmojiStatusType,
ApiFakeType,
ApiPeerColor,
ApiPeerSettings,
ApiProfileTab,
} from './peers';
import type { ApiSavedStarGift, ApiStarsRating } from './stars'; import type { ApiSavedStarGift, ApiStarsRating } from './stars';
export interface ApiUser { export interface ApiUser {
@ -75,10 +81,9 @@ export interface ApiUserFullInfo {
botVerification?: ApiBotVerification; botVerification?: ApiBotVerification;
paidMessagesStars?: number; paidMessagesStars?: number;
settings?: ApiPeerSettings; settings?: ApiPeerSettings;
mainTab?: ApiProfileTab;
} }
export type ApiFakeType = 'fake' | 'scam';
export type ApiUserType = 'userTypeBot' | 'userTypeRegular' | 'userTypeDeleted' | 'userTypeUnknown'; export type ApiUserType = 'userTypeBot' | 'userTypeRegular' | 'userTypeDeleted' | 'userTypeUnknown';
export interface ApiUserStatus { export interface ApiUserStatus {
@ -136,28 +141,6 @@ export interface ApiAttachBotIcon {
document: ApiDocument; document: ApiDocument;
} }
export type ApiEmojiStatusType = ApiEmojiStatus | ApiEmojiStatusCollectible;
export interface ApiEmojiStatus {
type: 'regular';
documentId: string;
until?: number;
}
export interface ApiEmojiStatusCollectible {
type: 'collectible';
collectibleId: string;
documentId: string;
title: string;
slug: string;
patternDocumentId: string;
centerColor: string;
edgeColor: string;
patternColor: string;
textColor: string;
until?: number;
}
export interface ApiBirthday { export interface ApiBirthday {
day: number; day: number;
month: number; month: number;

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="32"><path d="M15.33 15.009a7.105 7.105 0 1 1 .001-14.21 7.105 7.105 0 0 1-.001 14.21zM1.122 27.236c0-1.363.339-2.725 1.306-3.686 1.817-1.804 5.722-4.482 12.901-4.482s11.085 2.678 12.901 4.482c.967.96 1.306 2.322 1.306 3.686a4.011 4.011 0 0 1-3.984 4.01H5.13a4.011 4.011 0 0 1-4.011-4.01zm30.601 4.01a7.346 7.346 0 0 0 1.184-4.01c0-1.839-.45-4.238-2.302-6.076l-.042-.041c.326-.013.66-.02 1.004-.02 6.145 0 9.493 2.226 11.052 3.729.83.8 1.126 1.952 1.126 3.105a3.314 3.314 0 0 1-3.314 3.314h-8.708zm-.156-14.207a6.089 6.089 0 1 1 0-12.178 6.089 6.089 0 0 1 0 12.178z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M11.002 15.286a5 5 0 1 1 0-10 5 5 0 0 1 0 10m-10 8.606c0-.96.239-1.918.92-2.595C3.2 20.027 5.947 18.143 11 18.143c5.052 0 7.802 1.885 9.08 3.154.68.676.919 1.635.919 2.595a2.823 2.823 0 0 1-2.804 2.822H3.823A2.823 2.823 0 0 1 1 23.892zm21.537 2.822a5.17 5.17 0 0 0 .833-2.822c0-1.295-.316-2.983-1.62-4.277l-.03-.028q.345-.015.707-.015c4.325 0 6.681 1.567 7.779 2.625.584.563.792 1.374.792 2.185a2.33 2.33 0 0 1-2.332 2.333zm-.11-9.999a4.285 4.285 0 1 1 0-8.57 4.285 4.285 0 0 1 0 8.57" /></svg>

Before

Width:  |  Height:  |  Size: 632 B

After

Width:  |  Height:  |  Size: 567 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M0 15C0 6.716 6.716 0 15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15Zm13.636-6.818a1.364 1.364 0 1 0 0 2.727h.682c.377 0 .682.305.682.682v8.864a1.364 1.364 0 1 0 2.727 0V11.59a3.41 3.41 0 0 0-3.409-3.41z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M1 16C1 7.716 7.716 1 16 1s15 6.716 15 15-6.716 15-15 15S1 24.284 1 16m13.636-6.818a1.364 1.364 0 1 0 0 2.727h.682c.377 0 .682.305.682.682v8.864a1.364 1.364 0 1 0 2.727 0V12.59a3.41 3.41 0 0 0-3.409-3.41Z"/></svg>

Before

Width:  |  Height:  |  Size: 335 B

After

Width:  |  Height:  |  Size: 309 B

View File

@ -1434,6 +1434,8 @@
"ProfileBirthdayValueYear" = "{date} ({age} years old)"; "ProfileBirthdayValueYear" = "{date} ({age} years old)";
"ProfileBirthdayTodayValue" = "🎂 {date}"; "ProfileBirthdayTodayValue" = "🎂 {date}";
"ProfileBirthdayTodayValueYear" = "🎂 {date} ({age} years old)"; "ProfileBirthdayTodayValueYear" = "🎂 {date} ({age} years old)";
"ProfileMenuSetMainTab" = "Set as Main Tab";
"ProfileItemSubscribers" = "Subscribers";
"MonetizationInfoTONTitle" = "What is 💎 TON?"; "MonetizationInfoTONTitle" = "What is 💎 TON?";
"ChannelEarnLearnCoinAbout" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}"; "ChannelEarnLearnCoinAbout" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
"MonetizationBalanceZeroInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by advertisers to pay for ads. {link}"; "MonetizationBalanceZeroInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by advertisers to pay for ads. {link}";
@ -1710,6 +1712,7 @@
"ProfileTabLinks" = "Links"; "ProfileTabLinks" = "Links";
"ProfileTabMusic" = "Music"; "ProfileTabMusic" = "Music";
"ProfileTabVoice" = "Voice"; "ProfileTabVoice" = "Voice";
"ProfileTabGifs" = "GIF";
"ProfileTabSharedGroups" = "Groups"; "ProfileTabSharedGroups" = "Groups";
"ProfileTabSimilarChannels" = "Similar Channels"; "ProfileTabSimilarChannels" = "Similar Channels";
"ProfileTabSimilarBots" = "Similar Bots"; "ProfileTabSimilarBots" = "Similar Bots";

View File

@ -3,6 +3,7 @@ import { memo, useEffect, useRef, useState } from '../../lib/teact/teact';
import type { ApiSticker } from '../../api/types'; import type { ApiSticker } from '../../api/types';
import type { AnimationLevel } from '../../types'; import type { AnimationLevel } from '../../types';
import { ANIMATION_LEVEL_MIN } from '../../config';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import useHorizontalScroll from '../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../hooks/useHorizontalScroll';
@ -38,7 +39,7 @@ const AnimatedTabList = ({
const clipPathContainerRef = useRef<HTMLDivElement>(); const clipPathContainerRef = useRef<HTMLDivElement>();
const selectedIndex = items.findIndex((item) => item.id === selectedItemId) || 0; const selectedIndex = items.findIndex((item) => item.id === selectedItemId) || 0;
const [clipPath, setClipPath] = useState<string>(''); const [clipPath, setClipPath] = useState<string>('');
const shouldAnimate = animationLevel > 0; const shouldAnimate = animationLevel > ANIMATION_LEVEL_MIN;
useHorizontalScroll(containerRef, !items.length, true); useHorizontalScroll(containerRef, !items.length, true);

View File

@ -1,4 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import { memo, useRef } from '../../lib/teact/teact'; import { memo, useRef } from '../../lib/teact/teact';
import type { ApiMessage } from '../../api/types'; import type { ApiMessage } from '../../api/types';
@ -8,7 +7,9 @@ import {
getMessageHtmlId, getMessageHtmlId,
getMessageIsSpoiler, getMessageIsSpoiler,
getMessageVideo, getMessageVideo,
getVideoMediaHash,
} from '../../global/helpers'; } from '../../global/helpers';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { formatMediaDuration } from '../../util/dates/dateFormat'; import { formatMediaDuration } from '../../util/dates/dateFormat';
import stopEvent from '../../util/stopEvent'; import stopEvent from '../../util/stopEvent';
@ -21,6 +22,7 @@ import useLastCallback from '../../hooks/useLastCallback';
import useMedia from '../../hooks/useMedia'; import useMedia from '../../hooks/useMedia';
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated'; import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
import OptimizedVideo from '../ui/OptimizedVideo';
import MediaSpoiler from './MediaSpoiler'; import MediaSpoiler from './MediaSpoiler';
import './Media.scss'; import './Media.scss';
@ -29,26 +31,32 @@ type OwnProps = {
message: ApiMessage; message: ApiMessage;
idPrefix?: string; idPrefix?: string;
isProtected?: boolean; isProtected?: boolean;
canAutoPlay?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
onClick?: (messageId: number, chatId: string) => void; onClick?: (messageId: number, chatId: string) => void;
}; };
const Media: FC<OwnProps> = ({ const Media = ({
message, message,
idPrefix = 'shared-media', idPrefix = 'shared-media',
isProtected, isProtected,
canAutoPlay,
observeIntersection, observeIntersection,
onClick, onClick,
}) => { }: OwnProps) => {
const ref = useRef<HTMLDivElement>(); const ref = useRef<HTMLDivElement>();
const isIntersecting = useIsIntersecting(ref, observeIntersection); const isIntersecting = useIsIntersecting(ref, observeIntersection);
const [isHovering, markMouseOver, markMouseOut] = useFlag();
const thumbDataUri = useThumbnail(message); const thumbDataUri = useThumbnail(message);
const mediaHash = useMessageMediaHash(message, 'pictogram'); const mediaHash = useMessageMediaHash(message, 'pictogram');
const mediaBlobUrl = useMedia(mediaHash, !isIntersecting); const mediaBlobUrl = useMedia(mediaHash, !isIntersecting);
const transitionClassNames = useMediaTransitionDeprecated(mediaBlobUrl); const transitionClassNames = useMediaTransitionDeprecated(mediaBlobUrl);
const video = getMessageVideo(message); const video = getMessageVideo(message);
const fullGiftHash = video?.isGif ? getVideoMediaHash(video, 'full') : undefined;
const fullGifBlobUrl = useMedia(fullGiftHash, !isIntersecting);
const hasSpoiler = getMessageIsSpoiler(message); const hasSpoiler = getMessageIsSpoiler(message);
const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler); const [isSpoilerShown, , hideSpoiler] = useFlag(hasSpoiler);
@ -64,6 +72,8 @@ const Media: FC<OwnProps> = ({
id={`${idPrefix}${getMessageHtmlId(message.id)}`} id={`${idPrefix}${getMessageHtmlId(message.id)}`}
className="Media scroll-item" className="Media scroll-item"
onClick={onClick ? handleClick : undefined} onClick={onClick ? handleClick : undefined}
onMouseOver={!IS_TOUCH_ENV ? markMouseOver : undefined}
onMouseOut={!IS_TOUCH_ENV ? markMouseOut : undefined}
> >
<img <img
src={thumbDataUri} src={thumbDataUri}
@ -73,14 +83,28 @@ const Media: FC<OwnProps> = ({
decoding="async" decoding="async"
onContextMenu={isProtected ? stopEvent : undefined} onContextMenu={isProtected ? stopEvent : undefined}
/> />
<img {fullGifBlobUrl ? (
src={mediaBlobUrl} <OptimizedVideo
className={buildClassName('full-media', 'media-miniature', transitionClassNames)} canPlay={isIntersecting && !hasSpoiler && isHovering && Boolean(canAutoPlay)}
alt="" src={fullGifBlobUrl}
draggable={!isProtected} className={buildClassName('full-media', 'media-miniature', transitionClassNames)}
decoding="async" muted
onContextMenu={isProtected ? stopEvent : undefined} loop
/> playsInline
draggable={false}
disablePictureInPicture
onContextMenu={isProtected ? stopEvent : undefined}
/>
) : (
<img
src={mediaBlobUrl}
className={buildClassName('full-media', 'media-miniature', transitionClassNames)}
alt=""
draggable={false}
decoding="async"
onContextMenu={isProtected ? stopEvent : undefined}
/>
)}
{hasSpoiler && ( {hasSpoiler && (
<MediaSpoiler <MediaSpoiler
thumbDataUri={mediaBlobUrl || thumbDataUri} thumbDataUri={mediaBlobUrl || thumbDataUri}

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../lib/teact/teact';
import { import {
memo, useMemo, memo, useMemo,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
@ -12,8 +11,8 @@ import type {
ApiUserFullInfo, ApiUserFullInfo,
ApiUsername, ApiUsername,
} from '../../../api/types'; } from '../../../api/types';
import type { BotAppPermissions } from '../../../types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { type BotAppPermissions, ManagementScreens } from '../../../types';
import { import {
FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH, MUTE_INDEFINITE_TIMESTAMP, UNMUTE_TIMESTAMP, FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH, MUTE_INDEFINITE_TIMESTAMP, UNMUTE_TIMESTAMP,
@ -22,6 +21,7 @@ import {
buildStaticMapHash, buildStaticMapHash,
getChatLink, getChatLink,
getHasAdminRight, getHasAdminRight,
isChatAdmin,
isChatChannel, isChatChannel,
isUserRightBanned, isUserRightBanned,
} from '../../../global/helpers'; } from '../../../global/helpers';
@ -92,6 +92,7 @@ type StateProps = {
isBotCanManageEmojiStatus?: boolean; isBotCanManageEmojiStatus?: boolean;
botAppPermissions?: BotAppPermissions; botAppPermissions?: BotAppPermissions;
botVerification?: ApiBotVerification; botVerification?: ApiBotVerification;
canViewSubscribers?: boolean;
}; };
const DEFAULT_MAP_CONFIG = { const DEFAULT_MAP_CONFIG = {
@ -102,7 +103,7 @@ const DEFAULT_MAP_CONFIG = {
const BOT_VERIFICATION_ICON_SIZE = 16; const BOT_VERIFICATION_ICON_SIZE = 16;
const ChatExtra: FC<OwnProps & StateProps> = ({ const ChatExtra = ({
chatOrUserId, chatOrUserId,
user, user,
chat, chat,
@ -124,7 +125,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
className, className,
style, style,
isInSettings, isInSettings,
}) => { canViewSubscribers,
}: OwnProps & StateProps) => {
const { const {
showNotification, showNotification,
updateChatMutedState, updateChatMutedState,
@ -136,6 +138,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
requestMainWebView, requestMainWebView,
toggleUserEmojiStatusPermission, toggleUserEmojiStatusPermission,
toggleUserLocationPermission, toggleUserLocationPermission,
requestNextManagementScreen,
} = getActions(); } = getActions();
const { const {
@ -261,6 +264,10 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
copy(formatUsername(username.username, isChat), oldLang(isChat ? 'Link' : 'Username')); copy(formatUsername(username.username, isChat), oldLang(isChat ? 'Link' : 'Username'));
}); });
const handleOpenSubscribers = useLastCallback(() => {
requestNextManagementScreen({ screen: ManagementScreens.ChannelSubscribers });
});
const handleOpenApp = useLastCallback(() => { const handleOpenApp = useLastCallback(() => {
const botId = user?.id; const botId = user?.id;
if (!botId) { if (!botId) {
@ -476,6 +483,12 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
/> />
</ListItem> </ListItem>
)} )}
{canViewSubscribers && (
<ListItem icon="group" narrow multiline ripple onClick={handleOpenSubscribers}>
<div className="title">{lang('ProfileItemSubscribers')}</div>
<span className="subtitle">{lang.number(chat?.membersCount || 0)}</span>
</ListItem>
)}
{botVerification && ( {botVerification && (
<div className={styles.botVerificationSection}> <div className={styles.botVerificationSection}>
<CustomEmoji <CustomEmoji
@ -510,6 +523,7 @@ export default memo(withGlobal<OwnProps>(
const chatInviteLink = chatFullInfo?.inviteLink; const chatInviteLink = chatFullInfo?.inviteLink;
const description = userFullInfo?.bio || chatFullInfo?.about; const description = userFullInfo?.bio || chatFullInfo?.about;
const canViewSubscribers = chat && isChatChannel(chat) && isChatAdmin(chat);
const canInviteUsers = chat && !user && ( const canInviteUsers = chat && !user && (
(!isChatChannel(chat) && !isUserRightBanned(chat, 'inviteUsers')) (!isChatChannel(chat) && !isUserRightBanned(chat, 'inviteUsers'))
|| getHasAdminRight(chat, 'inviteUsers') || getHasAdminRight(chat, 'inviteUsers')
@ -542,6 +556,7 @@ export default memo(withGlobal<OwnProps>(
hasMainMiniApp, hasMainMiniApp,
isBotCanManageEmojiStatus: userFullInfo?.isBotCanManageEmojiStatus, isBotCanManageEmojiStatus: userFullInfo?.isBotCanManageEmojiStatus,
botVerification, botVerification,
canViewSubscribers,
}; };
}, },
)(ChatExtra)); )(ChatExtra));

View File

@ -18,7 +18,8 @@ import { type MediaViewerMedia, MediaViewerOrigin, type ThreadId } from '../../t
import { ANIMATION_END_DELAY } from '../../config'; import { ANIMATION_END_DELAY } from '../../config';
import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { import {
getChatMediaMessageIds, getMessagePaidMedia, isChatAdmin, getMessageContentIds,
getMessagePaidMedia, isChatAdmin,
} from '../../global/helpers'; } from '../../global/helpers';
import { import {
selectChatMessage, selectChatMessage,
@ -156,7 +157,7 @@ const MediaViewer = ({
bestData, bestData,
dimensions, dimensions,
isGif, isGif,
isFromSharedMedia, contentType,
} = useMediaProps({ } = useMediaProps({
media, isAvatar: Boolean(avatarOwner), origin, delay: isGhostAnimation && ANIMATION_DURATION, media, isAvatar: Boolean(avatarOwner), origin, delay: isGhostAnimation && ANIMATION_DURATION,
}); });
@ -173,8 +174,8 @@ const MediaViewer = ({
const messageMediaIds = useMemo(() => { const messageMediaIds = useMemo(() => {
return withDynamicLoading return withDynamicLoading
? collectedMessageIds ? collectedMessageIds
: getChatMediaMessageIds(chatMessages || {}, collectedMessageIds || [], isFromSharedMedia); : getMessageContentIds(chatMessages || {}, collectedMessageIds || [], contentType);
}, [chatMessages, collectedMessageIds, isFromSharedMedia, withDynamicLoading]); }, [chatMessages, collectedMessageIds, contentType, withDynamicLoading]);
if (isOpen && (!prevSenderId || prevSenderId !== senderId || animationKey.current === undefined)) { if (isOpen && (!prevSenderId || prevSenderId !== senderId || animationKey.current === undefined)) {
animationKey.current = isSingle ? 0 : (messageId || mediaIndex); animationKey.current = isSingle ? 0 : (messageId || mediaIndex);
@ -520,7 +521,7 @@ export default memo(withGlobal(
const currentItem = getMediaViewerItem({ const currentItem = getMediaViewerItem({
avatarOwner, standaloneMedia, profilePhotos, mediaIndex, avatarOwner, standaloneMedia, profilePhotos, mediaIndex,
}); });
const viewableMedia = selectViewableMedia(global, currentItem); const viewableMedia = selectViewableMedia(global, origin, currentItem);
return { return {
profilePhotos, profilePhotos,
@ -565,6 +566,11 @@ export default memo(withGlobal(
} }
} }
const currentItem = getMediaViewerItem({
message, standaloneMedia, mediaIndex, sponsoredMessage,
});
const viewableMedia = selectViewableMedia(global, origin, currentItem);
let chatMessages: Record<number, ApiMessage> | undefined; let chatMessages: Record<number, ApiMessage> | undefined;
if (chatId) { if (chatId) {
@ -588,7 +594,8 @@ export default memo(withGlobal(
collectedMessageIds = foundIds; collectedMessageIds = foundIds;
} else if (origin === MediaViewerOrigin.SharedMedia) { } else if (origin === MediaViewerOrigin.SharedMedia) {
const currentSearch = selectCurrentSharedMediaSearch(global); const currentSearch = selectCurrentSharedMediaSearch(global);
const { foundIds } = (currentSearch && currentSearch.resultsByType && currentSearch.resultsByType.media) || {}; const resultsByType = currentSearch?.resultsByType;
const { foundIds } = (viewableMedia?.isGif ? resultsByType?.gif : resultsByType?.media) || {};
collectedMessageIds = foundIds; collectedMessageIds = foundIds;
} else if (isOriginInline || isOriginAlbum) { } else if (isOriginInline || isOriginAlbum) {
const outlyingList = selectOutlyingListByMessageId(global, chatId, threadId, messageId); const outlyingList = selectOutlyingListByMessageId(global, chatId, threadId, messageId);
@ -596,11 +603,6 @@ export default memo(withGlobal(
} }
} }
const currentItem = getMediaViewerItem({
message, standaloneMedia, mediaIndex, sponsoredMessage,
});
const viewableMedia = selectViewableMedia(global, currentItem);
return { return {
chatId, chatId,
threadId, threadId,

View File

@ -413,7 +413,7 @@ export default memo(withGlobal<OwnProps>(
const canDelete = canDeleteMessage || canDeleteAvatar; const canDelete = canDeleteMessage || canDeleteAvatar;
const canUpdate = canUpdateMedia && Boolean(avatarPhoto) && !isCurrentAvatar; const canUpdate = canUpdateMedia && Boolean(avatarPhoto) && !isCurrentAvatar;
const messageListType = currentMessageList?.type; const messageListType = currentMessageList?.type;
const viewableMedia = selectViewableMedia(global, item); const viewableMedia = selectViewableMedia(global, origin, item);
return { return {
activeDownloads, activeDownloads,

View File

@ -257,7 +257,7 @@ export default memo(withGlobal<OwnProps>(
const message = item.type === 'message' ? item.message : undefined; const message = item.type === 'message' ? item.message : undefined;
const sponsoredMessage = item.type === 'sponsoredMessage' ? item.message : undefined; const sponsoredMessage = item.type === 'sponsoredMessage' ? item.message : undefined;
const textMessage = message || sponsoredMessage; const textMessage = message || sponsoredMessage;
const viewableMedia = selectViewableMedia(global, item); const viewableMedia = selectViewableMedia(global, origin, item);
const maxTimestamp = message && selectMessageTimestampableDuration(global, message, true); const maxTimestamp = message && selectMessageTimestampableDuration(global, message, true);

View File

@ -2,7 +2,7 @@ import type {
ApiMessage, ApiPeer, ApiPeerPhotos, ApiSponsoredMessage, ApiMessage, ApiPeer, ApiPeerPhotos, ApiSponsoredMessage,
} from '../../../api/types'; } from '../../../api/types';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
import type { MediaViewerMedia } from '../../../types'; import { type MediaViewerMedia, MediaViewerOrigin } from '../../../types';
import { getMessageContent, isDocumentPhoto, isDocumentVideo } from '../../../global/helpers'; import { getMessageContent, isDocumentPhoto, isDocumentVideo } from '../../../global/helpers';
import { selectWebPageFromMessage } from '../../../global/selectors'; import { selectWebPageFromMessage } from '../../../global/selectors';
@ -28,6 +28,7 @@ export type MediaViewerItem = {
export type ViewableMedia = { export type ViewableMedia = {
media: MediaViewerMedia; media: MediaViewerMedia;
isGif?: boolean;
isSingle?: boolean; isSingle?: boolean;
}; };
@ -77,12 +78,16 @@ export function getMediaViewerItem({
return undefined; return undefined;
} }
export default function selectViewableMedia(global: GlobalState, params?: MediaViewerItem): ViewableMedia | undefined { export default function selectViewableMedia(
global: GlobalState, origin?: MediaViewerOrigin, params?: MediaViewerItem,
): ViewableMedia | undefined {
if (!params) return undefined; if (!params) return undefined;
if (params.type === 'standalone') { if (params.type === 'standalone') {
const media = params.media[params.mediaIndex];
return { return {
media: params.media[params.mediaIndex], media,
isGif: media.mediaType === 'video' && media.isGif,
isSingle: params.media.length === 1, isSingle: params.media.length === 1,
}; };
} }
@ -134,6 +139,7 @@ export default function selectViewableMedia(global: GlobalState, params?: MediaV
const { photo: extendedPhoto, video: extendedVideo } = extendedMedia; const { photo: extendedPhoto, video: extendedVideo } = extendedMedia;
return { return {
media: (extendedPhoto || extendedVideo)!, media: (extendedPhoto || extendedVideo)!,
isGif: extendedVideo?.isGif,
}; };
} }
} }
@ -143,7 +149,8 @@ export default function selectViewableMedia(global: GlobalState, params?: MediaV
if (media) { if (media) {
return { return {
media, media,
isSingle: video?.isGif, isGif: video?.isGif,
isSingle: video?.isGif && origin !== MediaViewerOrigin.SharedMedia,
}; };
} }

View File

@ -1,7 +1,7 @@
import { useMemo } from '../../../lib/teact/teact'; import { useMemo } from '../../../lib/teact/teact';
import type { MediaViewerMedia } from '../../../types'; import type { MediaViewerMedia } from '../../../types';
import { ApiMediaFormat } from '../../../api/types'; import { ApiMediaFormat, type ApiMessageSearchType } from '../../../api/types';
import { MediaViewerOrigin } from '../../../types'; import { MediaViewerOrigin } from '../../../types';
import { import {
@ -46,6 +46,8 @@ export const useMediaProps = ({
const isFromSharedMedia = origin === MediaViewerOrigin.SharedMedia; const isFromSharedMedia = origin === MediaViewerOrigin.SharedMedia;
const isFromSearch = origin === MediaViewerOrigin.SearchResult; const isFromSearch = origin === MediaViewerOrigin.SearchResult;
const contentType: ApiMessageSearchType = isGif ? 'gif' : 'media';
const getMediaOrAvatarHash = useMemo(() => (isFull?: boolean) => { const getMediaOrAvatarHash = useMemo(() => (isFull?: boolean) => {
if (!media) return undefined; if (!media) return undefined;
@ -133,7 +135,7 @@ export const useMediaProps = ({
bestImageData, bestImageData,
bestData, bestData,
dimensions, dimensions,
isFromSharedMedia, contentType,
isVideoAvatar, isVideoAvatar,
isLocal, isLocal,
loadProgress, loadProgress,

View File

@ -1,6 +1,8 @@
@use '../../styles/mixins'; @use '../../styles/mixins';
.Profile { .Profile {
scrollbar-gutter: stable;
overflow-x: hidden; overflow-x: hidden;
overflow-y: scroll; overflow-y: scroll;
display: flex; display: flex;
@ -134,6 +136,7 @@
&.storiesArchive-list, &.storiesArchive-list,
&.stories-list, &.stories-list,
&.media-list, &.media-list,
&.gif-list,
&.previewMedia-list, &.previewMedia-list,
&.gifts-list { &.gifts-list {
display: grid; display: grid;

View File

@ -6,6 +6,7 @@ import type {
ApiChat, ApiChat,
ApiChatMember, ApiChatMember,
ApiMessage, ApiMessage,
ApiProfileTab,
ApiSavedStarGift, ApiSavedStarGift,
ApiStarGiftCollection, ApiStarGiftCollection,
ApiStoryAlbum, ApiStoryAlbum,
@ -29,7 +30,6 @@ import {
getIsDownloading, getIsDownloading,
getIsSavedDialog, getIsSavedDialog,
getMessageDocument, getMessageDocument,
isChatAdmin,
isChatChannel, isChatChannel,
isChatGroup, isChatGroup,
isUserBot, isUserBot,
@ -38,6 +38,7 @@ import {
import { getSavedGiftKey } from '../../global/helpers/stars'; import { getSavedGiftKey } from '../../global/helpers/stars';
import { import {
selectActiveDownloads, selectActiveDownloads,
selectCanUpdateMainTab,
selectChat, selectChat,
selectChatFullInfo, selectChatFullInfo,
selectChatMessages, selectChatMessages,
@ -47,6 +48,7 @@ import {
selectIsRightColumnShown, selectIsRightColumnShown,
selectMonoforumChannel, selectMonoforumChannel,
selectPeerStories, selectPeerStories,
selectPerformanceSettingsValue,
selectSimilarBotsIds, selectSimilarBotsIds,
selectSimilarChannelIds, selectSimilarChannelIds,
selectTabState, selectTabState,
@ -86,6 +88,7 @@ import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
import useSyncEffect from '../../hooks/useSyncEffect'; import useSyncEffect from '../../hooks/useSyncEffect';
import useSyncEffectWithPrevDeps from '../../hooks/useSyncEffectWithPrevDeps.ts';
import useAsyncRendering from './hooks/useAsyncRendering'; import useAsyncRendering from './hooks/useAsyncRendering';
import useProfileState from './hooks/useProfileState'; import useProfileState from './hooks/useProfileState';
import useProfileViewportIds from './hooks/useProfileViewportIds'; import useProfileViewportIds from './hooks/useProfileViewportIds';
@ -112,7 +115,7 @@ import InfiniteScroll from '../ui/InfiniteScroll';
import Link from '../ui/Link'; import Link from '../ui/Link';
import ListItem, { type MenuItemContextAction } from '../ui/ListItem'; import ListItem, { type MenuItemContextAction } from '../ui/ListItem';
import Spinner from '../ui/Spinner'; import Spinner from '../ui/Spinner';
import TabList from '../ui/TabList'; import TabList, { type TabWithProperties } from '../ui/TabList';
import Transition from '../ui/Transition'; import Transition from '../ui/Transition';
import DeleteMemberModal from './DeleteMemberModal'; import DeleteMemberModal from './DeleteMemberModal';
import StarGiftCollectionList from './gifts/StarGiftCollectionList'; import StarGiftCollectionList from './gifts/StarGiftCollectionList';
@ -179,24 +182,40 @@ type StateProps = {
isSavedMessages?: boolean; isSavedMessages?: boolean;
isSynced?: boolean; isSynced?: boolean;
hasAvatar?: boolean; hasAvatar?: boolean;
mainTab?: ApiProfileTab;
canUpdateMainTab?: boolean;
canAutoPlayGifs?: boolean;
}; };
type TabProps = { type LocalTabProps = {
type: ProfileTabType; type: ProfileTabType;
key: RegularLangKey; key: RegularLangKey;
}; };
const TABS: TabProps[] = [ type TabWithPropertiesAndType = TabWithProperties & {
type: ProfileTabType;
};
const TABS: LocalTabProps[] = [
{ type: 'media', key: 'ProfileTabMedia' }, { type: 'media', key: 'ProfileTabMedia' },
{ type: 'documents', key: 'ProfileTabFiles' }, { type: 'documents', key: 'ProfileTabFiles' },
{ type: 'links', key: 'ProfileTabLinks' }, { type: 'links', key: 'ProfileTabLinks' },
{ type: 'audio', key: 'ProfileTabMusic' }, { type: 'audio', key: 'ProfileTabMusic' },
{ type: 'gif', key: 'ProfileTabGifs' },
]; ];
const HIDDEN_RENDER_DELAY = 1000; const HIDDEN_RENDER_DELAY = 1000;
const INTERSECTION_THROTTLE = 500; const INTERSECTION_THROTTLE = 500;
const SHARED_MEDIA_TYPES = new Set<string>(['media', 'documents', 'links', 'audio', 'voice']); const VALID_CHANNEL_MAIN_TAB_TYPES = new Set<StringAutocomplete<ApiProfileTab>>([
'stories', 'gifts', 'media', 'documents', 'audio', 'voice', 'links', 'gif',
]);
const VALID_USER_MAIN_TAB_TYPES = new Set<StringAutocomplete<ApiProfileTab>>([
'stories', 'gifts',
]);
const SHARED_MEDIA_TYPES = new Set<StringAutocomplete<SharedMediaType>>([
'media', 'documents', 'links', 'audio', 'voice', 'gif',
]);
const Profile = ({ const Profile = ({
chatId, chatId,
@ -252,6 +271,9 @@ const Profile = ({
isSavedMessages, isSavedMessages,
isSynced, isSynced,
hasAvatar, hasAvatar,
mainTab,
canUpdateMainTab,
canAutoPlayGifs,
onProfileStateChange, onProfileStateChange,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { const {
@ -276,11 +298,14 @@ const Profile = ({
loadStoryAlbums, loadStoryAlbums,
resetSelectedStoryAlbum, resetSelectedStoryAlbum,
changeProfileTab, changeProfileTab,
setMainProfileTab,
} = getActions(); } = getActions();
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const transitionRef = useRef<HTMLDivElement>(); const transitionRef = useRef<HTMLDivElement>();
const shouldSkipTransitionRef = useRef(false);
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang(); const lang = useLang();
@ -296,8 +321,11 @@ const Profile = ({
const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0); const [restoreContentHeightKey, setRestoreContentHeightKey] = useState(0);
const isUser = isUserId(chatId);
const validMainTabTypes = isUser ? VALID_USER_MAIN_TAB_TYPES : VALID_CHANNEL_MAIN_TAB_TYPES;
const tabs = useMemo(() => { const tabs = useMemo(() => {
const arr: TabProps[] = []; const arr: LocalTabProps[] = [];
if (isGeneralSavedMessages) { if (isGeneralSavedMessages) {
arr.push({ type: 'dialogs', key: 'ProfileTabSavedDialogs' }); arr.push({ type: 'dialogs', key: 'ProfileTabSavedDialogs' });
} }
@ -306,16 +334,16 @@ const Profile = ({
arr.push({ type: 'stories', key: 'ProfileTabStories' }); arr.push({ type: 'stories', key: 'ProfileTabStories' });
} }
if (hasStoriesTab && isOwnProfile) {
arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' });
}
if (hasGiftsTab) { if (hasGiftsTab) {
arr.push({ type: 'gifts', key: 'ProfileTabGifts' }); arr.push({ type: 'gifts', key: 'ProfileTabGifts' });
} }
if (hasStoriesTab && isOwnProfile) {
arr.push({ type: 'storiesArchive', key: 'ProfileTabStoriesArchive' });
}
if (hasMembersTab && !isOwnProfile) { if (hasMembersTab && !isOwnProfile) {
arr.push({ type: 'members', key: isChannel ? 'ProfileTabSubscribers' : 'ProfileTabMembers' }); arr.push({ type: 'members', key: 'ProfileTabMembers' });
} }
if (hasPreviewMediaTab && !isOwnProfile) { if (hasPreviewMediaTab && !isOwnProfile) {
@ -349,13 +377,35 @@ const Profile = ({
arr.push(TABS[0]); arr.push(TABS[0]);
} }
return arr.map((tab) => ({ if (mainTab) {
type: tab.type, const mainTabIndex = arr.findIndex((tab) => tab.type === mainTab);
title: lang(tab.key), if (mainTabIndex !== -1) {
})); const newFirstTab = arr[mainTabIndex];
arr.splice(mainTabIndex, 1);
arr.unshift(newFirstTab);
}
}
return arr.map((tab) => {
const contextActions: MenuItemContextAction[] | undefined = canUpdateMainTab && mainTab !== tab.type
&& validMainTabTypes.has(tab.type) ? [{
title: lang('ProfileMenuSetMainTab'),
icon: 'replace',
handler: () => {
setMainProfileTab({ chatId, tab: tab.type as ApiProfileTab });
},
}] : undefined;
return {
type: tab.type,
title: lang(tab.key),
contextActions,
} satisfies TabWithPropertiesAndType;
});
}, [ }, [
isGeneralSavedMessages, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo, isGeneralSavedMessages, hasStoriesTab, hasGiftsTab, hasMembersTab, hasPreviewMediaTab, isTopicInfo,
hasCommonChatsTab, isChannel, isBot, similarChannels?.length, similarBots?.length, lang, isOwnProfile, hasCommonChatsTab, isChannel, isBot, similarChannels?.length, similarBots?.length, lang, isOwnProfile,
mainTab, chatId, canUpdateMainTab, validMainTabTypes,
]); ]);
const [allowAutoScrollToTabs, startAutoScrollToTabsIfNeeded, stopAutoScrollToTabs] = useFlag(false); const [allowAutoScrollToTabs, startAutoScrollToTabsIfNeeded, stopAutoScrollToTabs] = useFlag(false);
@ -377,6 +427,11 @@ const Profile = ({
setActiveTab(tabs[0].type); // Set default tab setActiveTab(tabs[0].type); // Set default tab
}, [isClosed, profileTab, tabs]); }, [isClosed, profileTab, tabs]);
useEffectWithPrevDeps(([prevMainTab]) => {
if (prevMainTab || !mainTab) return;
setActiveTab(mainTab); // Only focus when loading full info
}, [mainTab]);
const handleSwitchTab = useCallback((index: number) => { const handleSwitchTab = useCallback((index: number) => {
startAutoScrollToTabsIfNeeded(); startAutoScrollToTabsIfNeeded();
setActiveTab(tabs[index].type); setActiveTab(tabs[index].type);
@ -430,6 +485,17 @@ const Profile = ({
return index === -1 ? 0 : index; return index === -1 ? 0 : index;
}, [profileTab, tabs]); }, [profileTab, tabs]);
// Reset skip transition flag from previous render
if (shouldSkipTransitionRef.current) {
shouldSkipTransitionRef.current = false;
}
useSyncEffectWithPrevDeps(([prevProfileTab, prevActiveTabIndex]) => {
if (prevProfileTab === profileTab && prevActiveTabIndex !== activeTabIndex) {
shouldSkipTransitionRef.current = true;
}
}, [profileTab, activeTabIndex]);
const tabType = tabs[activeTabIndex].type; const tabType = tabs[activeTabIndex].type;
const handleLoadCommonChats = useCallback(() => { const handleLoadCommonChats = useCallback(() => {
loadCommonChats({ userId: chatId }); loadCommonChats({ userId: chatId });
@ -498,10 +564,7 @@ const Profile = ({
const shouldRenderProfileInfo = !noProfileInfo && !isSavedMessages; const shouldRenderProfileInfo = !noProfileInfo && !isSavedMessages;
const isFirstTab = (isGeneralSavedMessages && resultType === 'dialogs') const isFirstTab = tabs[0].type === resultType;
|| (hasStoriesTab && resultType === 'stories')
|| resultType === 'members'
|| (!hasMembersTab && resultType === 'media');
const activeKey = tabs.findIndex(({ type }) => type === resultType); const activeKey = tabs.findIndex(({ type }) => type === resultType);
const [isGiftCollectionsShowed, markGiftCollectionsShowed, unmarkGiftCollectionsShowed] = useFlag(false); const [isGiftCollectionsShowed, markGiftCollectionsShowed, unmarkGiftCollectionsShowed] = useFlag(false);
@ -561,7 +624,7 @@ const Profile = ({
handleStopAutoScrollToTabs, handleStopAutoScrollToTabs,
}); });
const { applyTransitionFix, releaseTransitionFix } = useTransitionFixes(containerRef); useTransitionFixes(containerRef);
const [cacheBuster, resetCacheBuster] = useCacheBuster(); const [cacheBuster, resetCacheBuster] = useCacheBuster();
@ -570,11 +633,6 @@ const Profile = ({
throttleMs: INTERSECTION_THROTTLE, throttleMs: INTERSECTION_THROTTLE,
}); });
const handleTransitionStop = useLastCallback(() => {
releaseTransitionFix();
resetCacheBuster();
});
const handleNewMemberDialogOpen = useLastCallback(() => { const handleNewMemberDialogOpen = useLastCallback(() => {
setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.InProgress }); setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.InProgress });
}); });
@ -613,16 +671,6 @@ const Profile = ({
setDeletingUserId(undefined); setDeletingUserId(undefined);
}); });
useEffectWithPrevDeps(([prevHasMemberTabs]) => {
if (prevHasMemberTabs === undefined || activeTabIndex === 0 || prevHasMemberTabs === hasMembersTab) {
return;
}
const newActiveTab = Math.min(activeTabIndex + (hasMembersTab ? 1 : -1), tabs.length - 1);
setActiveTab(tabs[newActiveTab].type);
}, [hasMembersTab, activeTabIndex, tabs]);
const handleResetGiftsFilter = useLastCallback(() => { const handleResetGiftsFilter = useLastCallback(() => {
resetGiftProfileFilter({ peerId: chatId }); resetGiftProfileFilter({ peerId: chatId });
}); });
@ -800,6 +848,9 @@ const Profile = ({
case 'storiesArchive': case 'storiesArchive':
text = oldLang('StoryList.ArchivedEmptyState.Title'); text = oldLang('StoryList.ArchivedEmptyState.Title');
break; break;
case 'gif':
text = oldLang('lng_media_gif_empty');
break;
default: default:
text = oldLang('SharedMedia.EmptyTitle'); text = oldLang('SharedMedia.EmptyTitle');
} }
@ -825,15 +876,16 @@ const Profile = ({
shouldShowContentPanel && 'showContentPanel', shouldShowContentPanel && 'showContentPanel',
noTransition && 'noTransition', noTransition && 'noTransition',
)} )}
dir={oldLang.isRtl && resultType === 'media' ? 'rtl' : undefined} dir={lang.isRtl && (resultType === 'media' || resultType === 'gif') ? 'rtl' : undefined}
teactFastList teactFastList
> >
{resultType === 'media' ? ( {resultType === 'media' || resultType === 'gif' ? (
(viewportIds as number[]).map((id) => messagesById[id] && ( (viewportIds as number[]).map((id) => messagesById[id] && (
<Media <Media
key={id} key={id}
message={messagesById[id]} message={messagesById[id]}
isProtected={isChatProtected || messagesById[id].isProtected} isProtected={isChatProtected || messagesById[id].isProtected}
canAutoPlay={canAutoPlayGifs}
observeIntersection={observeIntersectionForMedia} observeIntersection={observeIntersectionForMedia}
onClick={handleSelectMedia} onClick={handleSelectMedia}
/> />
@ -988,17 +1040,10 @@ const Profile = ({
onClick={() => openChat({ id: userId })} onClick={() => openChat({ id: userId })}
> >
{isUserId(userId) ? ( <PrivateChatInfo
<PrivateChatInfo userId={userId}
userId={userId} avatarSize="medium"
avatarSize="medium" />
/>
) : (
<GroupChatInfo
chatId={userId}
avatarSize="medium"
/>
)}
</ListItem> </ListItem>
))} ))}
{!isCurrentUserPremium && ( {!isCurrentUserPremium && (
@ -1081,7 +1126,7 @@ const Profile = ({
<Transition <Transition
className={`${resultType}-list`} className={`${resultType}-list`}
activeKey={contentTransitionKey} activeKey={contentTransitionKey}
name={resolveTransitionName('slideOptimized', animationLevel, undefined, oldLang.isRtl)} name={resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
shouldCleanup shouldCleanup
shouldRestoreHeight shouldRestoreHeight
restoreHeightKey={restoreContentHeightKey} restoreHeightKey={restoreContentHeightKey}
@ -1149,13 +1194,13 @@ const Profile = ({
> >
<Transition <Transition
ref={transitionRef} ref={transitionRef}
name={resolveTransitionName('slideOptimized', animationLevel, undefined, oldLang.isRtl)} name={shouldSkipTransitionRef.current ? 'none'
: resolveTransitionName('slideOptimized', animationLevel, undefined, lang.isRtl)}
activeKey={activeKey} activeKey={activeKey}
renderCount={tabs.length} renderCount={tabs.length}
shouldRestoreHeight shouldRestoreHeight
className="shared-media-transition" className="shared-media-transition"
onStart={applyTransitionFix} onStop={resetCacheBuster}
onStop={handleTransitionStop}
restoreHeightKey={shouldUseTransitionForContent ? restoreContentHeightKey : undefined} restoreHeightKey={shouldUseTransitionForContent ? restoreContentHeightKey : undefined}
contentSelector={shouldUseTransitionForContent contentSelector={shouldUseTransitionForContent
? '.Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .content' ? '.Transition > .Transition_slide-active > .Transition > .Transition_slide-active > .content'
@ -1218,8 +1263,7 @@ export default memo(withGlobal<OwnProps>(
const isGroup = chat && isChatGroup(chat); const isGroup = chat && isChatGroup(chat);
const isChannel = chat && isChatChannel(chat); const isChannel = chat && isChatChannel(chat);
const isBot = user && isUserBot(user); const isBot = user && isUserBot(user);
const hasMembersTab = !isTopicInfo && !isSavedDialog const hasMembersTab = !isTopicInfo && !isSavedDialog && isGroup && !chat?.isMonoforum;
&& (isGroup || (isChannel && isChatAdmin(chat))) && !chat?.isMonoforum;
const members = chatFullInfo?.members; const members = chatFullInfo?.members;
const adminMembersById = chatFullInfo?.adminMembersById; const adminMembersById = chatFullInfo?.adminMembersById;
const areMembersHidden = hasMembersTab && chat const areMembersHidden = hasMembersTab && chat
@ -1265,6 +1309,8 @@ export default memo(withGlobal<OwnProps>(
const isRestricted = chat && selectIsChatRestricted(global, chat.id); const isRestricted = chat && selectIsChatRestricted(global, chat.id);
const hasAvatar = Boolean(peer?.avatarPhotoId); const hasAvatar = Boolean(peer?.avatarPhotoId);
const canAutoPlayGifs = selectPerformanceSettingsValue(global, 'autoplayGifs');
return { return {
theme: selectTheme(global), theme: selectTheme(global),
isChannel, isChannel,
@ -1315,6 +1361,9 @@ export default memo(withGlobal<OwnProps>(
commonChatIds: commonChats?.ids, commonChatIds: commonChats?.ids,
monoforumChannel, monoforumChannel,
hasAvatar, hasAvatar,
mainTab: peerFullInfo?.mainTab,
canUpdateMainTab: selectCanUpdateMainTab(global, chatId),
canAutoPlayGifs,
}; };
}, },
)(Profile)); )(Profile));

View File

@ -89,6 +89,10 @@ export default function useProfileViewportIds({
'media', resultType, searchMessages, chatMessages, foundIds, threadId, 'media', resultType, searchMessages, chatMessages, foundIds, threadId,
); );
const [gifViewportIds, getMoreGifs, noProfileInfoForGifs] = useInfiniteScrollForSharedMedia(
'gif', resultType, searchMessages, chatMessages, foundIds, threadId,
);
const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia( const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia(
'documents', resultType, searchMessages, chatMessages, foundIds, threadId, 'documents', resultType, searchMessages, chatMessages, foundIds, threadId,
); );
@ -153,6 +157,11 @@ export default function useProfileViewportIds({
getMore = getMoreMedia; getMore = getMoreMedia;
noProfileInfo = noProfileInfoForMedia; noProfileInfo = noProfileInfoForMedia;
break; break;
case 'gif':
viewportIds = gifViewportIds;
getMore = getMoreGifs;
noProfileInfo = noProfileInfoForGifs;
break;
case 'documents': case 'documents':
viewportIds = documentViewportIds; viewportIds = documentViewportIds;
getMore = getMoreDocuments; getMore = getMoreDocuments;

View File

@ -1,9 +1,7 @@
import type { ElementRef } from '../../../lib/teact/teact'; import type { ElementRef } from '../../../lib/teact/teact';
import { useEffect } from '../../../lib/teact/teact'; import { useEffect } from '../../../lib/teact/teact';
import { requestMeasure, requestMutation } from '../../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import useLastCallback from '../../../hooks/useLastCallback';
export default function useTransitionFixes( export default function useTransitionFixes(
containerRef: ElementRef<HTMLDivElement>, containerRef: ElementRef<HTMLDivElement>,
@ -32,28 +30,4 @@ export default function useTransitionFixes(
window.removeEventListener('resize', setMinHeight, false); window.removeEventListener('resize', setMinHeight, false);
}; };
}, [containerRef, transitionElSelector]); }, [containerRef, transitionElSelector]);
// Workaround for scrollable content flickering during animation.
const applyTransitionFix = useLastCallback(() => {
// This callback is called from `Transition.onStart` which is "mutate" phase
requestMeasure(() => {
const container = containerRef.current!;
if (container.style.overflowY === 'hidden') return;
const scrollBarWidth = container.offsetWidth - container.clientWidth;
requestMutation(() => {
container.style.overflowY = 'hidden';
container.style.paddingRight = `${scrollBarWidth}px`;
});
});
});
const releaseTransitionFix = useLastCallback(() => {
const container = containerRef.current!;
container.style.overflowY = 'scroll';
container.style.paddingRight = '0';
});
return { applyTransitionFix, releaseTransitionFix };
} }

View File

@ -1,5 +1,4 @@
import type { FC, TeactNode } from '../../lib/teact/teact'; import type { TeactNode } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import { useEffect, useLayoutEffect, useRef } from '../../lib/teact/teact'; import { useEffect, useLayoutEffect, useRef } from '../../lib/teact/teact';
import type { MenuItemContextAction } from './ListItem'; import type { MenuItemContextAction } from './ListItem';
@ -40,7 +39,7 @@ const classNames = {
badgeActive: 'Tab__badge--active', badgeActive: 'Tab__badge--active',
}; };
const Tab: FC<OwnProps> = ({ const Tab = ({
className, className,
title, title,
isActive, isActive,
@ -48,11 +47,11 @@ const Tab: FC<OwnProps> = ({
badgeCount, badgeCount,
isBadgeActive, isBadgeActive,
previousActiveTab, previousActiveTab,
onClick,
clickArg,
contextActions, contextActions,
contextRootElementSelector, contextRootElementSelector,
}) => { clickArg,
onClick,
}: OwnProps) => {
const tabRef = useRef<HTMLDivElement>(); const tabRef = useRef<HTMLDivElement>();
useLayoutEffect(() => { useLayoutEffect(() => {

View File

@ -1,4 +1,4 @@
import type { FC, TeactNode } from '../../lib/teact/teact'; import type { TeactNode } from '../../lib/teact/teact';
import { memo, useEffect, useRef } from '../../lib/teact/teact'; import { memo, useEffect, useRef } from '../../lib/teact/teact';
import type { MenuItemContextAction } from './ListItem'; import type { MenuItemContextAction } from './ListItem';
@ -37,10 +37,14 @@ const TAB_SCROLL_THRESHOLD_PX = 16;
// Should match duration from `--slide-transition` CSS variable // Should match duration from `--slide-transition` CSS variable
const SCROLL_DURATION = IS_IOS ? 450 : IS_ANDROID ? 400 : 300; const SCROLL_DURATION = IS_IOS ? 450 : IS_ANDROID ? 400 : 300;
const TabList: FC<OwnProps> = ({ const TabList = ({
tabs, activeTab, onSwitchTab, tabs,
contextRootElementSelector, className, tabClassName, activeTab,
}) => { className,
tabClassName,
contextRootElementSelector,
onSwitchTab,
}: OwnProps) => {
const containerRef = useRef<HTMLDivElement>(); const containerRef = useRef<HTMLDivElement>();
const previousActiveTab = usePreviousDeprecated(activeTab); const previousActiveTab = usePreviousDeprecated(activeTab);

View File

@ -118,6 +118,7 @@ import {
selectIsCurrentUserFrozen, selectIsCurrentUserFrozen,
selectLastServiceNotification, selectLastServiceNotification,
selectPeer, selectPeer,
selectPeerFullInfo,
selectSimilarChannelIds, selectSimilarChannelIds,
selectStickerSet, selectStickerSet,
selectSupportChat, selectSupportChat,
@ -3061,6 +3062,32 @@ addActionHandler('toggleAutoTranslation', async (global, actions, payload): Prom
setGlobal(global); setGlobal(global);
}); });
addActionHandler('setMainProfileTab', async (global, actions, payload): Promise<void> => {
const { chatId, tab } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
const peerFullInfo = selectPeerFullInfo(global, chatId);
const oldMainTab = peerFullInfo?.mainTab;
if (oldMainTab === tab) return;
global = updatePeerFullInfo(global, chatId, { mainTab: tab });
setGlobal(global);
let result;
if (chatId === global.currentUserId) {
result = await callApi('setAccountMainProfileTab', { tab });
} else {
result = await callApi('setChannelMainProfileTab', { chat, tab });
}
if (!result) {
global = getGlobal();
global = updatePeerFullInfo(global, chatId, { mainTab: oldMainTab });
setGlobal(global);
}
});
addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Promise<void> => { addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Promise<void> => {
const { slug, tabId = getCurrentTabId() } = payload; const { slug, tabId = getCurrentTabId() } = payload;
const result = await callApi('resolveBusinessChatLink', { slug }); const result = await callApi('resolveBusinessChatLink', { slug });

View File

@ -12,7 +12,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { buildCollectionByKey, isInsideSortedArrayRange } from '../../../util/iteratees'; import { buildCollectionByKey, isInsideSortedArrayRange } from '../../../util/iteratees';
import { getSearchResultKey } from '../../../util/keys/searchResultKey'; import { getSearchResultKey } from '../../../util/keys/searchResultKey';
import { callApi } from '../../../api/gramjs'; import { callApi } from '../../../api/gramjs';
import { getChatMediaMessageIds, getIsSavedDialog, isSameReaction } from '../../helpers'; import { getIsSavedDialog, getMessageContentIds, isSameReaction } from '../../helpers';
import { import {
addActionHandler, getGlobal, setGlobal, addActionHandler, getGlobal, setGlobal,
} from '../../index'; } from '../../index';
@ -479,7 +479,7 @@ async function searchChatMedia<T extends GlobalState>(
const loadingState = calcLoadingState(direction, limit, newFoundIds.length, currentSegment); const loadingState = calcLoadingState(direction, limit, newFoundIds.length, currentSegment);
const filteredIds = getChatMediaMessageIds(byId, newFoundIds, false); const filteredIds = getMessageContentIds(byId, newFoundIds, 'inlineMedia');
currentSegment = mergeWithChatMediaSearchSegment( currentSegment = mergeWithChatMediaSearchSegment(
filteredIds, filteredIds,
loadingState, loadingState,

View File

@ -429,12 +429,6 @@ export function hasMediaLocalBlobUrl(media: ApiPhoto | ApiVideo | ApiDocument) {
return false; return false;
} }
export function getChatMediaMessageIds(
messages: Record<number, ApiMessage>, listedIds: number[], isFromSharedMedia = false,
) {
return getMessageContentIds(messages, listedIds, isFromSharedMedia ? 'media' : 'inlineMedia');
}
export function getPhotoFullDimensions(photo: Pick<ApiPhoto, 'sizes' | 'thumbnail'>): ApiDimensions | undefined { export function getPhotoFullDimensions(photo: Pick<ApiPhoto, 'sizes' | 'thumbnail'>): ApiDimensions | undefined {
return ( return (
photo.sizes.find((size) => size.type === 'w') photo.sizes.find((size) => size.type === 'w')
@ -488,6 +482,13 @@ export function getMessageContentIds(
validator = getMessageDocument; validator = getMessageDocument;
break; break;
case 'gif':
validator = (message: ApiMessage) => {
const video = getMessageVideo(message);
return video?.isGif;
};
break;
case 'links': case 'links':
validator = (message: ApiMessage) => getMessageWebPage(message) || matchLinkInMessageText(message); validator = (message: ApiMessage) => getMessageWebPage(message) || matchLinkInMessageText(message);
break; break;

View File

@ -4,7 +4,7 @@ import type { GlobalState, TabArgs } from '../types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config'; import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
import { isUserId } from '../../util/entities/ids'; import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getCurrentTabId } from '../../util/establishMultitabRole';
import { isChatAdmin, isDeletedUser } from '../helpers'; import { getHasAdminRight, isChatAdmin, isChatChannel, isDeletedUser } from '../helpers';
import { selectChat, selectChatFullInfo } from './chats'; import { selectChat, selectChatFullInfo } from './chats';
import { type ProfileCollectionKey } from './payments'; import { type ProfileCollectionKey } from './payments';
import { selectTabState } from './tabs'; import { selectTabState } from './tabs';
@ -74,3 +74,12 @@ export function selectPeerHasProfileBackground<T extends GlobalState>(global: T,
const peer = selectPeer(global, peerId); const peer = selectPeer(global, peerId);
return Boolean(peer?.profileColor || peer?.emojiStatus?.type === 'collectible'); return Boolean(peer?.profileColor || peer?.emojiStatus?.type === 'collectible');
} }
export function selectCanUpdateMainTab<T extends GlobalState>(global: T, peerId: string) {
if (global.currentUserId === peerId) {
return true;
}
const chat = selectChat(global, peerId);
return Boolean(chat && isChatChannel(chat) && getHasAdminRight(chat, 'postMessages'));
}

View File

@ -37,6 +37,7 @@ import type {
ApiPreparedInlineMessage, ApiPreparedInlineMessage,
ApiPrivacyKey, ApiPrivacyKey,
ApiPrivacySettings, ApiPrivacySettings,
ApiProfileTab,
ApiReaction, ApiReaction,
ApiReactionWithPaid, ApiReactionWithPaid,
ApiReportReason, ApiReportReason,
@ -1142,6 +1143,10 @@ export interface ActionPayloads {
chatId: string; chatId: string;
isEnabled: boolean; isEnabled: boolean;
} & WithTabId; } & WithTabId;
setMainProfileTab: {
chatId: string;
tab: ApiProfileTab;
};
updateChat: { updateChat: {
chatId: string; chatId: string;

View File

@ -10,6 +10,7 @@ import type {
ApiCheckedGiftCode, ApiCheckedGiftCode,
ApiCollectibleInfo, ApiCollectibleInfo,
ApiContact, ApiContact,
ApiEmojiStatusCollectible,
ApiError, ApiError,
ApiFormattedText, ApiFormattedText,
ApiGeoPoint, ApiGeoPoint,
@ -54,7 +55,6 @@ import type {
ApiUser, ApiUser,
ApiVideo, ApiVideo,
} from '../../api/types'; } from '../../api/types';
import type { ApiEmojiStatusCollectible } from '../../api/types/users';
import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer'; import type { FoldersActions } from '../../hooks/reducers/useFoldersReducer';
import type { ReducerAction } from '../../hooks/useReducer'; import type { ReducerAction } from '../../hooks/useReducer';
import type { import type {

View File

@ -9,7 +9,7 @@ import { getGlobal } from '../../global';
import type { AnimationLevel } from '../../types'; import type { AnimationLevel } from '../../types';
import type { VTTypes } from '../../util/animations/viewTransitionTypes'; import type { VTTypes } from '../../util/animations/viewTransitionTypes';
import { VT_CLASS_NAME, VT_TYPE_CLASS_PREFIX } from '../../config'; import { ANIMATION_LEVEL_MED, VT_CLASS_NAME, VT_TYPE_CLASS_PREFIX } from '../../config';
import { requestMutation, requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { requestMutation, requestNextMutation } from '../../lib/fasterdom/fasterdom';
import { selectSharedSettings } from '../../global/selectors/sharedState'; import { selectSharedSettings } from '../../global/selectors/sharedState';
import { IS_VIEW_TRANSITION_SUPPORTED } from '../../util/browser/windowEnvironment'; import { IS_VIEW_TRANSITION_SUPPORTED } from '../../util/browser/windowEnvironment';
@ -98,7 +98,7 @@ export function useViewTransition(): ViewTransitionController {
function startViewTransition( function startViewTransition(
types: VTTypes, types: VTTypes,
updateCallback?: TransitionFunction, updateCallback?: TransitionFunction,
minimumAnimationLevel: AnimationLevel = 1, minimumAnimationLevel: AnimationLevel = ANIMATION_LEVEL_MED,
): PromiseLike<void> | void { ): PromiseLike<void> | void {
const global = getGlobal(); const global = getGlobal();
const { animationLevel } = selectSharedSettings(global); const { animationLevel } = selectSharedSettings(global);

View File

@ -1537,6 +1537,7 @@ account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses; account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue; account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue;
account.toggleNoPaidMessagesException#fe2eda76 flags:# refund_charged:flags.0?true require_payment:flags.2?true parent_peer:flags.1?InputPeer user_id:InputUser = Bool; account.toggleNoPaidMessagesException#fe2eda76 flags:# refund_charged:flags.0?true require_payment:flags.2?true parent_peer:flags.1?InputPeer user_id:InputUser = Bool;
account.setMainProfileTab#5dee78b0 tab:ProfileTab = Bool;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.getFullUser#b60f5918 id:InputUser = users.UserFull;
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
@ -1770,6 +1771,7 @@ channels.searchPosts#f2c4f24d flags:# hashtag:flags.0?string query:flags.1?strin
channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates; channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates;
channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates; channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates;
channels.checkSearchPostsFlood#22567115 flags:# query:flags.0?string = SearchPostsFlood; channels.checkSearchPostsFlood#22567115 flags:# query:flags.0?string = SearchPostsFlood;
channels.setMainProfileTab#3583fcb1 channel:InputChannel tab:ProfileTab = Bool;
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool; bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
bots.canSendMessage#1359f4e6 bot:InputUser = Bool; bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
bots.allowSendMessage#f132e3ef bot:InputUser = Updates; bots.allowSendMessage#f132e3ef bot:InputUser = Updates;

View File

@ -66,6 +66,7 @@
"account.getPaidMessagesRevenue", "account.getPaidMessagesRevenue",
"account.getAccountTTL", "account.getAccountTTL",
"account.setAccountTTL", "account.setAccountTTL",
"account.setMainProfileTab",
"users.getUsers", "users.getUsers",
"users.getFullUser", "users.getFullUser",
"contacts.getContacts", "contacts.getContacts",
@ -288,6 +289,7 @@
"channels.updatePaidMessagesPrice", "channels.updatePaidMessagesPrice",
"channels.checkSearchPostsFlood", "channels.checkSearchPostsFlood",
"channels.toggleAutotranslation", "channels.toggleAutotranslation",
"channels.setMainProfileTab",
"bots.getBotRecommendations", "bots.getBotRecommendations",
"bots.canSendMessage", "bots.canSendMessage",
"bots.allowSendMessage", "bots.allowSendMessage",

Binary file not shown.

Binary file not shown.

View File

@ -394,13 +394,14 @@ export type ProfileTabType =
| 'links' | 'links'
| 'audio' | 'audio'
| 'voice' | 'voice'
| 'gif'
| 'stories' | 'stories'
| 'storiesArchive' | 'storiesArchive'
| 'similarChannels' | 'similarChannels'
| 'similarBots' | 'similarBots'
| 'dialogs' | 'dialogs'
| 'gifts'; | 'gifts';
export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice'; export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice' | 'gif';
export type MiddleSearchType = 'chat' | 'myChats' | 'channels'; export type MiddleSearchType = 'chat' | 'myChats' | 'channels';
export type MiddleSearchParams = { export type MiddleSearchParams = {
requestedQuery?: string; requestedQuery?: string;

View File

@ -1222,6 +1222,8 @@ export interface LangPair {
'ProfileBotOpenAppInfoLink': undefined; 'ProfileBotOpenAppInfoLink': undefined;
'ProfileBirthday': undefined; 'ProfileBirthday': undefined;
'ProfileBirthdayToday': undefined; 'ProfileBirthdayToday': undefined;
'ProfileMenuSetMainTab': undefined;
'ProfileItemSubscribers': undefined;
'MonetizationInfoTONTitle': undefined; 'MonetizationInfoTONTitle': undefined;
'AriaSearchOlderResult': undefined; 'AriaSearchOlderResult': undefined;
'AriaSearchNewerResult': undefined; 'AriaSearchNewerResult': undefined;
@ -1395,6 +1397,7 @@ export interface LangPair {
'ProfileTabLinks': undefined; 'ProfileTabLinks': undefined;
'ProfileTabMusic': undefined; 'ProfileTabMusic': undefined;
'ProfileTabVoice': undefined; 'ProfileTabVoice': undefined;
'ProfileTabGifs': undefined;
'ProfileTabSharedGroups': undefined; 'ProfileTabSharedGroups': undefined;
'ProfileTabSimilarChannels': undefined; 'ProfileTabSimilarChannels': undefined;
'ProfileTabSimilarBots': undefined; 'ProfileTabSimilarBots': undefined;