Privacy Settings: Implement premium category (#4536)
This commit is contained in:
parent
d64ed45af8
commit
cf8eaf270e
@ -1,4 +1,5 @@
|
|||||||
.Avatar {
|
.Avatar {
|
||||||
|
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||||
--color-user: var(--color-primary);
|
--color-user: var(--color-primary);
|
||||||
--radius: 50%;
|
--radius: 50%;
|
||||||
|
|
||||||
@ -258,7 +259,7 @@
|
|||||||
--color-user: var(--color-deleted-account);
|
--color-user: var(--color-deleted-account);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unknown-user {
|
&.premium-gradient-bg > .inner {
|
||||||
background: var(--premium-gradient);
|
background-image: var(--premium-gradient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import type {
|
|||||||
ApiChat, ApiPeer, ApiPhoto, ApiUser,
|
ApiChat, ApiPeer, ApiPhoto, ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
import type { StoryViewerOrigin } from '../../types';
|
import type { CustomPeer, StoryViewerOrigin } from '../../types';
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
import { ApiMediaFormat } from '../../api/types';
|
||||||
|
|
||||||
import { IS_TEST } from '../../config';
|
import { IS_TEST } from '../../config';
|
||||||
@ -49,11 +49,10 @@ cn.icon = cn('icon');
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: AvatarSize;
|
size?: AvatarSize;
|
||||||
peer?: ApiPeer;
|
peer?: ApiPeer | CustomPeer;
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
text?: string;
|
text?: string;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
isUnknownUser?: boolean;
|
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
withVideo?: boolean;
|
withVideo?: boolean;
|
||||||
withStory?: boolean;
|
withStory?: boolean;
|
||||||
@ -78,7 +77,6 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
text,
|
text,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isUnknownUser,
|
|
||||||
withVideo,
|
withVideo,
|
||||||
withStory,
|
withStory,
|
||||||
forPremiumPromo,
|
forPremiumPromo,
|
||||||
@ -97,12 +95,14 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const videoLoopCountRef = useRef(0);
|
const videoLoopCountRef = useRef(0);
|
||||||
const isPeerChat = peer && 'title' in peer;
|
const isCustomPeer = peer && 'isCustomPeer' in peer;
|
||||||
|
const realPeer = peer && !isCustomPeer ? peer : undefined;
|
||||||
|
const isPeerChat = realPeer && 'title' in realPeer;
|
||||||
const user = peer && !isPeerChat ? peer as ApiUser : undefined;
|
const user = peer && !isPeerChat ? peer as ApiUser : undefined;
|
||||||
const chat = peer && isPeerChat ? peer as ApiChat : undefined;
|
const chat = peer && isPeerChat ? peer as ApiChat : undefined;
|
||||||
const isDeleted = user && isDeletedUser(user);
|
const isDeleted = user && isDeletedUser(user);
|
||||||
const isReplies = peer && isChatWithRepliesBot(peer.id);
|
const isReplies = realPeer && isChatWithRepliesBot(realPeer.id);
|
||||||
const isAnonymousForwards = peer && isAnonymousForwardsChat(peer.id);
|
const isAnonymousForwards = realPeer && isAnonymousForwardsChat(realPeer.id);
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
let imageHash: string | undefined;
|
let imageHash: string | undefined;
|
||||||
let videoHash: string | undefined;
|
let videoHash: string | undefined;
|
||||||
@ -112,7 +112,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const shouldFetchBig = size === 'jumbo';
|
const shouldFetchBig = size === 'jumbo';
|
||||||
if (!isSavedMessages && !isDeleted) {
|
if (!isSavedMessages && !isDeleted) {
|
||||||
if ((user && !noPersonalPhoto) || chat) {
|
if ((user && !noPersonalPhoto) || chat) {
|
||||||
imageHash = getChatAvatarHash(peer!, shouldFetchBig ? 'big' : undefined);
|
imageHash = getChatAvatarHash(peer as ApiPeer, shouldFetchBig ? 'big' : undefined);
|
||||||
} else if (photo) {
|
} else if (photo) {
|
||||||
imageHash = `photo${photo.id}?size=m`;
|
imageHash = `photo${photo.id}?size=m`;
|
||||||
if (photo.isVideo && withVideo) {
|
if (photo.isVideo && withVideo) {
|
||||||
@ -122,8 +122,8 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const specialIcon = useMemo(() => {
|
const specialIcon = useMemo(() => {
|
||||||
if (isUnknownUser) {
|
if (isCustomPeer) {
|
||||||
return 'user';
|
return peer.avatarIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSavedMessages) {
|
if (isSavedMessages) {
|
||||||
@ -143,7 +143,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isUnknownUser, isSavedMessages, isDeleted, isReplies, isAnonymousForwards, isSavedDialog]);
|
}, [isCustomPeer, isSavedMessages, isDeleted, isReplies, isAnonymousForwards, peer, isSavedDialog]);
|
||||||
|
|
||||||
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
||||||
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
|
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
|
||||||
@ -215,23 +215,25 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
content = getFirstLetters(text, 2);
|
content = getFirstLetters(text, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRoundedRect = isForum && !((withStory || withStorySolid) && peer?.hasStories);
|
const isRoundedRect = (isCustomPeer && peer.isAvatarSquare)
|
||||||
|
|| (isForum && !((withStory || withStorySolid) && realPeer?.hasStories));
|
||||||
|
const isPremiumGradient = isCustomPeer && peer.withPremiumGradient;
|
||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
`Avatar size-${size}`,
|
`Avatar size-${size}`,
|
||||||
className,
|
className,
|
||||||
getPeerColorClass(peer),
|
getPeerColorClass(peer),
|
||||||
isUnknownUser && 'unknown-user',
|
|
||||||
!peer && text && 'hidden-user',
|
!peer && text && 'hidden-user',
|
||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
isAnonymousForwards && 'anonymous-forwards',
|
isAnonymousForwards && 'anonymous-forwards',
|
||||||
isDeleted && 'deleted-account',
|
isDeleted && 'deleted-account',
|
||||||
isReplies && 'replies-bot-account',
|
isReplies && 'replies-bot-account',
|
||||||
|
isPremiumGradient && 'premium-gradient-bg',
|
||||||
isRoundedRect && 'forum',
|
isRoundedRect && 'forum',
|
||||||
((withStory && peer?.hasStories) || forPremiumPromo) && 'with-story-circle',
|
((withStory && realPeer?.hasStories) || forPremiumPromo) && 'with-story-circle',
|
||||||
withStorySolid && peer?.hasStories && 'with-story-solid',
|
withStorySolid && realPeer?.hasStories && 'with-story-solid',
|
||||||
withStorySolid && forceFriendStorySolid && 'close-friend',
|
withStorySolid && forceFriendStorySolid && 'close-friend',
|
||||||
withStorySolid && (peer?.hasUnreadStories || forceUnreadStorySolid) && 'has-unread-story',
|
withStorySolid && (realPeer?.hasUnreadStories || forceUnreadStorySolid) && 'has-unread-story',
|
||||||
onClick && 'interactive',
|
onClick && 'interactive',
|
||||||
(!isSavedMessages && !imgBlobUrl) && 'no-photo',
|
(!isSavedMessages && !imgBlobUrl) && 'no-photo',
|
||||||
);
|
);
|
||||||
@ -239,11 +241,11 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const hasMedia = Boolean(isSavedMessages || imgBlobUrl);
|
const hasMedia = Boolean(isSavedMessages || imgBlobUrl);
|
||||||
|
|
||||||
const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
|
const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
if (withStory && storyViewerMode !== 'disabled' && peer?.hasStories) {
|
if (withStory && storyViewerMode !== 'disabled' && realPeer?.hasStories) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
openStoryViewer({
|
openStoryViewer({
|
||||||
peerId: peer.id,
|
peerId: realPeer.id,
|
||||||
isSinglePeer: storyViewerMode === 'single-peer',
|
isSinglePeer: storyViewerMode === 'single-peer',
|
||||||
origin: storyViewerOrigin,
|
origin: storyViewerOrigin,
|
||||||
});
|
});
|
||||||
@ -259,9 +261,9 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={fullClassName}
|
className={fullClassName}
|
||||||
id={peer?.id && withStory ? getPeerStoryHtmlId(peer.id) : undefined}
|
id={realPeer?.id && withStory ? getPeerStoryHtmlId(realPeer.id) : undefined}
|
||||||
data-peer-id={peer?.id}
|
data-peer-id={realPeer?.id}
|
||||||
data-test-sender-id={IS_TEST ? peer?.id : undefined}
|
data-test-sender-id={IS_TEST ? realPeer?.id : undefined}
|
||||||
aria-label={typeof content === 'string' ? author : undefined}
|
aria-label={typeof content === 'string' ? author : undefined}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
@ -269,8 +271,8 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
<div className="inner">
|
<div className="inner">
|
||||||
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
|
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
|
||||||
</div>
|
</div>
|
||||||
{withStory && peer?.hasStories && (
|
{withStory && realPeer?.hasStories && (
|
||||||
<AvatarStoryCircle peerId={peer.id} size={size} withExtraGap={withStoryGap} />
|
<AvatarStoryCircle peerId={realPeer.id} size={size} withExtraGap={withStoryGap} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,8 +2,11 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import React, { memo, useMemo } from '../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiPeer, ApiUser } from '../../api/types';
|
import type {
|
||||||
|
ApiChat, ApiPeer, ApiUser,
|
||||||
|
} from '../../api/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
import type { CustomPeer } from '../../types';
|
||||||
|
|
||||||
import { EMOJI_STATUS_LOOP_LIMIT } from '../../config';
|
import { EMOJI_STATUS_LOOP_LIMIT } from '../../config';
|
||||||
import {
|
import {
|
||||||
@ -25,7 +28,7 @@ import VerifiedIcon from './VerifiedIcon';
|
|||||||
import styles from './FullNameTitle.module.scss';
|
import styles from './FullNameTitle.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
peer?: ApiPeer;
|
peer: ApiPeer | CustomPeer;
|
||||||
className?: string;
|
className?: string;
|
||||||
noVerified?: boolean;
|
noVerified?: boolean;
|
||||||
noFake?: boolean;
|
noFake?: boolean;
|
||||||
@ -34,7 +37,6 @@ type OwnProps = {
|
|||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
noLoopLimit?: boolean;
|
noLoopLimit?: boolean;
|
||||||
isUnknownUser?: boolean;
|
|
||||||
canCopyTitle?: boolean;
|
canCopyTitle?: boolean;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
@ -55,25 +57,15 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
iconElement,
|
iconElement,
|
||||||
isUnknownUser,
|
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const { showNotification } = getActions();
|
const { showNotification } = getActions();
|
||||||
const isUser = peer && isUserId(peer.id);
|
const realPeer = 'id' in peer ? peer : undefined;
|
||||||
|
const customPeer = 'isCustomPeer' in peer ? peer : undefined;
|
||||||
|
const isUser = realPeer && isUserId(realPeer.id);
|
||||||
|
const title = realPeer && (isUser ? getUserFullName(realPeer as ApiUser) : getChatTitle(lang, realPeer as ApiChat));
|
||||||
const isPremium = isUser && (peer as ApiUser).isPremium;
|
const isPremium = isUser && (peer as ApiUser).isPremium;
|
||||||
|
|
||||||
const title = useMemo(() => {
|
|
||||||
if (isUnknownUser) {
|
|
||||||
return lang('BoostingToBeDistributed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peer && isUserId(peer.id)) {
|
|
||||||
return getUserFullName(peer as ApiUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
return peer && getChatTitle(lang, peer as ApiChat);
|
|
||||||
}, [isUnknownUser, lang, peer]);
|
|
||||||
|
|
||||||
const handleTitleClick = useLastCallback((e) => {
|
const handleTitleClick = useLastCallback((e) => {
|
||||||
if (!title || !canCopyTitle) {
|
if (!title || !canCopyTitle) {
|
||||||
return;
|
return;
|
||||||
@ -85,20 +77,24 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const specialTitle = useMemo(() => {
|
const specialTitle = useMemo(() => {
|
||||||
|
if (customPeer) {
|
||||||
|
return lang(customPeer.titleKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (isSavedMessages) {
|
if (isSavedMessages) {
|
||||||
return lang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
|
return lang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peer && isAnonymousForwardsChat(peer.id)) {
|
if (isAnonymousForwardsChat(realPeer!.id)) {
|
||||||
return lang('AnonymousForward');
|
return lang('AnonymousForward');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (peer && isChatWithRepliesBot(peer.id)) {
|
if (isChatWithRepliesBot(realPeer!.id)) {
|
||||||
return lang('RepliesTitle');
|
return lang('RepliesTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isSavedDialog, isSavedMessages, lang, peer]);
|
}, [customPeer, isSavedDialog, isSavedMessages, lang, realPeer]);
|
||||||
|
|
||||||
if (specialTitle) {
|
if (specialTitle) {
|
||||||
return (
|
return (
|
||||||
@ -120,18 +116,18 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
</h3>
|
</h3>
|
||||||
{!iconElement && peer && (
|
{!iconElement && peer && (
|
||||||
<>
|
<>
|
||||||
{!noVerified && peer?.isVerified && <VerifiedIcon />}
|
{!noVerified && realPeer?.isVerified && <VerifiedIcon />}
|
||||||
{!noFake && peer?.fakeType && <FakeIcon fakeType={peer.fakeType} />}
|
{!noFake && realPeer?.fakeType && <FakeIcon fakeType={realPeer.fakeType} />}
|
||||||
{withEmojiStatus && peer.emojiStatus && (
|
{withEmojiStatus && realPeer?.emojiStatus && (
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
documentId={peer.emojiStatus.documentId}
|
documentId={realPeer.emojiStatus.documentId}
|
||||||
size={emojiStatusSize}
|
size={emojiStatusSize}
|
||||||
loopLimit={!noLoopLimit ? EMOJI_STATUS_LOOP_LIMIT : undefined}
|
loopLimit={!noLoopLimit ? EMOJI_STATUS_LOOP_LIMIT : undefined}
|
||||||
observeIntersectionForLoading={observeIntersection}
|
observeIntersectionForLoading={observeIntersection}
|
||||||
onClick={onEmojiStatusClick}
|
onClick={onEmojiStatusClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{withEmojiStatus && !peer.emojiStatus && isPremium && <PremiumIcon />}
|
{withEmojiStatus && !realPeer?.emojiStatus && isPremium && <PremiumIcon />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{iconElement}
|
{iconElement}
|
||||||
|
|||||||
@ -29,6 +29,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.picker-category-title {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding-inline: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top: 1px solid var(--color-borders);
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.picker-list {
|
.picker-list {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useMemo, useRef,
|
memo, useCallback, useEffect, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiCountry } from '../../api/types';
|
import type { ApiCountry } from '../../api/types';
|
||||||
|
import type { CustomPeer, CustomPeerType } from '../../types';
|
||||||
|
|
||||||
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||||
import { isUserId } from '../../global/helpers';
|
import { isUserId } from '../../global/helpers';
|
||||||
@ -27,7 +28,9 @@ import './Picker.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
categories?: CustomPeer[];
|
||||||
itemIds: string[];
|
itemIds: string[];
|
||||||
|
selectedCategories?: CustomPeerType[];
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
lockedSelectedIds?: string[];
|
lockedSelectedIds?: string[];
|
||||||
lockedUnselectedIds?: string[];
|
lockedUnselectedIds?: string[];
|
||||||
@ -42,6 +45,7 @@ type OwnProps = {
|
|||||||
isRoundCheckbox?: boolean;
|
isRoundCheckbox?: boolean;
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
isViewOnly?: boolean;
|
isViewOnly?: boolean;
|
||||||
|
onSelectedCategoriesChange?: (categories: CustomPeerType[]) => void;
|
||||||
onSelectedIdsChange?: (ids: string[]) => void;
|
onSelectedIdsChange?: (ids: string[]) => void;
|
||||||
onFilterChange?: (value: string) => void;
|
onFilterChange?: (value: string) => void;
|
||||||
onDisabledClick?: (id: string, isSelected: boolean) => void;
|
onDisabledClick?: (id: string, isSelected: boolean) => void;
|
||||||
@ -58,7 +62,9 @@ const ALWAYS_FULL_ITEMS_COUNT = 5;
|
|||||||
|
|
||||||
const Picker: FC<OwnProps> = ({
|
const Picker: FC<OwnProps> = ({
|
||||||
className,
|
className,
|
||||||
|
categories,
|
||||||
itemIds,
|
itemIds,
|
||||||
|
selectedCategories,
|
||||||
selectedIds,
|
selectedIds,
|
||||||
filterValue,
|
filterValue,
|
||||||
filterPlaceholder,
|
filterPlaceholder,
|
||||||
@ -73,6 +79,7 @@ const Picker: FC<OwnProps> = ({
|
|||||||
lockedUnselectedSubtitle,
|
lockedUnselectedSubtitle,
|
||||||
forceShowSelf,
|
forceShowSelf,
|
||||||
isViewOnly,
|
isViewOnly,
|
||||||
|
onSelectedCategoriesChange,
|
||||||
onSelectedIdsChange,
|
onSelectedIdsChange,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
onDisabledClick,
|
onDisabledClick,
|
||||||
@ -100,7 +107,16 @@ const Picker: FC<OwnProps> = ({
|
|||||||
return selectedIds.filter((id) => !lockedSelectedIdsSet.has(id));
|
return selectedIds.filter((id) => !lockedSelectedIdsSet.has(id));
|
||||||
}, [lockedSelectedIdsSet, selectedIds]);
|
}, [lockedSelectedIdsSet, selectedIds]);
|
||||||
|
|
||||||
|
const categoriesByType = useMemo(() => {
|
||||||
|
if (!categories) return {};
|
||||||
|
return buildCollectionByKey(categories, 'type');
|
||||||
|
}, [categories]);
|
||||||
|
|
||||||
const sortedItemIds = useMemo(() => {
|
const sortedItemIds = useMemo(() => {
|
||||||
|
if (filterValue) {
|
||||||
|
return itemIds;
|
||||||
|
}
|
||||||
|
|
||||||
const lockedSelectedBucket: string[] = [];
|
const lockedSelectedBucket: string[] = [];
|
||||||
const unlockedBucket: string[] = [];
|
const unlockedBucket: string[] = [];
|
||||||
const lockedUnselectableBucket: string[] = [];
|
const lockedUnselectableBucket: string[] = [];
|
||||||
@ -115,8 +131,8 @@ const Picker: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return lockedSelectedBucket.concat(unlockedBucket).concat(lockedUnselectableBucket);
|
return lockedSelectedBucket.concat(unlockedBucket, lockedUnselectableBucket);
|
||||||
}, [itemIds, lockedSelectedIdsSet, lockedUnselectedIdsSet]);
|
}, [filterValue, itemIds, lockedSelectedIdsSet, lockedUnselectedIdsSet]);
|
||||||
|
|
||||||
const handleItemClick = useLastCallback((id: string) => {
|
const handleItemClick = useLastCallback((id: string) => {
|
||||||
if (lockedSelectedIdsSet.has(id)) {
|
if (lockedSelectedIdsSet.has(id)) {
|
||||||
@ -129,13 +145,24 @@ const Picker: FC<OwnProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSelectedIds = selectedIds.slice();
|
if (categoriesByType[id]) {
|
||||||
if (newSelectedIds.includes(id)) {
|
const categoryType = categoriesByType[id].type;
|
||||||
newSelectedIds.splice(newSelectedIds.indexOf(id), 1);
|
const newSelectedCategories = selectedCategories?.slice() || [];
|
||||||
|
if (newSelectedCategories.includes(categoryType)) {
|
||||||
|
newSelectedCategories.splice(newSelectedCategories.indexOf(categoryType), 1);
|
||||||
|
} else {
|
||||||
|
newSelectedCategories.push(categoryType);
|
||||||
|
}
|
||||||
|
onSelectedCategoriesChange?.(newSelectedCategories);
|
||||||
} else {
|
} else {
|
||||||
newSelectedIds.push(id);
|
const newSelectedIds = selectedIds.slice();
|
||||||
|
if (newSelectedIds.includes(id)) {
|
||||||
|
newSelectedIds.splice(newSelectedIds.indexOf(id), 1);
|
||||||
|
} else {
|
||||||
|
newSelectedIds.push(id);
|
||||||
|
}
|
||||||
|
onSelectedIdsChange?.(newSelectedIds);
|
||||||
}
|
}
|
||||||
onSelectedIdsChange?.(newSelectedIds);
|
|
||||||
onFilterChange?.('');
|
onFilterChange?.('');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -153,7 +180,15 @@ const Picker: FC<OwnProps> = ({
|
|||||||
return buildCollectionByKey(countryList, 'iso2');
|
return buildCollectionByKey(countryList, 'iso2');
|
||||||
}, [countryList]);
|
}, [countryList]);
|
||||||
|
|
||||||
const renderChatInfo = (id: string) => {
|
const renderCategory = useLastCallback((category: CustomPeer) => {
|
||||||
|
return (
|
||||||
|
<PrivateChatInfo
|
||||||
|
customPeer={category}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderChatInfo = useLastCallback((id: string) => {
|
||||||
const isUnselectable = lockedUnselectedIdsSet.has(id);
|
const isUnselectable = lockedUnselectedIdsSet.has(id);
|
||||||
if (isCountryList && countriesByIso) {
|
if (isCountryList && countriesByIso) {
|
||||||
const country = countriesByIso[id];
|
const country = countriesByIso[id];
|
||||||
@ -169,12 +204,69 @@ const Picker: FC<OwnProps> = ({
|
|||||||
} else {
|
} else {
|
||||||
return <GroupChatInfo chatId={id} status={isUnselectable ? lockedUnselectedSubtitle : undefined} />;
|
return <GroupChatInfo chatId={id} status={isUnselectable ? lockedUnselectedSubtitle : undefined} />;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
const renderItem = useCallback((id: string, isCategory?: boolean) => {
|
||||||
|
const category = isCategory ? categoriesByType[id] : undefined;
|
||||||
|
const shouldRenderLockIcon = lockedUnselectedIdsSet.has(id);
|
||||||
|
const isLocked = lockedSelectedIdsSet.has(id) || shouldRenderLockIcon;
|
||||||
|
const isChecked = category ? selectedCategories?.includes(category.type) : selectedIds.includes(id);
|
||||||
|
const renderCheckbox = () => {
|
||||||
|
return (isViewOnly || shouldRenderLockIcon) ? undefined : (
|
||||||
|
<Checkbox
|
||||||
|
label=""
|
||||||
|
disabled={isLocked}
|
||||||
|
checked={isChecked}
|
||||||
|
round={isRoundCheckbox}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
key={id}
|
||||||
|
className={buildClassName('chat-item-clickable picker-list-item', isRoundCheckbox && 'chat-item')}
|
||||||
|
disabled={isLocked}
|
||||||
|
inactive={isViewOnly}
|
||||||
|
allowDisabledClick={Boolean(onDisabledClick)}
|
||||||
|
secondaryIcon={shouldRenderLockIcon ? 'lock-badge' : undefined}
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => handleItemClick(id)}
|
||||||
|
ripple
|
||||||
|
>
|
||||||
|
{!isRoundCheckbox ? renderCheckbox() : undefined}
|
||||||
|
{category ? renderCategory(category) : renderChatInfo(id)}
|
||||||
|
{isRoundCheckbox ? renderCheckbox() : undefined}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
categoriesByType, isRoundCheckbox, isViewOnly, lockedSelectedIdsSet, lockedUnselectedIdsSet,
|
||||||
|
onDisabledClick, renderChatInfo, selectedCategories, selectedIds,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const beforeChildren = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<div key="categories">
|
||||||
|
{Boolean(categories?.length) && (
|
||||||
|
<div className="picker-category-title">{lang('PrivacyUserTypes')}</div>
|
||||||
|
)}
|
||||||
|
{categories?.map((category) => renderItem(category.type, true))}
|
||||||
|
<div className="picker-category-title">{lang('FilterChats')}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [categories, lang, renderItem]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('Picker', className)}>
|
<div className={buildClassName('Picker', className)}>
|
||||||
{isSearchable && (
|
{isSearchable && (
|
||||||
<div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
|
<div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{selectedCategories?.map((category) => (
|
||||||
|
<PickerSelectedItem
|
||||||
|
customPeer={categoriesByType[category]}
|
||||||
|
onClick={handleItemClick}
|
||||||
|
clickArg={category}
|
||||||
|
canClose
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{lockedSelectedIds?.map((id, i) => (
|
{lockedSelectedIds?.map((id, i) => (
|
||||||
<PickerSelectedItem
|
<PickerSelectedItem
|
||||||
peerId={id}
|
peerId={id}
|
||||||
@ -209,40 +301,11 @@ const Picker: FC<OwnProps> = ({
|
|||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className={buildClassName('picker-list', 'custom-scroll', isRoundCheckbox && 'withRoundedCheckbox')}
|
className={buildClassName('picker-list', 'custom-scroll', isRoundCheckbox && 'withRoundedCheckbox')}
|
||||||
items={viewportIds}
|
items={viewportIds}
|
||||||
|
beforeChildren={beforeChildren}
|
||||||
onLoadMore={getMore}
|
onLoadMore={getMore}
|
||||||
noScrollRestore={noScrollRestore}
|
noScrollRestore={noScrollRestore}
|
||||||
>
|
>
|
||||||
{viewportIds.map((id) => {
|
{viewportIds.map((id) => renderItem(id))}
|
||||||
const shouldRenderLockIcon = lockedUnselectedIdsSet.has(id);
|
|
||||||
const isLocked = lockedSelectedIdsSet.has(id) || shouldRenderLockIcon;
|
|
||||||
const renderCheckbox = () => {
|
|
||||||
return (isViewOnly || shouldRenderLockIcon) ? undefined : (
|
|
||||||
<Checkbox
|
|
||||||
label=""
|
|
||||||
disabled={isLocked}
|
|
||||||
checked={selectedIds.includes(id)}
|
|
||||||
round={isRoundCheckbox}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
key={id}
|
|
||||||
className={buildClassName('chat-item-clickable picker-list-item', isRoundCheckbox && 'chat-item')}
|
|
||||||
disabled={isLocked}
|
|
||||||
inactive={isViewOnly}
|
|
||||||
allowDisabledClick={Boolean(onDisabledClick)}
|
|
||||||
secondaryIcon={shouldRenderLockIcon ? 'lock-badge' : undefined}
|
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
|
||||||
onClick={() => handleItemClick(id)}
|
|
||||||
ripple
|
|
||||||
>
|
|
||||||
{!isRoundCheckbox ? renderCheckbox() : undefined}
|
|
||||||
{renderChatInfo(id)}
|
|
||||||
{isRoundCheckbox ? renderCheckbox() : undefined}
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
) : !isLoading && viewportIds && !viewportIds.length ? (
|
) : !isLoading && viewportIds && !viewportIds.length ? (
|
||||||
<p className="no-results">{notFoundText || 'Sorry, nothing found.'}</p>
|
<p className="no-results">{notFoundText || 'Sorry, nothing found.'}</p>
|
||||||
|
|||||||
@ -125,7 +125,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&.forum-avatar {
|
&.square-avatar {
|
||||||
border-start-start-radius: 0.625rem;
|
border-start-start-radius: 0.625rem;
|
||||||
border-end-start-radius: 0.625rem;
|
border-end-start-radius: 0.625rem;
|
||||||
--border-radius-forum-avatar: 0.625rem;
|
--border-radius-forum-avatar: 0.625rem;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import React, { memo } from '../../lib/teact/teact';
|
|||||||
import { withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiUser } from '../../api/types';
|
import type { ApiChat, ApiUser } from '../../api/types';
|
||||||
|
import type { CustomPeer } from '../../types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
|
|
||||||
import { getChatTitle, getUserFirstOrLastName } from '../../global/helpers';
|
import { getChatTitle, getUserFirstOrLastName } from '../../global/helpers';
|
||||||
@ -14,11 +15,13 @@ import renderText from './helpers/renderText';
|
|||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
import './PickerSelectedItem.scss';
|
import './PickerSelectedItem.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
peerId?: string;
|
peerId?: string;
|
||||||
|
customPeer?: CustomPeer;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
title?: string;
|
title?: string;
|
||||||
isMinimized?: boolean;
|
isMinimized?: boolean;
|
||||||
@ -45,6 +48,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
clickArg,
|
clickArg,
|
||||||
chat,
|
chat,
|
||||||
user,
|
user,
|
||||||
|
customPeer,
|
||||||
className,
|
className,
|
||||||
fluid,
|
fluid,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
@ -59,23 +63,24 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
if (icon && title) {
|
if (icon && title) {
|
||||||
iconElement = (
|
iconElement = (
|
||||||
<div className="item-icon">
|
<div className="item-icon">
|
||||||
<i className={buildClassName('icon', `icon-${icon}`)} />
|
<Icon name={icon} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
titleText = title;
|
titleText = title;
|
||||||
} else if (user || chat) {
|
} else if (customPeer || user || chat) {
|
||||||
iconElement = (
|
iconElement = (
|
||||||
<Avatar
|
<Avatar
|
||||||
peer={user || chat}
|
peer={customPeer || user || chat}
|
||||||
size="small"
|
size="small"
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const name = !chat || (user && !isSavedMessages)
|
const name = (customPeer && lang(customPeer.titleKey))
|
||||||
? getUserFirstOrLastName(user)
|
|| (!chat || (user && !isSavedMessages)
|
||||||
: getChatTitle(lang, chat, isSavedMessages);
|
? getUserFirstOrLastName(user)
|
||||||
|
: getChatTitle(lang, chat, isSavedMessages));
|
||||||
|
|
||||||
titleText = name ? renderText(name) : undefined;
|
titleText = name ? renderText(name) : undefined;
|
||||||
}
|
}
|
||||||
@ -83,11 +88,11 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'PickerSelectedItem',
|
'PickerSelectedItem',
|
||||||
className,
|
className,
|
||||||
chat?.isForum && 'forum-avatar',
|
(chat?.isForum || customPeer?.isAvatarSquare) && 'square-avatar',
|
||||||
isMinimized && 'minimized',
|
isMinimized && 'minimized',
|
||||||
canClose && 'closeable',
|
canClose && 'closeable',
|
||||||
fluid && 'fluid',
|
fluid && 'fluid',
|
||||||
withPeerColors && getPeerColorClass(chat || user),
|
withPeerColors && getPeerColorClass(customPeer || chat || user),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -105,7 +110,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
{canClose && (
|
{canClose && (
|
||||||
<div className="item-remove">
|
<div className="item-remove">
|
||||||
<i className="icon icon-close" />
|
<Icon name="close" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiChatMember, ApiTypingStatus, ApiUser, ApiUserStatus,
|
ApiChatMember, ApiTypingStatus, ApiUser, ApiUserStatus,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { StoryViewerOrigin } from '../../types';
|
import type { CustomPeer, StoryViewerOrigin } from '../../types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
@ -27,7 +27,8 @@ import Icon from './Icon';
|
|||||||
import TypingStatus from './TypingStatus';
|
import TypingStatus from './TypingStatus';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
userId: string;
|
userId?: string;
|
||||||
|
customPeer?: CustomPeer;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
avatarSize?: 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
avatarSize?: 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
@ -38,7 +39,6 @@ type OwnProps = {
|
|||||||
withMediaViewer?: boolean;
|
withMediaViewer?: boolean;
|
||||||
withUsername?: boolean;
|
withUsername?: boolean;
|
||||||
withStory?: boolean;
|
withStory?: boolean;
|
||||||
isUnknownUser?: boolean;
|
|
||||||
withFullInfo?: boolean;
|
withFullInfo?: boolean;
|
||||||
withUpdatingStatus?: boolean;
|
withUpdatingStatus?: boolean;
|
||||||
storyViewerOrigin?: StoryViewerOrigin;
|
storyViewerOrigin?: StoryViewerOrigin;
|
||||||
@ -67,6 +67,7 @@ type StateProps =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||||
|
customPeer,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
avatarSize = 'medium',
|
avatarSize = 'medium',
|
||||||
status,
|
status,
|
||||||
@ -82,7 +83,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
noEmojiStatus,
|
noEmojiStatus,
|
||||||
noFake,
|
noFake,
|
||||||
noVerified,
|
noVerified,
|
||||||
isUnknownUser,
|
|
||||||
noRtl,
|
noRtl,
|
||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
@ -131,7 +131,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
|
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
|
||||||
|
|
||||||
if (!user && !isUnknownUser) {
|
if (!user && !customPeer) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +153,14 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (customPeer?.subtitleKey) {
|
||||||
|
return (
|
||||||
|
<span className="status" dir="auto">
|
||||||
|
<span className="user-status" dir="auto">{lang(customPeer.subtitleKey)}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -194,7 +202,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FullNameTitle
|
<FullNameTitle
|
||||||
peer={user!}
|
peer={customPeer || user!}
|
||||||
noFake={noFake}
|
noFake={noFake}
|
||||||
noVerified={noVerified}
|
noVerified={noVerified}
|
||||||
withEmojiStatus={!noEmojiStatus}
|
withEmojiStatus={!noEmojiStatus}
|
||||||
@ -202,7 +210,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
isSavedDialog={isSavedDialog}
|
isSavedDialog={isSavedDialog}
|
||||||
onEmojiStatusClick={onEmojiStatusClick}
|
onEmojiStatusClick={onEmojiStatusClick}
|
||||||
isUnknownUser={isUnknownUser}
|
|
||||||
iconElement={iconElement}
|
iconElement={iconElement}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -220,12 +227,11 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Avatar
|
<Avatar
|
||||||
key={user?.id}
|
key={customPeer?.type || user?.id}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
peer={user}
|
peer={customPeer || user}
|
||||||
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
isUnknownUser={isUnknownUser}
|
|
||||||
isSavedDialog={isSavedDialog}
|
isSavedDialog={isSavedDialog}
|
||||||
withStory={withStory}
|
withStory={withStory}
|
||||||
storyViewerOrigin={storyViewerOrigin}
|
storyViewerOrigin={storyViewerOrigin}
|
||||||
@ -245,11 +251,11 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { userId, forceShowSelf }): StateProps => {
|
(global, { userId, forceShowSelf }): StateProps => {
|
||||||
const { isSynced } = global;
|
const { isSynced } = global;
|
||||||
const user = selectUser(global, userId);
|
const user = userId ? selectUser(global, userId) : undefined;
|
||||||
const userStatus = selectUserStatus(global, userId);
|
const userStatus = userId ? selectUserStatus(global, userId) : undefined;
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const self = isSavedMessages ? user : selectUser(global, global.currentUserId!);
|
const self = isSavedMessages ? user : selectUser(global, global.currentUserId!);
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
const areMessagesLoaded = Boolean(userId && selectChatMessages(global, userId));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
import type { ApiPeer, ApiPeerColor } from '../../../api/types';
|
import type { ApiPeer, ApiPeerColor } from '../../../api/types';
|
||||||
|
import type { CustomPeer } from '../../../types';
|
||||||
|
|
||||||
import { getPeerColorCount, getPeerColorKey } from '../../../global/helpers';
|
import { getPeerColorCount, getPeerColorKey } from '../../../global/helpers';
|
||||||
|
|
||||||
export function getPeerColorClass(peer?: ApiPeer, noUserColors?: boolean, shouldReset?: boolean) {
|
export function getPeerColorClass(peer?: ApiPeer | CustomPeer, noUserColors?: boolean, shouldReset?: boolean) {
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
if (!shouldReset) return undefined;
|
if (!shouldReset) return undefined;
|
||||||
return noUserColors ? 'peer-color-count-1' : 'peer-color-0';
|
return noUserColors ? 'peer-color-count-1' : 'peer-color-0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('isCustomPeer' in peer) {
|
||||||
|
if (!peer.peerColorId) return undefined;
|
||||||
|
return `peer-color-${peer.peerColorId}`;
|
||||||
|
}
|
||||||
return noUserColors ? `peer-color-count-${getPeerColorCount(peer)}` : `peer-color-${getPeerColorKey(peer)}`;
|
return noUserColors ? `peer-color-count-${getPeerColorCount(peer)}` : `peer-color-${getPeerColorKey(peer)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -356,6 +356,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<SettingsPrivacyVisibilityExceptionList
|
<SettingsPrivacyVisibilityExceptionList
|
||||||
isAllowList
|
isAllowList
|
||||||
|
withPremiumCategory={currentScreen === SettingsScreens.PrivacyGroupChatsAllowedContacts}
|
||||||
screen={currentScreen}
|
screen={currentScreen}
|
||||||
onScreenSelect={onScreenSelect}
|
onScreenSelect={onScreenSelect}
|
||||||
isActive={isScreenActive || privacyAllowScreens[currentScreen]}
|
isActive={isScreenActive || privacyAllowScreens[currentScreen]}
|
||||||
|
|||||||
@ -103,7 +103,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
}, [updateContentSettings]);
|
}, [updateContentSettings]);
|
||||||
|
|
||||||
function getVisibilityValue(setting?: ApiPrivacySettings) {
|
function getVisibilityValue(setting?: ApiPrivacySettings) {
|
||||||
const { visibility } = setting || {};
|
const { visibility, shouldAllowPremium } = setting || {};
|
||||||
const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0;
|
const blockCount = setting ? setting.blockChatIds.length + setting.blockUserIds.length : 0;
|
||||||
const allowCount = setting ? setting.allowChatIds.length + setting.allowUserIds.length : 0;
|
const allowCount = setting ? setting.allowChatIds.length + setting.allowUserIds.length : 0;
|
||||||
const total = [];
|
const total = [];
|
||||||
@ -112,6 +112,10 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const exceptionString = total.length ? `(${total.join(',')})` : '';
|
const exceptionString = total.length ? `(${total.join(',')})` : '';
|
||||||
|
|
||||||
|
if (shouldAllowPremium) {
|
||||||
|
return lang(exceptionString ? 'ContactsAndPremium' : 'PrivacyPremium');
|
||||||
|
}
|
||||||
|
|
||||||
switch (visibility) {
|
switch (visibility) {
|
||||||
case 'everybody':
|
case 'everybody':
|
||||||
return `${lang('P2PEverybody')} ${exceptionString}`;
|
return `${lang('P2PEverybody')} ${exceptionString}`;
|
||||||
|
|||||||
@ -196,12 +196,14 @@ function PrivacySubsection({
|
|||||||
}
|
}
|
||||||
}, [lang, screen]);
|
}, [lang, screen]);
|
||||||
|
|
||||||
const prepareSubtitle = useLastCallback((userIds?: string[], chatIds?: string[]) => {
|
const prepareSubtitle = useLastCallback((userIds?: string[], chatIds?: string[], shouldAllowPremium?: boolean) => {
|
||||||
const userIdsCount = userIds?.length || 0;
|
const userIdsCount = userIds?.length || 0;
|
||||||
const chatIdsCount = chatIds?.length || 0;
|
const chatIdsCount = chatIds?.length || 0;
|
||||||
|
|
||||||
if (!userIdsCount && !chatIdsCount) {
|
if (!userIdsCount && !chatIdsCount) {
|
||||||
return lang('EditAdminAddUsers');
|
return shouldAllowPremium ? lang('PrivacyPremium') : lang('EditAdminAddUsers');
|
||||||
|
} else if (shouldAllowPremium) {
|
||||||
|
return lang('ContactsAndPremium');
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCountString = userIdsCount > 0 ? lang('Users', userIdsCount) : undefined;
|
const userCountString = userIdsCount > 0 ? lang('Users', userIdsCount) : undefined;
|
||||||
@ -211,7 +213,7 @@ function PrivacySubsection({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const allowedString = useMemo(() => {
|
const allowedString = useMemo(() => {
|
||||||
return prepareSubtitle(privacy?.allowUserIds, privacy?.allowChatIds);
|
return prepareSubtitle(privacy?.allowUserIds, privacy?.allowChatIds, privacy?.shouldAllowPremium);
|
||||||
}, [privacy]);
|
}, [privacy]);
|
||||||
|
|
||||||
const blockString = useMemo(() => {
|
const blockString = useMemo(() => {
|
||||||
|
|||||||
@ -5,12 +5,15 @@ import React, {
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { GlobalState } from '../../../global/types';
|
||||||
import type { ApiPrivacySettings } from '../../../types';
|
import type { ApiPrivacySettings, CustomPeerType } from '../../../types';
|
||||||
import { SettingsScreens } from '../../../types';
|
import { SettingsScreens } from '../../../types';
|
||||||
|
|
||||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID } from '../../../config';
|
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||||
import { filterChatsByName } from '../../../global/helpers';
|
import {
|
||||||
|
filterChatsByName, isChatChannel, isDeletedUser,
|
||||||
|
} from '../../../global/helpers';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
|
import { CUSTOM_PEER_PREMIUM } from '../../../util/objects/customPeer';
|
||||||
import { getPrivacyKey } from './helpers/privacy';
|
import { getPrivacyKey } from './helpers/privacy';
|
||||||
|
|
||||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||||
@ -22,6 +25,7 @@ import FloatingActionButton from '../../ui/FloatingActionButton';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isAllowList?: boolean;
|
isAllowList?: boolean;
|
||||||
|
withPremiumCategory?: boolean;
|
||||||
screen: SettingsScreens;
|
screen: SettingsScreens;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
onScreenSelect: (screen: SettingsScreens) => void;
|
onScreenSelect: (screen: SettingsScreens) => void;
|
||||||
@ -33,8 +37,11 @@ type StateProps = {
|
|||||||
settings?: ApiPrivacySettings;
|
settings?: ApiPrivacySettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PREMIUM_CATEGORY = [CUSTOM_PEER_PREMIUM];
|
||||||
|
|
||||||
const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
||||||
isAllowList,
|
isAllowList,
|
||||||
|
withPremiumCategory,
|
||||||
screen,
|
screen,
|
||||||
isActive,
|
isActive,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
@ -57,23 +64,46 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
return [...settings.blockUserIds, ...settings.blockChatIds];
|
return [...settings.blockUserIds, ...settings.blockChatIds];
|
||||||
}
|
}
|
||||||
}, [isAllowList, settings]);
|
}, [isAllowList, settings]);
|
||||||
|
const selectedCategoryTypes = useMemo(() => {
|
||||||
|
if (!settings) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [settings.shouldAllowPremium ? CUSTOM_PEER_PREMIUM.type : undefined].filter(Boolean);
|
||||||
|
}, [settings]);
|
||||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||||
const [isSubmitShown, setIsSubmitShown] = useState<boolean>(false);
|
const [isSubmitShown, setIsSubmitShown] = useState<boolean>(false);
|
||||||
const [newSelectedContactIds, setNewSelectedContactIds] = useState<string[]>(selectedContactIds);
|
const [newSelectedContactIds, setNewSelectedContactIds] = useState<string[]>(selectedContactIds);
|
||||||
|
const [newSelectedCategoryTypes, setNewSelectedCategoryTypes] = useState<CustomPeerType[]>(selectedCategoryTypes);
|
||||||
|
|
||||||
// Reset selected contact ids on change from other client when screen is not active
|
// Reset selected contact ids on change from other client when screen is not active
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isActive) setNewSelectedContactIds(selectedContactIds);
|
if (!isActive) {
|
||||||
}, [isActive, selectedContactIds]);
|
setNewSelectedContactIds(selectedContactIds);
|
||||||
|
setNewSelectedCategoryTypes(selectedCategoryTypes);
|
||||||
|
}
|
||||||
|
}, [isActive, selectedCategoryTypes, selectedContactIds]);
|
||||||
|
|
||||||
const folderAllOrderedIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
const folderAllOrderedIds = useFolderManagerForOrderedIds(ALL_FOLDER_ID);
|
||||||
const folderArchivedOrderedIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
|
const folderArchivedOrderedIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
|
||||||
const displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
// No need for expensive global updates on chats, so we avoid them
|
// No need for expensive global updates on chats, so we avoid them
|
||||||
const chatsById = getGlobal().chats.byId;
|
const chatsById = getGlobal().chats.byId;
|
||||||
|
const usersById = getGlobal().users.byId;
|
||||||
|
|
||||||
const chatIds = unique([...folderAllOrderedIds || [], ...folderArchivedOrderedIds || []])
|
const chatIds = unique([...folderAllOrderedIds || [], ...folderArchivedOrderedIds || []])
|
||||||
.filter((chatId) => chatId !== currentUserId);
|
.filter((chatId) => {
|
||||||
|
const chat = chatsById[chatId];
|
||||||
|
const user = usersById[chatId];
|
||||||
|
const isDeleted = user && isDeletedUser(user);
|
||||||
|
const isChannel = chat && isChatChannel(chat);
|
||||||
|
return chatId !== currentUserId && chatId !== SERVICE_NOTIFICATIONS_USER_ID && !isChannel && !isDeleted;
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredChats = filterChatsByName(lang, chatIds, chatsById, searchQuery);
|
||||||
|
|
||||||
|
// Show only relevant items
|
||||||
|
if (searchQuery) return filteredChats;
|
||||||
|
|
||||||
return unique([
|
return unique([
|
||||||
...selectedContactIds,
|
...selectedContactIds,
|
||||||
@ -81,6 +111,11 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
]);
|
]);
|
||||||
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, lang, searchQuery, currentUserId]);
|
}, [folderAllOrderedIds, folderArchivedOrderedIds, selectedContactIds, lang, searchQuery, currentUserId]);
|
||||||
|
|
||||||
|
const handleSelectedCategoriesChange = useCallback((value: CustomPeerType[]) => {
|
||||||
|
setNewSelectedCategoryTypes(value);
|
||||||
|
setIsSubmitShown(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSelectedContactIdsChange = useCallback((value: string[]) => {
|
const handleSelectedContactIdsChange = useCallback((value: string[]) => {
|
||||||
setNewSelectedContactIds(value);
|
setNewSelectedContactIds(value);
|
||||||
setIsSubmitShown(true);
|
setIsSubmitShown(true);
|
||||||
@ -91,10 +126,11 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
privacyKey: getPrivacyKey(screen)!,
|
privacyKey: getPrivacyKey(screen)!,
|
||||||
isAllowList: Boolean(isAllowList),
|
isAllowList: Boolean(isAllowList),
|
||||||
updatedIds: newSelectedContactIds,
|
updatedIds: newSelectedContactIds,
|
||||||
|
isPremiumAllowed: newSelectedCategoryTypes.includes(CUSTOM_PEER_PREMIUM.type) || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
onScreenSelect(SettingsScreens.Privacy);
|
onScreenSelect(SettingsScreens.Privacy);
|
||||||
}, [isAllowList, newSelectedContactIds, onScreenSelect, screen, setPrivacySettings]);
|
}, [isAllowList, newSelectedCategoryTypes, newSelectedContactIds, onScreenSelect, screen]);
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive,
|
isActive,
|
||||||
@ -104,13 +140,16 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="NewChat-inner step-1">
|
<div className="NewChat-inner step-1">
|
||||||
<Picker
|
<Picker
|
||||||
|
categories={withPremiumCategory ? PREMIUM_CATEGORY : undefined}
|
||||||
itemIds={displayedIds || []}
|
itemIds={displayedIds || []}
|
||||||
selectedIds={newSelectedContactIds}
|
selectedIds={newSelectedContactIds}
|
||||||
|
selectedCategories={newSelectedCategoryTypes}
|
||||||
filterValue={searchQuery}
|
filterValue={searchQuery}
|
||||||
filterPlaceholder={isAllowList ? lang('AlwaysAllowPlaceholder') : lang('NeverAllowPlaceholder')}
|
filterPlaceholder={isAllowList ? lang('AlwaysAllowPlaceholder') : lang('NeverAllowPlaceholder')}
|
||||||
searchInputId="new-group-picker-search"
|
searchInputId="new-group-picker-search"
|
||||||
isSearchable
|
isSearchable
|
||||||
onSelectedIdsChange={handleSelectedContactIdsChange}
|
onSelectedIdsChange={handleSelectedContactIdsChange}
|
||||||
|
onSelectedCategoriesChange={handleSelectedCategoriesChange}
|
||||||
onFilterChange={setSearchQuery}
|
onFilterChange={setSearchQuery}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatDateAtTime } from '../../../util/date/dateFormat';
|
import { formatDateAtTime } from '../../../util/date/dateFormat';
|
||||||
|
import { CUSTOM_PEER_TO_BE_DISTRIBUTED } from '../../../util/objects/customPeer';
|
||||||
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
import { getBoostProgressInfo } from '../../common/helpers/boostInfo';
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -197,12 +198,12 @@ const BoostStatistics = ({
|
|||||||
<PrivateChatInfo
|
<PrivateChatInfo
|
||||||
className={styles.user}
|
className={styles.user}
|
||||||
userId={boost.userId}
|
userId={boost.userId}
|
||||||
|
customPeer={!boost.userId ? CUSTOM_PEER_TO_BE_DISTRIBUTED : undefined}
|
||||||
status={lang('BoostExpireOn', formatDateAtTime(lang, boost.expires * 1000))}
|
status={lang('BoostExpireOn', formatDateAtTime(lang, boost.expires * 1000))}
|
||||||
noEmojiStatus
|
noEmojiStatus
|
||||||
forceShowSelf
|
forceShowSelf
|
||||||
noFake
|
noFake
|
||||||
noVerified
|
noVerified
|
||||||
isUnknownUser={!boost.userId}
|
|
||||||
iconElement={boost.multiplier ? renderBoostIcon(boost.multiplier) : undefined}
|
iconElement={boost.multiplier ? renderBoostIcon(boost.multiplier) : undefined}
|
||||||
rightElement={renderBoostTypeIcon(boost)}
|
rightElement={renderBoostTypeIcon(boost)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type OwnProps = {
|
|||||||
value?: string;
|
value?: string;
|
||||||
label: TeactNode;
|
label: TeactNode;
|
||||||
subLabel?: string;
|
subLabel?: string;
|
||||||
checked: boolean;
|
checked?: boolean;
|
||||||
rightIcon?: IconName;
|
rightIcon?: IconName;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
|
|||||||
@ -533,7 +533,9 @@ addActionHandler('setPrivacyVisibility', async (global, actions, payload): Promi
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('setPrivacySettings', async (global, actions, payload): Promise<void> => {
|
addActionHandler('setPrivacySettings', async (global, actions, payload): Promise<void> => {
|
||||||
const { privacyKey, isAllowList, updatedIds } = payload!;
|
const {
|
||||||
|
privacyKey, isAllowList, updatedIds, isPremiumAllowed,
|
||||||
|
} = payload!;
|
||||||
const {
|
const {
|
||||||
privacy: { [privacyKey]: settings },
|
privacy: { [privacyKey]: settings },
|
||||||
} = global.settings;
|
} = global.settings;
|
||||||
@ -545,7 +547,7 @@ addActionHandler('setPrivacySettings', async (global, actions, payload): Promise
|
|||||||
const rules = buildApiInputPrivacyRules(global, {
|
const rules = buildApiInputPrivacyRules(global, {
|
||||||
visibility: settings.visibility,
|
visibility: settings.visibility,
|
||||||
isUnspecified: settings.isUnspecified,
|
isUnspecified: settings.isUnspecified,
|
||||||
shouldAllowPremium: settings.shouldAllowPremium,
|
shouldAllowPremium: isPremiumAllowed,
|
||||||
allowedIds: isAllowList ? updatedIds : [...settings.allowUserIds, ...settings.allowChatIds],
|
allowedIds: isAllowList ? updatedIds : [...settings.allowUserIds, ...settings.allowChatIds],
|
||||||
blockedIds: !isAllowList ? updatedIds : [...settings.blockUserIds, ...settings.blockChatIds],
|
blockedIds: !isAllowList ? updatedIds : [...settings.blockUserIds, ...settings.blockChatIds],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1238,6 +1238,7 @@ export interface ActionPayloads {
|
|||||||
privacyKey: ApiPrivacyKey;
|
privacyKey: ApiPrivacyKey;
|
||||||
isAllowList: boolean;
|
isAllowList: boolean;
|
||||||
updatedIds: string[];
|
updatedIds: string[];
|
||||||
|
isPremiumAllowed?: true;
|
||||||
};
|
};
|
||||||
loadNotificationExceptions: undefined;
|
loadNotificationExceptions: undefined;
|
||||||
setThemeSettings: { theme: ThemeKey } & Partial<IThemeSettings>;
|
setThemeSettings: { theme: ThemeKey } & Partial<IThemeSettings>;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
ApiLanguage, ApiMessage, ApiReaction, ApiStickerSet, ApiUser,
|
ApiLanguage, ApiMessage, ApiReaction, ApiStickerSet, ApiUser,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
|
import type { IconName } from './icons';
|
||||||
|
|
||||||
export type TextPart = TeactNode;
|
export type TextPart = TeactNode;
|
||||||
|
|
||||||
@ -457,3 +458,16 @@ export type InlineBotSettings = {
|
|||||||
switchWebview?: ApiBotInlineSwitchWebview;
|
switchWebview?: ApiBotInlineSwitchWebview;
|
||||||
cacheTime: number;
|
cacheTime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CustomPeerType = 'premium' | 'toBeDistributed';
|
||||||
|
|
||||||
|
export interface CustomPeer {
|
||||||
|
type: CustomPeerType;
|
||||||
|
isCustomPeer: true;
|
||||||
|
titleKey: string;
|
||||||
|
subtitleKey?: string;
|
||||||
|
avatarIcon: IconName;
|
||||||
|
isAvatarSquare?: boolean;
|
||||||
|
peerColorId?: number;
|
||||||
|
withPremiumGradient?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
19
src/util/objects/customPeer.ts
Normal file
19
src/util/objects/customPeer.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { CustomPeer } from '../../types';
|
||||||
|
|
||||||
|
export const CUSTOM_PEER_PREMIUM: CustomPeer = {
|
||||||
|
isCustomPeer: true,
|
||||||
|
type: 'premium',
|
||||||
|
titleKey: 'PrivacyPremium',
|
||||||
|
subtitleKey: 'PrivacyPremiumText',
|
||||||
|
avatarIcon: 'premium',
|
||||||
|
isAvatarSquare: true,
|
||||||
|
withPremiumGradient: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CUSTOM_PEER_TO_BE_DISTRIBUTED: CustomPeer = {
|
||||||
|
isCustomPeer: true,
|
||||||
|
type: 'toBeDistributed',
|
||||||
|
titleKey: 'BoostingToBeDistributed',
|
||||||
|
avatarIcon: 'user',
|
||||||
|
withPremiumGradient: true,
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user