Support birthdays (#4448)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
parent
bb2694dd8c
commit
956a59b457
@ -83,6 +83,10 @@ export function buildPrivacyKey(key: GramJs.TypePrivacyKey): ApiPrivacyKey | und
|
|||||||
return 'voiceMessages';
|
return 'voiceMessages';
|
||||||
case 'PrivacyKeyChatInvite':
|
case 'PrivacyKeyChatInvite':
|
||||||
return 'chatInvite';
|
return 'chatInvite';
|
||||||
|
case 'PrivacyKeyAbout':
|
||||||
|
return 'bio';
|
||||||
|
case 'PrivacyKeyBirthday':
|
||||||
|
return 'birthday';
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiBirthday,
|
||||||
ApiPremiumGiftOption,
|
ApiPremiumGiftOption,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiUserFullInfo,
|
ApiUserFullInfo,
|
||||||
@ -8,10 +9,10 @@ import type {
|
|||||||
ApiUserType,
|
ApiUserType,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { omitUndefined } from '../../../util/iteratees';
|
|
||||||
import { buildApiBotInfo } from './bots';
|
import { buildApiBotInfo } from './bots';
|
||||||
import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business';
|
import { buildApiBusinessIntro, buildApiBusinessLocation, buildApiBusinessWorkHours } from './business';
|
||||||
import { buildApiPhoto, buildApiUsernames } from './common';
|
import { buildApiPhoto, buildApiUsernames } from './common';
|
||||||
|
import { omitVirtualClassFields } from './helpers';
|
||||||
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers';
|
import { buildApiEmojiStatus, buildApiPeerColor, buildApiPeerId } from './peers';
|
||||||
|
|
||||||
export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo {
|
export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUserFullInfo {
|
||||||
@ -21,14 +22,14 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
profilePhoto, voiceMessagesForbidden, premiumGifts,
|
profilePhoto, voiceMessagesForbidden, premiumGifts,
|
||||||
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
||||||
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
||||||
personalChannelId, personalChannelMessage,
|
birthday, personalChannelId, personalChannelMessage,
|
||||||
},
|
},
|
||||||
users,
|
users,
|
||||||
} = mtpUserFull;
|
} = mtpUserFull;
|
||||||
|
|
||||||
const userId = buildApiPeerId(users[0].id, 'user');
|
const userId = buildApiPeerId(users[0].id, 'user');
|
||||||
|
|
||||||
return omitUndefined<ApiUserFullInfo>({
|
return {
|
||||||
bio: about,
|
bio: about,
|
||||||
commonChatsCount,
|
commonChatsCount,
|
||||||
pinnedMessageId: pinnedMsgId,
|
pinnedMessageId: pinnedMsgId,
|
||||||
@ -42,12 +43,13 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
|||||||
premiumGifts: premiumGifts?.map((gift) => buildApiPremiumGiftOption(gift)),
|
premiumGifts: premiumGifts?.map((gift) => buildApiPremiumGiftOption(gift)),
|
||||||
botInfo: botInfo && buildApiBotInfo(botInfo, userId),
|
botInfo: botInfo && buildApiBotInfo(botInfo, userId),
|
||||||
isContactRequirePremium: contactRequirePremium,
|
isContactRequirePremium: contactRequirePremium,
|
||||||
|
birthday: birthday && buildApiBirthday(birthday),
|
||||||
businessLocation: businessLocation && buildApiBusinessLocation(businessLocation),
|
businessLocation: businessLocation && buildApiBusinessLocation(businessLocation),
|
||||||
businessWorkHours: businessWorkHours && buildApiBusinessWorkHours(businessWorkHours),
|
businessWorkHours: businessWorkHours && buildApiBusinessWorkHours(businessWorkHours),
|
||||||
businessIntro: businessIntro && buildApiBusinessIntro(businessIntro),
|
businessIntro: businessIntro && buildApiBusinessIntro(businessIntro),
|
||||||
personalChannelId: personalChannelId && buildApiPeerId(personalChannelId, 'channel'),
|
personalChannelId: personalChannelId && buildApiPeerId(personalChannelId, 'channel'),
|
||||||
personalChannelMessageId: personalChannelMessage,
|
personalChannelMessageId: personalChannelMessage,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||||
@ -161,3 +163,7 @@ export function buildApiPremiumGiftOption(option: GramJs.TypePremiumGiftOption):
|
|||||||
botUrl,
|
botUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiBirthday(birthday: GramJs.TypeBirthday): ApiBirthday {
|
||||||
|
return omitVirtualClassFields(birthday);
|
||||||
|
}
|
||||||
|
|||||||
@ -482,6 +482,9 @@ export function buildInputPrivacyKey(privacyKey: ApiPrivacyKey) {
|
|||||||
|
|
||||||
case 'bio':
|
case 'bio':
|
||||||
return new GramJs.InputPrivacyKeyAbout();
|
return new GramJs.InputPrivacyKeyAbout();
|
||||||
|
|
||||||
|
case 'birthday':
|
||||||
|
return new GramJs.InputPrivacyKeyBirthday();
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export interface ApiUserFullInfo {
|
|||||||
isTranslationDisabled?: true;
|
isTranslationDisabled?: true;
|
||||||
hasPinnedStories?: boolean;
|
hasPinnedStories?: boolean;
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
|
birthday?: ApiBirthday;
|
||||||
personalChannelId?: string;
|
personalChannelId?: string;
|
||||||
personalChannelMessageId?: number;
|
personalChannelMessageId?: number;
|
||||||
businessLocation?: ApiBusinessLocation;
|
businessLocation?: ApiBusinessLocation;
|
||||||
@ -119,3 +120,9 @@ export interface ApiEmojiStatus {
|
|||||||
documentId: string;
|
documentId: string;
|
||||||
until?: number;
|
until?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiBirthday {
|
||||||
|
day: number;
|
||||||
|
month: number;
|
||||||
|
year?: number;
|
||||||
|
}
|
||||||
|
|||||||
@ -27,6 +27,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
|
margin-inline-end: 0.375rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,28 +1,28 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useMemo, useRef,
|
memo, useEffect, useMemo, useRef,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiBusinessWorkHours } from '../../api/types';
|
import type { ApiBusinessWorkHours } from '../../../api/types';
|
||||||
|
|
||||||
import { requestMeasure, requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatTime, formatWeekday } from '../../util/date/dateFormat';
|
import { formatTime, formatWeekday } from '../../../util/date/dateFormat';
|
||||||
import {
|
import {
|
||||||
getUtcOffset, getWeekStart, shiftTimeRanges, splitDays,
|
getUtcOffset, getWeekStart, shiftTimeRanges, splitDays,
|
||||||
} from '../../util/date/workHours';
|
} from '../../../util/date/workHours';
|
||||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
|
|
||||||
import useInterval from '../../hooks/schedulers/useInterval';
|
import useInterval from '../../../hooks/schedulers/useInterval';
|
||||||
import useDerivedState from '../../hooks/useDerivedState';
|
import useDerivedState from '../../../hooks/useDerivedState';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useSelectorSignal from '../../hooks/useSelectorSignal';
|
import useSelectorSignal from '../../../hooks/useSelectorSignal';
|
||||||
|
|
||||||
import ListItem from '../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import Transition, { ACTIVE_SLIDE_CLASS_NAME, TO_SLIDE_CLASS_NAME } from '../ui/Transition';
|
import Transition, { ACTIVE_SLIDE_CLASS_NAME, TO_SLIDE_CLASS_NAME } from '../../ui/Transition';
|
||||||
import Icon from './Icon';
|
import Icon from '../Icon';
|
||||||
|
|
||||||
import styles from './BusinessHours.module.scss';
|
import styles from './BusinessHours.module.scss';
|
||||||
|
|
||||||
@ -142,6 +142,7 @@ const BusinessHours = ({
|
|||||||
className={styles.root}
|
className={styles.root}
|
||||||
isStatic={isExpanded}
|
isStatic={isExpanded}
|
||||||
ripple
|
ripple
|
||||||
|
narrow
|
||||||
withColorTransition
|
withColorTransition
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
38
src/components/common/profile/ChatExtra.module.scss
Normal file
38
src/components/common/profile/ChatExtra.module.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.businessLocation {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-inline-start: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personalChannel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personalChannelTitle {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-inline-start: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personalChannelSubscribers {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-inline-end: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personalChannelItem {
|
||||||
|
grid-column: 1 / span 2;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useMemo, useState,
|
memo, useEffect, useMemo, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiCountryCode, ApiUser, ApiUserFullInfo, ApiUsername,
|
ApiChat, ApiCountryCode, ApiUser, ApiUserFullInfo, ApiUsername,
|
||||||
} from '../../api/types';
|
} from '../../../api/types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { TME_LINK_PREFIX } from '../../config';
|
import { TME_LINK_PREFIX } from '../../../config';
|
||||||
import {
|
import {
|
||||||
buildStaticMapHash,
|
buildStaticMapHash,
|
||||||
getChatLink,
|
getChatLink,
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
isChatChannel,
|
isChatChannel,
|
||||||
isUserRightBanned,
|
isUserRightBanned,
|
||||||
selectIsChatMuted,
|
selectIsChatMuted,
|
||||||
} from '../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
@ -27,25 +27,26 @@ import {
|
|||||||
selectTopicLink,
|
selectTopicLink,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import { copyTextToClipboard } from '../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
|
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||||
import { debounce } from '../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import stopEvent from '../../util/stopEvent';
|
import stopEvent from '../../../util/stopEvent';
|
||||||
import { ChatAnimationTypes } from '../left/main/hooks';
|
import { ChatAnimationTypes } from '../../left/main/hooks';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from '../helpers/renderText';
|
||||||
|
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio';
|
import useDevicePixelRatio from '../../../hooks/window/useDevicePixelRatio';
|
||||||
|
|
||||||
import Chat from '../left/main/Chat';
|
import Chat from '../../left/main/Chat';
|
||||||
import ListItem from '../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import Skeleton from '../ui/placeholder/Skeleton';
|
import Skeleton from '../../ui/placeholder/Skeleton';
|
||||||
import Switcher from '../ui/Switcher';
|
import Switcher from '../../ui/Switcher';
|
||||||
import BusinessHours from './BusinessHours';
|
import BusinessHours from './BusinessHours';
|
||||||
|
import UserBirthday from './UserBirthday';
|
||||||
|
|
||||||
import styles from './ChatExtra.module.scss';
|
import styles from './ChatExtra.module.scss';
|
||||||
|
|
||||||
@ -115,6 +116,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
businessLocation,
|
businessLocation,
|
||||||
businessWorkHours,
|
businessWorkHours,
|
||||||
personalChannelMessageId,
|
personalChannelMessageId,
|
||||||
|
birthday,
|
||||||
} = userFullInfo || {};
|
} = userFullInfo || {};
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -140,10 +142,10 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
const locationRightComponent = useMemo(() => {
|
const locationRightComponent = useMemo(() => {
|
||||||
if (!businessLocation?.geo) return undefined;
|
if (!businessLocation?.geo) return undefined;
|
||||||
if (locationBlobUrl) {
|
if (locationBlobUrl) {
|
||||||
return <img src={locationBlobUrl} alt="" className="business-location" />;
|
return <img src={locationBlobUrl} alt="" className={styles.businessLocation} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Skeleton className="business-location" />;
|
return <Skeleton className={styles.businessLocation} />;
|
||||||
}, [businessLocation, locationBlobUrl]);
|
}, [businessLocation, locationBlobUrl]);
|
||||||
|
|
||||||
const isTopicInfo = Boolean(topicId && topicId !== MAIN_THREAD_ID);
|
const isTopicInfo = Boolean(topicId && topicId !== MAIN_THREAD_ID);
|
||||||
@ -329,6 +331,9 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
<span className="subtitle">{lang('SetUrlPlaceholder')}</span>
|
<span className="subtitle">{lang('SetUrlPlaceholder')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{birthday && (
|
||||||
|
<UserBirthday key={peerId} birthday={birthday} user={user!} isInSettings={isInSettings} />
|
||||||
|
)}
|
||||||
{!isInSettings && (
|
{!isInSettings && (
|
||||||
<ListItem icon="unmute" ripple onClick={handleNotificationChange}>
|
<ListItem icon="unmute" ripple onClick={handleNotificationChange}>
|
||||||
<span>{lang('Notifications')}</span>
|
<span>{lang('Notifications')}</span>
|
||||||
@ -348,6 +353,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
icon="location"
|
icon="location"
|
||||||
ripple
|
ripple
|
||||||
multiline
|
multiline
|
||||||
|
narrow
|
||||||
rightElement={locationRightComponent}
|
rightElement={locationRightComponent}
|
||||||
onClick={handleClickLocation}
|
onClick={handleClickLocation}
|
||||||
>
|
>
|
||||||
83
src/components/common/profile/UserBirthday.module.scss
Normal file
83
src/components/common/profile/UserBirthday.module.scss
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
--digit-offset: 0;
|
||||||
|
--digit-offset-x: calc(8rem * var(--digit-offset) * 0.75);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 8rem;
|
||||||
|
height: 8rem;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
transform: scale(0);
|
||||||
|
top: 50%;
|
||||||
|
left: calc(10% + var(--digit-offset-x));
|
||||||
|
offset-path: path('M 0 0 C 128 -46 99 -376 93 -529');
|
||||||
|
offset-anchor: center;
|
||||||
|
offset-rotate: 0deg;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
animation: 2.75s float 0.25s,
|
||||||
|
2s show-up calc(var(--digit-offset) * 0.5s),
|
||||||
|
1s dissapear 2s;
|
||||||
|
animation-timing-function: ease-in;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiftOrigin {
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 1rem);
|
||||||
|
left: 10rem;
|
||||||
|
transform: translate(-50%, -50%) scaleX(-1);
|
||||||
|
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
width: 18rem;
|
||||||
|
height: 18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftIcon {
|
||||||
|
margin-inline-end: -0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes show-up {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: scale(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dissapear {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
from {
|
||||||
|
offset-distance: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
offset-distance: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/components/common/profile/UserBirthday.tsx
Normal file
230
src/components/common/profile/UserBirthday.tsx
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import React, {
|
||||||
|
memo, useEffect, useMemo, useRef,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type ApiBirthday, ApiMediaFormat, type ApiStickerSet, type ApiUser,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
|
||||||
|
import { getStickerMediaHash } from '../../../global/helpers';
|
||||||
|
import { selectIsPremiumPurchaseBlocked } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatDateToString } from '../../../util/date/dateFormat';
|
||||||
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
|
import * as mediaLoader from '../../../util/mediaLoader';
|
||||||
|
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment';
|
||||||
|
import renderText from '../helpers/renderText';
|
||||||
|
|
||||||
|
import useTimeout from '../../../hooks/schedulers/useTimeout';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import StickerView from '../StickerView';
|
||||||
|
|
||||||
|
import styles from './UserBirthday.module.scss';
|
||||||
|
|
||||||
|
const NUMBER_EMOJI_SUFFIX = '\uFE0F\u20E3';
|
||||||
|
const NUMBER_STICKER_SIZE = 128;
|
||||||
|
const EFFECT_EMOJIS = ['🎉', '🎆', '🎈'];
|
||||||
|
const EFFECT_SIZE = 288;
|
||||||
|
const ANIMATION_DURATION = 3000;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
user: ApiUser;
|
||||||
|
birthday: ApiBirthday;
|
||||||
|
isInSettings?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isPremiumPurchaseBlocked?: boolean;
|
||||||
|
birthdayNumbers?: ApiStickerSet;
|
||||||
|
animatedEmojiEffects?: ApiStickerSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserBirthday = ({
|
||||||
|
user,
|
||||||
|
birthday,
|
||||||
|
isPremiumPurchaseBlocked,
|
||||||
|
birthdayNumbers,
|
||||||
|
animatedEmojiEffects,
|
||||||
|
isInSettings,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const { openGiftPremiumModal, requestConfetti } = getActions();
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const animationPlayedRef = useRef(false);
|
||||||
|
const [isPlayingAnimation, playAnimation, stopAnimation] = useFlag();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const {
|
||||||
|
formattedDate,
|
||||||
|
isToday,
|
||||||
|
age,
|
||||||
|
} = useMemo(() => {
|
||||||
|
const today = new Date();
|
||||||
|
const date = new Date();
|
||||||
|
if (birthday.year) {
|
||||||
|
date.setFullYear(birthday.year);
|
||||||
|
}
|
||||||
|
date.setMonth(birthday.month - 1);
|
||||||
|
date.setDate(birthday.day);
|
||||||
|
date.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const formatted = formatDateToString(date, lang.code, true, 'long');
|
||||||
|
const isBirthdayToday = date.getDate() === today.getDate() && date.getMonth() === today.getMonth();
|
||||||
|
return {
|
||||||
|
formattedDate: formatted,
|
||||||
|
isToday: isBirthdayToday || true, // TODO REMOVE AFTER TESTING
|
||||||
|
age: birthday.year && getAge(date),
|
||||||
|
};
|
||||||
|
}, [birthday, lang]);
|
||||||
|
|
||||||
|
const numbersForAge = useMemo(() => {
|
||||||
|
if (!age || !isToday) return undefined;
|
||||||
|
const numbers = birthdayNumbers?.stickers?.filter(({ emoji }) => emoji?.endsWith(NUMBER_EMOJI_SUFFIX));
|
||||||
|
if (!numbers) return undefined;
|
||||||
|
const byEmoji = buildCollectionByKey(numbers, 'emoji');
|
||||||
|
|
||||||
|
const ageDigits = age.toString().split('');
|
||||||
|
return ageDigits.map((digit) => byEmoji[digit + NUMBER_EMOJI_SUFFIX]);
|
||||||
|
}, [age, birthdayNumbers?.stickers, isToday]);
|
||||||
|
|
||||||
|
const effectSticker = useMemo(() => {
|
||||||
|
if (!isToday) return undefined;
|
||||||
|
const randomEffect = EFFECT_EMOJIS[Math.floor(Math.random() * EFFECT_EMOJIS.length)];
|
||||||
|
return animatedEmojiEffects?.stickers?.find(({ emoji }) => emoji === randomEffect);
|
||||||
|
}, [animatedEmojiEffects?.stickers, isToday]);
|
||||||
|
|
||||||
|
// Preload stickers
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isToday || !numbersForAge) return;
|
||||||
|
|
||||||
|
numbersForAge.forEach((sticker) => {
|
||||||
|
const hash = getStickerMediaHash(sticker.id);
|
||||||
|
mediaLoader.fetch(hash, ApiMediaFormat.BlobUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (effectSticker) {
|
||||||
|
const effectHash = getStickerMediaHash(effectSticker.id);
|
||||||
|
mediaLoader.fetch(effectHash, ApiMediaFormat.BlobUrl);
|
||||||
|
}
|
||||||
|
}, [effectSticker, isToday, numbersForAge]);
|
||||||
|
|
||||||
|
useTimeout(stopAnimation, isPlayingAnimation ? ANIMATION_DURATION : undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPlayingAnimation) {
|
||||||
|
animationPlayedRef.current = true;
|
||||||
|
|
||||||
|
const column = document.getElementById(isInSettings ? 'LeftColumn' : 'RightColumn');
|
||||||
|
if (!column) return;
|
||||||
|
|
||||||
|
requestMeasure(() => {
|
||||||
|
const {
|
||||||
|
top, left, width, height,
|
||||||
|
} = column.getBoundingClientRect();
|
||||||
|
|
||||||
|
requestConfetti({
|
||||||
|
top,
|
||||||
|
left,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
style: 'top-down',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isInSettings, isPlayingAnimation]);
|
||||||
|
|
||||||
|
const valueKey = `ProfileBirthday${isToday ? 'Today' : ''}Value${age ? 'Year' : ''}`;
|
||||||
|
|
||||||
|
const canGiftPremium = isToday && !user.isPremium && !user.isSelf && !isPremiumPurchaseBlocked;
|
||||||
|
|
||||||
|
const handleOpenGiftModal = useLastCallback(() => {
|
||||||
|
openGiftPremiumModal({ forUserId: user.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
if (!isToday) return;
|
||||||
|
|
||||||
|
if (canGiftPremium && animationPlayedRef.current) {
|
||||||
|
handleOpenGiftModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playAnimation();
|
||||||
|
});
|
||||||
|
|
||||||
|
const isStatic = !isToday && !canGiftPremium;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<ListItem
|
||||||
|
icon="calendar"
|
||||||
|
secondaryIcon={canGiftPremium ? 'gift' : undefined}
|
||||||
|
secondaryIconClassName={styles.giftIcon}
|
||||||
|
multiline
|
||||||
|
narrow
|
||||||
|
ref={ref}
|
||||||
|
ripple={!isStatic}
|
||||||
|
onClick={handleClick}
|
||||||
|
isStatic={isStatic}
|
||||||
|
onSecondaryIconClick={handleOpenGiftModal}
|
||||||
|
>
|
||||||
|
<div className="title">{renderText(lang(valueKey, [formattedDate, age], undefined, age))}</div>
|
||||||
|
<span className="subtitle">{lang(isToday ? 'ProfileBirthdayToday' : 'ProfileBirthday')}</span>
|
||||||
|
</ListItem>
|
||||||
|
{isPlayingAnimation && IS_OFFSET_PATH_SUPPORTED && numbersForAge?.map((sticker, index) => (
|
||||||
|
<div
|
||||||
|
className={buildClassName(styles.number, index > 0 && styles.shiftOrigin)}
|
||||||
|
style={`--digit-offset: ${index}`}
|
||||||
|
>
|
||||||
|
<StickerView
|
||||||
|
containerRef={ref}
|
||||||
|
sticker={sticker}
|
||||||
|
size={NUMBER_STICKER_SIZE}
|
||||||
|
forceAlways
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{isPlayingAnimation && effectSticker && (
|
||||||
|
<div className={styles.effect}>
|
||||||
|
<StickerView
|
||||||
|
containerRef={ref}
|
||||||
|
sticker={effectSticker}
|
||||||
|
size={EFFECT_SIZE}
|
||||||
|
shouldLoop
|
||||||
|
forceAlways
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
const { birthdayNumbers, animatedEmojiEffects } = global;
|
||||||
|
return {
|
||||||
|
birthdayNumbers,
|
||||||
|
animatedEmojiEffects,
|
||||||
|
isPremiumPurchaseBlocked: selectIsPremiumPurchaseBlocked(global),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(UserBirthday));
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/7091965
|
||||||
|
function getAge(birthdate: Date) {
|
||||||
|
const today = new Date();
|
||||||
|
let age = today.getFullYear() - birthdate.getFullYear();
|
||||||
|
const m = today.getMonth() - birthdate.getMonth();
|
||||||
|
if (m < 0 || (m === 0 && today.getDate() < birthdate.getDate())) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return age;
|
||||||
|
}
|
||||||
@ -189,6 +189,7 @@ function LeftColumn({
|
|||||||
case SettingsScreens.PrivacyLastSeen:
|
case SettingsScreens.PrivacyLastSeen:
|
||||||
case SettingsScreens.PrivacyProfilePhoto:
|
case SettingsScreens.PrivacyProfilePhoto:
|
||||||
case SettingsScreens.PrivacyBio:
|
case SettingsScreens.PrivacyBio:
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
case SettingsScreens.PrivacyPhoneCall:
|
case SettingsScreens.PrivacyPhoneCall:
|
||||||
case SettingsScreens.PrivacyPhoneP2P:
|
case SettingsScreens.PrivacyPhoneP2P:
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
@ -243,6 +244,10 @@ function LeftColumn({
|
|||||||
case SettingsScreens.PrivacyBioDeniedContacts:
|
case SettingsScreens.PrivacyBioDeniedContacts:
|
||||||
setSettingsScreen(SettingsScreens.PrivacyBio);
|
setSettingsScreen(SettingsScreens.PrivacyBio);
|
||||||
return;
|
return;
|
||||||
|
case SettingsScreens.PrivacyBirthdayAllowedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayDeniedContacts:
|
||||||
|
setSettingsScreen(SettingsScreens.PrivacyBirthday);
|
||||||
|
return;
|
||||||
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
|
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
|
||||||
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
|
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
|
||||||
setSettingsScreen(SettingsScreens.PrivacyPhoneCall);
|
setSettingsScreen(SettingsScreens.PrivacyPhoneCall);
|
||||||
|
|||||||
@ -105,6 +105,11 @@ const PRIVACY_BIO_SCREENS = [
|
|||||||
SettingsScreens.PrivacyBioDeniedContacts,
|
SettingsScreens.PrivacyBioDeniedContacts,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const PRIVACY_BIRTHDAY_SCREENS = [
|
||||||
|
SettingsScreens.PrivacyBirthdayAllowedContacts,
|
||||||
|
SettingsScreens.PrivacyBirthdayDeniedContacts,
|
||||||
|
];
|
||||||
|
|
||||||
const PRIVACY_PHONE_CALL_SCREENS = [
|
const PRIVACY_PHONE_CALL_SCREENS = [
|
||||||
SettingsScreens.PrivacyPhoneCallAllowedContacts,
|
SettingsScreens.PrivacyPhoneCallAllowedContacts,
|
||||||
SettingsScreens.PrivacyPhoneCallDeniedContacts,
|
SettingsScreens.PrivacyPhoneCallDeniedContacts,
|
||||||
@ -198,6 +203,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
[SettingsScreens.PrivacyLastSeen]: PRIVACY_LAST_SEEN_PHONE_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyLastSeen]: PRIVACY_LAST_SEEN_PHONE_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyProfilePhoto]: PRIVACY_PROFILE_PHOTO_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyProfilePhoto]: PRIVACY_PROFILE_PHOTO_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyBio]: PRIVACY_BIO_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyBio]: PRIVACY_BIO_SCREENS.includes(activeScreen),
|
||||||
|
[SettingsScreens.PrivacyBirthday]: PRIVACY_BIRTHDAY_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyPhoneCall]: PRIVACY_PHONE_CALL_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyPhoneCall]: PRIVACY_PHONE_CALL_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyPhoneP2P]: PRIVACY_PHONE_P2P_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyPhoneP2P]: PRIVACY_PHONE_P2P_SCREENS.includes(activeScreen),
|
||||||
[SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_SCREENS.includes(activeScreen),
|
[SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_SCREENS.includes(activeScreen),
|
||||||
@ -323,6 +329,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyLastSeen:
|
case SettingsScreens.PrivacyLastSeen:
|
||||||
case SettingsScreens.PrivacyProfilePhoto:
|
case SettingsScreens.PrivacyProfilePhoto:
|
||||||
case SettingsScreens.PrivacyBio:
|
case SettingsScreens.PrivacyBio:
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
case SettingsScreens.PrivacyPhoneCall:
|
case SettingsScreens.PrivacyPhoneCall:
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
case SettingsScreens.PrivacyVoiceMessages:
|
case SettingsScreens.PrivacyVoiceMessages:
|
||||||
@ -340,6 +347,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyLastSeenAllowedContacts:
|
case SettingsScreens.PrivacyLastSeenAllowedContacts:
|
||||||
case SettingsScreens.PrivacyProfilePhotoAllowedContacts:
|
case SettingsScreens.PrivacyProfilePhotoAllowedContacts:
|
||||||
case SettingsScreens.PrivacyBioAllowedContacts:
|
case SettingsScreens.PrivacyBioAllowedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayAllowedContacts:
|
||||||
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
|
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
|
||||||
case SettingsScreens.PrivacyPhoneP2PAllowedContacts:
|
case SettingsScreens.PrivacyPhoneP2PAllowedContacts:
|
||||||
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
||||||
@ -359,6 +367,7 @@ const Settings: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyLastSeenDeniedContacts:
|
case SettingsScreens.PrivacyLastSeenDeniedContacts:
|
||||||
case SettingsScreens.PrivacyProfilePhotoDeniedContacts:
|
case SettingsScreens.PrivacyProfilePhotoDeniedContacts:
|
||||||
case SettingsScreens.PrivacyBioDeniedContacts:
|
case SettingsScreens.PrivacyBioDeniedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayDeniedContacts:
|
||||||
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
|
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
|
||||||
case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
|
case SettingsScreens.PrivacyPhoneP2PDeniedContacts:
|
||||||
case SettingsScreens.PrivacyForwardingDeniedContacts:
|
case SettingsScreens.PrivacyForwardingDeniedContacts:
|
||||||
|
|||||||
@ -115,6 +115,8 @@ const SettingsHeader: FC<OwnProps> = ({
|
|||||||
return <h3>{lang('Privacy.ProfilePhoto')}</h3>;
|
return <h3>{lang('Privacy.ProfilePhoto')}</h3>;
|
||||||
case SettingsScreens.PrivacyBio:
|
case SettingsScreens.PrivacyBio:
|
||||||
return <h3>{lang('PrivacyBio')}</h3>;
|
return <h3>{lang('PrivacyBio')}</h3>;
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
|
return <h3>{lang('PrivacyBirthday')}</h3>;
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
return <h3>{lang('PrivacyForwards')}</h3>;
|
return <h3>{lang('PrivacyForwards')}</h3>;
|
||||||
case SettingsScreens.PrivacyVoiceMessages:
|
case SettingsScreens.PrivacyVoiceMessages:
|
||||||
@ -130,6 +132,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyLastSeenAllowedContacts:
|
case SettingsScreens.PrivacyLastSeenAllowedContacts:
|
||||||
case SettingsScreens.PrivacyProfilePhotoAllowedContacts:
|
case SettingsScreens.PrivacyProfilePhotoAllowedContacts:
|
||||||
case SettingsScreens.PrivacyBioAllowedContacts:
|
case SettingsScreens.PrivacyBioAllowedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayAllowedContacts:
|
||||||
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
||||||
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
|
case SettingsScreens.PrivacyVoiceMessagesAllowedContacts:
|
||||||
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
|
case SettingsScreens.PrivacyGroupChatsAllowedContacts:
|
||||||
@ -140,6 +143,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
|||||||
case SettingsScreens.PrivacyLastSeenDeniedContacts:
|
case SettingsScreens.PrivacyLastSeenDeniedContacts:
|
||||||
case SettingsScreens.PrivacyProfilePhotoDeniedContacts:
|
case SettingsScreens.PrivacyProfilePhotoDeniedContacts:
|
||||||
case SettingsScreens.PrivacyBioDeniedContacts:
|
case SettingsScreens.PrivacyBioDeniedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayDeniedContacts:
|
||||||
case SettingsScreens.PrivacyForwardingDeniedContacts:
|
case SettingsScreens.PrivacyForwardingDeniedContacts:
|
||||||
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
|
case SettingsScreens.PrivacyVoiceMessagesDeniedContacts:
|
||||||
case SettingsScreens.PrivacyGroupChatsDeniedContacts:
|
case SettingsScreens.PrivacyGroupChatsDeniedContacts:
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
|
|||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import ChatExtra from '../../common/ChatExtra';
|
|
||||||
import PremiumIcon from '../../common/PremiumIcon';
|
import PremiumIcon from '../../common/PremiumIcon';
|
||||||
|
import ChatExtra from '../../common/profile/ChatExtra';
|
||||||
import ProfileInfo from '../../common/ProfileInfo';
|
import ProfileInfo from '../../common/ProfileInfo';
|
||||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { GlobalState } from '../../../global/types';
|
||||||
import type { ApiPrivacySettings } from '../../../types';
|
import type { ApiPrivacySettings } from '../../../types';
|
||||||
import { SettingsScreens } from '../../../types';
|
import { SettingsScreens } from '../../../types';
|
||||||
|
|
||||||
@ -33,14 +34,7 @@ type StateProps = {
|
|||||||
shouldArchiveAndMuteNewNonContact?: boolean;
|
shouldArchiveAndMuteNewNonContact?: boolean;
|
||||||
shouldNewNonContactPeersRequirePremium?: boolean;
|
shouldNewNonContactPeersRequirePremium?: boolean;
|
||||||
canDisplayChatInTitle?: boolean;
|
canDisplayChatInTitle?: boolean;
|
||||||
privacyPhoneNumber?: ApiPrivacySettings;
|
privacy: GlobalState['settings']['privacy'];
|
||||||
privacyLastSeen?: ApiPrivacySettings;
|
|
||||||
privacyProfilePhoto?: ApiPrivacySettings;
|
|
||||||
privacyForwarding?: ApiPrivacySettings;
|
|
||||||
privacyVoiceMessages?: ApiPrivacySettings;
|
|
||||||
privacyGroupChats?: ApiPrivacySettings;
|
|
||||||
privacyPhoneCall?: ApiPrivacySettings;
|
|
||||||
privacyBio?: ApiPrivacySettings;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||||
@ -57,14 +51,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
canDisplayChatInTitle,
|
canDisplayChatInTitle,
|
||||||
canSetPasscode,
|
canSetPasscode,
|
||||||
privacyPhoneNumber,
|
privacy,
|
||||||
privacyLastSeen,
|
|
||||||
privacyProfilePhoto,
|
|
||||||
privacyForwarding,
|
|
||||||
privacyVoiceMessages,
|
|
||||||
privacyGroupChats,
|
|
||||||
privacyPhoneCall,
|
|
||||||
privacyBio,
|
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onReset,
|
onReset,
|
||||||
}) => {
|
}) => {
|
||||||
@ -206,7 +193,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('PrivacyPhoneTitle')}</span>
|
<span className="title">{lang('PrivacyPhoneTitle')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyPhoneNumber)}
|
{getVisibilityValue(privacy.phoneNumber)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -219,7 +206,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('LastSeenTitle')}</span>
|
<span className="title">{lang('LastSeenTitle')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyLastSeen)}
|
{getVisibilityValue(privacy.lastSeen)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -232,7 +219,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('PrivacyProfilePhotoTitle')}</span>
|
<span className="title">{lang('PrivacyProfilePhotoTitle')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyProfilePhoto)}
|
{getVisibilityValue(privacy.profilePhoto)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -245,7 +232,20 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('PrivacyBio')}</span>
|
<span className="title">{lang('PrivacyBio')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyBio)}
|
{getVisibilityValue(privacy.bio)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem
|
||||||
|
narrow
|
||||||
|
className="no-icon"
|
||||||
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
|
onClick={() => onScreenSelect(SettingsScreens.PrivacyBirthday)}
|
||||||
|
>
|
||||||
|
<div className="multiline-menu-item">
|
||||||
|
<span className="title">{lang('PrivacyBirthday')}</span>
|
||||||
|
<span className="subtitle" dir="auto">
|
||||||
|
{getVisibilityValue(privacy.birthday)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -258,7 +258,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('PrivacyForwardsTitle')}</span>
|
<span className="title">{lang('PrivacyForwardsTitle')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyForwarding)}
|
{getVisibilityValue(privacy.forwards)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -271,7 +271,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('WhoCanCallMe')}</span>
|
<span className="title">{lang('WhoCanCallMe')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyPhoneCall)}
|
{getVisibilityValue(privacy.phoneCall)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -286,7 +286,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('PrivacyVoiceMessagesTitle')}</span>
|
<span className="title">{lang('PrivacyVoiceMessagesTitle')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyVoiceMessages)}
|
{getVisibilityValue(privacy.voiceMessages)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -315,7 +315,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
|||||||
<div className="multiline-menu-item">
|
<div className="multiline-menu-item">
|
||||||
<span className="title">{lang('WhoCanAddMe')}</span>
|
<span className="title">{lang('WhoCanAddMe')}</span>
|
||||||
<span className="subtitle" dir="auto">
|
<span className="subtitle" dir="auto">
|
||||||
{getVisibilityValue(privacyGroupChats)}
|
{getVisibilityValue(privacy.chatInvite)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -392,14 +392,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
shouldArchiveAndMuteNewNonContact,
|
shouldArchiveAndMuteNewNonContact,
|
||||||
canChangeSensitive,
|
canChangeSensitive,
|
||||||
shouldNewNonContactPeersRequirePremium,
|
shouldNewNonContactPeersRequirePremium,
|
||||||
privacyPhoneNumber: privacy.phoneNumber,
|
privacy,
|
||||||
privacyLastSeen: privacy.lastSeen,
|
|
||||||
privacyProfilePhoto: privacy.profilePhoto,
|
|
||||||
privacyForwarding: privacy.forwards,
|
|
||||||
privacyVoiceMessages: privacy.voiceMessages,
|
|
||||||
privacyGroupChats: privacy.chatInvite,
|
|
||||||
privacyPhoneCall: privacy.phoneCall,
|
|
||||||
privacyBio: privacy.bio,
|
|
||||||
canDisplayChatInTitle,
|
canDisplayChatInTitle,
|
||||||
canSetPasscode: selectCanSetPasscode(global),
|
canSetPasscode: selectCanSetPasscode(global),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -179,6 +179,8 @@ function PrivacySubsection({
|
|||||||
return lang('PrivacyProfilePhotoTitle');
|
return lang('PrivacyProfilePhotoTitle');
|
||||||
case SettingsScreens.PrivacyBio:
|
case SettingsScreens.PrivacyBio:
|
||||||
return lang('PrivacyBioTitle');
|
return lang('PrivacyBioTitle');
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
|
return lang('PrivacyBirthdayTitle');
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
return lang('PrivacyForwardsTitle');
|
return lang('PrivacyForwardsTitle');
|
||||||
case SettingsScreens.PrivacyVoiceMessages:
|
case SettingsScreens.PrivacyVoiceMessages:
|
||||||
@ -233,6 +235,8 @@ function PrivacySubsection({
|
|||||||
return SettingsScreens.PrivacyProfilePhotoAllowedContacts;
|
return SettingsScreens.PrivacyProfilePhotoAllowedContacts;
|
||||||
case SettingsScreens.PrivacyBio:
|
case SettingsScreens.PrivacyBio:
|
||||||
return SettingsScreens.PrivacyBioAllowedContacts;
|
return SettingsScreens.PrivacyBioAllowedContacts;
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
|
return SettingsScreens.PrivacyBirthdayAllowedContacts;
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
return SettingsScreens.PrivacyForwardingAllowedContacts;
|
return SettingsScreens.PrivacyForwardingAllowedContacts;
|
||||||
case SettingsScreens.PrivacyPhoneCall:
|
case SettingsScreens.PrivacyPhoneCall:
|
||||||
@ -256,6 +260,8 @@ function PrivacySubsection({
|
|||||||
return SettingsScreens.PrivacyProfilePhotoDeniedContacts;
|
return SettingsScreens.PrivacyProfilePhotoDeniedContacts;
|
||||||
case SettingsScreens.PrivacyBio:
|
case SettingsScreens.PrivacyBio:
|
||||||
return SettingsScreens.PrivacyBioDeniedContacts;
|
return SettingsScreens.PrivacyBioDeniedContacts;
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
|
return SettingsScreens.PrivacyBirthdayDeniedContacts;
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
return SettingsScreens.PrivacyForwardingDeniedContacts;
|
return SettingsScreens.PrivacyForwardingDeniedContacts;
|
||||||
case SettingsScreens.PrivacyPhoneCall:
|
case SettingsScreens.PrivacyPhoneCall:
|
||||||
@ -355,6 +361,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
primaryPrivacy = privacy.bio;
|
primaryPrivacy = privacy.bio;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
|
primaryPrivacy = privacy.birthday;
|
||||||
|
break;
|
||||||
|
|
||||||
case SettingsScreens.PrivacyPhoneP2P:
|
case SettingsScreens.PrivacyPhoneP2P:
|
||||||
case SettingsScreens.PrivacyPhoneCall:
|
case SettingsScreens.PrivacyPhoneCall:
|
||||||
primaryPrivacy = privacy.phoneCall;
|
primaryPrivacy = privacy.phoneCall;
|
||||||
|
|||||||
@ -140,6 +140,9 @@ function getCurrentPrivacySettings(global: GlobalState, screen: SettingsScreens)
|
|||||||
case SettingsScreens.PrivacyBioAllowedContacts:
|
case SettingsScreens.PrivacyBioAllowedContacts:
|
||||||
case SettingsScreens.PrivacyBioDeniedContacts:
|
case SettingsScreens.PrivacyBioDeniedContacts:
|
||||||
return privacy.bio;
|
return privacy.bio;
|
||||||
|
case SettingsScreens.PrivacyBirthdayAllowedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayDeniedContacts:
|
||||||
|
return privacy.birthday;
|
||||||
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
|
case SettingsScreens.PrivacyPhoneCallAllowedContacts:
|
||||||
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
|
case SettingsScreens.PrivacyPhoneCallDeniedContacts:
|
||||||
return privacy.phoneCall;
|
return privacy.phoneCall;
|
||||||
|
|||||||
@ -19,6 +19,10 @@ export function getPrivacyKey(screen: SettingsScreens): ApiPrivacyKey | undefine
|
|||||||
case SettingsScreens.PrivacyBioAllowedContacts:
|
case SettingsScreens.PrivacyBioAllowedContacts:
|
||||||
case SettingsScreens.PrivacyBioDeniedContacts:
|
case SettingsScreens.PrivacyBioDeniedContacts:
|
||||||
return 'bio';
|
return 'bio';
|
||||||
|
case SettingsScreens.PrivacyBirthday:
|
||||||
|
case SettingsScreens.PrivacyBirthdayAllowedContacts:
|
||||||
|
case SettingsScreens.PrivacyBirthdayDeniedContacts:
|
||||||
|
return 'birthday';
|
||||||
case SettingsScreens.PrivacyForwarding:
|
case SettingsScreens.PrivacyForwarding:
|
||||||
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
case SettingsScreens.PrivacyForwardingAllowedContacts:
|
||||||
case SettingsScreens.PrivacyForwardingDeniedContacts:
|
case SettingsScreens.PrivacyForwardingDeniedContacts:
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useCallback, useRef } from '../../lib/teact/teact';
|
import React, { memo, useRef } from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { TabState } from '../../global/types';
|
import type { ConfettiStyle, TabState } from '../../global/types';
|
||||||
|
|
||||||
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||||
import { selectTabState } from '../../global/selectors';
|
import { selectTabState } from '../../global/selectors';
|
||||||
@ -11,6 +11,7 @@ import { pick } from '../../util/iteratees';
|
|||||||
|
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||||
import useWindowSize from '../../hooks/window/useWindowSize';
|
import useWindowSize from '../../hooks/window/useWindowSize';
|
||||||
|
|
||||||
@ -53,27 +54,20 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
|
|
||||||
const defaultConfettiAmount = isMobile ? 50 : 100;
|
const defaultConfettiAmount = isMobile ? 50 : 100;
|
||||||
const {
|
const {
|
||||||
lastConfettiTime, top, width, left, height,
|
lastConfettiTime, top, width, left, height, style = 'poppers',
|
||||||
} = confetti || {};
|
} = confetti || {};
|
||||||
|
|
||||||
const generateConfetti = useCallback((w: number, h: number, amount = defaultConfettiAmount) => {
|
const generateConfetti = useLastCallback((w: number, h: number, amount = defaultConfettiAmount) => {
|
||||||
for (let i = 0; i < amount; i++) {
|
for (let i = 0; i < amount; i++) {
|
||||||
const leftSide = i % 2;
|
const {
|
||||||
const pos = {
|
position, velocity,
|
||||||
x: w * (leftSide ? -0.1 : 1.1),
|
} = generateRandomPositionData(style, w, h, i);
|
||||||
y: h * 0.75,
|
|
||||||
};
|
const size = DEFAULT_CONFETTI_SIZE + randomNumberAroundZero(DEFAULT_CONFETTI_SIZE / 2);
|
||||||
const randomX = Math.random() * w * 1.5;
|
|
||||||
const randomY = -h / 2 - Math.random() * h;
|
|
||||||
const velocity = {
|
|
||||||
x: leftSide ? randomX : randomX * -1,
|
|
||||||
y: randomY,
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomColor = CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)];
|
const randomColor = CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)];
|
||||||
const size = DEFAULT_CONFETTI_SIZE;
|
|
||||||
confettiRef.current.push({
|
confettiRef.current.push({
|
||||||
pos,
|
pos: position,
|
||||||
size,
|
size,
|
||||||
color: randomColor,
|
color: randomColor,
|
||||||
velocity,
|
velocity,
|
||||||
@ -84,9 +78,9 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
frameCount: 0,
|
frameCount: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [defaultConfettiAmount]);
|
});
|
||||||
|
|
||||||
const updateCanvas = useCallback(() => {
|
const updateCanvas = useLastCallback(() => {
|
||||||
if (!canvasRef.current || !isRafStartedRef.current) {
|
if (!canvasRef.current || !isRafStartedRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -121,7 +115,7 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const newVelocity = {
|
const newVelocity = {
|
||||||
x: velocity.x * 0.98, // Air Resistance
|
x: velocity.x * 0.5 ** (diff / 1), // Air Resistance
|
||||||
y: velocity.y += diff * 1000, // Gravity
|
y: velocity.y += diff * 1000, // Gravity
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,7 +161,7 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
} else {
|
} else {
|
||||||
isRafStartedRef.current = false;
|
isRafStartedRef.current = false;
|
||||||
}
|
}
|
||||||
}, []);
|
});
|
||||||
|
|
||||||
useSyncEffect(([prevConfettiTime]) => {
|
useSyncEffect(([prevConfettiTime]) => {
|
||||||
let hideTimeout: ReturnType<typeof setTimeout>;
|
let hideTimeout: ReturnType<typeof setTimeout>;
|
||||||
@ -189,7 +183,7 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = buildStyle(
|
const containerStyle = buildStyle(
|
||||||
Boolean(top) && `top: ${top}px`,
|
Boolean(top) && `top: ${top}px`,
|
||||||
Boolean(left) && `left: ${left}px`,
|
Boolean(left) && `left: ${left}px`,
|
||||||
Boolean(width) && `width: ${width}px`,
|
Boolean(width) && `width: ${width}px`,
|
||||||
@ -197,7 +191,7 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="Confetti" className={styles.root} style={style}>
|
<div id="Confetti" className={styles.root} style={containerStyle}>
|
||||||
<canvas ref={canvasRef} className={styles.canvas} width={windowSize.width} height={windowSize.height} />
|
<canvas ref={canvasRef} className={styles.canvas} width={windowSize.width} height={windowSize.height} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -206,3 +200,46 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
|||||||
export default memo(withGlobal(
|
export default memo(withGlobal(
|
||||||
(global): StateProps => pick(selectTabState(global), ['confetti']),
|
(global): StateProps => pick(selectTabState(global), ['confetti']),
|
||||||
)(ConfettiContainer));
|
)(ConfettiContainer));
|
||||||
|
|
||||||
|
function generateRandomPositionData(
|
||||||
|
style: ConfettiStyle, containerWidth: number, containerHeight: number, index: number,
|
||||||
|
) {
|
||||||
|
if (style === 'poppers') {
|
||||||
|
const leftSide = index % 2;
|
||||||
|
const position = {
|
||||||
|
x: containerWidth * (leftSide ? -0.1 : 1.1),
|
||||||
|
y: containerHeight * 0.66,
|
||||||
|
};
|
||||||
|
const randomX = Math.random() * containerWidth;
|
||||||
|
const randomY = -containerHeight - randomNumberAroundZero(containerHeight * 0.75);
|
||||||
|
const velocity = {
|
||||||
|
x: leftSide ? randomX : randomX * -1,
|
||||||
|
y: randomY,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const position = {
|
||||||
|
x: Math.random() * containerWidth,
|
||||||
|
y: -DEFAULT_CONFETTI_SIZE * 2,
|
||||||
|
};
|
||||||
|
const randomX = randomNumberAroundZero(containerWidth);
|
||||||
|
const randomY = -containerHeight * Math.random() * 1.25;
|
||||||
|
const velocity = {
|
||||||
|
x: randomX,
|
||||||
|
y: randomY,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomNumberAroundZero(max: number = 1) {
|
||||||
|
return Math.random() * max - max / 2;
|
||||||
|
}
|
||||||
|
|||||||
@ -236,6 +236,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
initMain,
|
initMain,
|
||||||
loadAnimatedEmojis,
|
loadAnimatedEmojis,
|
||||||
|
loadBirthdayNumbersStickers,
|
||||||
loadNotificationSettings,
|
loadNotificationSettings,
|
||||||
loadNotificationExceptions,
|
loadNotificationExceptions,
|
||||||
updateIsOnline,
|
updateIsOnline,
|
||||||
@ -338,6 +339,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
initMain();
|
initMain();
|
||||||
loadAvailableReactions();
|
loadAvailableReactions();
|
||||||
loadAnimatedEmojis();
|
loadAnimatedEmojis();
|
||||||
|
loadBirthdayNumbersStickers();
|
||||||
loadGenericEmojiEffects();
|
loadGenericEmojiEffects();
|
||||||
loadNotificationSettings();
|
loadNotificationSettings();
|
||||||
loadNotificationExceptions();
|
loadNotificationExceptions();
|
||||||
|
|||||||
@ -62,12 +62,12 @@ import useProfileViewportIds from './hooks/useProfileViewportIds';
|
|||||||
import useTransitionFixes from './hooks/useTransitionFixes';
|
import useTransitionFixes from './hooks/useTransitionFixes';
|
||||||
|
|
||||||
import Audio from '../common/Audio';
|
import Audio from '../common/Audio';
|
||||||
import ChatExtra from '../common/ChatExtra';
|
|
||||||
import Document from '../common/Document';
|
import Document from '../common/Document';
|
||||||
import GroupChatInfo from '../common/GroupChatInfo';
|
import GroupChatInfo from '../common/GroupChatInfo';
|
||||||
import Media from '../common/Media';
|
import Media from '../common/Media';
|
||||||
import NothingFound from '../common/NothingFound';
|
import NothingFound from '../common/NothingFound';
|
||||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||||
|
import ChatExtra from '../common/profile/ChatExtra';
|
||||||
import ProfileInfo from '../common/ProfileInfo';
|
import ProfileInfo from '../common/ProfileInfo';
|
||||||
import WebLink from '../common/WebLink';
|
import WebLink from '../common/WebLink';
|
||||||
import ChatList from '../left/main/ChatList';
|
import ChatList from '../left/main/ChatList';
|
||||||
|
|||||||
@ -80,7 +80,7 @@
|
|||||||
border-radius: 0 ;
|
border-radius: 0 ;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .icon {
|
> .ListItem-main-icon {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-inline-start: 0.125rem;
|
margin-inline-start: 0.125rem;
|
||||||
margin-inline-end: 1.25rem;
|
margin-inline-end: 1.25rem;
|
||||||
@ -125,7 +125,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.multiline {
|
&.multiline {
|
||||||
.ListItem-button > .icon {
|
.ListItem-button > .ListItem-main-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@
|
|||||||
.ListItem-button {
|
.ListItem-button {
|
||||||
color: var(--color-error);
|
color: var(--color-error);
|
||||||
|
|
||||||
.icon {
|
.ListItem-main-icon {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import useLang from '../../hooks/useLang';
|
|||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
import useMenuPosition from '../../hooks/useMenuPosition';
|
||||||
|
|
||||||
|
import Icon from '../common/Icon';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import MenuItem from './MenuItem';
|
import MenuItem from './MenuItem';
|
||||||
@ -47,6 +48,7 @@ interface OwnProps {
|
|||||||
iconClassName?: string;
|
iconClassName?: string;
|
||||||
leftElement?: TeactNode;
|
leftElement?: TeactNode;
|
||||||
secondaryIcon?: IconName;
|
secondaryIcon?: IconName;
|
||||||
|
secondaryIconClassName?: string;
|
||||||
rightElement?: TeactNode;
|
rightElement?: TeactNode;
|
||||||
buttonClassName?: string;
|
buttonClassName?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -84,6 +86,7 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
buttonClassName,
|
buttonClassName,
|
||||||
menuBubbleClassName,
|
menuBubbleClassName,
|
||||||
secondaryIcon,
|
secondaryIcon,
|
||||||
|
secondaryIconClassName,
|
||||||
rightElement,
|
rightElement,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
@ -246,20 +249,20 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
{leftElement}
|
{leftElement}
|
||||||
{icon && (
|
{icon && (
|
||||||
<i className={buildClassName('icon', `icon-${icon}`, iconClassName)} />
|
<Icon name={icon} className={buildClassName('ListItem-main-icon', iconClassName)} />
|
||||||
)}
|
)}
|
||||||
{multiline && (<div className="multiline-item">{children}</div>)}
|
{multiline && (<div className="multiline-item">{children}</div>)}
|
||||||
{!multiline && children}
|
{!multiline && children}
|
||||||
{secondaryIcon && (
|
{secondaryIcon && (
|
||||||
<Button
|
<Button
|
||||||
className="secondary-icon"
|
className={buildClassName('secondary-icon', secondaryIconClassName)}
|
||||||
round
|
round
|
||||||
color="translucent"
|
color="translucent"
|
||||||
size="smaller"
|
size="smaller"
|
||||||
onClick={handleSecondaryIconClick}
|
onClick={handleSecondaryIconClick}
|
||||||
onMouseDown={handleSecondaryIconMouseDown}
|
onMouseDown={handleSecondaryIconMouseDown}
|
||||||
>
|
>
|
||||||
<i className={`icon icon-${secondaryIcon}`} />
|
<Icon name={secondaryIcon} />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{rightElement}
|
{rightElement}
|
||||||
|
|||||||
@ -213,6 +213,8 @@ export const BASE_EMOJI_KEYWORD_LANG = 'en';
|
|||||||
export const MENU_TRANSITION_DURATION = 200;
|
export const MENU_TRANSITION_DURATION = 200;
|
||||||
export const SLIDE_TRANSITION_DURATION = 450;
|
export const SLIDE_TRANSITION_DURATION = 450;
|
||||||
|
|
||||||
|
export const BIRTHDAY_NUMBERS_SET = 'FestiveFontEmoji';
|
||||||
|
|
||||||
export const VIDEO_WEBM_TYPE = 'video/webm';
|
export const VIDEO_WEBM_TYPE = 'video/webm';
|
||||||
export const GIF_MIME_TYPE = 'image/gif';
|
export const GIF_MIME_TYPE = 'image/gif';
|
||||||
|
|
||||||
|
|||||||
@ -421,6 +421,7 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
|
|||||||
callApi('fetchPrivacySettings', 'phoneP2P'),
|
callApi('fetchPrivacySettings', 'phoneP2P'),
|
||||||
callApi('fetchPrivacySettings', 'voiceMessages'),
|
callApi('fetchPrivacySettings', 'voiceMessages'),
|
||||||
callApi('fetchPrivacySettings', 'bio'),
|
callApi('fetchPrivacySettings', 'bio'),
|
||||||
|
callApi('fetchPrivacySettings', 'birthday'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (result.some((e) => e === undefined)) {
|
if (result.some((e) => e === undefined)) {
|
||||||
@ -438,6 +439,7 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
|
|||||||
phoneP2PSettings,
|
phoneP2PSettings,
|
||||||
voiceMessagesSettings,
|
voiceMessagesSettings,
|
||||||
bioSettings,
|
bioSettings,
|
||||||
|
birthdaySettings,
|
||||||
] = result as {
|
] = result as {
|
||||||
users: ApiUser[];
|
users: ApiUser[];
|
||||||
rules: ApiPrivacySettings;
|
rules: ApiPrivacySettings;
|
||||||
@ -463,6 +465,7 @@ addActionHandler('loadPrivacySettings', async (global): Promise<void> => {
|
|||||||
phoneP2P: phoneP2PSettings.rules,
|
phoneP2P: phoneP2PSettings.rules,
|
||||||
voiceMessages: voiceMessagesSettings.rules,
|
voiceMessages: voiceMessagesSettings.rules,
|
||||||
bio: bioSettings.rules,
|
bio: bioSettings.rules,
|
||||||
|
birthday: birthdaySettings.rules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type {
|
|||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||||
|
|
||||||
|
import { BIRTHDAY_NUMBERS_SET } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { translate } from '../../../util/langProvider';
|
import { translate } from '../../../util/langProvider';
|
||||||
@ -267,6 +268,26 @@ addActionHandler('loadAnimatedEmojis', async (global): Promise<void> => {
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadBirthdayNumbersStickers', async (global): Promise<void> => {
|
||||||
|
const emojis = await callApi('fetchStickers', {
|
||||||
|
stickerSetInfo: {
|
||||||
|
shortName: BIRTHDAY_NUMBERS_SET,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!emojis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
birthdayNumbers: { ...emojis.set, stickers: emojis.stickers },
|
||||||
|
};
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadGenericEmojiEffects', async (global): Promise<void> => {
|
addActionHandler('loadGenericEmojiEffects', async (global): Promise<void> => {
|
||||||
const stickerSet = await callApi('fetchGenericEmojiEffects');
|
const stickerSet = await callApi('fetchGenericEmojiEffects');
|
||||||
if (!stickerSet) {
|
if (!stickerSet) {
|
||||||
|
|||||||
@ -5,6 +5,10 @@ export function getStickerPreviewHash(stickerId: string) {
|
|||||||
return `sticker${stickerId}?size=m`;
|
return `sticker${stickerId}?size=m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStickerMediaHash(stickerId: string) {
|
||||||
|
return `document${stickerId}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function containsCustomEmoji(formattedText: ApiFormattedText) {
|
export function containsCustomEmoji(formattedText: ApiFormattedText) {
|
||||||
return formattedText.entities?.some((e) => e.type === ApiMessageEntityTypes.CustomEmoji);
|
return formattedText.entities?.some((e) => e.type === ApiMessageEntityTypes.CustomEmoji);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,6 +148,8 @@ export type IDimensions = {
|
|||||||
|
|
||||||
export type ApiPaymentStatus = 'paid' | 'failed' | 'pending' | 'cancelled';
|
export type ApiPaymentStatus = 'paid' | 'failed' | 'pending' | 'cancelled';
|
||||||
|
|
||||||
|
export type ConfettiStyle = 'poppers' | 'top-down';
|
||||||
|
|
||||||
export interface TabThread {
|
export interface TabThread {
|
||||||
scrollOffset?: number;
|
scrollOffset?: number;
|
||||||
replyStack?: number[];
|
replyStack?: number[];
|
||||||
@ -602,6 +604,7 @@ export type TabState = {
|
|||||||
left?: number;
|
left?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
style?: ConfettiStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
urlAuth?: {
|
urlAuth?: {
|
||||||
@ -970,6 +973,7 @@ export type GlobalState = {
|
|||||||
animatedEmojis?: ApiStickerSet;
|
animatedEmojis?: ApiStickerSet;
|
||||||
animatedEmojiEffects?: ApiStickerSet;
|
animatedEmojiEffects?: ApiStickerSet;
|
||||||
genericEmojiEffects?: ApiStickerSet;
|
genericEmojiEffects?: ApiStickerSet;
|
||||||
|
birthdayNumbers?: ApiStickerSet;
|
||||||
defaultTopicIconsId?: string;
|
defaultTopicIconsId?: string;
|
||||||
defaultStatusIconsId?: string;
|
defaultStatusIconsId?: string;
|
||||||
premiumGifts?: ApiStickerSet;
|
premiumGifts?: ApiStickerSet;
|
||||||
@ -2518,6 +2522,7 @@ export interface ActionPayloads {
|
|||||||
loadAnimatedEmojis: undefined;
|
loadAnimatedEmojis: undefined;
|
||||||
loadGreetingStickers: undefined;
|
loadGreetingStickers: undefined;
|
||||||
loadGenericEmojiEffects: undefined;
|
loadGenericEmojiEffects: undefined;
|
||||||
|
loadBirthdayNumbersStickers: undefined;
|
||||||
|
|
||||||
addRecentSticker: {
|
addRecentSticker: {
|
||||||
sticker: ApiSticker;
|
sticker: ApiSticker;
|
||||||
@ -2783,6 +2788,7 @@ export interface ActionPayloads {
|
|||||||
left: number;
|
left: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
style?: ConfettiStyle;
|
||||||
} & WithTabId) | WithTabId;
|
} & WithTabId) | WithTabId;
|
||||||
|
|
||||||
updateAttachmentSettings: {
|
updateAttachmentSettings: {
|
||||||
|
|||||||
@ -185,6 +185,7 @@ export enum SettingsScreens {
|
|||||||
PrivacyLastSeen,
|
PrivacyLastSeen,
|
||||||
PrivacyProfilePhoto,
|
PrivacyProfilePhoto,
|
||||||
PrivacyBio,
|
PrivacyBio,
|
||||||
|
PrivacyBirthday,
|
||||||
PrivacyPhoneCall,
|
PrivacyPhoneCall,
|
||||||
PrivacyPhoneP2P,
|
PrivacyPhoneP2P,
|
||||||
PrivacyForwarding,
|
PrivacyForwarding,
|
||||||
@ -199,6 +200,8 @@ export enum SettingsScreens {
|
|||||||
PrivacyProfilePhotoDeniedContacts,
|
PrivacyProfilePhotoDeniedContacts,
|
||||||
PrivacyBioAllowedContacts,
|
PrivacyBioAllowedContacts,
|
||||||
PrivacyBioDeniedContacts,
|
PrivacyBioDeniedContacts,
|
||||||
|
PrivacyBirthdayAllowedContacts,
|
||||||
|
PrivacyBirthdayDeniedContacts,
|
||||||
PrivacyPhoneCallAllowedContacts,
|
PrivacyPhoneCallAllowedContacts,
|
||||||
PrivacyPhoneCallDeniedContacts,
|
PrivacyPhoneCallDeniedContacts,
|
||||||
PrivacyPhoneP2PAllowedContacts,
|
PrivacyPhoneP2PAllowedContacts,
|
||||||
@ -380,7 +383,7 @@ export type ProfileTabType =
|
|||||||
| 'dialogs';
|
| 'dialogs';
|
||||||
export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice';
|
export type SharedMediaType = 'media' | 'documents' | 'links' | 'audio' | 'voice';
|
||||||
export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' |
|
export type ApiPrivacyKey = 'phoneNumber' | 'addByPhone' | 'lastSeen' | 'profilePhoto' | 'voiceMessages' |
|
||||||
'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio';
|
'forwards' | 'chatInvite' | 'phoneCall' | 'phoneP2P' | 'bio' | 'birthday';
|
||||||
export type PrivacyVisibility = 'everybody' | 'contacts' | 'closeFriends' | 'nonContacts' | 'nobody';
|
export type PrivacyVisibility = 'everybody' | 'contacts' | 'closeFriends' | 'nonContacts' | 'nobody';
|
||||||
|
|
||||||
export enum ProfileState {
|
export enum ProfileState {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user