Introduce Multi-Accounts

This commit is contained in:
Alexander Zinchuk 2025-04-23 18:57:46 +02:00
parent 65e0d05ac4
commit a6f8f232a8
248 changed files with 1841 additions and 853 deletions

View File

@ -11,9 +11,10 @@ function compatTest() {
var hasNumberFormat = hasIntl && typeof Intl.NumberFormat !== 'undefined'; var hasNumberFormat = hasIntl && typeof Intl.NumberFormat !== 'undefined';
var hasWebLocks = typeof navigator.locks !== 'undefined'; var hasWebLocks = typeof navigator.locks !== 'undefined';
var hasBigInt = typeof BigInt !== 'undefined'; var hasBigInt = typeof BigInt !== 'undefined';
var hasBroadcastChannel = typeof BroadcastChannel !== 'undefined';
var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries && hasResizeObserver var isCompatible = hasPromise && hasWebSockets && hasWebCrypto && hasObjectFromEntries && hasResizeObserver
&& hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt; && hasCssSupports && hasDisplayNames && hasPluralRules && hasNumberFormat && hasWebLocks && hasBigInt && hasBroadcastChannel;
if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) { if (isCompatible || (window.localStorage && window.localStorage.getItem('tt-ignore-compat'))) {
window.isCompatTestPassed = true; window.isCompatTestPassed = true;
@ -33,6 +34,7 @@ function compatTest() {
console.warn('Intl.NumberFormat', hasNumberFormat); console.warn('Intl.NumberFormat', hasNumberFormat);
console.warn('WebLocks', hasWebLocks); console.warn('WebLocks', hasWebLocks);
console.warn('BigInt', hasBigInt); console.warn('BigInt', hasBigInt);
console.warn('BroadcastChannel', hasBroadcastChannel);
} }
// Hardcoded page because server forbids iframe embedding // Hardcoded page because server forbids iframe embedding

View File

@ -159,6 +159,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'), chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'),
recommendedChannels: getLimit(appConfig, 'recommended_channels_limit', 'recommendedChannels'), recommendedChannels: getLimit(appConfig, 'recommended_channels_limit', 'recommendedChannels'),
savedDialogsPinned: getLimit(appConfig, 'saved_dialogs_pinned_limit', 'savedDialogsPinned'), savedDialogsPinned: getLimit(appConfig, 'saved_dialogs_pinned_limit', 'savedDialogsPinned'),
moreAccounts: DEFAULT_LIMITS.moreAccounts,
}, },
hash, hash,
areStoriesHidden: appConfig.stories_all_hidden, areStoriesHidden: appConfig.stories_all_hidden,

View File

@ -1,13 +1,11 @@
import BigInt from 'big-integer'; import BigInt from 'big-integer';
import { Api as GramJs } from '../../lib/gramjs'; import { Api as GramJs } from '../../lib/gramjs';
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG } from '../../config'; import { DEBUG } from '../../config';
import { DATA_BROADCAST_CHANNEL_NAME } from '../../util/multiaccount';
import { throttle } from '../../util/schedulers'; import { throttle } from '../../util/schedulers';
import { omitVirtualClassFields } from './apiBuilders/helpers'; import { omitVirtualClassFields } from './apiBuilders/helpers';
// eslint-disable-next-line no-restricted-globals
const IS_MULTITAB_SUPPORTED = 'BroadcastChannel' in self;
export type StoryRepairInfo = { export type StoryRepairInfo = {
type: 'story'; type: 'story';
peerId: string; peerId: string;
@ -36,7 +34,7 @@ export interface LocalDb {
channelPtsById: Record<string, number>; channelPtsById: Record<string, number>;
} }
const channel = IS_MULTITAB_SUPPORTED ? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) : undefined; const channel = new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME);
let batchedUpdates: { let batchedUpdates: {
name: string; name: string;
@ -44,7 +42,7 @@ let batchedUpdates: {
value: any; value: any;
}[] = []; }[] = [];
const throttledLocalDbUpdate = throttle(() => { const throttledLocalDbUpdate = throttle(() => {
channel!.postMessage({ channel.postMessage({
type: 'localDbUpdate', type: 'localDbUpdate',
batchedUpdates, batchedUpdates,
}); });
@ -109,9 +107,7 @@ function createLocalDbInitial(initial?: LocalDb): LocalDb {
return acc2; return acc2;
}, {} as Record<string, any>); }, {} as Record<string, any>);
acc[key] = IS_MULTITAB_SUPPORTED acc[key] = createProxy(key, convertedValue);
? createProxy(key, convertedValue)
: convertedValue;
return acc; return acc;
}, {} as LocalDb) as LocalDb; }, {} as LocalDb) as LocalDb;
} }

View File

@ -76,7 +76,7 @@ export async function init(initialArgs: ApiInitialArgs) {
const { const {
userAgent, platform, sessionData, isWebmSupported, maxBufferSize, webAuthToken, dcId, userAgent, platform, sessionData, isWebmSupported, maxBufferSize, webAuthToken, dcId,
mockScenario, shouldForceHttpTransport, shouldAllowHttpTransport, mockScenario, shouldForceHttpTransport, shouldAllowHttpTransport,
shouldDebugExportedSenders, langCode, isTestServerRequested, shouldDebugExportedSenders, langCode, isTestServerRequested, accountIds,
} = initialArgs; } = initialArgs;
const session = new sessions.CallbackSession(sessionData, onSessionUpdate); const session = new sessions.CallbackSession(sessionData, onSessionUpdate);
@ -133,6 +133,7 @@ export async function init(initialArgs: ApiInitialArgs) {
webAuthToken, webAuthToken,
webAuthTokenFailed: onWebAuthTokenFailed, webAuthTokenFailed: onWebAuthTokenFailed,
mockScenario, mockScenario,
accountIds,
}); });
} catch (err: any) { } catch (err: any) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -1,17 +1,17 @@
import type { Api } from '../../../lib/gramjs'; import type { Api } from '../../../lib/gramjs';
import type { TypedBroadcastChannel } from '../../../util/multitab'; import type { TypedBroadcastChannel } from '../../../util/browser/multitab';
import type { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types'; import type { ApiInitialArgs, ApiOnProgress, OnApiUpdate } from '../../types';
import type { LocalDb } from '../localDb'; import type { LocalDb } from '../localDb';
import type { MethodArgs, MethodResponse, Methods } from '../methods/types'; import type { MethodArgs, MethodResponse, Methods } from '../methods/types';
import type { OriginPayload, ThenArg, WorkerMessageEvent } from './types'; import type { OriginPayload, ThenArg, WorkerMessageEvent } from './types';
import { DATA_BROADCAST_CHANNEL_NAME, DEBUG, IGNORE_UNHANDLED_ERRORS } from '../../../config'; import { DEBUG, IGNORE_UNHANDLED_ERRORS } from '../../../config';
import { logDebugMessage } from '../../../util/debugConsole'; import { logDebugMessage } from '../../../util/debugConsole';
import Deferred from '../../../util/Deferred'; import Deferred from '../../../util/Deferred';
import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole'; import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole';
import generateUniqueId from '../../../util/generateUniqueId'; import generateUniqueId from '../../../util/generateUniqueId';
import { ACCOUNT_SLOT, DATA_BROADCAST_CHANNEL_NAME } from '../../../util/multiaccount';
import { pause, throttleWithTickEnd } from '../../../util/schedulers'; import { pause, throttleWithTickEnd } from '../../../util/schedulers';
import { IS_MULTITAB_SUPPORTED } from '../../../util/windowEnvironment';
type RequestState = { type RequestState = {
messageId: string; messageId: string;
@ -48,9 +48,7 @@ subscribeToMasterChange((isMasterTabNew) => {
isMasterTab = isMasterTabNew; isMasterTab = isMasterTabNew;
}); });
const channel = IS_MULTITAB_SUPPORTED const channel = new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) as TypedBroadcastChannel;
? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) as TypedBroadcastChannel
: undefined;
const postMessagesOnTickEnd = throttleWithTickEnd(() => { const postMessagesOnTickEnd = throttleWithTickEnd(() => {
const payloads = pendingPayloads; const payloads = pendingPayloads;
@ -64,8 +62,6 @@ function postMessageOnTickEnd(payload: OriginPayload) {
} }
export function initApiOnMasterTab(initialArgs: ApiInitialArgs) { export function initApiOnMasterTab(initialArgs: ApiInitialArgs) {
if (!channel) return;
channel.postMessage({ channel.postMessage({
type: 'initApi', type: 'initApi',
token: getCurrentTabId(), token: getCurrentTabId(),
@ -93,7 +89,14 @@ export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
console.log('>>> START LOAD WORKER'); console.log('>>> START LOAD WORKER');
} }
worker = new Worker(new URL('./worker.ts', import.meta.url)); const params = new URLSearchParams();
if (ACCOUNT_SLOT) {
params.set('account', String(ACCOUNT_SLOT));
}
worker = new Worker(new URL('./worker.ts', import.meta.url), {
name: params.toString(),
});
subscribeToWorker(onUpdate); subscribeToWorker(onUpdate);
if (initialArgs.platform === 'iOS') { if (initialArgs.platform === 'iOS') {
@ -132,8 +135,6 @@ export function updateFullLocalDb(initial: LocalDb) {
} }
export function callApiOnMasterTab(payload: any) { export function callApiOnMasterTab(payload: any) {
if (!channel) return;
channel.postMessage({ channel.postMessage({
type: 'callApi', type: 'callApi',
token: getCurrentTabId(), token: getCurrentTabId(),
@ -254,8 +255,6 @@ export function cancelApiProgress(progressCallback: ApiOnProgress) {
if (isMasterTab) { if (isMasterTab) {
cancelApiProgressMaster(messageId); cancelApiProgressMaster(messageId);
} else { } else {
if (!channel) return;
channel.postMessage({ channel.postMessage({
type: 'cancelApiProgress', type: 'cancelApiProgress',
token: getCurrentTabId(), token: getCurrentTabId(),

View File

@ -23,6 +23,7 @@ export interface ApiInitialArgs {
shouldDebugExportedSenders?: boolean; shouldDebugExportedSenders?: boolean;
langCode: string; langCode: string;
isTestServerRequested?: boolean; isTestServerRequested?: boolean;
accountIds?: string[];
} }
export interface ApiOnProgress { export interface ApiOnProgress {
@ -102,7 +103,7 @@ export interface ApiWebSession {
export interface ApiSessionData { export interface ApiSessionData {
mainDcId: number; mainDcId: number;
keys: Record<number, string | number[]>; keys: Record<number, string>;
isTest?: true; isTest?: true;
} }
@ -343,10 +344,11 @@ export type ApiLimitType =
| 'chatlistInvites' | 'chatlistInvites'
| 'chatlistJoined' | 'chatlistJoined'
| 'recommendedChannels' | 'recommendedChannels'
| 'savedDialogsPinned'; | 'savedDialogsPinned'
| 'moreAccounts';
export type ApiLimitTypeWithModal = Exclude<ApiLimitType, ( export type ApiLimitTypeWithModal = Exclude<ApiLimitType, (
'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels' 'captionLength' | 'aboutLength' | 'stickersFaved' | 'savedGifs' | 'recommendedChannels' | 'moreAccounts'
)>; )>;
export type ApiLimitTypeForPromo = Exclude<ApiLimitType, export type ApiLimitTypeForPromo = Exclude<ApiLimitType,

View File

@ -71,6 +71,9 @@
"PremiumPreviewUploadsDescription" = "4 GB per each document, unlimited storage for your chats and media overall."; "PremiumPreviewUploadsDescription" = "4 GB per each document, unlimited storage for your chats and media overall.";
"PremiumPreviewAdvancedChatManagementDescription" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts."; "PremiumPreviewAdvancedChatManagementDescription" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts.";
"PremiumPreviewAnimatedProfilesDescription" = "Video avatars animated in chat lists and chats to allow for additional self-expression."; "PremiumPreviewAnimatedProfilesDescription" = "Video avatars animated in chat lists and chats to allow for additional self-expression.";
"PremiumLimitAccountsTitle" = "Limit Reached";
"PremiumLimitAccountsNoPremium" = "You have reached your current limit of connected accounts. You can free one more place by subscribing to Telegram Premium.";
"PremiumLimitAccounts" = "You have reached your current limit of connected accounts. You can free one more place by subscribing to Telegram Premium with one of these connected accounts.";
"SendMessage" = "Send Message"; "SendMessage" = "Send Message";
"ConversationDefaultRestrictedMedia" = "Sending media isn't allowed in this group."; "ConversationDefaultRestrictedMedia" = "Sending media isn't allowed in this group.";
"AccDescrVoiceMessage" = "Record voice message"; "AccDescrVoiceMessage" = "Record voice message";
@ -172,6 +175,7 @@
"LoginRegisterLastNamePlaceholder" = "Last Name"; "LoginRegisterLastNamePlaceholder" = "Last Name";
"Next" = "Next"; "Next" = "Next";
"LoginSelectCountryTitle" = "Country"; "LoginSelectCountryTitle" = "Country";
"MenuAddAccount" = "Add Account";
"CountryNone" = "Country not found"; "CountryNone" = "Country not found";
"VoipGroupVoiceChat" = "Video Chat"; "VoipGroupVoiceChat" = "Video Chat";
"AccDescrMoreOptions" = "More options"; "AccDescrMoreOptions" = "More options";

View File

@ -1,5 +1,5 @@
import { initializeSoundsForSafari } from '../global/actions/ui/calls'; import { initializeSoundsForSafari } from '../global/actions/ui/calls';
import { IS_IOS, IS_SAFARI } from '../util/windowEnvironment'; import { IS_IOS, IS_SAFARI } from '../util/browser/windowEnvironment';
export { default as GroupCall } from '../components/calls/group/GroupCall'; export { default as GroupCall } from '../components/calls/group/GroupCall';
export { default as ActiveCallHeader } from '../components/calls/ActiveCallHeader'; export { default as ActiveCallHeader } from '../components/calls/ActiveCallHeader';

View File

@ -1,7 +1,4 @@
import { getActions, getGlobal } from '../global';
import { DEBUG } from '../config'; import { DEBUG } from '../config';
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
export { default as Main } from '../components/main/Main'; export { default as Main } from '../components/main/Main';
export { default as LockScreen } from '../components/main/LockScreen'; export { default as LockScreen } from '../components/main/LockScreen';
@ -10,8 +7,3 @@ if (DEBUG) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('>>> FINISH LOAD MAIN BUNDLE'); console.log('>>> FINISH LOAD MAIN BUNDLE');
} }
const { passcode: { isScreenLocked }, connectionState } = getGlobal();
if (!connectionState && !isScreenLocked && !IS_MULTITAB_SUPPORTED) {
getActions().initApi();
}

View File

@ -1,6 +1,6 @@
import type { FC } from '../lib/teact/teact'; import type { FC } from '../lib/teact/teact';
import React, { useEffect, useLayoutEffect } from '../lib/teact/teact'; import React, { useEffect, useLayoutEffect } from '../lib/teact/teact';
import { getActions, withGlobal } from '../global'; import { withGlobal } from '../global';
import type { GlobalState } from '../global/types'; import type { GlobalState } from '../global/types';
import type { ThemeKey } from '../types'; import type { ThemeKey } from '../types';
@ -10,12 +10,12 @@ import {
DARK_THEME_BG_COLOR, INACTIVE_MARKER, LIGHT_THEME_BG_COLOR, PAGE_TITLE, DARK_THEME_BG_COLOR, INACTIVE_MARKER, LIGHT_THEME_BG_COLOR, PAGE_TITLE,
} from '../config'; } from '../config';
import { selectTabState, selectTheme } from '../global/selectors'; import { selectTabState, selectTheme } from '../global/selectors';
import { addActiveTabChangeListener } from '../util/activeTabMonitor'; import { IS_INSTALL_PROMPT_SUPPORTED, PLATFORM_ENV } from '../util/browser/windowEnvironment';
import buildClassName from '../util/buildClassName'; import buildClassName from '../util/buildClassName';
import { setupBeforeInstallPrompt } from '../util/installPrompt'; import { setupBeforeInstallPrompt } from '../util/installPrompt';
import { parseInitialLocationHash } from '../util/routing'; import { ACCOUNT_SLOT, getAccountsInfo, getAccountSlotUrl } from '../util/multiaccount';
import { getInitialLocationHash, parseInitialLocationHash } from '../util/routing';
import { hasStoredSession } from '../util/sessions'; import { hasStoredSession } from '../util/sessions';
import { IS_INSTALL_PROMPT_SUPPORTED, IS_MULTITAB_SUPPORTED, PLATFORM_ENV } from '../util/windowEnvironment';
import { updateSizes } from '../util/windowSize'; import { updateSizes } from '../util/windowSize';
import useAppLayout from '../hooks/useAppLayout'; import useAppLayout from '../hooks/useAppLayout';
@ -61,8 +61,6 @@ const App: FC<StateProps> = ({
isTestServer, isTestServer,
theme, theme,
}) => { }) => {
const { disconnect } = getActions();
const [isInactive, markInactive, unmarkInactive] = useFlag(false); const [isInactive, markInactive, unmarkInactive] = useFlag(false);
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android'; const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
@ -73,6 +71,22 @@ const App: FC<StateProps> = ({
} }
}, []); }, []);
useEffect(() => {
const hash = getInitialLocationHash();
// If there is no stored session on first slot, navigate to any other slot with stored session
if (!hasStoredSession() && !ACCOUNT_SLOT && !hash) {
const accounts = getAccountsInfo();
Object.keys(accounts).forEach((key) => {
const slot = Number(key);
const account = accounts[slot];
if (account) {
const url = getAccountSlotUrl(slot);
window.location.href = `${url}#${hash || 'login'}`;
}
});
}
}, []);
// Prevent drop on elements that do not accept it // Prevent drop on elements that do not accept it
useEffect(() => { useEffect(() => {
const body = document.body; const body = document.body;
@ -161,17 +175,6 @@ const App: FC<StateProps> = ({
updateSizes(); updateSizes();
}, []); }, []);
useEffect(() => {
if (IS_MULTITAB_SUPPORTED) return;
addActiveTabChangeListener(() => {
disconnect();
document.title = INACTIVE_PAGE_TITLE;
markInactive();
});
}, [activeKey, disconnect, markInactive]);
useEffect(() => { useEffect(() => {
if (isInactiveAuth) { if (isInactiveAuth) {
document.title = INACTIVE_PAGE_TITLE; document.title = INACTIVE_PAGE_TITLE;

View File

@ -225,6 +225,12 @@
right: 0.5rem; right: 0.5rem;
} }
.auth-close {
position: absolute;
top: 1rem;
left: 1rem;
}
@keyframes qr-show { @keyframes qr-show {
0% { 0% {
opacity: 0; opacity: 0;

View File

@ -6,7 +6,7 @@ import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import { PLATFORM_ENV } from '../../util/windowEnvironment'; import { PLATFORM_ENV } from '../../util/browser/windowEnvironment';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useElectronDrag from '../../hooks/useElectronDrag'; import useElectronDrag from '../../hooks/useElectronDrag';

View File

@ -7,8 +7,8 @@ import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';

View File

@ -1,7 +1,7 @@
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState, memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
@ -9,18 +9,23 @@ import type { ApiCountryCode } from '../../api/types';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import { requestMeasure } from '../../lib/fasterdom/fasterdom'; import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import { preloadImage } from '../../util/files'; import { preloadImage } from '../../util/files';
import preloadFonts from '../../util/fonts'; import preloadFonts from '../../util/fonts';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import { getAccountSlotUrl } from '../../util/multiaccount';
import { oldSetLanguage } from '../../util/oldLangProvider'; import { oldSetLanguage } from '../../util/oldLangProvider';
import { formatPhoneNumber, getCountryCodeByIso, getCountryFromPhoneNumber } from '../../util/phoneNumber'; import { formatPhoneNumber, getCountryCodeByIso, getCountryFromPhoneNumber } from '../../util/phoneNumber';
import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/windowEnvironment'; import { navigateBack } from './helpers/backNavigation';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import useLangString from '../../hooks/useLangString'; import useLangString from '../../hooks/useLangString';
import useLastCallback from '../../hooks/useLastCallback';
import useMultiaccountInfo from '../../hooks/useMultiaccountInfo';
import Icon from '../common/icons/Icon';
import Button from '../ui/Button'; import Button from '../ui/Button';
import Checkbox from '../ui/Checkbox'; import Checkbox from '../ui/Checkbox';
import InputText from '../ui/InputText'; import InputText from '../ui/InputText';
@ -62,7 +67,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
loadCountryList, loadCountryList,
clearAuthErrorKey, clearAuthErrorKey,
goToAuthQrCode, goToAuthQrCode,
setSettingOption, setSharedSettingOption,
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
@ -78,6 +83,16 @@ const AuthPhoneNumber: FC<StateProps> = ({
const [lastSelection, setLastSelection] = useState<[number, number] | undefined>(); const [lastSelection, setLastSelection] = useState<[number, number] | undefined>();
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const accountsInfo = useMultiaccountInfo();
const hasActiveAccount = Object.values(accountsInfo).length > 0;
const phoneNumberSlots = useMemo(() => (
Object.entries(accountsInfo)
.reduce((acc, [key, { phone }]) => {
if (phone) acc[phone] = Number(key);
return acc;
}, {} as Record<string, number>)
), [accountsInfo]);
const fullNumber = country ? `+${country.countryCode} ${phoneNumber || ''}` : phoneNumber; const fullNumber = country ? `+${country.countryCode} ${phoneNumber || ''}` : phoneNumber;
const canSubmit = fullNumber && fullNumber.replace(/[^\d]+/g, '').length >= MIN_NUMBER_LENGTH; const canSubmit = fullNumber && fullNumber.replace(/[^\d]+/g, '').length >= MIN_NUMBER_LENGTH;
@ -105,7 +120,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
} }
}, [country, authNearestCountry, isTouched, phoneCodeList]); }, [country, authNearestCountry, isTouched, phoneCodeList]);
const parseFullNumber = useCallback((newFullNumber: string) => { const parseFullNumber = useLastCallback((newFullNumber: string) => {
if (!newFullNumber.length) { if (!newFullNumber.length) {
setPhoneNumber(''); setPhoneNumber('');
} }
@ -123,17 +138,17 @@ const AuthPhoneNumber: FC<StateProps> = ({
setCountry(selectedCountry); setCountry(selectedCountry);
} }
setPhoneNumber(formatPhoneNumber(newFullNumber, selectedCountry)); setPhoneNumber(formatPhoneNumber(newFullNumber, selectedCountry));
}, [phoneCodeList, country]); });
const handleLangChange = useCallback(() => { const handleLangChange = useLastCallback(() => {
markIsLoading(); markIsLoading();
void oldSetLanguage(suggestedLanguage, () => { void oldSetLanguage(suggestedLanguage, () => {
unmarkIsLoading(); unmarkIsLoading();
setSettingOption({ language: suggestedLanguage }); setSharedSettingOption({ language: suggestedLanguage });
}); });
}, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]); });
useEffect(() => { useEffect(() => {
if (phoneNumber === undefined && authPhoneNumber) { if (phoneNumber === undefined && authPhoneNumber) {
@ -148,19 +163,23 @@ const AuthPhoneNumber: FC<StateProps> = ({
}, [lastSelection]); }, [lastSelection]);
const isJustPastedRef = useRef(false); const isJustPastedRef = useRef(false);
const handlePaste = useCallback(() => { const handlePaste = useLastCallback(() => {
isJustPastedRef.current = true; isJustPastedRef.current = true;
requestMeasure(() => { requestMeasure(() => {
isJustPastedRef.current = false; isJustPastedRef.current = false;
}); });
}, []); });
const handleCountryChange = useCallback((value: ApiCountryCode) => { const handleBackNavigation = useLastCallback(() => {
navigateBack();
});
const handleCountryChange = useLastCallback((value: ApiCountryCode) => {
setCountry(value); setCountry(value);
setPhoneNumber(''); setPhoneNumber('');
}, []); });
const handlePhoneNumberChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handlePhoneNumberChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
if (authErrorKey) { if (authErrorKey) {
clearAuthErrorKey(); clearAuthErrorKey();
} }
@ -186,11 +205,11 @@ const AuthPhoneNumber: FC<StateProps> = ({
&& value.length - fullNumber.length > 1 && !isJustPastedRef.current && value.length - fullNumber.length > 1 && !isJustPastedRef.current
); );
parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value); parseFullNumber(shouldFixSafariAutoComplete ? `${country!.countryCode} ${value}` : value);
}, [authErrorKey, country, fullNumber, parseFullNumber]); });
const handleKeepSessionChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleKeepSessionChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
setAuthRememberMe(e.target.checked); setAuthRememberMe(e.target.checked);
}, [setAuthRememberMe]); });
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault(); event.preventDefault();
@ -199,19 +218,30 @@ const AuthPhoneNumber: FC<StateProps> = ({
return; return;
} }
const adaptedPhoneNumber = fullNumber?.replace(/[^\d]/g, '');
if (adaptedPhoneNumber && phoneNumberSlots[adaptedPhoneNumber]) {
window.location.replace(getAccountSlotUrl(phoneNumberSlots[adaptedPhoneNumber]));
return;
}
if (canSubmit) { if (canSubmit) {
setAuthPhoneNumber({ phoneNumber: fullNumber }); setAuthPhoneNumber({ phoneNumber: fullNumber });
} }
} }
const handleGoToAuthQrCode = useCallback(() => { const handleGoToAuthQrCode = useLastCallback(() => {
goToAuthQrCode(); goToAuthQrCode();
}, [goToAuthQrCode]); });
const isAuthReady = authState === 'authorizationStateWaitPhoneNumber'; const isAuthReady = authState === 'authorizationStateWaitPhoneNumber';
return ( return (
<div id="auth-phone-number-form" className="custom-scroll"> <div id="auth-phone-number-form" className="custom-scroll">
{hasActiveAccount && (
<Button size="smaller" round color="translucent" className="auth-close" onClick={handleBackNavigation}>
<Icon name="close" />
</Button>
)}
<div className="auth-form"> <div className="auth-form">
<div id="logo" /> <div id="logo" />
<h1>{lang('AuthTitle')}</h1> <h1>{lang('AuthTitle')}</h1>
@ -263,7 +293,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
const { const {
settings: { byKey: { language } }, sharedState: { settings: { language } },
countryList: { phoneCodes: phoneCodeList }, countryList: { phoneCodes: phoneCodeList },
} = global; } = global;

View File

@ -1,5 +1,5 @@
import React, { import React, {
memo, useCallback, useLayoutEffect, useRef, memo, useLayoutEffect, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
@ -7,18 +7,23 @@ import type { GlobalState } from '../../global/types';
import { STRICTERDOM_ENABLED } from '../../config'; import { STRICTERDOM_ENABLED } from '../../config';
import { disableStrict, enableStrict } from '../../lib/fasterdom/stricterdom'; import { disableStrict, enableStrict } from '../../lib/fasterdom/stricterdom';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { oldSetLanguage } from '../../util/oldLangProvider'; import { oldSetLanguage } from '../../util/oldLangProvider';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import { navigateBack } from './helpers/backNavigation';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import useAsync from '../../hooks/useAsync'; import useAsync from '../../hooks/useAsync';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import useLangString from '../../hooks/useLangString'; import useLangString from '../../hooks/useLangString';
import useLastCallback from '../../hooks/useLastCallback';
import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated'; import useMediaTransitionDeprecated from '../../hooks/useMediaTransitionDeprecated';
import useMultiaccountInfo from '../../hooks/useMultiaccountInfo';
import AnimatedIcon from '../common/AnimatedIcon'; import AnimatedIcon from '../common/AnimatedIcon';
import Icon from '../common/icons/Icon';
import Button from '../ui/Button'; import Button from '../ui/Button';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
@ -26,7 +31,9 @@ import blankUrl from '../../assets/blank.png';
type StateProps = type StateProps =
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'> Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
& { language?: string }; & {
language?: string;
};
const DATA_PREFIX = 'tg://login?token='; const DATA_PREFIX = 'tg://login?token=';
const QR_SIZE = 280; const QR_SIZE = 280;
@ -50,7 +57,7 @@ const AuthCode = ({
}: StateProps) => { }: StateProps) => {
const { const {
returnToAuthPhoneNumber, returnToAuthPhoneNumber,
setSettingOption, setSharedSettingOption,
} = getActions(); } = getActions();
const suggestedLanguage = getSuggestedLanguage(); const suggestedLanguage = getSuggestedLanguage();
@ -63,6 +70,9 @@ const AuthCode = ({
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag(); const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
const accountsInfo = useMultiaccountInfo();
const hasActiveAccount = Object.values(accountsInfo).length > 0;
const { result: qrCode } = useAsync(async () => { const { result: qrCode } = useAsync(async () => {
const QrCodeStyling = (await ensureQrCodeStyling()).default; const QrCodeStyling = (await ensureQrCodeStyling()).default;
return new QrCodeStyling({ return new QrCodeStyling({
@ -125,24 +135,33 @@ const AuthCode = ({
return undefined; return undefined;
}, [isConnected, authQrCode, isQrMounted, markQrMounted, unmarkQrMounted, qrCode]); }, [isConnected, authQrCode, isQrMounted, markQrMounted, unmarkQrMounted, qrCode]);
const handleLangChange = useCallback(() => { const handleBackNavigation = useLastCallback(() => {
navigateBack();
});
const handleLangChange = useLastCallback(() => {
markIsLoading(); markIsLoading();
void oldSetLanguage(suggestedLanguage, () => { void oldSetLanguage(suggestedLanguage, () => {
unmarkIsLoading(); unmarkIsLoading();
setSettingOption({ language: suggestedLanguage }); setSharedSettingOption({ language: suggestedLanguage });
}); });
}, [markIsLoading, setSettingOption, suggestedLanguage, unmarkIsLoading]); });
const habdleReturnToAuthPhoneNumber = useCallback(() => { const handleReturnToAuthPhoneNumber = useLastCallback(() => {
returnToAuthPhoneNumber(); returnToAuthPhoneNumber();
}, [returnToAuthPhoneNumber]); });
const isAuthReady = authState === 'authorizationStateWaitQrCode'; const isAuthReady = authState === 'authorizationStateWaitQrCode';
return ( return (
<div id="auth-qr-form" className="custom-scroll"> <div id="auth-qr-form" className="custom-scroll">
{hasActiveAccount && (
<Button size="smaller" round color="translucent" className="auth-close" onClick={handleBackNavigation}>
<Icon name="close" />
</Button>
)}
<div className="auth-form qr"> <div className="auth-form qr">
<div className="qr-outer"> <div className="qr-outer">
<div <div
@ -172,7 +191,7 @@ const AuthCode = ({
<li><span>{lang('LoginQRHelp3')}</span></li> <li><span>{lang('LoginQRHelp3')}</span></li>
</ol> </ol>
{isAuthReady && ( {isAuthReady && (
<Button size="smaller" isText onClick={habdleReturnToAuthPhoneNumber}>{lang('LoginQRCancel')}</Button> <Button size="smaller" isText onClick={handleReturnToAuthPhoneNumber}>{lang('LoginQRCancel')}</Button>
)} )}
{suggestedLanguage && suggestedLanguage !== language && continueText && ( {suggestedLanguage && suggestedLanguage !== language && continueText && (
<Button size="smaller" isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button> <Button size="smaller" isText isLoading={isLoading} onClick={handleLangChange}>{continueText}</Button>
@ -185,9 +204,11 @@ const AuthCode = ({
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
const { const {
connectionState, authState, authQrCode, settings: { byKey: { language } }, connectionState, authState, authQrCode,
} = global; } = global;
const { language } = selectSharedSettings(global);
return { return {
connectionState, connectionState,
authState, authState,

View File

@ -7,10 +7,10 @@ import { withGlobal } from '../../global';
import type { ApiCountryCode } from '../../api/types'; import type { ApiCountryCode } from '../../api/types';
import { ANIMATION_END_DELAY } from '../../config'; import { ANIMATION_END_DELAY } from '../../config';
import { IS_EMOJI_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { isoToEmoji } from '../../util/emoji/emoji'; import { isoToEmoji } from '../../util/emoji/emoji';
import { prepareSearchWordsForNeedle } from '../../util/searchWords'; import { prepareSearchWordsForNeedle } from '../../util/searchWords';
import { IS_EMOJI_SUPPORTED } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';

View File

@ -0,0 +1,13 @@
import { getAccountSlotUrl } from '../../../util/multiaccount';
export function navigateBack() {
const currentUrl = new URL(window.location.href);
const referrer = document.referrer ? new URL(document.referrer) : undefined;
if (referrer?.origin === currentUrl.origin && referrer.pathname === currentUrl.pathname) {
window.history.back(); // Return to previous account with it's state
return;
}
const url = getAccountSlotUrl(1);
window.location.href = url;
}

View File

@ -14,9 +14,9 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getUserStreams, THRESHOLD } from '../../../lib/secret-sauce'; import { getUserStreams, THRESHOLD } from '../../../lib/secret-sauce';
import { selectChat, selectUser } from '../../../global/selectors'; import { selectChat, selectUser } from '../../../global/selectors';
import { animate } from '../../../util/animation'; import { animate } from '../../../util/animation';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { fastRaf } from '../../../util/schedulers'; import { fastRaf } from '../../../util/schedulers';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../../util/windowEnvironment';
import formatGroupCallVolume from './helpers/formatGroupCallVolume'; import formatGroupCallVolume from './helpers/formatGroupCallVolume';
import useInterval from '../../../hooks/schedulers/useInterval'; import useInterval from '../../../hooks/schedulers/useInterval';

View File

@ -13,13 +13,13 @@ import {
} from '../../../lib/secret-sauce'; } from '../../../lib/secret-sauce';
import { selectTabState } from '../../../global/selectors'; import { selectTabState } from '../../../global/selectors';
import { selectPhoneCallUser } from '../../../global/selectors/calls'; import { selectPhoneCallUser } from '../../../global/selectors/calls';
import buildClassName from '../../../util/buildClassName';
import { formatMediaDuration } from '../../../util/dates/dateFormat';
import { import {
IS_ANDROID, IS_ANDROID,
IS_IOS, IS_IOS,
IS_REQUEST_FULLSCREEN_SUPPORTED, IS_REQUEST_FULLSCREEN_SUPPORTED,
} from '../../../util/windowEnvironment'; } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { formatMediaDuration } from '../../../util/dates/dateFormat';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';

View File

@ -13,11 +13,11 @@ import type RLottieInstance from '../../lib/rlottie/RLottie';
import { requestMeasure } from '../../lib/fasterdom/fasterdom'; import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async'; import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
import { IS_ELECTRON } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle'; import buildStyle from '../../util/buildStyle';
import generateUniqueId from '../../util/generateUniqueId'; import generateUniqueId from '../../util/generateUniqueId';
import { hexToRgb } from '../../util/switchTheme'; import { hexToRgb } from '../../util/switchTheme';
import { IS_ELECTRON } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter'; import useColorFilter from '../../hooks/stickers/useColorFilter';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';

View File

@ -9,7 +9,7 @@ import type {
} from '../../api/types'; } from '../../api/types';
import type { BufferedRange } from '../../hooks/useBuffering'; import type { BufferedRange } from '../../hooks/useBuffering';
import type { OldLangFn } from '../../hooks/useOldLang'; import type { OldLangFn } from '../../hooks/useOldLang';
import type { ISettings } from '../../types'; import type { ThemeKey } from '../../types';
import { ApiMediaFormat } from '../../api/types'; import { ApiMediaFormat } from '../../api/types';
import { AudioOrigin } from '../../types'; import { AudioOrigin } from '../../types';
@ -51,7 +51,7 @@ import Icon from './icons/Icon';
import './Audio.scss'; import './Audio.scss';
type OwnProps = { type OwnProps = {
theme: ISettings['theme']; theme: ThemeKey;
message: ApiMessage; message: ApiMessage;
senderTitle?: string; senderTitle?: string;
uploadProgress?: number; uploadProgress?: number;
@ -628,7 +628,7 @@ function renderVoice(
} }
function useWaveformCanvas( function useWaveformCanvas(
theme: ISettings['theme'], theme: ThemeKey,
media?: ApiVoice | ApiVideo, media?: ApiVoice | ApiVideo,
playProgress = 0, playProgress = 0,
isOwn = false, isOwn = false,

View File

@ -69,6 +69,7 @@ type OwnProps = {
peer?: ApiPeer | CustomPeer; peer?: ApiPeer | CustomPeer;
photo?: ApiPhoto; photo?: ApiPhoto;
webPhoto?: ApiWebDocument; webPhoto?: ApiWebDocument;
previewUrl?: string;
text?: string; text?: string;
isSavedMessages?: boolean; isSavedMessages?: boolean;
isSavedDialog?: boolean; isSavedDialog?: boolean;
@ -93,6 +94,7 @@ const Avatar: FC<OwnProps> = ({
peer, peer,
photo, photo,
webPhoto, webPhoto,
previewUrl,
text, text,
isSavedMessages, isSavedMessages,
isSavedDialog, isSavedDialog,
@ -169,7 +171,8 @@ const Avatar: FC<OwnProps> = ({
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);
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl); const imgUrl = imgBlobUrl || previewUrl;
const hasBlobUrl = Boolean(imgUrl || videoBlobUrl);
// `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again // `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again
const shouldPlayVideo = Boolean(videoBlobUrl && shouldLoadVideo); const shouldPlayVideo = Boolean(videoBlobUrl && shouldLoadVideo);
@ -205,7 +208,7 @@ const Avatar: FC<OwnProps> = ({
content = ( content = (
<> <>
<img <img
src={imgBlobUrl} src={imgUrl}
className={buildClassName(cn.media, 'avatar-media', transitionClassNames, videoBlobUrl && 'poster')} className={buildClassName(cn.media, 'avatar-media', transitionClassNames, videoBlobUrl && 'poster')}
alt={author} alt={author}
decoding="async" decoding="async"
@ -262,10 +265,10 @@ const Avatar: FC<OwnProps> = ({
withStorySolid && forceFriendStorySolid && 'close-friend', withStorySolid && forceFriendStorySolid && 'close-friend',
withStorySolid && (realPeer?.hasUnreadStories || forceUnreadStorySolid) && 'has-unread-story', withStorySolid && (realPeer?.hasUnreadStories || forceUnreadStorySolid) && 'has-unread-story',
onClick && 'interactive', onClick && 'interactive',
(!isSavedMessages && !imgBlobUrl) && 'no-photo', (!isSavedMessages && !imgUrl) && 'no-photo',
); );
const hasMedia = Boolean(isSavedMessages || imgBlobUrl); const hasMedia = Boolean(isSavedMessages || imgUrl);
const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => { const { handleClick, handleMouseDown } = useFastClick((e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => {
if (withStory && storyViewerMode !== 'disabled' && realPeer?.hasStories) { if (withStory && storyViewerMode !== 'disabled' && realPeer?.hasStories) {

View File

@ -35,9 +35,9 @@ import type {
import type { import type {
IAnchorPosition, IAnchorPosition,
InlineBotSettings, InlineBotSettings,
ISettings,
MessageList, MessageList,
MessageListType, MessageListType,
ThemeKey,
ThreadId, ThreadId,
} from '../../types'; } from '../../types';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
@ -103,6 +103,8 @@ import {
selectUserFullInfo, selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectCurrentLimit } from '../../global/selectors/limits'; import { selectCurrentLimit } from '../../global/selectors/limits';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import { IS_IOS, IS_VOICE_RECORDING_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { formatMediaDuration, formatVoiceRecordDuration } from '../../util/dates/dateFormat'; import { formatMediaDuration, formatVoiceRecordDuration } from '../../util/dates/dateFormat';
import { processDeepLink } from '../../util/deeplink'; import { processDeepLink } from '../../util/deeplink';
@ -115,7 +117,6 @@ import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import parseHtmlAsFormattedText from '../../util/parseHtmlAsFormattedText'; import parseHtmlAsFormattedText from '../../util/parseHtmlAsFormattedText';
import { insertHtmlInSelection } from '../../util/selection'; import { insertHtmlInSelection } from '../../util/selection';
import { getServerTime } from '../../util/serverTime'; import { getServerTime } from '../../util/serverTime';
import { IS_IOS, IS_VOICE_RECORDING_SUPPORTED } from '../../util/windowEnvironment';
import windowSize from '../../util/windowSize'; import windowSize from '../../util/windowSize';
import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAutoCapitalizationFix'; import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAutoCapitalizationFix';
import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment'; import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment';
@ -256,7 +257,7 @@ type StateProps =
requestedDraftFiles?: File[]; requestedDraftFiles?: File[];
attachBots: GlobalState['attachMenu']['bots']; attachBots: GlobalState['attachMenu']['bots'];
attachMenuPeerType?: ApiAttachMenuPeerType; attachMenuPeerType?: ApiAttachMenuPeerType;
theme: ISettings['theme']; theme: ThemeKey;
fileSizeLimit: number; fileSizeLimit: number;
captionLimit: number; captionLimit: number;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
@ -2300,9 +2301,9 @@ export default memo(withGlobal<OwnProps>(
const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser) const messageWithActualBotKeyboard = (isChatWithBot || !isChatWithUser)
&& selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId); && selectNewestMessageWithBotKeyboardButtons(global, chatId, threadId);
const { const {
language, shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder, shouldSuggestStickers, shouldSuggestCustomEmoji, shouldUpdateStickerSetOrder, shouldPaidMessageAutoApprove,
shouldPaidMessageAutoApprove,
} = global.settings.byKey; } = global.settings.byKey;
const { language, shouldCollectDebugLogs } = selectSharedSettings(global);
const { const {
forwardMessages: { messageIds: forwardMessageIds }, forwardMessages: { messageIds: forwardMessageIds },
} = selectTabState(global); } = selectTabState(global);
@ -2434,7 +2435,7 @@ export default memo(withGlobal<OwnProps>(
canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global), canBuyPremium: !isCurrentUserPremium && !selectIsPremiumPurchaseBlocked(global),
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global), canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
canSendOneTimeMedia: !isChatWithSelf && isChatWithUser && !isChatWithBot && !isInScheduledList, canSendOneTimeMedia: !isChatWithSelf && isChatWithUser && !isChatWithBot && !isInScheduledList,
shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs, shouldCollectDebugLogs,
sentStoryReaction, sentStoryReaction,
stealthMode: global.stories.stealthMode, stealthMode: global.stories.stealthMode,
replyToTopic, replyToTopic,

View File

@ -30,9 +30,9 @@ import {
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
} from '../../global/selectors'; } from '../../global/selectors';
import animateHorizontalScroll from '../../util/animateHorizontalScroll'; import animateHorizontalScroll from '../../util/animateHorizontalScroll';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { pickTruthy, unique, uniqueByField } from '../../util/iteratees'; import { pickTruthy, unique, uniqueByField } from '../../util/iteratees';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { REM } from './helpers/mediaDimensions'; import { REM } from './helpers/mediaDimensions';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';

View File

@ -74,7 +74,7 @@ const Document = ({
onMediaClick, onMediaClick,
onDateClick, onDateClick,
}: OwnProps) => { }: OwnProps) => {
const { cancelMediaDownload, downloadMedia, setSettingOption } = getActions(); const { cancelMediaDownload, downloadMedia, setSharedSettingOption } = getActions();
// 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);
@ -158,7 +158,7 @@ const Document = ({
}); });
const handleSvgConfirm = useLastCallback(() => { const handleSvgConfirm = useLastCallback(() => {
setSettingOption({ shouldWarnAboutSvg: !shouldNotWarnAboutSvg }); setSharedSettingOption({ shouldWarnAboutSvg: !shouldNotWarnAboutSvg });
closeSvgDialog(); closeSvgDialog();
handleDownload(); handleDownload();
}); });

View File

@ -5,9 +5,9 @@ import React, {
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { formatMediaDateTime, formatPastTimeShort } from '../../util/dates/dateFormat'; import { formatMediaDateTime, formatPastTimeShort } from '../../util/dates/dateFormat';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
import { getColorFromExtension, getFileSizeString } from './helpers/documentInfo'; import { getColorFromExtension, getFileSizeString } from './helpers/documentInfo';
import { getDocumentThumbnailDimensions } from './helpers/mediaDimensions'; import { getDocumentThumbnailDimensions } from './helpers/mediaDimensions';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';

View File

@ -63,9 +63,9 @@ const FullNameTitle: FC<OwnProps> = ({
noLoopLimit, noLoopLimit,
canCopyTitle, canCopyTitle,
iconElement, iconElement,
statusSparklesColor,
onEmojiStatusClick, onEmojiStatusClick,
observeIntersection, observeIntersection,
statusSparklesColor,
}) => { }) => {
const lang = useOldLang(); const lang = useOldLang();
const { showNotification } = getActions(); const { showNotification } = getActions();
@ -73,9 +73,10 @@ const FullNameTitle: FC<OwnProps> = ({
const customPeer = 'isCustomPeer' in peer ? peer : undefined; const customPeer = 'isCustomPeer' in peer ? peer : undefined;
const isUser = realPeer && isApiPeerUser(realPeer); const isUser = realPeer && isApiPeerUser(realPeer);
const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(lang, realPeer)); const title = realPeer && (isUser ? getUserFullName(realPeer) : getChatTitle(lang, realPeer));
const isPremium = isUser && realPeer.isPremium; const isPremium = (isUser && realPeer.isPremium) || customPeer?.isPremium;
const canShowEmojiStatus = withEmojiStatus && !isSavedMessages && realPeer; const canShowEmojiStatus = withEmojiStatus && !isSavedMessages;
const emojiStatus = realPeer?.emojiStatus; const emojiStatus = realPeer?.emojiStatus
|| (customPeer?.emojiStatusId ? { type: 'regular', documentId: customPeer.emojiStatusId } : undefined);
const handleTitleClick = useLastCallback((e) => { const handleTitleClick = useLastCallback((e) => {
if (!title || !canCopyTitle) { if (!title || !canCopyTitle) {
@ -89,7 +90,7 @@ const FullNameTitle: FC<OwnProps> = ({
const specialTitle = useMemo(() => { const specialTitle = useMemo(() => {
if (customPeer) { if (customPeer) {
return customPeer.title || lang(customPeer.titleKey!); return renderText(customPeer.title || lang(customPeer.titleKey!));
} }
if (isSavedMessages) { if (isSavedMessages) {

View File

@ -7,8 +7,8 @@ import type { ApiVideo } from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { getVideoMediaHash, getVideoPreviewMediaHash } from '../../global/helpers'; import { getVideoMediaHash, getVideoPreviewMediaHash } from '../../global/helpers';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
import useBuffering from '../../hooks/useBuffering'; import useBuffering from '../../hooks/useBuffering';

View File

@ -6,9 +6,9 @@ import React, {
import { MIN_PASSWORD_LENGTH } from '../../config'; import { MIN_PASSWORD_LENGTH } from '../../config';
import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import stopEvent from '../../util/stopEvent'; import stopEvent from '../../util/stopEvent';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useTimeout from '../../hooks/schedulers/useTimeout'; import useTimeout from '../../hooks/schedulers/useTimeout';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';

View File

@ -20,10 +20,10 @@ import {
selectUser, selectUser,
selectUserStatus, selectUserStatus,
} from '../../global/selectors'; } from '../../global/selectors';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { captureEvents, SwipeDirection } from '../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../util/memo';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';
import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate'; import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate';

View File

@ -17,9 +17,9 @@ import {
isDeletedUser, isDeletedUser,
isUserId, isUserId,
} from '../../global/helpers'; } from '../../global/helpers';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { getFirstLetters } from '../../util/textFormat'; import { getFirstLetters } from '../../util/textFormat';
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
import { getPeerColorClass } from './helpers/peerColor'; import { getPeerColorClass } from './helpers/peerColor';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';

View File

@ -1,8 +1,8 @@
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import React, { memo, useCallback } from '../../lib/teact/teact'; import React, { memo, useCallback } from '../../lib/teact/teact';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import Button from '../ui/Button'; import Button from '../ui/Button';
import Icon from './icons/Icon'; import Icon from './icons/Icon';

View File

@ -7,9 +7,9 @@ import { getActions } from '../../global';
import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types'; import type { ApiBotInlineMediaResult, ApiSticker } from '../../api/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { getServerTime } from '../../util/serverTime'; import { getServerTime } from '../../util/serverTime';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur'; import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMessageInputBlur';
import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener'; import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListener';

View File

@ -7,9 +7,9 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { getStickerMediaHash } from '../../global/helpers'; import { getStickerMediaHash } from '../../global/helpers';
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors'; import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
import { IS_ANDROID, IS_IOS, IS_WEBM_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import * as mediaLoader from '../../util/mediaLoader'; import * as mediaLoader from '../../util/mediaLoader';
import { IS_ANDROID, IS_IOS, IS_WEBM_SUPPORTED } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter'; import useColorFilter from '../../hooks/stickers/useColorFilter';
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas'; import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';

View File

@ -4,7 +4,7 @@ import type {
import { STICKER_SIZE_INLINE_DESKTOP_FACTOR, STICKER_SIZE_INLINE_MOBILE_FACTOR } from '../../../config'; import { STICKER_SIZE_INLINE_DESKTOP_FACTOR, STICKER_SIZE_INLINE_MOBILE_FACTOR } from '../../../config';
import { getPhotoInlineDimensions, getVideoDimensions } from '../../../global/helpers'; import { getPhotoInlineDimensions, getVideoDimensions } from '../../../global/helpers';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import windowSize from '../../../util/windowSize'; import windowSize from '../../../util/windowSize';
export const MEDIA_VIEWER_MEDIA_QUERY = '(max-height: 640px)'; export const MEDIA_VIEWER_MEDIA_QUERY = '(max-height: 640px)';

View File

@ -7,6 +7,7 @@ import {
BASE_URL, IS_PACKAGED_ELECTRON, RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE, BASE_URL, IS_PACKAGED_ELECTRON, RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE,
} from '../../../config'; } from '../../../config';
import EMOJI_REGEX from '../../../lib/twemojiRegex'; import EMOJI_REGEX from '../../../lib/twemojiRegex';
import { IS_EMOJI_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { isDeepLink } from '../../../util/deepLinkParser'; import { isDeepLink } from '../../../util/deepLinkParser';
import { import {
@ -16,7 +17,6 @@ import {
} from '../../../util/emoji/emoji'; } from '../../../util/emoji/emoji';
import fixNonStandardEmoji from '../../../util/emoji/fixNonStandardEmoji'; import fixNonStandardEmoji from '../../../util/emoji/fixNonStandardEmoji';
import { compact } from '../../../util/iteratees'; import { compact } from '../../../util/iteratees';
import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
import MentionLink from '../../middle/message/MentionLink'; import MentionLink from '../../middle/message/MentionLink';
import SafeLink from '../SafeLink'; import SafeLink from '../SafeLink';

View File

@ -3,9 +3,9 @@ import { getActions } from '../../../global';
import type { ActiveEmojiInteraction } from '../../../types'; import type { ActiveEmojiInteraction } from '../../../types';
import { IS_ELECTRON } from '../../../util/browser/windowEnvironment';
import buildStyle from '../../../util/buildStyle'; import buildStyle from '../../../util/buildStyle';
import safePlay from '../../../util/safePlay'; import safePlay from '../../../util/safePlay';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { REM } from '../helpers/mediaDimensions'; import { REM } from '../helpers/mediaDimensions';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -1,7 +1,7 @@
import React, { type TeactNode } from '../../../lib/teact/teact'; import React, { type TeactNode } from '../../../lib/teact/teact';
import { IS_IOS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { IS_IOS } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';

View File

@ -5,12 +5,12 @@ import React, {
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 { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatTime, formatWeekday } from '../../../util/dates/dateFormat'; import { formatTime, formatWeekday } from '../../../util/dates/dateFormat';
import { import {
getUtcOffset, getWeekStart, shiftTimeRanges, splitDays, getUtcOffset, getWeekStart, shiftTimeRanges, splitDays,
} from '../../../util/dates/workHours'; } from '../../../util/dates/workHours';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useSelectorSignal from '../../../hooks/data/useSelectorSignal'; import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
import useInterval from '../../../hooks/schedulers/useInterval'; import useInterval from '../../../hooks/schedulers/useInterval';

View File

@ -10,11 +10,11 @@ import {
import { requestMeasure } from '../../../lib/fasterdom/fasterdom'; import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
import { getStickerMediaHash } from '../../../global/helpers'; import { getStickerMediaHash } from '../../../global/helpers';
import { selectIsPremiumPurchaseBlocked } from '../../../global/selectors'; import { selectIsPremiumPurchaseBlocked } from '../../../global/selectors';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatDateToString } from '../../../util/dates/dateFormat'; import { formatDateToString } from '../../../util/dates/dateFormat';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import * as mediaLoader from '../../../util/mediaLoader'; import * as mediaLoader from '../../../util/mediaLoader';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment';
import renderText from '../helpers/renderText'; import renderText from '../helpers/renderText';
import useTimeout from '../../../hooks/schedulers/useTimeout'; import useTimeout from '../../../hooks/schedulers/useTimeout';

View File

@ -4,9 +4,9 @@ import React, { memo, useMemo } from '../../../lib/teact/teact';
import type { ApiEmojiStatusType, ApiReactionCustomEmoji } from '../../../api/types'; import type { ApiEmojiStatusType, ApiReactionCustomEmoji } from '../../../api/types';
import { getStickerHashById } from '../../../global/helpers'; import { getStickerHashById } from '../../../global/helpers';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle'; import buildStyle from '../../../util/buildStyle';
import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment';
import useMedia from '../../../hooks/useMedia'; import useMedia from '../../../hooks/useMedia';

View File

@ -8,8 +8,8 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { isSameReaction } from '../../../global/helpers'; import { isSameReaction } from '../../../global/helpers';
import { selectPerformanceSettingsValue, selectTabState } from '../../../global/selectors'; import { selectPerformanceSettingsValue, selectTabState } from '../../../global/selectors';
import { IS_ANDROID, IS_IOS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { IS_ANDROID, IS_IOS } from '../../../util/windowEnvironment';
import { LOCAL_TGS_URLS } from '../helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../helpers/animatedAssets';
import { REM } from '../helpers/mediaDimensions'; import { REM } from '../helpers/mediaDimensions';

View File

@ -10,11 +10,11 @@ import type { ReducerAction } from '../../hooks/useReducer';
import { LeftColumnContent, SettingsScreens } from '../../types'; import { LeftColumnContent, SettingsScreens } from '../../types';
import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors'; import { selectCurrentChat, selectIsForumPanelOpen, selectTabState } from '../../global/selectors';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import { captureControlledSwipe } from '../../util/swipeController';
import { import {
IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME, IS_APP, IS_FIREFOX, IS_MAC_OS, IS_TOUCH_ENV, LAYERS_ANIMATION_NAME,
} from '../../util/windowEnvironment'; } from '../../util/browser/windowEnvironment';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import { captureControlledSwipe } from '../../util/swipeController';
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer'; import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
import { useHotkeys } from '../../hooks/useHotkeys'; import { useHotkeys } from '../../hooks/useHotkeys';

View File

@ -0,0 +1,117 @@
import React, { memo, useMemo } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { ApiUser } from '../../../api/types';
import type { CustomPeer } from '../../../types';
import { getCurrentMaxAccountCount, getCurrentProdAccountCount } from '../../../global/helpers';
import { getAccountSlotUrl } from '../../../util/multiaccount';
import { REM } from '../../common/helpers/mediaDimensions';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import useMultiaccountInfo from '../../../hooks/useMultiaccountInfo';
import Avatar from '../../common/Avatar';
import FullNameTitle from '../../common/FullNameTitle';
import MenuItem from '../../ui/MenuItem';
import MenuSeparator from '../../ui/MenuSeparator';
type OwnProps = {
currentUser: ApiUser;
totalLimit: number;
onSelectCurrent?: VoidFunction;
};
const NOTIFICATION_DURATION = 7000;
const AccountMenuItems = ({
currentUser,
totalLimit,
onSelectCurrent,
}: OwnProps) => {
const { showNotification } = getActions();
const lang = useLang();
const accounts = useMultiaccountInfo(currentUser);
const currentCount = getCurrentProdAccountCount();
const maxCount = getCurrentMaxAccountCount();
const shouldShowLimit = currentCount >= maxCount;
const handleLimitClick = useLastCallback(() => {
showNotification({
title: lang('PremiumLimitAccountsTitle'),
message: currentUser.isPremium ? lang('PremiumLimitAccounts') : lang('PremiumLimitAccountsNoPremium'),
duration: NOTIFICATION_DURATION,
});
});
const newAccountUrl = useMemo(() => {
if (!Object.values(accounts).length) {
return undefined;
}
if (currentCount === totalLimit) {
return undefined;
}
let freeIndex = 1;
while (accounts[freeIndex]) {
freeIndex += 1;
}
return getAccountSlotUrl(freeIndex, true);
}, [accounts, currentCount, totalLimit]);
return (
<>
{Object.entries(accounts || {})
.sort(([, account]) => (account.userId === currentUser.id ? -1 : 1))
.map(([slot, account], index, arr) => {
const mockUser: CustomPeer = {
title: [account.firstName, account.lastName].filter(Boolean).join(' '),
isCustomPeer: true,
peerColorId: account.color,
emojiStatusId: account.emojiStatusId,
isPremium: account.isPremium,
};
const hasSeparator = account.userId === currentUser.id && (newAccountUrl || arr.length > 1);
return (
<>
<MenuItem
className="account-menu-item"
customIcon={(
<Avatar
size="mini"
className="account-avatar"
peer={mockUser}
previewUrl={account.avatarUri}
/>
)}
onClick={account.userId === currentUser.id ? onSelectCurrent : undefined}
href={account.userId !== currentUser.id ? getAccountSlotUrl(Number(slot)) : undefined}
>
<FullNameTitle peer={mockUser} withEmojiStatus emojiStatusSize={REM} />
</MenuItem>
{hasSeparator && <MenuSeparator />}
</>
);
})}
{newAccountUrl && (
<MenuItem
icon="add"
rel="noopener" // Allow referrer to be passed
href={!shouldShowLimit ? newAccountUrl : undefined}
onClick={shouldShowLimit ? handleLimitClick : undefined}
>
{lang('MenuAddAccount')}
</MenuItem>
)}
</>
);
};
export default memo(AccountMenuItems);

View File

@ -48,9 +48,9 @@ import {
selectUser, selectUser,
selectUserStatus, selectUserStatus,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing'; import { createLocationHash } from '../../../util/routing';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
import useSelectorSignal from '../../../hooks/data/useSelectorSignal'; import useSelectorSignal from '../../../hooks/data/useSelectorSignal';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';

View File

@ -14,11 +14,11 @@ import type { TabWithProperties } from '../../ui/TabList';
import { ALL_FOLDER_ID } from '../../../config'; import { ALL_FOLDER_ID } from '../../../config';
import { selectCanShareFolder, selectTabState } from '../../../global/selectors'; import { selectCanShareFolder, selectTabState } from '../../../global/selectors';
import { selectCurrentLimit } from '../../../global/selectors/limits'; import { selectCurrentLimit } from '../../../global/selectors/limits';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities'; import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import useDerivedState from '../../../hooks/useDerivedState'; import useDerivedState from '../../../hooks/useDerivedState';

View File

@ -19,10 +19,10 @@ import {
FRESH_AUTH_PERIOD, FRESH_AUTH_PERIOD,
SAVED_FOLDER_ID, SAVED_FOLDER_ID,
} from '../../../config'; } from '../../../config';
import { IS_APP, IS_MAC_OS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager'; import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';
import { IS_APP, IS_MAC_OS } from '../../../util/windowEnvironment';
import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling'; import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling';
import useTopOverscroll from '../../../hooks/scroll/useTopOverscroll'; import useTopOverscroll from '../../../hooks/scroll/useTopOverscroll';

View File

@ -22,11 +22,11 @@ import {
selectTabState, selectTabState,
selectTopicsInfo, selectTopicsInfo,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners'; import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';

View File

@ -9,8 +9,8 @@ import type { SettingsScreens } from '../../../types';
import { LeftColumnContent } from '../../../types'; import { LeftColumnContent } from '../../../types';
import { PRODUCTION_URL } from '../../../config'; import { PRODUCTION_URL } from '../../../config';
import { IS_ELECTRON, IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { IS_ELECTRON, IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useForumPanelRender from '../../../hooks/useForumPanelRender'; import useForumPanelRender from '../../../hooks/useForumPanelRender';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -111,20 +111,22 @@
} }
} }
.emoji-status-effect { .StatusButton {
top: 50%; .emoji-status-effect {
left: 50%; top: 50%;
} left: 50%;
}
.emoji-status { .emoji-status {
overflow: visible; overflow: visible;
--custom-emoji-size: 1.5rem; --custom-emoji-size: 1.5rem;
color: var(--color-primary); color: var(--color-primary);
} }
.StarIcon { .StarIcon {
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
}
} }
// @optimization // @optimization
@ -153,4 +155,19 @@
right: -0.125rem; right: -0.125rem;
} }
} }
.account-menu-item {
--custom-emoji-size: 1rem;
.account-avatar {
margin-inline: 0.375rem 1.125rem;
}
.fullName {
margin: 0;
font-size: 1em;
line-height: 1;
padding-top: 0.1875rem;
}
}
} }

View File

@ -5,7 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
import type { ISettings } from '../../../types'; import type { ThemeKey } from '../../../types';
import { LeftColumnContent, SettingsScreens } from '../../../types'; import { LeftColumnContent, SettingsScreens } from '../../../types';
import { import {
@ -20,10 +20,11 @@ import {
selectTabState, selectTabState,
selectTheme, selectTheme,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { formatDateToString } from '../../../util/dates/dateFormat'; import { formatDateToString } from '../../../util/dates/dateFormat';
import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useConnectionStatus from '../../../hooks/useConnectionStatus'; import useConnectionStatus from '../../../hooks/useConnectionStatus';
@ -68,10 +69,10 @@ type StateProps =
isLoading: boolean; isLoading: boolean;
globalSearchChatId?: string; globalSearchChatId?: string;
searchDate?: number; searchDate?: number;
theme: ISettings['theme']; theme: ThemeKey;
isMessageListOpen: boolean; isMessageListOpen: boolean;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
isConnectionStatusMinimized: ISettings['isConnectionStatusMinimized']; isConnectionStatusMinimized?: boolean;
areChatsLoaded?: boolean; areChatsLoaded?: boolean;
hasPasscode?: boolean; hasPasscode?: boolean;
canSetPasscode?: boolean; canSetPasscode?: boolean;
@ -109,7 +110,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
}) => { }) => {
const { const {
setGlobalSearchDate, setGlobalSearchDate,
setSettingOption, setSharedSettingOption,
setGlobalSearchChatId, setGlobalSearchChatId,
lockScreen, lockScreen,
requestNextSettingsScreen, requestNextSettingsScreen,
@ -185,7 +186,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
}); });
const toggleConnectionStatus = useLastCallback(() => { const toggleConnectionStatus = useLastCallback(() => {
setSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized }); setSharedSettingOption({ isConnectionStatusMinimized: !isConnectionStatusMinimized });
}); });
const handleLockScreen = useLastCallback(() => { const handleLockScreen = useLastCallback(() => {
@ -340,7 +341,7 @@ export default memo(withGlobal<OwnProps>(
const { const {
connectionState, isSyncing, isFetchingDifference, connectionState, isSyncing, isFetchingDifference,
} = global; } = global;
const { isConnectionStatusMinimized } = global.settings.byKey; const { isConnectionStatusMinimized } = selectSharedSettings(global);
return { return {
searchQuery, searchQuery,

View File

@ -1,6 +1,7 @@
import React, { memo, useMemo } from '../../../lib/teact/teact'; import React, { memo, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiUser } from '../../../api/types';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
import type { AnimationLevel, ThemeKey } from '../../../types'; import type { AnimationLevel, ThemeKey } from '../../../types';
@ -20,10 +21,13 @@ import {
INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANCE_STATE_MID,
INITIAL_PERFORMANCE_STATE_MIN, INITIAL_PERFORMANCE_STATE_MIN,
} from '../../../global/initialState'; } from '../../../global/initialState';
import { selectTabState, selectTheme } from '../../../global/selectors'; import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
import { selectPremiumLimit } from '../../../global/selectors/limits';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_MULTIACCOUNT_SUPPORTED } from '../../../util/browser/globalEnvironment';
import { IS_ELECTRON } from '../../../util/browser/windowEnvironment';
import { getPromptInstall } from '../../../util/installPrompt'; import { getPromptInstall } from '../../../util/installPrompt';
import { switchPermanentWebVersion } from '../../../util/permanentWebVersion'; import { switchPermanentWebVersion } from '../../../util/permanentWebVersion';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager'; import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -32,8 +36,10 @@ import useOldLang from '../../../hooks/useOldLang';
import AttachBotItem from '../../middle/composer/AttachBotItem'; import AttachBotItem from '../../middle/composer/AttachBotItem';
import MenuItem from '../../ui/MenuItem'; import MenuItem from '../../ui/MenuItem';
import MenuSeparator from '../../ui/MenuSeparator';
import Switcher from '../../ui/Switcher'; import Switcher from '../../ui/Switcher';
import Toggle from '../../ui/Toggle'; import Toggle from '../../ui/Toggle';
import AccountMenuItems from './AccountMenuItems';
type OwnProps = { type OwnProps = {
onSelectSettings: NoneToVoidFunction; onSelectSettings: NoneToVoidFunction;
@ -45,9 +51,11 @@ type OwnProps = {
type StateProps = { type StateProps = {
animationLevel: AnimationLevel; animationLevel: AnimationLevel;
currentUser?: ApiUser;
theme: ThemeKey; theme: ThemeKey;
canInstall?: boolean; canInstall?: boolean;
attachBots: GlobalState['attachMenu']['bots']; attachBots: GlobalState['attachMenu']['bots'];
accountsTotalLimit: number;
} & Pick<GlobalState, 'currentUserId' | 'archiveSettings'>; } & Pick<GlobalState, 'currentUserId' | 'archiveSettings'>;
const LeftSideMenuItems = ({ const LeftSideMenuItems = ({
@ -57,6 +65,8 @@ const LeftSideMenuItems = ({
theme, theme,
canInstall, canInstall,
attachBots, attachBots,
currentUser,
accountsTotalLimit,
onSelectArchived, onSelectArchived,
onSelectContacts, onSelectContacts,
onSelectSettings, onSelectSettings,
@ -65,7 +75,7 @@ const LeftSideMenuItems = ({
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { const {
openChat, openChat,
setSettingOption, setSharedSettingOption,
updatePerformanceSettings, updatePerformanceSettings,
openChatByUsername, openChatByUsername,
openUrl, openUrl,
@ -91,8 +101,8 @@ const LeftSideMenuItems = ({
e.stopPropagation(); e.stopPropagation();
const newTheme = theme === 'light' ? 'dark' : 'light'; const newTheme = theme === 'light' ? 'dark' : 'light';
setSettingOption({ theme: newTheme }); setSharedSettingOption({ theme: newTheme });
setSettingOption({ shouldUseSystemTheme: false }); setSharedSettingOption({ shouldUseSystemTheme: false });
}); });
const handleAnimationLevelChange = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => { const handleAnimationLevelChange = useLastCallback((e: React.SyntheticEvent<HTMLElement>) => {
@ -106,7 +116,7 @@ const LeftSideMenuItems = ({
? INITIAL_PERFORMANCE_STATE_MIN ? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MAX ? INITIAL_PERFORMANCE_STATE_MAX : INITIAL_PERFORMANCE_STATE_MID); : (newLevel === ANIMATION_LEVEL_MAX ? INITIAL_PERFORMANCE_STATE_MAX : INITIAL_PERFORMANCE_STATE_MID);
setSettingOption({ animationLevel: newLevel as AnimationLevel }); setSharedSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performanceSettings); updatePerformanceSettings(performanceSettings);
}); });
@ -132,6 +142,16 @@ const LeftSideMenuItems = ({
return ( return (
<> <>
{IS_MULTIACCOUNT_SUPPORTED && currentUser && (
<>
<AccountMenuItems
currentUser={currentUser}
totalLimit={accountsTotalLimit}
onSelectCurrent={onSelectSettings}
/>
<MenuSeparator />
</>
)}
<MenuItem <MenuItem
icon="saved-messages" icon="saved-messages"
onClick={handleSelectSaved} onClick={handleSelectSaved}
@ -244,16 +264,18 @@ export default memo(withGlobal<OwnProps>(
const { const {
currentUserId, archiveSettings, currentUserId, archiveSettings,
} = global; } = global;
const { animationLevel } = global.settings.byKey; const { animationLevel } = selectSharedSettings(global);
const attachBots = global.attachMenu.bots; const attachBots = global.attachMenu.bots;
return { return {
currentUserId, currentUserId,
currentUser: selectUser(global, currentUserId!),
theme: selectTheme(global), theme: selectTheme(global),
animationLevel, animationLevel,
canInstall: Boolean(tabState.canInstall), canInstall: Boolean(tabState.canInstall),
archiveSettings, archiveSettings,
attachBots, attachBots,
accountsTotalLimit: selectPremiumLimit(global, 'moreAccounts'),
}; };
}, },
)(LeftSideMenuItems)); )(LeftSideMenuItems));

View File

@ -64,7 +64,7 @@ const StatusButton: FC<StateProps> = ({ emojiStatus, collectibleStatuses }) => {
}, [openStatusPicker]); }, [openStatusPicker]);
return ( return (
<div className="extra-spacing"> <div className="StatusButton extra-spacing">
{Boolean(isEffectShown && emojiStatus) && ( {Boolean(isEffectShown && emojiStatus) && (
<CustomEmojiEffect <CustomEmojiEffect
reaction={emojiStatus!} reaction={emojiStatus!}

View File

@ -27,9 +27,9 @@ import {
selectThreadParam, selectThreadParam,
selectTopics, selectTopics,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { createLocationHash } from '../../../util/routing'; import { createLocationHash } from '../../../util/routing';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';

View File

@ -5,8 +5,8 @@ import type { ApiChat, ApiTopic } from '../../../../api/types';
import type { MenuItemContextAction } from '../../../ui/ListItem'; import type { MenuItemContextAction } from '../../../ui/ListItem';
import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers'; import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
import { compact } from '../../../../util/iteratees'; import { compact } from '../../../../util/iteratees';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/windowEnvironment';
import useOldLang from '../../../../hooks/useOldLang'; import useOldLang from '../../../../hooks/useOldLang';

View File

@ -3,7 +3,7 @@ import React, { memo, useCallback, useState } from '../../../lib/teact/teact';
import { LeftColumnContent } from '../../../types'; import { LeftColumnContent } from '../../../types';
import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment'; import { LAYERS_ANIMATION_NAME } from '../../../util/browser/windowEnvironment';
import Transition from '../../ui/Transition'; import Transition from '../../ui/Transition';
import NewChatStep1 from './NewChatStep1'; import NewChatStep1 from './NewChatStep1';

View File

@ -2,13 +2,14 @@ import type {
ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser, ApiChat, ApiGlobalMessageSearchType, ApiMessage, ApiUser,
} from '../../../../api/types'; } from '../../../../api/types';
import type { GlobalState, TabState } from '../../../../global/types'; import type { GlobalState, TabState } from '../../../../global/types';
import type { ISettings } from '../../../../types'; import type { ThemeKey } from '../../../../types';
import type { SearchResultKey } from '../../../../util/keys/searchResultKey'; import type { SearchResultKey } from '../../../../util/keys/searchResultKey';
import { selectChat, selectTabState, selectTheme } from '../../../../global/selectors'; import { selectChat, selectTabState, selectTheme } from '../../../../global/selectors';
import { selectSharedSettings } from '../../../../global/selectors/sharedState';
export type StateProps = { export type StateProps = {
theme: ISettings['theme']; theme: ThemeKey;
isLoading?: boolean; isLoading?: boolean;
chatsById: Record<string, ApiChat>; chatsById: Record<string, ApiChat>;
usersById: Record<string, ApiUser>; usersById: Record<string, ApiUser>;
@ -29,6 +30,8 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
fetchingStatus, resultsByType, chatId, fetchingStatus, resultsByType, chatId,
} = tabState.globalSearch; } = tabState.globalSearch;
const { shouldWarnAboutSvg } = selectSharedSettings(global);
// One component is used for two different types of results. // One component is used for two different types of results.
// The differences between them are only in the isVoice property. // The differences between them are only in the isVoice property.
// The rest of the search results use their own personal components. // The rest of the search results use their own personal components.
@ -50,7 +53,7 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
searchChatId: chatId, searchChatId: chatId,
activeDownloads, activeDownloads,
isChatProtected: chatId ? selectChat(global, chatId)?.isProtected : undefined, isChatProtected: chatId ? selectChat(global, chatId)?.isProtected : undefined,
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg, shouldWarnAboutSvg,
}; };
}; };
} }

View File

@ -6,7 +6,7 @@ import type { FolderEditDispatch, FoldersState } from '../../../hooks/reducers/u
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectTabState } from '../../../global/selectors'; import { selectTabState } from '../../../global/selectors';
import { LAYERS_ANIMATION_NAME } from '../../../util/windowEnvironment'; import { LAYERS_ANIMATION_NAME } from '../../../util/browser/windowEnvironment';
import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer'; import useTwoFaReducer from '../../../hooks/reducers/useTwoFaReducer';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -5,7 +5,7 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiSticker, ApiStickerSet } from '../../../api/types'; import type { ApiSticker, ApiStickerSet } from '../../../api/types';
import type { ISettings } from '../../../types'; import type { AccountSettings } from '../../../types';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors'; import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
@ -23,7 +23,7 @@ type OwnProps = {
onReset: () => void; onReset: () => void;
}; };
type StateProps = Pick<ISettings, ( type StateProps = Pick<AccountSettings, (
'shouldSuggestCustomEmoji' 'shouldSuggestCustomEmoji'
)> & { )> & {
customEmojiSetIds?: string[]; customEmojiSetIds?: string[];

View File

@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback } from '../../../lib/teact/teact'; import React, { memo, useCallback } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ISettings } from '../../../types'; import type { AccountSettings } from '../../../types';
import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config'; import { AUTODOWNLOAD_FILESIZE_MB_LIMITS } from '../../../config';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
@ -18,7 +18,7 @@ type OwnProps = {
onReset: () => void; onReset: () => void;
}; };
type StateProps = Pick<ISettings, ( type StateProps = Pick<AccountSettings, (
'canAutoLoadPhotoFromContacts' | 'canAutoLoadPhotoFromContacts' |
'canAutoLoadPhotoInPrivateChats' | 'canAutoLoadPhotoInPrivateChats' |
'canAutoLoadPhotoInGroups' | 'canAutoLoadPhotoInGroups' |

View File

@ -4,7 +4,7 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ISettings } from '../../../types'; import type { AccountSettings } from '../../../types';
import { SUPPORTED_TRANSLATION_LANGUAGES } from '../../../config'; import { SUPPORTED_TRANSLATION_LANGUAGES } from '../../../config';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -50,7 +50,7 @@ type OwnProps = {
onReset: () => void; onReset: () => void;
}; };
type StateProps = Pick<ISettings, 'doNotTranslate'>; type StateProps = Pick<AccountSettings, 'doNotTranslate'>;
const SettingsDoNotTranslate: FC<OwnProps & StateProps> = ({ const SettingsDoNotTranslate: FC<OwnProps & StateProps> = ({
isActive, isActive,

View File

@ -5,9 +5,14 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import { DEBUG_LOG_FILENAME } from '../../../config'; import { DEBUG_LOG_FILENAME } from '../../../config';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import {
IS_ELECTRON,
IS_SNAP_EFFECT_SUPPORTED,
IS_WAVE_TRANSFORM_SUPPORTED,
} from '../../../util/browser/windowEnvironment';
import { getDebugLogs } from '../../../util/debugConsole'; import { getDebugLogs } from '../../../util/debugConsole';
import download from '../../../util/download'; import download from '../../../util/download';
import { IS_ELECTRON, IS_SNAP_EFFECT_SUPPORTED, IS_WAVE_TRANSFORM_SUPPORTED } from '../../../util/windowEnvironment';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
@ -39,7 +44,7 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
shouldCollectDebugLogs, shouldCollectDebugLogs,
shouldDebugExportedSenders, shouldDebugExportedSenders,
}) => { }) => {
const { requestConfetti, setSettingOption, requestWave } = getActions(); const { requestConfetti, setSharedSettingOption, requestWave } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const snapButtonRef = useRef<HTMLDivElement>(null); const snapButtonRef = useRef<HTMLDivElement>(null);
@ -128,7 +133,7 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
label="Allow HTTP Transport" label="Allow HTTP Transport"
checked={Boolean(shouldAllowHttpTransport)} checked={Boolean(shouldAllowHttpTransport)}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldAllowHttpTransport: !shouldAllowHttpTransport })} onCheck={() => setSharedSettingOption({ shouldAllowHttpTransport: !shouldAllowHttpTransport })}
/> />
<Checkbox <Checkbox
@ -136,21 +141,21 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
disabled={!shouldAllowHttpTransport} disabled={!shouldAllowHttpTransport}
checked={Boolean(shouldForceHttpTransport)} checked={Boolean(shouldForceHttpTransport)}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldForceHttpTransport: !shouldForceHttpTransport })} onCheck={() => setSharedSettingOption({ shouldForceHttpTransport: !shouldForceHttpTransport })}
/> />
<Checkbox <Checkbox
label={lang('DebugMenuEnableLogs')} label={lang('DebugMenuEnableLogs')}
checked={Boolean(shouldCollectDebugLogs)} checked={Boolean(shouldCollectDebugLogs)}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldCollectDebugLogs: !shouldCollectDebugLogs })} onCheck={() => setSharedSettingOption({ shouldCollectDebugLogs: !shouldCollectDebugLogs })}
/> />
<Checkbox <Checkbox
label="Enable exported senders debug" label="Enable exported senders debug"
checked={Boolean(shouldDebugExportedSenders)} checked={Boolean(shouldDebugExportedSenders)}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onCheck={() => setSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })} onCheck={() => setSharedSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })}
/> />
{IS_ELECTRON && ( {IS_ELECTRON && (
@ -174,11 +179,18 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => { (global): StateProps => {
const {
shouldForceHttpTransport,
shouldAllowHttpTransport,
shouldCollectDebugLogs,
shouldDebugExportedSenders,
} = selectSharedSettings(global);
return { return {
shouldForceHttpTransport: global.settings.byKey.shouldForceHttpTransport, shouldForceHttpTransport,
shouldAllowHttpTransport: global.settings.byKey.shouldAllowHttpTransport, shouldAllowHttpTransport,
shouldCollectDebugLogs: global.settings.byKey.shouldCollectDebugLogs, shouldCollectDebugLogs,
shouldDebugExportedSenders: global.settings.byKey.shouldDebugExportedSenders, shouldDebugExportedSenders,
}; };
}, },
)(SettingsExperimental)); )(SettingsExperimental));

View File

@ -4,16 +4,16 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ISettings, TimeFormat } from '../../../types'; import type { SharedSettings, ThemeKey, TimeFormat } from '../../../types';
import type { IRadioOption } from '../../ui/RadioGroup'; import type { IRadioOption } from '../../ui/RadioGroup';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { pick } from '../../../util/iteratees'; import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { setTimeFormat } from '../../../util/oldLangProvider';
import { getSystemTheme } from '../../../util/systemTheme';
import { import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_MAC_OS, IS_WINDOWS, IS_ANDROID, IS_ELECTRON, IS_IOS, IS_MAC_OS, IS_WINDOWS,
} from '../../../util/windowEnvironment'; } from '../../../util/browser/windowEnvironment';
import { setTimeFormat } from '../../../util/oldLangProvider';
import { getSystemTheme } from '../../../util/systemTheme';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
@ -31,28 +31,26 @@ type OwnProps = {
}; };
type StateProps = type StateProps =
Pick<ISettings, ( Pick<SharedSettings, (
'messageTextSize' | 'messageTextSize' |
'animationLevel' |
'messageSendKeyCombo' | 'messageSendKeyCombo' |
'timeFormat' 'timeFormat' |
)> & { 'theme' |
theme: ISettings['theme']; 'shouldUseSystemTheme'
shouldUseSystemTheme: boolean; )>;
};
const SettingsGeneral: FC<OwnProps & StateProps> = ({ const SettingsGeneral: FC<OwnProps & StateProps> = ({
isActive, isActive,
onScreenSelect,
onReset,
messageTextSize, messageTextSize,
messageSendKeyCombo, messageSendKeyCombo,
timeFormat, timeFormat,
theme, theme,
shouldUseSystemTheme, shouldUseSystemTheme,
onScreenSelect,
onReset,
}) => { }) => {
const { const {
setSettingOption, setSharedSettingOption,
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
@ -96,26 +94,26 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
document.documentElement.style.setProperty('--message-text-size', `${newSize}px`); document.documentElement.style.setProperty('--message-text-size', `${newSize}px`);
document.documentElement.setAttribute('data-message-text-size', newSize.toString()); document.documentElement.setAttribute('data-message-text-size', newSize.toString());
setSettingOption({ messageTextSize: newSize }); setSharedSettingOption({ messageTextSize: newSize });
}, [setSettingOption]); }, []);
const handleAppearanceThemeChange = useCallback((value: string) => { const handleAppearanceThemeChange = useCallback((value: string) => {
const newTheme = value === 'auto' ? getSystemTheme() : value as ISettings['theme']; const newTheme = value === 'auto' ? getSystemTheme() : value as ThemeKey;
setSettingOption({ theme: newTheme }); setSharedSettingOption({ theme: newTheme });
setSettingOption({ shouldUseSystemTheme: value === 'auto' }); setSharedSettingOption({ shouldUseSystemTheme: value === 'auto' });
}, [setSettingOption]); }, []);
const handleTimeFormatChange = useCallback((newTimeFormat: string) => { const handleTimeFormatChange = useCallback((newTimeFormat: string) => {
setSettingOption({ timeFormat: newTimeFormat as TimeFormat }); setSharedSettingOption({ timeFormat: newTimeFormat as TimeFormat });
setSettingOption({ wasTimeFormatSetManually: true }); setSharedSettingOption({ wasTimeFormatSetManually: true });
setTimeFormat(newTimeFormat as TimeFormat); setTimeFormat(newTimeFormat as TimeFormat);
}, [setSettingOption]); }, []);
const handleMessageSendComboChange = useCallback((newCombo: string) => { const handleMessageSendComboChange = useCallback((newCombo: string) => {
setSettingOption({ messageSendKeyCombo: newCombo as ISettings['messageSendKeyCombo'] }); setSharedSettingOption({ messageSendKeyCombo: newCombo as SharedSettings['messageSendKeyCombo'] });
}, [setSettingOption]); }, []);
const [isTrayIconEnabled, setIsTrayIconEnabled] = useState(false); const [isTrayIconEnabled, setIsTrayIconEnabled] = useState(false);
useEffect(() => { useEffect(() => {
@ -204,17 +202,18 @@ const SettingsGeneral: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { theme, shouldUseSystemTheme } = global.settings.byKey; const {
theme,
shouldUseSystemTheme,
messageSendKeyCombo,
messageTextSize,
timeFormat,
} = selectSharedSettings(global);
return { return {
...pick(global.settings.byKey, [ messageSendKeyCombo,
'messageTextSize', messageTextSize,
'animationLevel', timeFormat,
'messageSendKeyCombo',
'isSensitiveEnabled',
'canChangeSensitive',
'timeFormat',
]),
theme, theme,
shouldUseSystemTheme, shouldUseSystemTheme,
}; };

View File

@ -9,7 +9,7 @@ import type { ThemeKey } from '../../../types';
import { SettingsScreens, UPLOADING_WALLPAPER_SLUG } from '../../../types'; import { SettingsScreens, UPLOADING_WALLPAPER_SLUG } from '../../../types';
import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config'; import { DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../../../config';
import { selectTheme } from '../../../global/selectors'; import { selectTheme, selectThemeValues } from '../../../global/selectors';
import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors'; import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors';
import { validateFiles } from '../../../util/files'; import { validateFiles } from '../../../util/files';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
@ -173,7 +173,7 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const theme = selectTheme(global); const theme = selectTheme(global);
const { background, isBlurred } = global.settings.themes[theme] || {}; const { background, isBlurred } = selectThemeValues(global, theme) || {};
const { loadedWallpapers } = global.settings; const { loadedWallpapers } = global.settings;
return { return {

View File

@ -8,7 +8,7 @@ import { getActions, withGlobal } from '../../../global';
import type { ThemeKey } from '../../../types'; import type { ThemeKey } from '../../../types';
import type { RealTouchEvent } from '../../../util/captureEvents'; import type { RealTouchEvent } from '../../../util/captureEvents';
import { selectTheme } from '../../../global/selectors'; import { selectTheme, selectThemeValues } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { captureEvents } from '../../../util/captureEvents'; import { captureEvents } from '../../../util/captureEvents';
import { import {
@ -351,7 +351,7 @@ function drawHue(canvas: HTMLCanvasElement) {
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const theme = selectTheme(global); const theme = selectTheme(global);
const { backgroundColor } = global.settings.themes[theme] || {}; const { backgroundColor } = selectThemeValues(global, theme) || {};
return { return {
backgroundColor, backgroundColor,
theme, theme,

View File

@ -4,13 +4,13 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiLanguage } from '../../../api/types'; import type { AccountSettings, LangCode, SharedSettings } from '../../../types';
import type { ISettings, LangCode } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectIsCurrentUserPremium } from '../../../global/selectors'; import { selectIsCurrentUserPremium } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import { IS_TRANSLATION_SUPPORTED } from '../../../util/browser/windowEnvironment';
import { oldSetLanguage } from '../../../util/oldLangProvider'; import { oldSetLanguage } from '../../../util/oldLangProvider';
import { IS_TRANSLATION_SUPPORTED } from '../../../util/windowEnvironment';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
@ -30,8 +30,8 @@ type OwnProps = {
type StateProps = { type StateProps = {
isCurrentUserPremium: boolean; isCurrentUserPremium: boolean;
languages?: ApiLanguage[]; } & Pick<AccountSettings, 'canTranslate' | 'canTranslateChats' | 'doNotTranslate'>
} & Pick<ISettings, | 'language' | 'canTranslate' | 'canTranslateChats' | 'doNotTranslate'>; & Pick<SharedSettings, 'language' | 'languages'>;
const SettingsLanguage: FC<OwnProps & StateProps> = ({ const SettingsLanguage: FC<OwnProps & StateProps> = ({
isActive, isActive,
@ -47,6 +47,7 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
const { const {
loadLanguages, loadLanguages,
setSettingOption, setSettingOption,
setSharedSettingOption,
openPremiumModal, openPremiumModal,
} = getActions(); } = getActions();
@ -70,7 +71,7 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
void oldSetLanguage(langCode as LangCode, () => { void oldSetLanguage(langCode as LangCode, () => {
unmarkIsLoading(); unmarkIsLoading();
setSettingOption({ language: langCode as LangCode }); setSharedSettingOption({ language: langCode as LangCode });
}); });
}); });
@ -182,9 +183,9 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { const {
language, canTranslate, canTranslateChats, doNotTranslate, canTranslate, canTranslateChats, doNotTranslate,
} = global.settings.byKey; } = global.settings.byKey;
const languages = global.settings.languages; const { language, languages } = selectSharedSettings(global);
const isCurrentUserPremium = selectIsCurrentUserPremium(global); const isCurrentUserPremium = selectIsCurrentUserPremium(global);

View File

@ -16,7 +16,7 @@ import {
} from '../../../global/initialState'; } from '../../../global/initialState';
import { selectPerformanceSettings } from '../../../global/selectors'; import { selectPerformanceSettings } from '../../../global/selectors';
import { areDeepEqual } from '../../../util/areDeepEqual'; import { areDeepEqual } from '../../../util/areDeepEqual';
import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/windowEnvironment'; import { IS_BACKDROP_BLUR_SUPPORTED, IS_SNAP_EFFECT_SUPPORTED } from '../../../util/browser/windowEnvironment';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -81,7 +81,7 @@ function SettingsPerformance({
onReset, onReset,
}: OwnProps & StateProps) { }: OwnProps & StateProps) {
const { const {
setSettingOption, setSharedSettingOption,
updatePerformanceSettings, updatePerformanceSettings,
} = getActions(); } = getActions();
@ -138,9 +138,9 @@ function SettingsPerformance({
? INITIAL_PERFORMANCE_STATE_MIN ? INITIAL_PERFORMANCE_STATE_MIN
: (newLevel === ANIMATION_LEVEL_MED ? INITIAL_PERFORMANCE_STATE_MID : INITIAL_PERFORMANCE_STATE_MAX); : (newLevel === ANIMATION_LEVEL_MED ? INITIAL_PERFORMANCE_STATE_MID : INITIAL_PERFORMANCE_STATE_MAX);
setSettingOption({ animationLevel: newLevel as AnimationLevel }); setSharedSettingOption({ animationLevel: newLevel as AnimationLevel });
updatePerformanceSettings(performance); updatePerformanceSettings(performance);
}, [setSettingOption]); }, []);
const handlePropertyGroupChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const handlePropertyGroupChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target; const { name, checked } = e.target;

View File

@ -7,6 +7,7 @@ import type { GlobalState } from '../../../global/types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../global/selectors'; import { selectCanSetPasscode, selectIsCurrentUserPremium } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import useHistoryBack from '../../../hooks/useHistoryBack'; import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -66,7 +67,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
loadGlobalPrivacySettings, loadGlobalPrivacySettings,
updateGlobalPrivacySettings, updateGlobalPrivacySettings,
loadWebAuthorizations, loadWebAuthorizations,
setSettingOption, setSharedSettingOption,
} = getActions(); } = getActions();
useEffect(() => { useEffect(() => {
@ -97,7 +98,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
}, [updateGlobalPrivacySettings]); }, [updateGlobalPrivacySettings]);
const handleChatInTitleChange = useCallback((isChecked: boolean) => { const handleChatInTitleChange = useCallback((isChecked: boolean) => {
setSettingOption({ setSharedSettingOption({
canDisplayChatInTitle: isChecked, canDisplayChatInTitle: isChecked,
}); });
}, []); }, []);
@ -405,7 +406,7 @@ export default memo(withGlobal<OwnProps>(
settings: { settings: {
byKey: { byKey: {
hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact, hasPassword, isSensitiveEnabled, canChangeSensitive, shouldArchiveAndMuteNewNonContact,
canDisplayChatInTitle, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars, shouldNewNonContactPeersRequirePremium, nonContactPeersPaidStars,
}, },
privacy, privacy,
}, },
@ -416,6 +417,7 @@ export default memo(withGlobal<OwnProps>(
appConfig, appConfig,
} = global; } = global;
const { canDisplayChatInTitle } = selectSharedSettings(global);
const shouldChargeForMessages = Boolean(nonContactPeersPaidStars); const shouldChargeForMessages = Boolean(nonContactPeersPaidStars);
return { return {

View File

@ -10,7 +10,7 @@ import type {
ApiSticker, ApiSticker,
ApiStickerSet, ApiStickerSet,
} from '../../../api/types'; } from '../../../api/types';
import type { ISettings } from '../../../types'; import type { AccountSettings } from '../../../types';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import { selectCanPlayAnimatedEmojis } from '../../../global/selectors'; import { selectCanPlayAnimatedEmojis } from '../../../global/selectors';
@ -36,7 +36,7 @@ type OwnProps = {
}; };
type StateProps = type StateProps =
Pick<ISettings, ( Pick<AccountSettings, (
'shouldSuggestStickers' | 'shouldUpdateStickerSetOrder' 'shouldSuggestStickers' | 'shouldUpdateStickerSetOrder'
)> & { )> & {
addedSetIds?: string[]; addedSetIds?: string[];

View File

@ -7,7 +7,7 @@ import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types'; import type { ApiSticker } from '../../../../api/types';
import { selectAnimatedEmoji, selectTabState } from '../../../../global/selectors'; import { selectAnimatedEmoji, selectTabState } from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
import useAppLayout from '../../../../hooks/useAppLayout'; import useAppLayout from '../../../../hooks/useAppLayout';
import useHistoryBack from '../../../../hooks/useHistoryBack'; import useHistoryBack from '../../../../hooks/useHistoryBack';

View File

@ -7,7 +7,7 @@ import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types'; import type { ApiSticker } from '../../../../api/types';
import { selectAnimatedEmoji } from '../../../../global/selectors'; import { selectAnimatedEmoji } from '../../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
import renderText from '../../../common/helpers/renderText'; import renderText from '../../../common/helpers/renderText';
import useAppLayout from '../../../../hooks/useAppLayout'; import useAppLayout from '../../../../hooks/useAppLayout';

View File

@ -6,10 +6,10 @@ import type { TabState } from '../../global/types';
import { ApiMediaFormat } from '../../api/types'; import { ApiMediaFormat } from '../../api/types';
import { selectTabState } from '../../global/selectors'; import { selectTabState } from '../../global/selectors';
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/browser/windowEnvironment';
import download from '../../util/download'; import download from '../../util/download';
import generateUniqueId from '../../util/generateUniqueId'; import generateUniqueId from '../../util/generateUniqueId';
import * as mediaLoader from '../../util/mediaLoader'; import * as mediaLoader from '../../util/mediaLoader';
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/windowEnvironment';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useRunDebounced from '../../hooks/useRunDebounced'; import useRunDebounced from '../../hooks/useRunDebounced';

View File

@ -30,13 +30,14 @@ import {
selectTabState, selectTabState,
selectUser, selectUser,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import { IS_ANDROID, IS_ELECTRON, IS_WAVE_TRANSFORM_SUPPORTED } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners'; import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
import { processDeepLink } from '../../util/deeplink'; import { processDeepLink } from '../../util/deeplink';
import { Bundles, loadBundle } from '../../util/moduleLoader'; import { Bundles, loadBundle } from '../../util/moduleLoader';
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing'; import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
import updateIcon from '../../util/updateIcon'; import updateIcon from '../../util/updateIcon';
import { IS_ANDROID, IS_ELECTRON, IS_WAVE_TRANSFORM_SUPPORTED } from '../../util/windowEnvironment';
import useInterval from '../../hooks/schedulers/useInterval'; import useInterval from '../../hooks/schedulers/useInterval';
import useTimeout from '../../hooks/schedulers/useTimeout'; import useTimeout from '../../hooks/schedulers/useTimeout';
@ -598,11 +599,6 @@ const Main = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { isMobile }): StateProps => { (global, { isMobile }): StateProps => {
const { const {
settings: {
byKey: {
wasTimeFormatSetManually,
},
},
currentUserId, currentUserId,
} = global; } = global;
@ -631,6 +627,8 @@ export default memo(withGlobal<OwnProps>(
deleteFolderDialogModal, deleteFolderDialogModal,
} = selectTabState(global); } = selectTabState(global);
const { wasTimeFormatSetManually } = selectSharedSettings(global);
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId); const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
const gameTitle = gameMessage?.content.game?.title; const gameTitle = gameMessage?.content.game?.title;
const { chatId } = selectCurrentMessageList(global) || {}; const { chatId } = selectCurrentMessageList(global) || {};

View File

@ -8,8 +8,8 @@ import type { ApiCountryCode, ApiUser, ApiUserStatus } from '../../api/types';
import { getUserStatus } from '../../global/helpers'; import { getUserStatus } from '../../global/helpers';
import { selectUser, selectUserStatus } from '../../global/selectors'; import { selectUser, selectUserStatus } from '../../global/selectors';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import { formatPhoneNumberWithCode } from '../../util/phoneNumber'; import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';

View File

@ -88,6 +88,7 @@ const LIMITS_TITLES: Record<ApiLimitTypeForPromo, string> = {
dialogFilters: 'FoldersLimitTitle', dialogFilters: 'FoldersLimitTitle',
dialogFiltersChats: 'ChatPerFolderLimitTitle', dialogFiltersChats: 'ChatPerFolderLimitTitle',
recommendedChannels: 'SimilarChannelsLimitTitle', recommendedChannels: 'SimilarChannelsLimitTitle',
moreAccounts: 'ConnectedAccountsLimitTitle',
}; };
const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeForPromo, string> = { const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeForPromo, string> = {
@ -101,6 +102,7 @@ const LIMITS_DESCRIPTIONS: Record<ApiLimitTypeForPromo, string> = {
dialogFilters: 'FoldersLimitSubtitle', dialogFilters: 'FoldersLimitSubtitle',
dialogFiltersChats: 'ChatPerFolderLimitSubtitle', dialogFiltersChats: 'ChatPerFolderLimitSubtitle',
recommendedChannels: 'SimilarChannelsLimitSubtitle', recommendedChannels: 'SimilarChannelsLimitSubtitle',
moreAccounts: 'ConnectedAccountsLimitSubtitle',
}; };
const BORDER_THRESHOLD = 20; const BORDER_THRESHOLD = 20;
@ -216,13 +218,16 @@ const PremiumFeatureModal: FC<OwnProps> = ({
stopScrolling(); stopScrolling();
}); });
const currentSection = filteredSections[currentSlideIndex];
const hasHeaderBackdrop = currentSection !== 'double_limits' && currentSection !== 'stories';
return ( return (
<div className={styles.root}> <div className={styles.root}>
<Button <Button
round round
size="smaller" size="smaller"
className={buildClassName(styles.backButton, currentSlideIndex !== 0 && styles.whiteBackButton)} className={buildClassName(styles.backButton, hasHeaderBackdrop && styles.whiteBackButton)}
color={currentSlideIndex === 0 ? 'translucent' : 'translucent-white'} color={hasHeaderBackdrop ? 'translucent-white' : 'translucent'}
onClick={onBack} onClick={onBack}
ariaLabel={oldLang('Back')} ariaLabel={oldLang('Back')}
> >

View File

@ -11,9 +11,10 @@ import { MEDIA_TIMESTAMP_SAVE_MINIMUM_DURATION } from '../../config';
import { import {
selectIsMessageProtected, selectMessageTimestampableDuration, selectTabState, selectIsMessageProtected, selectMessageTimestampableDuration, selectTabState,
} from '../../global/selectors'; } from '../../global/selectors';
import { ARE_WEBCODECS_SUPPORTED } from '../../util/browser/globalEnvironment';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import stopEvent from '../../util/stopEvent'; import stopEvent from '../../util/stopEvent';
import { ARE_WEBCODECS_SUPPORTED, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { calculateMediaViewerDimensions } from '../common/helpers/mediaDimensions'; import { calculateMediaViewerDimensions } from '../common/helpers/mediaDimensions';
import { renderMessageText } from '../common/helpers/renderMessageText'; import { renderMessageText } from '../common/helpers/renderMessageText';
import getViewableMedia from './helpers/getViewableMedia'; import getViewableMedia from './helpers/getViewableMedia';

View File

@ -3,9 +3,9 @@ import React, { useEffect, useState } from '../../lib/teact/teact';
import type { TextPart } from '../../types'; import type { TextPart } from '../../types';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { throttle } from '../../util/schedulers'; import { throttle } from '../../util/schedulers';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { REM } from '../common/helpers/mediaDimensions'; import { REM } from '../common/helpers/mediaDimensions';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';

View File

@ -8,6 +8,7 @@ import type { RealTouchEvent } from '../../util/captureEvents';
import type { MediaViewerItem } from './helpers/getViewableMedia'; import type { MediaViewerItem } from './helpers/getViewableMedia';
import { animateNumber, timingFunctions } from '../../util/animation'; import { animateNumber, timingFunctions } from '../../util/animation';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { import {
captureEvents, captureEvents,
@ -17,7 +18,6 @@ import {
} from '../../util/captureEvents'; } from '../../util/captureEvents';
import { clamp, isBetween, round } from '../../util/math'; import { clamp, isBetween, round } from '../../util/math';
import { debounce } from '../../util/schedulers'; import { debounce } from '../../util/schedulers';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useTimeout from '../../hooks/schedulers/useTimeout'; import useTimeout from '../../hooks/schedulers/useTimeout';
import useDebouncedCallback from '../../hooks/useDebouncedCallback'; import useDebouncedCallback from '../../hooks/useDebouncedCallback';

View File

@ -9,11 +9,11 @@ import type { BufferedRange } from '../../hooks/useBuffering';
import { createVideoPreviews, getPreviewDimensions, renderVideoPreview } from '../../lib/video-preview/VideoPreview'; import { createVideoPreviews, getPreviewDimensions, renderVideoPreview } from '../../lib/video-preview/VideoPreview';
import { animateNumber } from '../../util/animation'; import { animateNumber } from '../../util/animation';
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { captureEvents } from '../../util/captureEvents'; import { captureEvents } from '../../util/captureEvents';
import { formatMediaDuration } from '../../util/dates/dateFormat'; import { formatMediaDuration } from '../../util/dates/dateFormat';
import { clamp, round } from '../../util/math'; import { clamp, round } from '../../util/math';
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
import { useThrottledSignal } from '../../hooks/useAsyncResolvers'; import { useThrottledSignal } from '../../hooks/useAsyncResolvers';
import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal'; import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal';

View File

@ -6,10 +6,10 @@ import { getActions } from '../../global';
import type { ApiDimensions } from '../../api/types'; import type { ApiDimensions } from '../../api/types';
import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/browser/windowEnvironment';
import { clamp } from '../../util/math'; import { clamp } from '../../util/math';
import safePlay from '../../util/safePlay'; import safePlay from '../../util/safePlay';
import stopEvent from '../../util/stopEvent'; import stopEvent from '../../util/stopEvent';
import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/windowEnvironment';
import useUnsupportedMedia from '../../hooks/media/useUnsupportedMedia'; import useUnsupportedMedia from '../../hooks/media/useUnsupportedMedia';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';

View File

@ -9,10 +9,10 @@ import type { ApiDimensions } from '../../api/types';
import type { BufferedRange } from '../../hooks/useBuffering'; import type { BufferedRange } from '../../hooks/useBuffering';
import type { IconName } from '../../types/icons'; import type { IconName } from '../../types/icons';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { formatMediaDuration } from '../../util/dates/dateFormat'; import { formatMediaDuration } from '../../util/dates/dateFormat';
import { formatFileSize } from '../../util/textFormat'; import { formatFileSize } from '../../util/textFormat';
import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';
import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal'; import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal';

View File

@ -5,10 +5,10 @@ import { ANIMATION_END_DELAY, MESSAGE_CONTENT_SELECTOR } from '../../../config';
import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getMessageHtmlId } from '../../../global/helpers'; import { getMessageHtmlId } from '../../../global/helpers';
import { applyStyles } from '../../../util/animation'; import { applyStyles } from '../../../util/animation';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import stopEvent from '../../../util/stopEvent'; import stopEvent from '../../../util/stopEvent';
import getOffsetToContainer from '../../../util/visibility/getOffsetToContainer'; import getOffsetToContainer from '../../../util/visibility/getOffsetToContainer';
import { isElementInViewport } from '../../../util/visibility/isElementInViewport'; import { isElementInViewport } from '../../../util/visibility/isElementInViewport';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import windowSize from '../../../util/windowSize'; import windowSize from '../../../util/windowSize';
import { import {
calculateDimensions, calculateDimensions,

View File

@ -10,8 +10,8 @@ import type { ActiveEmojiInteraction } from '../../types';
import { import {
selectAnimatedEmojiEffect, selectAnimatedEmojiEffect,
} from '../../global/selectors'; } from '../../global/selectors';
import { IS_ANDROID } from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { IS_ANDROID } from '../../util/windowEnvironment';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';

View File

@ -31,7 +31,7 @@ import {
selectTranslationLanguage, selectTranslationLanguage,
selectUserFullInfo, selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
import { ARE_CALLS_SUPPORTED, IS_APP } from '../../util/windowEnvironment'; import { ARE_CALLS_SUPPORTED, IS_APP } from '../../util/browser/windowEnvironment';
import { useHotkeys } from '../../hooks/useHotkeys'; import { useHotkeys } from '../../hooks/useHotkeys';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';

View File

@ -15,6 +15,7 @@ import {
selectSelectedMessagesCount, selectSelectedMessagesCount,
selectTabState, selectTabState,
} from '../../global/selectors'; } from '../../global/selectors';
import { selectSharedSettings } from '../../global/selectors/sharedState';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import captureKeyboardListeners from '../../util/captureKeyboardListeners'; import captureKeyboardListeners from '../../util/captureKeyboardListeners';
@ -75,7 +76,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
showNotification, showNotification,
reportMessages, reportMessages,
openDeleteMessageModal, openDeleteMessageModal,
setSettingOption, setSharedSettingOption,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useOldLang();
@ -133,7 +134,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
}); });
const handleSvgConfirm = useLastCallback(() => { const handleSvgConfirm = useLastCallback(() => {
setSettingOption({ shouldWarnAboutSvg: false }); setSharedSettingOption({ shouldWarnAboutSvg: false });
closeSvgDialog(); closeSvgDialog();
handleDownload(); handleDownload();
}); });
@ -238,7 +239,9 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const tabState = selectTabState(global); const tabState = selectTabState(global);
const { shouldWarnAboutSvg } = selectSharedSettings(global);
const chat = selectCurrentChat(global); const chat = selectCurrentChat(global);
const { type: messageListType, chatId } = selectCurrentMessageList(global) || {}; const { type: messageListType, chatId } = selectCurrentMessageList(global) || {};
const isSchedule = messageListType === 'scheduled'; const isSchedule = messageListType === 'scheduled';
const { canDelete } = selectCanDeleteSelectedMessages(global); const { canDelete } = selectCanDeleteSelectedMessages(global);
@ -263,7 +266,7 @@ export default memo(withGlobal<OwnProps>(
selectedMessageIds, selectedMessageIds,
hasProtectedMessage, hasProtectedMessage,
isAnyModalOpen, isAnyModalOpen,
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg, shouldWarnAboutSvg,
}; };
}, },
)(MessageSelectToolbar)); )(MessageSelectToolbar));

View File

@ -54,17 +54,18 @@ import {
selectPinnedIds, selectPinnedIds,
selectTabState, selectTabState,
selectTheme, selectTheme,
selectThemeValues,
selectThreadInfo, selectThreadInfo,
selectTopic, selectTopic,
selectTopics, selectTopics,
selectUserFullInfo, selectUserFullInfo,
} from '../../global/selectors'; } from '../../global/selectors';
import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_SAFARI, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED,
} from '../../util/browser/windowEnvironment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle'; import buildStyle from '../../util/buildStyle';
import captureEscKeyListener from '../../util/captureEscKeyListener'; import captureEscKeyListener from '../../util/captureEscKeyListener';
import {
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_SAFARI, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED,
} from '../../util/windowEnvironment';
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms'; import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
import useAppLayout from '../../hooks/useAppLayout'; import useAppLayout from '../../hooks/useAppLayout';
@ -726,7 +727,7 @@ export default memo(withGlobal<OwnProps>(
const theme = selectTheme(global); const theme = selectTheme(global);
const { const {
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor, isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
} = global.settings.themes[theme] || {}; } = selectThemeValues(global, theme) || {};
const { const {
messageLists, isLeftColumnShown, activeEmojiInteractions, messageLists, isLeftColumnShown, activeEmojiInteractions,

View File

@ -2,7 +2,7 @@ import React, { memo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import { getUserFirstOrLastName } from '../../global/helpers'; import { getUserFirstOrLastName } from '../../global/helpers';
import { selectTheme, selectUser } from '../../global/selectors'; import { selectTheme, selectThemeValues, selectUser } from '../../global/selectors';
import { formatStarsAsIcon } from '../../util/localization/format'; import { formatStarsAsIcon } from '../../util/localization/format';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets'; import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';
@ -95,7 +95,7 @@ function RequirementToContactMessage({ patternColor, userName, paidMessagesStars
export default memo( export default memo(
withGlobal<OwnProps>((global, { userId }): StateProps => { withGlobal<OwnProps>((global, { userId }): StateProps => {
const theme = selectTheme(global); const theme = selectTheme(global);
const { patternColor } = global.settings.themes[theme] || {}; const { patternColor } = selectThemeValues(global, theme) || {};
const user = selectUser(global, userId); const user = selectUser(global, userId);
return { return {

View File

@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo } from '../../../lib/teact/teact'; import React, { memo, useMemo } from '../../../lib/teact/teact';
import type { ApiDocument } from '../../../api/types'; import type { ApiDocument } from '../../../api/types';
import type { ISettings } from '../../../types'; import type { ThemeKey } from '../../../types';
import { ApiMediaFormat } from '../../../api/types'; import { ApiMediaFormat } from '../../../api/types';
import { getDocumentMediaHash } from '../../../global/helpers'; import { getDocumentMediaHash } from '../../../global/helpers';
@ -15,7 +15,7 @@ import styles from './AttachBotIcon.module.scss';
type OwnProps = { type OwnProps = {
icon: ApiDocument; icon: ApiDocument;
theme: ISettings['theme']; theme: ThemeKey;
}; };
const ADDITIONAL_STROKE_WIDTH = '0.5px'; const ADDITIONAL_STROKE_WIDTH = '0.5px';

View File

@ -5,7 +5,7 @@ import React, {
import { getActions } from '../../../global'; import { getActions } from '../../../global';
import type { ApiAttachBot } from '../../../api/types'; import type { ApiAttachBot } from '../../../api/types';
import type { IAnchorPosition, ISettings, ThreadId } from '../../../types'; import type { IAnchorPosition, ThemeKey, ThreadId } from '../../../types';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
@ -17,7 +17,7 @@ import AttachBotIcon from './AttachBotIcon';
type OwnProps = { type OwnProps = {
bot: ApiAttachBot; bot: ApiAttachBot;
theme: ISettings['theme']; theme: ThemeKey;
isInSideMenu?: true; isInSideMenu?: true;
chatId?: string; chatId?: string;
threadId?: ThreadId; threadId?: ThreadId;

View File

@ -6,7 +6,7 @@ import React, {
import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types'; import type { ApiAttachMenuPeerType, ApiMessage } from '../../../api/types';
import type { GlobalState } from '../../../global/types'; import type { GlobalState } from '../../../global/types';
import type { ISettings, MessageListType, ThreadId } from '../../../types'; import type { MessageListType, ThemeKey, ThreadId } from '../../../types';
import { import {
CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES, CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES,
@ -20,11 +20,11 @@ import {
getMessageWebPagePhoto, getMessageWebPagePhoto,
getMessageWebPageVideo, getMessageWebPageVideo,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { getDebugLogs } from '../../../util/debugConsole'; import { getDebugLogs } from '../../../util/debugConsole';
import { validateFiles } from '../../../util/files'; import { validateFiles } from '../../../util/files';
import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -54,7 +54,7 @@ export type OwnProps = {
attachBots?: GlobalState['attachMenu']['bots']; attachBots?: GlobalState['attachMenu']['bots'];
peerType?: ApiAttachMenuPeerType; peerType?: ApiAttachMenuPeerType;
shouldCollectDebugLogs?: boolean; shouldCollectDebugLogs?: boolean;
theme: ISettings['theme']; theme: ThemeKey;
onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void; onFileSelect: (files: File[], shouldSuggestCompression?: boolean) => void;
onPollCreate: NoneToVoidFunction; onPollCreate: NoneToVoidFunction;
onMenuOpen: NoneToVoidFunction; onMenuOpen: NoneToVoidFunction;

View File

@ -22,6 +22,7 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getAttachmentMediaType, isUserId } from '../../../global/helpers'; import { getAttachmentMediaType, isUserId } from '../../../global/helpers';
import { selectChatFullInfo, selectIsChatWithSelf } from '../../../global/selectors'; import { selectChatFullInfo, selectIsChatWithSelf } from '../../../global/selectors';
import { selectCurrentLimit } from '../../../global/selectors/limits'; import { selectCurrentLimit } from '../../../global/selectors/limits';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureEscKeyListener from '../../../util/captureEscKeyListener'; import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { validateFiles } from '../../../util/files'; import { validateFiles } from '../../../util/files';
@ -750,7 +751,8 @@ export default memo(withGlobal<OwnProps>(
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined; const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
const isChatWithSelf = selectIsChatWithSelf(global, chatId); const isChatWithSelf = selectIsChatWithSelf(global, chatId);
const { language, shouldSuggestCustomEmoji } = global.settings.byKey; const { shouldSuggestCustomEmoji } = global.settings.byKey;
const { language } = selectSharedSettings(global);
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG]; const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined; const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;

View File

@ -4,7 +4,7 @@ import { getActions } from '../../../global';
import type { ApiBotCommand } from '../../../api/types'; import type { ApiBotCommand } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiMessage } from '../../../api/types'; import type { ApiMessage } from '../../../api/types';
import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors'; import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment'; import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import renderKeyboardButtonText from './helpers/renderKeyboardButtonText'; import renderKeyboardButtonText from './helpers/renderKeyboardButtonText';
import useMouseInside from '../../../hooks/useMouseInside'; import useMouseInside from '../../../hooks/useMouseInside';

View File

@ -10,8 +10,8 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import type { IAnchorPosition } from '../../../types'; import type { IAnchorPosition } from '../../../types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';

View File

@ -2,9 +2,9 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo } from '../../../lib/teact/teact'; import React, { memo } from '../../../lib/teact/teact';
import { BASE_URL, IS_PACKAGED_ELECTRON } from '../../../config'; import { BASE_URL, IS_PACKAGED_ELECTRON } from '../../../config';
import { IS_EMOJI_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji/emoji'; import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji/emoji';
import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -16,11 +16,11 @@ import type {
import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config'; import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config';
import animateHorizontalScroll from '../../../util/animateHorizontalScroll'; import animateHorizontalScroll from '../../../util/animateHorizontalScroll';
import animateScroll from '../../../util/animateScroll'; import animateScroll from '../../../util/animateScroll';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { uncompressEmoji } from '../../../util/emoji/emoji'; import { uncompressEmoji } from '../../../util/emoji/emoji';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { REM } from '../../common/helpers/mediaDimensions'; import { REM } from '../../common/helpers/mediaDimensions';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';

View File

@ -8,8 +8,8 @@ import type { ApiVideo } from '../../../api/types';
import { SLIDE_TRANSITION_DURATION } from '../../../config'; import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors'; import { selectCurrentMessageList, selectIsChatWithSelf } from '../../../global/selectors';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -7,11 +7,11 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { extractCurrentThemeParams } from '../../../util/themeStyle';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';

View File

@ -9,22 +9,23 @@ import { getActions, withGlobal } from '../../../global';
import type { ApiInputMessageReplyInfo } from '../../../api/types'; import type { ApiInputMessageReplyInfo } from '../../../api/types';
import type { import type {
IAnchorPosition, ISettings, MessageListType, ThreadId, IAnchorPosition, MessageListType, SharedSettings, ThreadId,
} from '../../../types'; } from '../../../types';
import type { Signal } from '../../../util/signals'; import type { Signal } from '../../../util/signals';
import { EDITABLE_INPUT_ID } from '../../../config'; import { EDITABLE_INPUT_ID } from '../../../config';
import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom'; import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom';
import { selectCanPlayAnimatedEmojis, selectDraft, selectIsInSelectMode } from '../../../global/selectors'; import { selectCanPlayAnimatedEmojis, selectDraft, selectIsInSelectMode } from '../../../global/selectors';
import { selectSharedSettings } from '../../../global/selectors/sharedState';
import {
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
} from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import captureKeyboardListeners from '../../../util/captureKeyboardListeners'; import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager'; import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
import parseEmojiOnlyString from '../../../util/emoji/parseEmojiOnlyString'; import parseEmojiOnlyString from '../../../util/emoji/parseEmojiOnlyString';
import focusEditableElement from '../../../util/focusEditableElement'; import focusEditableElement from '../../../util/focusEditableElement';
import { debounce } from '../../../util/schedulers'; import { debounce } from '../../../util/schedulers';
import {
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
} from '../../../util/windowEnvironment';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import { isSelectionInsideInput } from './helpers/selection'; import { isSelectionInsideInput } from './helpers/selection';
@ -83,7 +84,7 @@ type OwnProps = {
type StateProps = { type StateProps = {
replyInfo?: ApiInputMessageReplyInfo; replyInfo?: ApiInputMessageReplyInfo;
isSelectModeActive?: boolean; isSelectModeActive?: boolean;
messageSendKeyCombo?: ISettings['messageSendKeyCombo']; messageSendKeyCombo?: SharedSettings['messageSendKeyCombo'];
canPlayAnimatedEmojis: boolean; canPlayAnimatedEmojis: boolean;
}; };
@ -649,7 +650,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { chatId, threadId }: OwnProps): StateProps => { (global, { chatId, threadId }: OwnProps): StateProps => {
const { messageSendKeyCombo } = global.settings.byKey; const { messageSendKeyCombo } = selectSharedSettings(global);
return { return {
messageSendKeyCombo, messageSendKeyCombo,

View File

@ -4,9 +4,9 @@ import { getActions, getGlobal } from '../../../global';
import type { ApiSendAsPeerId } from '../../../api/types'; import type { ApiSendAsPeerId } from '../../../api/types';
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import setTooltipItemVisible from '../../../util/setTooltipItemVisible'; import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useMouseInside from '../../../hooks/useMouseInside'; import useMouseInside from '../../../hooks/useMouseInside';

Some files were not shown because too many files have changed in this diff Show More