Profile Info: Support rating (#6140)

Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
Alexander Zinchuk 2025-09-09 20:26:05 +02:00
parent 201c510b11
commit 794e442d8b
44 changed files with 1226 additions and 152 deletions

View File

@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
import type {
ApiBirthday,
ApiPeerSettings,
ApiStarsRating,
ApiUser,
ApiUserFullInfo,
ApiUserStatus,
@ -27,6 +28,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts,
starsRating, starsMyPendingRating, starsMyPendingRatingDate,
},
users,
} = mtpUserFull;
@ -57,6 +59,9 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
botVerification: botVerification && buildApiBotVerification(botVerification),
areAdsEnabled: sponsoredEnabled,
starGiftCount: stargiftsCount,
starsRating: starsRating && buildApiStarsRating(starsRating),
starsMyPendingRating: starsMyPendingRating && buildApiStarsRating(starsMyPendingRating),
starsMyPendingRatingDate,
isBotCanManageEmojiStatus: botCanManageEmojiStatus,
hasScheduledMessages: hasScheduled,
paidMessagesStars: sendPaidMessagesStars?.toJSNumber(),
@ -182,3 +187,12 @@ export function buildApiUserStatuses(mtpUsers: GramJs.TypeUser[]) {
export function buildApiBirthday(birthday: GramJs.TypeBirthday): ApiBirthday {
return omitVirtualClassFields(birthday);
}
export function buildApiStarsRating(starsRating: GramJs.StarsRating): ApiStarsRating {
return {
level: starsRating.level,
currentLevelStars: starsRating.currentLevelStars.toJSNumber(),
stars: starsRating.stars.toJSNumber(),
nextLevelStars: starsRating.nextLevelStars?.toJSNumber(),
};
}

View File

@ -281,3 +281,10 @@ export interface ApiDisallowedGiftsSettings {
shouldDisallowUniqueStarGifts?: true;
shouldDisallowPremiumGifts?: true;
}
export interface ApiStarsRating {
level: number;
currentLevelStars: number;
stars: number;
nextLevelStars?: number;
}

View File

@ -4,7 +4,7 @@ import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from
import type { ApiPeerColor, ApiPeerSettings } from './chats';
import type { ApiDocument, ApiPhoto } from './messages';
import type { ApiBotVerification } from './misc';
import type { ApiSavedStarGift } from './stars';
import type { ApiSavedStarGift, ApiStarsRating } from './stars';
export interface ApiUser {
id: string;
@ -65,6 +65,9 @@ export interface ApiUserFullInfo {
businessWorkHours?: ApiBusinessWorkHours;
businessIntro?: ApiBusinessIntro;
starGiftCount?: number;
starsRating?: ApiStarsRating;
starsMyPendingRating?: ApiStarsRating;
starsMyPendingRatingDate?: number;
isBotCanManageEmojiStatus?: boolean;
isBotAccessEmojiGranted?: boolean;
hasScheduledMessages?: boolean;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M48.512 13a10 10 0 0 1 9.973 9.292c.731 10.345.201 18.298-1.69 23.973-2.526 7.577-14.793 15.84-20.846 15.734-5.774-.1-18.458-8.877-20.744-15.734-1.891-5.675-2.421-13.628-1.69-23.975A10 10 0 0 1 23.49 13z"/></svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><g ><path d="M49 11c6.627 0 12 5.373 12 12v23.843a9 9 0 0 1-5.13 8.126l-16.86 8.028a7 7 0 0 1-6.02 0L16.13 54.97A9 9 0 0 1 11 46.843V23c0-6.627 5.373-12 12-12z"/><path d="M23 15h26a8 8 0 0 1 8 8v23.843a5 5 0 0 1-2.85 4.514l-16.86 8.029a3 3 0 0 1-2.58 0l-16.86-8.029A5 5 0 0 1 15 46.843V23a8 8 0 0 1 8-8"/></g></svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M44 11c9.389 0 17 7.611 17 17v16c0 9.389-7.611 17-17 17H28c-9.389 0-17-7.611-17-17V28c0-9.389 7.611-17 17-17z"/></svg>

After

Width:  |  Height:  |  Size: 210 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M60.804 13.571a6 6 0 0 1 .196 1.52V50c0 6.075-4.925 11-11 11H22c-6.075 0-11-4.925-11-11V15.091a6 6 0 0 1 7.52-5.804L36 13.864l17.48-4.577a6 6 0 0 1 7.324 4.284"/></svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M41.006 61.833Q49.502 56.412 55.5 46q5.314-9.225 7.343-19.241l.157-.803a9.45 9.45 0 0 0-6.862-10.882Q46.425 12.5 35.854 12.5q-10.236 0-19.531 2.413l-.883.236a9.45 9.45 0 0 0-6.868 10.389Q10.075 36.583 15.5 46q5.902 10.245 15.616 15.997a9.44 9.44 0 0 0 9.615.006z"/></svg>

After

Width:  |  Height:  |  Size: 364 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M60.804 13.571a6 6 0 0 1 .196 1.52v32.183a9 9 0 0 1-5.29 8.2l-16.825 7.611a7 7 0 0 1-5.77 0l-16.825-7.61a9 9 0 0 1-5.29-8.2V15.09a6 6 0 0 1 7.52-5.804L36 13.865l17.48-4.578a6 6 0 0 1 7.324 4.284"/></svg>

After

Width:  |  Height:  |  Size: 296 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M47.199 13.639 58.361 24.8c6.185 6.185 6.185 16.213 0 22.398L47.2 58.361c-6.185 6.185-16.213 6.185-22.398 0L13.639 47.2c-6.185-6.185-6.185-16.213 0-22.398L24.8 13.639c6.185-6.185 16.213-6.185 22.398 0"/></svg>

After

Width:  |  Height:  |  Size: 302 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m39.8 7.94 8.307 6.798 9.369-1.672a6 6 0 0 1 6.918 4.631l.043.22a6 6 0 0 1 .002 2.103l-5.33 30.074A12 12 0 0 1 47.293 60H24.707a12 12 0 0 1-11.816-9.906L7.561 20.02a6 6 0 0 1 6.963-6.954l9.368 1.672 8.309-6.798a6 6 0 0 1 7.598 0"/></svg>

After

Width:  |  Height:  |  Size: 330 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M46.391 10.5a9.03 9.03 0 0 1 8.45 5.752L58.747 26.3a9 9 0 0 1 .16.445l3.64 11.046A9 9 0 0 1 59.5 47.73l-9.307 7.199-8.345 6.615a9.046 9.046 0 0 1-10.971.202l-8.579-6.301-9.548-6.867a9 9 0 0 1-3.392-9.827l3.29-11.291.06-.19 3.568-10.3a9.03 9.03 0 0 1 8.228-6.063l10.88-.368.122-.003z"/></svg>

After

Width:  |  Height:  |  Size: 384 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m39.8 7.94 8.307 6.798 9.344-1.668a6 6 0 0 1 6.918 4.632l.043.22a6 6 0 0 1-.007 2.15L59.19 48.154a12 12 0 0 1-6.788 8.713L39.34 62.867a8 8 0 0 1-6.68 0l-13.062-6a12 12 0 0 1-6.788-8.714l-5.215-28.08a6 6 0 0 1 6.954-7.003l9.343 1.668 8.309-6.798a6 6 0 0 1 7.598 0"/></svg>

After

Width:  |  Height:  |  Size: 364 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m40.7 10.268 15.122 8.792a9.42 9.42 0 0 1 4.678 8.148v17.584a9.42 9.42 0 0 1-4.678 8.148l-15.123 8.792a9.35 9.35 0 0 1-9.398 0L16.178 52.94a9.42 9.42 0 0 1-4.678-8.148V27.208a9.42 9.42 0 0 1 4.678-8.148l15.123-8.792a9.35 9.35 0 0 1 9.398 0"/></svg>

After

Width:  |  Height:  |  Size: 341 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m42.967 9.658 5.377 5.327c.145.143.326.247.526.3l7.335 1.96c4.884 1.306 7.792 6.307 6.482 11.182l-1.958 7.288c-.05.187-.05.383 0 .57l1.958 7.288c1.31 4.875-1.598 9.876-6.482 11.181l-7.335 1.96c-.2.054-.381.158-.526.301l-5.377 5.327c-3.577 3.544-9.357 3.544-12.934 0l-5.377-5.327a1.2 1.2 0 0 0-.526-.3l-7.335-1.96c-4.884-1.306-7.792-6.307-6.482-11.182l1.958-7.288c.05-.187.05-.383 0-.57l-1.958-7.288c-1.31-4.875 1.598-9.876 6.482-11.181l7.335-1.96c.2-.054.381-.158.526-.301l5.377-5.327c3.577-3.544 9.357-3.544 12.934 0"/></svg>

After

Width:  |  Height:  |  Size: 619 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m28.489 10 15.23.078a8.86 8.86 0 0 1 6.955 3.445l9.437 12.182a9.06 9.06 0 0 1 1.66 7.562L58.308 48.38a8.96 8.96 0 0 1-4.817 6.073l-13.755 6.664a8.8 8.8 0 0 1-7.766-.04l-13.69-6.803a8.97 8.97 0 0 1-4.755-6.12L10.21 33.007a9.05 9.05 0 0 1 1.734-7.546L21.5 13.375A8.86 8.86 0 0 1 28.49 10"/></svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m53.1 13.65 1.873 5.163c.106.292.3.534.547.69l4.601 2.89c4.31 2.708 5.66 8.401 3.055 12.785l-2.798 4.709c-.16.27-.229.591-.191.91l.648 5.464c.603 5.086-2.952 9.74-8.01 10.41l-5.361.709c-.273.036-.53.166-.732.375l-3.793 3.922a9.215 9.215 0 0 1-13.113.165l-3.888-3.825a1.24 1.24 0 0 0-.739-.355l-5.377-.573c-5.074-.54-8.744-5.106-8.266-10.206l.514-5.479a1.45 1.45 0 0 0-.212-.904l-2.913-4.636c-2.712-4.317-1.501-10.042 2.738-12.857L16.212 20c.244-.162.433-.41.532-.705l1.746-5.208C20.124 9.21 25.357 6.563 30.215 8.2l5.134 1.729c.258.087.536.083.793-.01l5.09-1.86c4.816-1.758 10.115.757 11.868 5.592"/></svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M44.015 11a10 10 0 0 1 7.07 2.929l6.986 6.985A10 10 0 0 1 61 27.985v16.03a10 10 0 0 1-2.929 7.07l-6.985 6.986A10 10 0 0 1 44.015 61h-16.03a10 10 0 0 1-7.07-2.929l-6.986-6.985A10 10 0 0 1 11 44.015v-16.03a10 10 0 0 1 2.929-7.07l6.985-6.986A10 10 0 0 1 27.985 11z"/></svg>

After

Width:  |  Height:  |  Size: 363 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m42.992 9.395 3.545 3.277c.155.144.35.235.56.262l4.79.609a9.014 9.014 0 0 1 7.87 8.588l.189 4.824c.008.211.082.415.211.582l2.956 3.817a9.014 9.014 0 0 1-.508 11.638l-3.277 3.545a1 1 0 0 0-.262.56l-.609 4.79a9.014 9.014 0 0 1-8.588 7.87l-4.824.189a1 1 0 0 0-.582.211l-3.817 2.956a9.014 9.014 0 0 1-11.638-.508l-3.545-3.277a1 1 0 0 0-.56-.262l-4.79-.609a9.014 9.014 0 0 1-7.87-8.588l-.189-4.824a1 1 0 0 0-.211-.582l-2.956-3.817a9.014 9.014 0 0 1 .508-11.638l3.277-3.545c.144-.155.235-.35.262-.56l.609-4.79a9.014 9.014 0 0 1 8.588-7.87l4.824-.189c.211-.008.415-.082.582-.211l3.817-2.956a9.014 9.014 0 0 1 11.638.508"/></svg>

After

Width:  |  Height:  |  Size: 714 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M36 11c13.807 0 25 11.193 25 25S49.807 61 36 61 11 49.807 11 36s11.193-25 25-25"/></svg>

After

Width:  |  Height:  |  Size: 181 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m39.395 9.26 17.933 8.438a9.025 9.025 0 0 1 5.105 9.264l-2.336 19.111a9.03 9.03 0 0 1-3.503 6.098L41.421 63.676a8.97 8.97 0 0 1-10.842 0L15.406 52.171a9.03 9.03 0 0 1-3.503-6.098L9.567 26.962a9.025 9.025 0 0 1 5.105-9.264L32.605 9.26a7.97 7.97 0 0 1 6.79 0"/></svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M31 25.4 18.5 3.7c-1.1-1.9-3.8-1.9-4.9 0L1 25.4c-1.1 1.9.3 4.3 2.5 4.3h25.1c2.2 0 3.5-2.4 2.4-4.3"/></svg>

After

Width:  |  Height:  |  Size: 196 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M19.7 1.4C13.8 0 8 2.1 4.4 6.4V3.9c0-.6-.5-1.2-1.2-1.2-.6 0-1.2.5-1.2 1.2v5.4c0 .6.5 1.2 1.2 1.2h5.4c.6 0 1.2-.5 1.2-1.2 0-.6-.5-1.2-1.2-1.2H6.1c3.1-4 8.4-5.8 13.6-4.3 4.7 1.4 8.3 5.5 8.9 10.4 1 7.8-5 14.4-12.6 14.4-6.6 0-12.1-5.1-12.6-11.6 0-.6-.6-1.1-1.2-1.1-.7.1-1.2.7-1.2 1.4C1.7 25 8.1 31 16 31c8.9 0 16.1-7.8 14.9-17-.8-6-5.3-11.1-11.2-12.6"/><path d="M21.2 19c0-2.1-1-3.6-4.5-4.6-2.3-.7-3-1.1-3-2.2 0-.8.4-1.7 2-1.7 1.4 0 2 .6 2.2 1.2.1.3.4.5.7.5h1.1c.6 0 1-.6.8-1.1-.4-1-1.3-2-3-2.4-.3-.1-.5-.4-.5-.8s-.4-.8-.8-.8h-.6c-.4 0-.8.4-.8.8s-.2.7-.6.8c-1.8.5-3.1 1.7-3.1 3.7 0 2.1 1.2 3.4 4.6 4.4 2.2.6 2.8 1.3 2.8 2.5s-.8 2-2.5 2c-1.3 0-2.2-.5-2.6-1.6-.1-.3-.4-.5-.7-.5h-1c-.6 0-.9.6-.8 1.1q.75 2.1 3.3 2.7c.4.1.6.4.6.8v.3c0 .4.4.8.8.8h.6c.4 0 .8-.4.8-.8v-.3c0-.4.2-.7.6-.8 2.3-.5 3.6-1.9 3.6-4"/></svg>

After

Width:  |  Height:  |  Size: 895 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M8 18c2.4.4 2.4 3.4 4.3 4.5 1.8 1.1 4.7 1 5.8-1.1 1.5-2.9-.7-5.8-3.7-6.1-2.1-.2-3.9 1.4-6 1.4-.8 0-1.5-.6-1.6-1.4-.1-.7.1-1.2.6-1.7C9 12 11 11.1 12.5 10.9c1.9-.2 5 .1 7.5 1.1 1.1.5 2.9 1.7 3.6 2.6 1-.3-2.8-2.9-2.8-2.9s-.9-1.7-1.5-3c-.9-1.7-1.8-3-2.6-4.7-.4-.8-.4-2.1.4-2.7.3-.2.7-.3 1.1-.3 1.8.2 2.7 1.7 3.6 3C23 5.8 24 7.7 24.9 9.6c1 2.2 2.4 4.4 2.5 6.9.2 2.6-1.1 5.5-1.9 8-.5 1.6-1.3 3.4-2.7 4.5s-3.4 1.5-5.1 1.6c-2.3.1-5 .1-7.1-.8S7.6 27 6.5 25c-.9-1.6-2.1-3.9-1.4-5.7.6-1 1.7-1.5 2.9-1.3"/><path d="m13.3 3.7-.6.3c-.7.6-.3 1.7 0 2.4.3 1 .6 1.6 1.2 2.4.1.1.5.9.6.9 2.4.2 4.7 1.1 4.7 1.1s-1-1.9-1.3-2.3c-.3-.6-.6-1.1-1-1.6-.5-.8-.9-1.6-1.5-2.3-.5-.7-1.1-1.1-2-.9z"/></svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M19.5 26.4c-.5 0-.9-.4-1-.8-.4-2.1-2.2-3.6-4.4-3.6H7.6c-2.2 0-4 1.6-4.4 3.6-.1.5-.5.8-1 .8-.6 0-1.1-.6-1-1.2.5-3 3.2-5.3 6.4-5.3h6.6c3.2 0 5.8 2.3 6.4 5.3 0 .7-.5 1.2-1.1 1.2m10.3 0c-.5 0-.9-.4-1-.8-.4-2.1-2.2-3.6-4.4-3.6h-2.8l-1.5-2h4.3c3.2 0 5.8 2.3 6.4 5.3.1.6-.4 1.1-1 1.1M15.9 9.3h-2.3c-.2 0-.4-.1-.4-.3l-.7-2.2c-.2-.7-.8-1.1-1.6-1.1s-1.4.3-1.6 1l-.7 2.2c-.1.2-.2.4-.4.4H5.8c-.7 0-1.3.5-1.6 1.1s0 1.4.6 1.8l1.9 1.4c.2.1.2.3.2.5l-.7 2.2c-.2.7 0 1.4.6 1.8s1.3.4 1.9 0l1.9-1.4c.2-.1.4-.1.5 0l1.9 1.4c.3.2.6.3 1 .3s.7-.1 1-.3c.6-.4.8-1.1.6-1.8l-.7-2.2c-.1-.2 0-.4.2-.5l1.9-1.4c.6-.4.8-1.1.6-1.8s-1-1.1-1.7-1.1m-2.3 3.1c-.6.4-.8 1.2-.6 1.8l.7 2.2-1.9-1.4c-.3-.2-.6-.3-1-.3s-.7.1-1 .3L8 16.3l.6-1.7c.1-.2.2-.4.3-.5l1.9-1.6-2.3.1-.4-.2-1.8-1.3h2.3c.7 0 1.3-.5 1.6-1.1l.7-2.2.7 2.2c.2.7.8 1.1 1.6 1.1h2.3zm16-.6c-.2-.6-.8-1-1.4-1h-1.4c-.3 0-.6-.2-.6-.5L25.7 9c-.2-.6-.8-1-1.4-1s-1.2.4-1.4 1l-.4 1.3c-.1.3-.4.5-.6.5h-1.4c-.6 0-1.2.4-1.4 1s0 1.3.5 1.6l1.1.8c.2.2.3.5.2.8l-.4 1.3c-.2.6 0 1.3.5 1.6q.45.3.9.3c.45 0 .6-.1.9-.3L24 17c.2-.2.5-.2.7 0l1.2.9c.5.4 1.2.4 1.7 0s.7-1 .5-1.6l-.4-1.3c-.1-.3 0-.6.2-.8l1.1-.8c.5-.3.7-1 .6-1.6"/><path d="M25.8 13.6c-.3.2-.4.6-.3.9L26 16l-1.3-.9c-.3-.2-.7-.2-1 0l-1.2.9.4-1.4c0-.1.1-.2.2-.3l1.2-1-1.4.1c-.1 0-.3 0-.4-.1l-1.1-.8H23c.4 0 .7-.2.8-.6l.5-1.5.5 1.5c.1.3.4.6.8.6h1.6z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M31 25.4 18.5 3.7c-1.1-1.9-3.8-1.9-4.9 0L1 25.4c-1.1 1.9.3 4.3 2.5 4.3h25.1c2.2 0 3.5-2.4 2.4-4.3m-15 .2c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2m2-7c0 1.2-1.1 2.2-2.3 2-1-.1-1.7-1.1-1.7-2.1v-3.7c0-1 .7-2 1.7-2.1 1.2-.2 2.3.8 2.3 2z"/></svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@ -2200,3 +2200,22 @@
"PublicPostsSubscribeToPremium" = "Subscribe to Premium";
"NotificationPaidExtraSearch" = "{stars} spent on extra search.";
"PostsSearchTransaction" = "Posts Search";
"TitleRating" = "Rating";
"RatingReflectsActivity" = "This rating reflects {name}'s activity on Telegram. It is based on:";
"RatingYourReflectsActivity" = "This rating reflects your activity on Telegram. It is based on:";
"RatingGiftsFromTelegram" = "Gifts from Telegram";
"RatingGiftsFromTelegramDesc" = "100% of the Stars spent on gifts purchased from Telegram.";
"RatingGiftsAndPostsFromUsers" = "Gifts and Posts from Users";
"RatingGiftsAndPostsFromUsersDesc" = "20% of the Stars spent on gifts or posts from users and channels.";
"RatingRefundsAndConversions" = "Refunds and Conversions";
"RatingRefundsAndConversionsDesc" = "10x of refunded Stars and 85% of bought gifts converted to Stars.";
"RatingBadgeAdded" = "Added";
"RatingBadgeDeducted" = "Deducted";
"RatingLevel" = "Level {level}";
"RatingNegativeLevel" = "Negative Rating";
"DescriptionPendingRating_one" = "Rating updates in {time} from purchases. {points} point is pending. {link}";
"DescriptionPendingRating_other" = "Rating updates in {time} from purchases. {points} points are pending. {link}";
"DescriptionFutureRating_one" = "This will be your rating in {time}, after {points} point is added. {link}";
"DescriptionFutureRating_other" = "This will be your rating in {time}, after {points} points are added. {link}";
"LinkDescriptionRatingBack" = "Back >";
"LinkDescriptionRatingPreview" = "Preview >";

View File

@ -103,3 +103,4 @@ export { default as InviteViaLinkModal } from '../components/modals/inviteViaLin
export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal';
export { default as WebAppsCloseConfirmationModal } from '../components/main/WebAppsCloseConfirmationModal';
export { default as FrozenAccountModal } from '../components/modals/frozenAccount/FrozenAccountModal';
export { default as ProfileRatingModal } from '../components/modals/profileRating/ProfileRatingModal';

View File

@ -1,5 +1,8 @@
/* stylelint-disable plugin/no-low-performance-animation-properties */
.root {
--percent: calc(var(--progress, 0.5) * 100%);
--color-negative-progress: #E05356;
position: relative;
@ -73,21 +76,44 @@
align-items: center;
justify-content: center;
height: 2rem;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
color: #ffffff;
background-color: #7E85FF;
transition: width 0.25s ease-in-out;
&.noTransition {
transition: none;
}
}
.floatingBadgeContent {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
max-width: 20rem;
margin-inline: auto;
text-overflow: ellipsis;
white-space: nowrap;
}
.floating-badge-triangle {
position: absolute;
bottom: -4px;
left: calc(var(--tail-position, 0.5) * 100%);
transform: translateX(-50%);
display: inline-block;
color: #7E85FF;
transition: left 0.3s ease;
}
.floating-badge-icon {
@ -109,6 +135,10 @@
align-items: center;
font-weight: var(--font-weight-medium);
opacity: 1;
transition: opacity 0.15s ease;
}
.left {
@ -119,8 +149,21 @@
right: 0.75rem;
}
.progress {
--multiplier: calc(1 / var(--progress) - 1);
.progressWrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
border-radius: 0.625rem;
}
.positiveLayer,
.positiveProgress {
--multiplier: calc(1 / var(--positive-progress) - 1);
position: absolute;
top: 0;
@ -129,16 +172,18 @@
overflow: hidden;
width: max(var(--percent), 0.625rem);
border-top-left-radius: 0.625rem;
border-bottom-left-radius: 0.625rem;
width: calc(var(--positive-progress) * 100%);
background-image: var(--premium-gradient);
background-size: calc(1 / var(--progress) * 100%) 100%;
background-size: calc(1 / var(--positive-progress) * 100%) 100%;
transition: opacity 0.15s ease, width 0.2s, background-size 0.3s ease;
.left, .right {
color: white;
white-space: nowrap;
opacity: 1;
transition: opacity 0.15s ease;
}
.right {
@ -146,12 +191,44 @@
}
}
.fullProgress {
border-radius: 0.625rem;
.negativeLayer,
.negativeProgress {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: auto;
overflow: hidden;
width: calc(var(--negative-progress) * 100%);
background-color: var(--color-negative-progress);
background-image: none;
transition: opacity 0.15s, width 0.2s;
.left, .right {
color: white;
white-space: nowrap;
opacity: 1;
transition: opacity 0.15s ease;
}
.right {
right: 0.75rem;
}
.left {
right: calc(100% - 0.75rem);
left: auto;
transition: right 0.3s ease;
}
}
.primary {
.progress {
.positiveLayer,
.positiveProgress {
background-color: var(--color-primary);
background-image: none;
}
@ -159,4 +236,69 @@
.floating-badge {
background-color: var(--color-primary);
}
.floating-badge-triangle {
color: var(--color-primary);
}
}
.negative {
.floating-badge {
background-color: var(--color-negative-progress);
}
.floating-badge-triangle {
color: var(--color-negative-progress);
}
}
.transitioning {
.left,
.right {
opacity: 0;
}
}
.noTransition {
&.positiveProgress,
&.negativeProgress {
transition: opacity 0.15s !important;
}
}
.hidden {
opacity: 0 !important;
}
.cycling {
.badgeContainer {
transition-duration: var(--cycling-animation-badge-position);
}
.positiveProgress,
.negativeProgress {
transition: opacity 0.15s, width var(--cycling-animation-progress), background-size 0.3s;
}
}
.positiveLayer {
--positive-progress: var(--layer-progress);
z-index: 2;
opacity: 0;
&.show {
opacity: 1;
}
}
.negativeLayer {
--negative-progress: var(--layer-progress);
z-index: 2;
opacity: 0;
&.show {
opacity: 1;
}
}

View File

@ -7,14 +7,22 @@ import type { IconName } from '../../types/icons';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import { REM } from './helpers/mediaDimensions';
import { useTransitionActiveKey } from '../../hooks/animations/useTransitionActiveKey';
import useForceUpdate from '../../hooks/useForceUpdate';
import useOldLang from '../../hooks/useOldLang';
import usePrevious from '../../hooks/usePrevious';
import useResizeObserver from '../../hooks/useResizeObserver';
import useSyncEffect from '../../hooks/useSyncEffect';
import Transition from '../ui/Transition';
import Icon from './icons/Icon';
import styles from './PremiumProgress.module.scss';
export type AnimationDirection = 'forward' | 'backward' | 'none';
type OwnProps = {
leftText?: string;
rightText?: string;
@ -22,6 +30,8 @@ type OwnProps = {
floatingBadgeText?: string;
progress?: number;
isPrimary?: boolean;
isNegative?: boolean;
animationDirection?: AnimationDirection;
className?: string;
};
@ -30,47 +40,216 @@ const PremiumProgress: FC<OwnProps> = ({
rightText,
floatingBadgeText,
floatingBadgeIcon,
progress,
progress = 0,
isPrimary,
isNegative,
animationDirection = 'none',
className,
}) => {
const lang = useOldLang();
const floatingBadgeRef = useRef<HTMLDivElement>();
const floatingBadgeContentRef = useRef<HTMLDivElement>();
const parentContainerRef = useRef<HTMLDivElement>();
const [shiftX, setShiftX] = useState(0);
const [tailPosition, setTailPosition] = useState(0);
const [beakPosition, setBeakPosition] = useState(0);
const [badgeWidth, setBadgeWidth] = useState(0);
const prevBadgeWidth = usePrevious(badgeWidth);
const [positiveProgress, setPositiveProgress] = useState(isNegative ? 0 : progress);
const [negativeProgress, setNegativeProgress] = useState(isNegative ? progress : 0);
const [badgeProgress, setBadgeProgress] = useState(progress);
const [layerProgress, setLayerProgress] = useState(0);
const [showLayer, setShowLayer] = useState(false);
const [disableMainProgressTransition, setDisableMainProgressTransition] = useState(false);
const [disableLayerProgressTransition, setDisableLayerProgressTransition] = useState(false);
const [hideMainLayer, setHideMainLayer] = useState(false);
const [isCycling, setIsCycling] = useState(false);
const badgeActiveKey = useTransitionActiveKey([floatingBadgeText, floatingBadgeIcon]);
const shouldAnimateCaptionsRef = useRef(false);
const prevLeftText = usePrevious(leftText);
const prevRightText = usePrevious(rightText);
const prevIsNegative = usePrevious(isNegative);
const BEAK_WIDTH_PX = 28;
const PROGRESS_BORDER_RADIUS_PX = REM;
const CORNER_BEAK_THRESHOLD = BEAK_WIDTH_PX / 2 + PROGRESS_BORDER_RADIUS_PX;
const BADGE_HORIZONTAL_PADDING_PX = 0.75 * 2 * REM;
const LAYER_PROGRESS_TRANSITION_MS = 400;
const FULL_CYCLE_TRANSITION_MS = LAYER_PROGRESS_TRANSITION_MS * 2;
const APPLY_TRANSITION_DELAY_MS = 50;
const updateBadgePosition = () => {
if (floatingBadgeRef.current && parentContainerRef.current && progress !== undefined) {
const badgeWidth = floatingBadgeRef.current.offsetWidth;
if (floatingBadgeContentRef.current && parentContainerRef.current) {
const parentWidth = parentContainerRef.current.offsetWidth;
const minShift = badgeWidth / 2;
const maxShift = parentWidth - badgeWidth / 2;
const currentShift = progress * parentWidth;
const safeShift = Math.max(minShift, Math.min(currentShift, maxShift));
const halfBadgeWidth = badgeWidth / 2;
const minBadgeShift = halfBadgeWidth;
const maxBadgeShift = parentWidth - halfBadgeWidth;
const halfBeakWidth = BEAK_WIDTH_PX / 2;
const currentShift = isNegative ? (1 - badgeProgress) * parentWidth : badgeProgress * parentWidth;
let safeShift = Math.max(minBadgeShift, Math.min(currentShift, maxBadgeShift));
if (currentShift < CORNER_BEAK_THRESHOLD) {
safeShift = currentShift + halfBadgeWidth;
}
if (currentShift > parentWidth - CORNER_BEAK_THRESHOLD) {
safeShift = currentShift - halfBadgeWidth;
}
const beakOffsetFromCenter = currentShift - safeShift;
const newBeakPositionPx = halfBadgeWidth + beakOffsetFromCenter - halfBeakWidth;
setShiftX(safeShift / parentWidth);
let newTailPosition;
if (currentShift < minShift) {
newTailPosition = (progress * parentWidth) / (minShift * 2);
} else if (currentShift > maxShift) {
const progressMapped = (progress - (maxShift / parentWidth)) / (1 - maxShift / parentWidth);
newTailPosition = 0.5 + (progressMapped * 0.4);
} else {
newTailPosition = 0.5;
}
setTailPosition(newTailPosition);
setBeakPosition(newBeakPositionPx);
}
};
useEffect(updateBadgePosition, [progress]);
useEffect(updateBadgePosition, [badgeProgress, badgeWidth, isNegative, CORNER_BEAK_THRESHOLD]);
useResizeObserver(parentContainerRef, updateBadgePosition);
useEffect(() => {
const width = floatingBadgeContentRef?.current?.clientWidth || 0;
setBadgeWidth(width + BADGE_HORIZONTAL_PADDING_PX);
}, [floatingBadgeText, floatingBadgeIcon, BADGE_HORIZONTAL_PADDING_PX]);
const forceUpdate = useForceUpdate();
useSyncEffect(() => {
let timeoutId: number | undefined;
const isNegativeTransition = prevIsNegative !== undefined && prevIsNegative !== isNegative;
const shouldAnimateCaptions = (prevLeftText || prevRightText) && (isNegativeTransition || isCycling);
if (shouldAnimateCaptions && !shouldAnimateCaptionsRef.current) {
shouldAnimateCaptionsRef.current = true;
const timeoutMs = isCycling ? LAYER_PROGRESS_TRANSITION_MS * 2 : LAYER_PROGRESS_TRANSITION_MS;
timeoutId = window.setTimeout(() => {
shouldAnimateCaptionsRef.current = false;
forceUpdate();
}, timeoutMs);
}
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
shouldAnimateCaptionsRef.current = false;
}
};
}, [
leftText, prevLeftText, rightText, prevRightText,
prevIsNegative, isNegative, animationDirection, isCycling,
]);
const shouldAnimateCaptions = shouldAnimateCaptionsRef.current;
useEffect(() => {
if (isNegative) {
setPositiveProgress(0);
setNegativeProgress(progress);
} else {
setNegativeProgress(0);
setPositiveProgress(progress);
}
setBadgeProgress(progress);
}, [progress, isNegative]);
const hasFloatingBadge = Boolean(floatingBadgeIcon || floatingBadgeText);
const isProgressFull = Boolean(progress) && progress > 0.99;
const displayLeftText = shouldAnimateCaptions ? prevLeftText : leftText;
const displayRightText = shouldAnimateCaptions ? prevRightText : rightText;
const prevProgress = usePrevious(progress);
useEffect(() => {
const timers: number[] = [];
if (animationDirection === 'none' || prevProgress === undefined) {
return;
}
const targetProgress = progress;
const setMainProgress = (value: number) => {
if (isNegative) {
setNegativeProgress(value);
} else {
setPositiveProgress(value);
}
};
if (animationDirection === 'forward' || animationDirection === 'backward') {
const isForward = animationDirection === 'forward';
setIsCycling(true);
setMainProgress(isForward ? 1 : 0);
setDisableLayerProgressTransition(true);
setLayerProgress(isForward ? 0 : 1);
timers.push(window.setTimeout(() => {
setDisableLayerProgressTransition(false);
setShowLayer(true);
setLayerProgress(targetProgress);
if (isForward) {
setDisableMainProgressTransition(true);
setMainProgress(0);
}
}, LAYER_PROGRESS_TRANSITION_MS));
timers.push(window.setTimeout(() => {
setDisableMainProgressTransition(true);
setDisableLayerProgressTransition(true);
setHideMainLayer(false);
setMainProgress(targetProgress);
setShowLayer(false);
timers.push(window.setTimeout(() => {
setDisableMainProgressTransition(false);
setDisableLayerProgressTransition(false);
setIsCycling(false);
}, APPLY_TRANSITION_DELAY_MS));
}, FULL_CYCLE_TRANSITION_MS));
}
return () => {
timers.forEach(clearTimeout);
};
}, [
progress, animationDirection, isNegative,
prevProgress, FULL_CYCLE_TRANSITION_MS,
]);
const renderProgressLayer = (
isPositive: boolean,
layerProgress: number,
layerClassName?: string,
disableTransition?: boolean,
) => {
const className = isPositive ? styles.positiveProgress : styles.negativeProgress;
const progressVar = '--layer-progress';
return (
<div
className={buildClassName(
className,
layerClassName,
disableTransition && styles.noTransition,
)}
style={`${progressVar}: ${layerProgress}`}
>
<div className={styles.left}>
<span>{displayLeftText}</span>
</div>
<div className={styles.right}>
<span>{displayRightText}</span>
</div>
</div>
);
};
return (
<div
@ -79,46 +258,88 @@ const PremiumProgress: FC<OwnProps> = ({
styles.root,
hasFloatingBadge && styles.withBadge,
isPrimary && styles.primary,
isNegative && styles.negative,
shouldAnimateCaptions && styles.transitioning,
isCycling && styles.cycling,
className,
)}
style={buildStyle(
progress !== undefined && `--progress: ${progress}`,
tailPosition !== undefined && `--tail-position: ${tailPosition}`,
`--positive-progress: ${positiveProgress}`,
`--negative-progress: ${negativeProgress}`,
`--layer-progress: ${layerProgress}`,
`--shift-x: ${shiftX}`,
`--cycling-animation-badge-position: ${FULL_CYCLE_TRANSITION_MS}ms`,
`--cycling-animation-progress: ${LAYER_PROGRESS_TRANSITION_MS}ms`,
)}
>
{hasFloatingBadge && (
<div className={styles.badgeContainer}>
<div className={styles.floatingBadgeWrapper}>
<div className={styles.floatingBadge} ref={floatingBadgeRef}>
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
{floatingBadgeText && (
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
{floatingBadgeText}
<div
className={
buildClassName(styles.floatingBadge,
(!prevBadgeWidth || prevBadgeWidth === 0)
&& styles.noTransition)
}
style={`width: ${badgeWidth}px;`}
>
<Transition
activeKey={badgeActiveKey}
name="fade"
shouldCleanup
>
<div
ref={floatingBadgeContentRef}
className={styles.floatingBadgeContent}
>
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
{floatingBadgeText && (
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
{floatingBadgeText}
</div>
)}
</div>
)}
</Transition>
</div>
<div className={styles.floatingBadgeTriangle}>
<div className={styles.floatingBadgeTriangle} style={`left: ${beakPosition}px`}>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<path d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z" fill="#7E85FF" />
<path d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z" fill="currentColor" />
</svg>
</div>
</div>
</div>
)}
<div className={styles.left}>
<span>{leftText}</span>
<span>{displayLeftText}</span>
</div>
<div className={styles.right}>
<span>{rightText}</span>
<span>{displayRightText}</span>
</div>
<div className={buildClassName(styles.progress, isProgressFull && styles.fullProgress)}>
<div className={styles.left}>
<span>{leftText}</span>
</div>
<div className={styles.right}>
<span>{rightText}</span>
</div>
<div className={styles.progressWrapper}>
{renderProgressLayer(
true,
positiveProgress,
buildClassName(hideMainLayer && styles.hidden),
disableMainProgressTransition,
)}
{renderProgressLayer(
false,
negativeProgress,
buildClassName(hideMainLayer && styles.hidden),
disableMainProgressTransition,
)}
{renderProgressLayer(
!isNegative,
layerProgress,
buildClassName(
isNegative ? styles.negativeLayer : styles.positiveLayer,
showLayer && styles.show,
),
disableLayerProgressTransition,
)}
</div>
</div>
);

View File

@ -191,11 +191,64 @@
font-size: 0.875rem;
}
.user-status {
.userRatingNegativeWrapper,
.userRatingWrapper {
pointer-events: all;
cursor: pointer;
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 0.25rem;
font-size: 1rem;
color: #000000;
}
.userRatingWrapper {
width: 1rem;
font-size: 1.5rem;
}
.ratingNegativeIcon {
pointer-events: none;
font-size: 1rem;
color: var(--color-white);
-webkit-text-stroke: 1px #000000;
}
.ratingIcon {
pointer-events: none;
color: var(--color-white);
-webkit-text-stroke: 1px #000000;
}
.ratingLevel {
pointer-events: none;
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.625rem;
font-weight: var(--font-weight-bold);
line-height: 1;
color: #000000;
}
.userStatus {
opacity: 0.5;
}
.get-status {
.getStatus {
--blured-background-color: #c4c9cc42;
pointer-events: all;

View File

@ -3,9 +3,10 @@ import { memo, useEffect, useState } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type {
ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserStatus,
ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserFullInfo, ApiUserStatus,
} from '../../api/types';
import type { AnimationLevel } from '../../types';
import type { IconName } from '../../types/icons';
import { MediaViewerOrigin } from '../../types';
import {
@ -20,6 +21,7 @@ import {
selectThreadMessagesCount,
selectTopic,
selectUser,
selectUserFullInfo,
selectUserStatus,
} from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState.ts';
@ -40,12 +42,15 @@ import usePhotosPreload from './hooks/usePhotosPreload';
import Transition from '../ui/Transition';
import Avatar from './Avatar';
import FullNameTitle from './FullNameTitle';
import Icon from './icons/Icon';
import ProfilePhoto from './ProfilePhoto';
import TopicIcon from './TopicIcon';
import './ProfileInfo.scss';
import styles from './ProfileInfo.module.scss';
const MAX_LEVEL_ICON = 90;
type OwnProps = {
peerId: string;
forceShowSelf?: boolean;
@ -56,6 +61,7 @@ type OwnProps = {
type StateProps =
{
user?: ApiUser;
userFullInfo?: ApiUserFullInfo;
userStatus?: ApiUserStatus;
chat?: ApiChat;
mediaIndex?: number;
@ -78,6 +84,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
forceShowSelf,
canPlayVideo,
user,
userFullInfo,
userStatus,
chat,
mediaIndex,
@ -98,6 +105,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
openPrivacySettingsNoticeModal,
loadMoreProfilePhotos,
openUniqueGiftBySlug,
openProfileRatingModal,
} = getActions();
const oldLang = useOldLang();
@ -182,6 +190,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
openPrivacySettingsNoticeModal({ chatId: chat!.id, isReadDate: false });
});
const handleRatingClick = useLastCallback((level: number) => {
if (user) {
openProfileRatingModal({ userId: user.id, level });
}
});
function handleSelectFallbackPhoto() {
if (!isFirst) return;
setHasSlideAnimation(true);
@ -268,6 +282,43 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
);
}
function renderUserRating() {
if (!userFullInfo?.starsRating) return undefined;
const level = userFullInfo.starsRating.level;
const isNegative = level < 0;
const onRatingClick = () => handleRatingClick(level);
if (isNegative) {
return (
<span className={styles.userRatingNegativeWrapper} onClick={onRatingClick}>
<Icon
name="rating-icons-negative"
className={styles.ratingNegativeIcon}
/>
<span className={styles.ratingLevel}>!</span>
</span>
);
}
const safeLevel = Math.max(level, 1);
const iconLevel = Math.min(safeLevel, MAX_LEVEL_ICON);
const iconName = (iconLevel < 10
? `rating-icons-level${iconLevel}`
: `rating-icons-level${Math.floor(iconLevel / 10) * 10}`) as IconName;
return (
<span className={styles.userRatingWrapper} onClick={onRatingClick}>
<Icon
name={iconName}
className={styles.ratingIcon}
/>
<span className={styles.ratingLevel}>{level}</span>
</span>
);
}
function renderStatus() {
const isAnonymousForwards = isAnonymousForwardsChat(peerId);
const isSystemBotChat = isSystemBot(peerId);
@ -290,6 +341,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
isUserOnline(user, userStatus) && 'online',
)}
>
{renderUserRating()}
<span className={styles.userStatus} dir="auto">
{getUserStatus(oldLang, user, userStatus)}
</span>
@ -400,6 +452,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { peerId }): StateProps => {
const user = selectUser(global, peerId);
const userFullInfo = user ? selectUserFullInfo(global, peerId) : undefined;
const userStatus = selectUserStatus(global, peerId);
const chat = selectChat(global, peerId);
const profilePhotos = selectPeerPhotos(global, peerId);
@ -415,6 +468,7 @@ export default memo(withGlobal<OwnProps>(
return {
user,
userFullInfo,
userStatus,
chat,
mediaIndex,

View File

@ -35,6 +35,7 @@ import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
import PriceConfirmModal from './priceConfirm/PriceConfirmModal.async';
import ProfileRatingModal from './profileRating/ProfileRatingModal.async';
import ReportAdModal from './reportAd/ReportAdModal.async';
import ReportModal from './reportModal/ReportModal.async';
import SharePreparedMessageModal from './sharePreparedMessage/SharePreparedMessageModal.async';
@ -93,7 +94,8 @@ type ModalKey = keyof Pick<TabState,
'priceConfirmModal' |
'isFrozenAccountModalOpen' |
'deleteAccountModal' |
'isAgeVerificationModalOpen'
'isAgeVerificationModalOpen' |
'profileRatingModal'
>;
type StateProps = {
@ -151,6 +153,7 @@ const MODALS: ModalRegistry = {
isFrozenAccountModalOpen: FrozenAccountModal,
deleteAccountModal: DeleteAccountModal,
isAgeVerificationModalOpen: AgeVerificationModal,
profileRatingModal: ProfileRatingModal,
};
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;

View File

@ -0,0 +1,16 @@
import type { FC } from '../../../lib/teact/teact';
import type { OwnProps } from './ProfileRatingModal';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const ProfileRatingModalAsync: FC<OwnProps> = (props) => {
const { modal } = props;
const ProfileRatingModal = useModuleLoader(Bundles.Extra, 'ProfileRatingModal', !modal);
return ProfileRatingModal ? <ProfileRatingModal {...props} /> : undefined;
};
export default ProfileRatingModalAsync;

View File

@ -0,0 +1,91 @@
.header {
width: 100%;
padding-top: 0;
padding-bottom: 1rem;
text-align: center;
}
.description {
margin: 0;
padding-inline: 1rem;
line-height: 1.25;
}
.descriptionPreview {
margin-bottom: 1rem;
font-size: 0.875rem;
line-height: 1.25;
color: var(--color-text-secondary);
p {
margin-bottom: 0;
margin-inline: 1rem;
}
}
.previewLink,
.backLink {
cursor: pointer;
color: var(--color-primary);
opacity: 1;
transition: opacity 0.15s;
&:hover {
opacity: 0.75;
}
}
.title {
margin-bottom: 4.75rem;
font-size: 1.5rem;
font-weight: var(--font-weight-medium);
line-height: 1.5rem;
}
.ratingProgress {
margin: 0 auto;
margin-bottom: 0.75rem;
&.withPreview {
margin-bottom: 0.5rem;
}
}
.subtitle {
line-height: 1.25;
}
.footer {
display: flex;
align-self: stretch;
margin-top: 1rem;
}
.badge {
transform: translateY(-1px);
display: inline-block;
margin-right: 0.25rem;
padding: 0.125rem 0.375rem;
border-radius: 0.375rem;
font-size: 0.6875rem;
font-weight: var(--font-weight-medium);
line-height: 0.875rem;
color: var(--color-white);
text-transform: uppercase;
}
.understoodIcon {
margin-right: 0.25rem;
}
.badgeAdded {
background-color: var(--color-primary);
}
.badgeDeducted {
color: var(--color-background);
background-color: var(--color-text-secondary);
}

View File

@ -0,0 +1,260 @@
import { memo, useEffect, useMemo, useState } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiStarsRating, ApiUser } from '../../../api/types';
import type { TabState } from '../../../global/types';
import { getPeerTitle } from '../../../global/helpers/peers';
import { selectUser, selectUserFullInfo } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { formatShortDuration } from '../../../util/dates/dateFormat';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import Icon from '../../common/icons/Icon';
import PremiumProgress, { type AnimationDirection } from '../../common/PremiumProgress';
import Button from '../../ui/Button';
import Transition from '../../ui/Transition';
import TableAboutModal, { type TableAboutData } from '../common/TableAboutModal';
import styles from './ProfileRatingModal.module.scss';
export type OwnProps = {
modal: TabState['profileRatingModal'];
};
type StateProps = {
user?: ApiUser;
currentUserId?: string;
starsRating?: ApiStarsRating;
pendingRating?: ApiStarsRating;
pendingRatingDate?: number;
};
const ProfileRatingModal = ({
modal,
user,
currentUserId,
starsRating,
pendingRating,
pendingRatingDate,
}: OwnProps & StateProps) => {
const {
closeProfileRatingModal,
} = getActions();
const lang = useLang();
const isOpen = Boolean(modal);
const [showFutureRating, setShowFutureRating] = useState(false);
const handleClose = useLastCallback(() => {
closeProfileRatingModal();
});
useEffect(() => {
if (!isOpen) {
setShowFutureRating(false);
}
}, [isOpen]);
const handleShowFuture = useLastCallback(() => {
setShowFutureRating(true);
});
const handleShowCurrent = useLastCallback(() => {
setShowFutureRating(false);
});
const renderBadge = (type: 'added' | 'deducted') => {
const isAdded = type === 'added';
const badgeText = isAdded ? lang('RatingBadgeAdded') : lang('RatingBadgeDeducted');
const badgeClass = isAdded ? styles.badgeAdded : styles.badgeDeducted;
return (
<span className={buildClassName(styles.badge, badgeClass)}>
{badgeText}
</span>
);
};
const header = useMemo(() => {
if (!modal || !user || !starsRating || !isOpen) return undefined;
const rating = showFutureRating && pendingRating ? pendingRating : starsRating;
const currentStars = rating.stars;
const currentLevelStars = rating.currentLevelStars;
const nextLevelStars = rating.nextLevelStars;
const currentLevel = rating.level;
const nextLevel = currentLevel + 1;
const isNegative = currentLevel < 0;
const pendingLevel = !showFutureRating && pendingRating ? pendingRating.level : starsRating.level;
let levelProgress = 0;
if (!nextLevelStars) {
levelProgress = 1;
} else if (nextLevelStars > currentLevelStars) {
levelProgress = Math.max(0.03, (currentStars - currentLevelStars) / (nextLevelStars - currentLevelStars));
} else {
levelProgress = 1;
}
const progress = isNegative ? 0.5 : Math.max(0, Math.min(1, levelProgress));
const waitTime = pendingRatingDate ? pendingRatingDate - Math.floor(Date.now() / 1000) : 0;
const pendingPoints = pendingRating ? pendingRating.stars - starsRating.stars : 0;
const shouldShowPreview = pendingRating && pendingRatingDate;
const renderPreviewDescription = () => {
if (!shouldShowPreview) return undefined;
return (
<Transition
name="fade"
className={styles.descriptionPreview}
activeKey={showFutureRating ? 1 : 0}
shouldCleanup
shouldRestoreHeight
>
{showFutureRating ? (
<p>
{lang('DescriptionFutureRating', {
time: formatShortDuration(lang, waitTime),
points: Math.abs(pendingPoints),
link: (
<span className={styles.backLink} onClick={handleShowCurrent}>
{lang('LinkDescriptionRatingBack')}
</span>
),
}, {
pluralValue: Math.abs(pendingPoints),
withNodes: true,
})}
</p>
) : (
<p>
{lang('DescriptionPendingRating', {
time: formatShortDuration(lang, waitTime),
points: Math.abs(pendingPoints),
link: (
<span className={styles.previewLink} onClick={handleShowFuture}>
{lang('LinkDescriptionRatingPreview')}
</span>
),
}, {
pluralValue: Math.abs(pendingPoints),
withNodes: true,
})}
</p>
)}
</Transition>
);
};
let animationDirection: AnimationDirection = 'none';
if (currentLevel >= 0 && pendingLevel >= 0 && currentLevel !== pendingLevel) {
animationDirection = currentLevel > pendingLevel ? 'forward' : 'backward';
}
if (currentLevel < 0 && pendingLevel < 0 && currentLevel !== pendingLevel) {
animationDirection = currentLevel < pendingLevel ? 'backward' : 'forward';
}
const userFallbackText = lang('ActionFallbackUser');
return (
<div className={styles.header}>
<div className={styles.title}>
{lang('TitleRating')}
</div>
<PremiumProgress
leftText={isNegative ? undefined : lang('RatingLevel', { level: currentLevel })}
rightText={isNegative ? lang('RatingNegativeLevel') : lang('RatingLevel', { level: nextLevel })}
floatingBadgeIcon={isNegative ? 'warning' : 'crown-wear'}
floatingBadgeText={isNegative ? currentLevel.toString()
: `${lang.number(currentStars)} / ${lang.number(nextLevelStars || currentStars)}`}
progress={progress}
isPrimary={currentLevel >= 0}
isNegative={currentLevel < 0}
animationDirection={animationDirection}
className={buildClassName(styles.ratingProgress, shouldShowPreview && styles.withPreview)}
/>
{renderPreviewDescription()}
<p className={styles.description}>
{user?.id === currentUserId
? lang('RatingYourReflectsActivity')
: lang('RatingReflectsActivity', { name: getPeerTitle(lang, user) || userFallbackText })}
</p>
</div>
);
}, [modal, user, currentUserId, starsRating,
pendingRating, pendingRatingDate, showFutureRating,
lang, handleShowFuture, handleShowCurrent, isOpen]);
const listItemData = [
['gift', lang('RatingGiftsFromTelegram'), (
<span className={styles.subtitle}>
{renderBadge('added')}
{lang('RatingGiftsFromTelegramDesc')}
</span>
)],
['user-stars', lang('RatingGiftsAndPostsFromUsers'), (
<span className={styles.subtitle}>
{renderBadge('added')}
{lang('RatingGiftsAndPostsFromUsersDesc')}
</span>
)],
['refund', lang('RatingRefundsAndConversions'), (
<span className={styles.subtitle}>
{renderBadge('deducted')}
{lang('RatingRefundsAndConversionsDesc')}
</span>
)],
] satisfies TableAboutData;
const footer = useMemo(() => {
if (!isOpen) return undefined;
return (
<div className={styles.footer}>
<Button
size="smaller"
onClick={handleClose}
>
<Icon name="understood" className={styles.understoodIcon} />
{lang('ButtonUnderstood')}
</Button>
</div>
);
}, [lang, isOpen, handleClose]);
return (
<TableAboutModal
isOpen={isOpen}
header={header}
listItemData={listItemData}
footer={footer}
onClose={handleClose}
/>
);
};
export default memo(withGlobal<OwnProps>(
(global, { modal }): StateProps => {
const currentUserId = global.currentUserId;
const user = modal?.userId ? selectUser(global, modal.userId) : undefined;
const userFullInfo = modal?.userId
? selectUserFullInfo(global, modal.userId) : undefined;
const starsRating = userFullInfo?.starsRating;
const pendingRating = userFullInfo?.starsMyPendingRating;
const pendingRatingDate = userFullInfo?.starsMyPendingRatingDate;
return {
user,
currentUserId,
starsRating,
pendingRating,
pendingRatingDate,
};
},
)(ProfileRatingModal));

View File

@ -64,3 +64,16 @@ addActionHandler('closeSuggestedStatusModal', (global, actions, payload): Action
});
addTabStateResetterAction('closeChatRefundModal', 'chatRefundModal');
addActionHandler('openProfileRatingModal', (global, actions, payload): ActionReturnType => {
const { userId, level, tabId = getCurrentTabId() } = payload;
return updateTabState(global, {
profileRatingModal: {
userId,
level,
},
}, tabId);
});
addTabStateResetterAction('closeProfileRatingModal', 'profileRatingModal');

View File

@ -1850,6 +1850,11 @@ export interface ActionPayloads {
userId: string;
} & WithTabId;
closeChatRefundModal: WithTabId | undefined;
openProfileRatingModal: {
userId: string;
level: number;
} & WithTabId;
closeProfileRatingModal: WithTabId | undefined;
loadMoreProfilePhotos: {
peerId: string;
isPreload?: boolean;

View File

@ -852,6 +852,11 @@ export type TabState = {
duration?: number;
};
profileRatingModal?: {
userId: string;
level: number;
};
monetizationVerificationModal?: {
chatId: string;
isLoading?: boolean;

View File

@ -207,101 +207,124 @@ $icons-map: (
"quote-text": "\f1aa",
"quote": "\f1ab",
"radial-badge": "\f1ac",
"readchats": "\f1ad",
"recent": "\f1ae",
"reload": "\f1af",
"remove-quote": "\f1b0",
"remove": "\f1b1",
"reopen-topic": "\f1b2",
"replace": "\f1b3",
"replies": "\f1b4",
"reply-filled": "\f1b5",
"reply": "\f1b6",
"revenue-split": "\f1b7",
"revote": "\f1b8",
"save-story": "\f1b9",
"saved-messages": "\f1ba",
"schedule": "\f1bb",
"sd-photo": "\f1bc",
"search": "\f1bd",
"select": "\f1be",
"sell-outline": "\f1bf",
"sell": "\f1c0",
"send-outline": "\f1c1",
"send": "\f1c2",
"settings-filled": "\f1c3",
"settings": "\f1c4",
"share-filled": "\f1c5",
"share-screen-outlined": "\f1c6",
"share-screen-stop": "\f1c7",
"share-screen": "\f1c8",
"show-message": "\f1c9",
"sidebar": "\f1ca",
"skip-next": "\f1cb",
"skip-previous": "\f1cc",
"smallscreen": "\f1cd",
"smile": "\f1ce",
"sort-by-date": "\f1cf",
"sort-by-number": "\f1d0",
"sort-by-price": "\f1d1",
"sort": "\f1d2",
"speaker-muted-story": "\f1d3",
"speaker-outline": "\f1d4",
"speaker-story": "\f1d5",
"speaker": "\f1d6",
"spoiler-disable": "\f1d7",
"spoiler": "\f1d8",
"sport": "\f1d9",
"star": "\f1da",
"stars-lock": "\f1db",
"stats": "\f1dc",
"stealth-future": "\f1dd",
"stealth-past": "\f1de",
"stickers": "\f1df",
"stop-raising-hand": "\f1e0",
"stop": "\f1e1",
"story-caption": "\f1e2",
"story-expired": "\f1e3",
"story-priority": "\f1e4",
"story-reply": "\f1e5",
"strikethrough": "\f1e6",
"tag-add": "\f1e7",
"tag-crossed": "\f1e8",
"tag-filter": "\f1e9",
"tag-name": "\f1ea",
"tag": "\f1eb",
"timer": "\f1ec",
"toncoin": "\f1ed",
"trade": "\f1ee",
"transcribe": "\f1ef",
"truck": "\f1f0",
"unarchive": "\f1f1",
"underlined": "\f1f2",
"unique-profile": "\f1f3",
"unlist-outline": "\f1f4",
"unlist": "\f1f5",
"unlock-badge": "\f1f6",
"unlock": "\f1f7",
"unmute": "\f1f8",
"unpin": "\f1f9",
"unread": "\f1fa",
"up": "\f1fb",
"user-filled": "\f1fc",
"user-online": "\f1fd",
"user": "\f1fe",
"video-outlined": "\f1ff",
"video-stop": "\f200",
"video": "\f201",
"view-once": "\f202",
"voice-chat": "\f203",
"volume-1": "\f204",
"volume-2": "\f205",
"volume-3": "\f206",
"web": "\f207",
"webapp": "\f208",
"word-wrap": "\f209",
"zoom-in": "\f20a",
"zoom-out": "\f20b",
"rating-icons-level1": "\f1ad",
"rating-icons-level10": "\f1ae",
"rating-icons-level2": "\f1af",
"rating-icons-level20": "\f1b0",
"rating-icons-level3": "\f1b1",
"rating-icons-level30": "\f1b2",
"rating-icons-level4": "\f1b3",
"rating-icons-level40": "\f1b4",
"rating-icons-level5": "\f1b5",
"rating-icons-level50": "\f1b6",
"rating-icons-level6": "\f1b7",
"rating-icons-level60": "\f1b8",
"rating-icons-level7": "\f1b9",
"rating-icons-level70": "\f1ba",
"rating-icons-level8": "\f1bb",
"rating-icons-level80": "\f1bc",
"rating-icons-level9": "\f1bd",
"rating-icons-level90": "\f1be",
"rating-icons-negative": "\f1bf",
"readchats": "\f1c0",
"recent": "\f1c1",
"refund": "\f1c2",
"reload": "\f1c3",
"remove-quote": "\f1c4",
"remove": "\f1c5",
"reopen-topic": "\f1c6",
"replace": "\f1c7",
"replies": "\f1c8",
"reply-filled": "\f1c9",
"reply": "\f1ca",
"revenue-split": "\f1cb",
"revote": "\f1cc",
"save-story": "\f1cd",
"saved-messages": "\f1ce",
"schedule": "\f1cf",
"sd-photo": "\f1d0",
"search": "\f1d1",
"select": "\f1d2",
"sell-outline": "\f1d3",
"sell": "\f1d4",
"send-outline": "\f1d5",
"send": "\f1d6",
"settings-filled": "\f1d7",
"settings": "\f1d8",
"share-filled": "\f1d9",
"share-screen-outlined": "\f1da",
"share-screen-stop": "\f1db",
"share-screen": "\f1dc",
"show-message": "\f1dd",
"sidebar": "\f1de",
"skip-next": "\f1df",
"skip-previous": "\f1e0",
"smallscreen": "\f1e1",
"smile": "\f1e2",
"sort-by-date": "\f1e3",
"sort-by-number": "\f1e4",
"sort-by-price": "\f1e5",
"sort": "\f1e6",
"speaker-muted-story": "\f1e7",
"speaker-outline": "\f1e8",
"speaker-story": "\f1e9",
"speaker": "\f1ea",
"spoiler-disable": "\f1eb",
"spoiler": "\f1ec",
"sport": "\f1ed",
"star": "\f1ee",
"stars-lock": "\f1ef",
"stats": "\f1f0",
"stealth-future": "\f1f1",
"stealth-past": "\f1f2",
"stickers": "\f1f3",
"stop-raising-hand": "\f1f4",
"stop": "\f1f5",
"story-caption": "\f1f6",
"story-expired": "\f1f7",
"story-priority": "\f1f8",
"story-reply": "\f1f9",
"strikethrough": "\f1fa",
"tag-add": "\f1fb",
"tag-crossed": "\f1fc",
"tag-filter": "\f1fd",
"tag-name": "\f1fe",
"tag": "\f1ff",
"timer": "\f200",
"toncoin": "\f201",
"trade": "\f202",
"transcribe": "\f203",
"truck": "\f204",
"unarchive": "\f205",
"underlined": "\f206",
"understood": "\f207",
"unique-profile": "\f208",
"unlist-outline": "\f209",
"unlist": "\f20a",
"unlock-badge": "\f20b",
"unlock": "\f20c",
"unmute": "\f20d",
"unpin": "\f20e",
"unread": "\f20f",
"up": "\f210",
"user-filled": "\f211",
"user-online": "\f212",
"user-stars": "\f213",
"user": "\f214",
"video-outlined": "\f215",
"video-stop": "\f216",
"video": "\f217",
"view-once": "\f218",
"voice-chat": "\f219",
"volume-1": "\f21a",
"volume-2": "\f21b",
"volume-3": "\f21c",
"warning": "\f21d",
"web": "\f21e",
"webapp": "\f21f",
"word-wrap": "\f220",
"zoom-in": "\f221",
"zoom-out": "\f222",
);
.icon-active-sessions::before {
@ -820,12 +843,72 @@ $icons-map: (
.icon-radial-badge::before {
content: map.get($icons-map, "radial-badge");
}
.icon-rating-icons-level1::before {
content: map.get($icons-map, "rating-icons-level1");
}
.icon-rating-icons-level10::before {
content: map.get($icons-map, "rating-icons-level10");
}
.icon-rating-icons-level2::before {
content: map.get($icons-map, "rating-icons-level2");
}
.icon-rating-icons-level20::before {
content: map.get($icons-map, "rating-icons-level20");
}
.icon-rating-icons-level3::before {
content: map.get($icons-map, "rating-icons-level3");
}
.icon-rating-icons-level30::before {
content: map.get($icons-map, "rating-icons-level30");
}
.icon-rating-icons-level4::before {
content: map.get($icons-map, "rating-icons-level4");
}
.icon-rating-icons-level40::before {
content: map.get($icons-map, "rating-icons-level40");
}
.icon-rating-icons-level5::before {
content: map.get($icons-map, "rating-icons-level5");
}
.icon-rating-icons-level50::before {
content: map.get($icons-map, "rating-icons-level50");
}
.icon-rating-icons-level6::before {
content: map.get($icons-map, "rating-icons-level6");
}
.icon-rating-icons-level60::before {
content: map.get($icons-map, "rating-icons-level60");
}
.icon-rating-icons-level7::before {
content: map.get($icons-map, "rating-icons-level7");
}
.icon-rating-icons-level70::before {
content: map.get($icons-map, "rating-icons-level70");
}
.icon-rating-icons-level8::before {
content: map.get($icons-map, "rating-icons-level8");
}
.icon-rating-icons-level80::before {
content: map.get($icons-map, "rating-icons-level80");
}
.icon-rating-icons-level9::before {
content: map.get($icons-map, "rating-icons-level9");
}
.icon-rating-icons-level90::before {
content: map.get($icons-map, "rating-icons-level90");
}
.icon-rating-icons-negative::before {
content: map.get($icons-map, "rating-icons-negative");
}
.icon-readchats::before {
content: map.get($icons-map, "readchats");
}
.icon-recent::before {
content: map.get($icons-map, "recent");
}
.icon-refund::before {
content: map.get($icons-map, "refund");
}
.icon-reload::before {
content: map.get($icons-map, "reload");
}
@ -1030,6 +1113,9 @@ $icons-map: (
.icon-underlined::before {
content: map.get($icons-map, "underlined");
}
.icon-understood::before {
content: map.get($icons-map, "understood");
}
.icon-unique-profile::before {
content: map.get($icons-map, "unique-profile");
}
@ -1063,6 +1149,9 @@ $icons-map: (
.icon-user-online::before {
content: map.get($icons-map, "user-online");
}
.icon-user-stars::before {
content: map.get($icons-map, "user-stars");
}
.icon-user::before {
content: map.get($icons-map, "user");
}
@ -1090,6 +1179,9 @@ $icons-map: (
.icon-volume-3::before {
content: map.get($icons-map, "volume-3");
}
.icon-warning::before {
content: map.get($icons-map, "warning");
}
.icon-web::before {
content: map.get($icons-map, "web");
}

Binary file not shown.

Binary file not shown.

View File

@ -171,8 +171,28 @@ export type FontIconName =
| 'quote-text'
| 'quote'
| 'radial-badge'
| 'rating-icons-level1'
| 'rating-icons-level10'
| 'rating-icons-level2'
| 'rating-icons-level20'
| 'rating-icons-level3'
| 'rating-icons-level30'
| 'rating-icons-level4'
| 'rating-icons-level40'
| 'rating-icons-level5'
| 'rating-icons-level50'
| 'rating-icons-level6'
| 'rating-icons-level60'
| 'rating-icons-level7'
| 'rating-icons-level70'
| 'rating-icons-level8'
| 'rating-icons-level80'
| 'rating-icons-level9'
| 'rating-icons-level90'
| 'rating-icons-negative'
| 'readchats'
| 'recent'
| 'refund'
| 'reload'
| 'remove-quote'
| 'remove'
@ -241,6 +261,7 @@ export type FontIconName =
| 'truck'
| 'unarchive'
| 'underlined'
| 'understood'
| 'unique-profile'
| 'unlist-outline'
| 'unlist'
@ -252,6 +273,7 @@ export type FontIconName =
| 'up'
| 'user-filled'
| 'user-online'
| 'user-stars'
| 'user'
| 'video-outlined'
| 'video-stop'
@ -261,6 +283,7 @@ export type FontIconName =
| 'volume-1'
| 'volume-2'
| 'volume-3'
| 'warning'
| 'web'
| 'webapp'
| 'word-wrap'

View File

@ -1642,6 +1642,19 @@ export interface LangPair {
'PublicPostsPremiumFeatureSubtitle': undefined;
'PublicPostsSubscribeToPremium': undefined;
'PostsSearchTransaction': undefined;
'TitleRating': undefined;
'RatingYourReflectsActivity': undefined;
'RatingGiftsFromTelegram': undefined;
'RatingGiftsFromTelegramDesc': undefined;
'RatingGiftsAndPostsFromUsers': undefined;
'RatingGiftsAndPostsFromUsersDesc': undefined;
'RatingRefundsAndConversions': undefined;
'RatingRefundsAndConversionsDesc': undefined;
'RatingBadgeAdded': undefined;
'RatingBadgeDeducted': undefined;
'RatingNegativeLevel': undefined;
'LinkDescriptionRatingBack': undefined;
'LinkDescriptionRatingPreview': undefined;
}
export interface LangPairWithVariables<V = LangVariable> {
@ -2850,6 +2863,12 @@ export interface LangPairWithVariables<V = LangVariable> {
'NotificationPaidExtraSearch': {
'stars': V;
};
'RatingReflectsActivity': {
'name': V;
};
'RatingLevel': {
'level': V;
};
}
export interface LangPairPlural {
@ -3177,6 +3196,16 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
'HintPublicPostsSearchQuota': {
'count': V;
};
'DescriptionPendingRating': {
'time': V;
'points': V;
'link': V;
};
'DescriptionFutureRating': {
'time': V;
'points': V;
'link': V;
};
}
export type RegularLangKey = keyof LangPair;
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;