Full browser history support (#1181)
This commit is contained in:
parent
c4e3a41ff1
commit
0a594a84e1
@ -6,6 +6,7 @@ import { GlobalActions, GlobalState } from '../../global/types';
|
||||
import '../../modules/actions/initial';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { PLATFORM_ENV } from '../../util/environment';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import UiLoader from '../common/UiLoader';
|
||||
import AuthPhoneNumber from './AuthPhoneNumber';
|
||||
@ -17,14 +18,31 @@ import AuthQrCode from './AuthQrCode';
|
||||
import './Auth.scss';
|
||||
|
||||
type StateProps = Pick<GlobalState, 'authState'>;
|
||||
type DispatchProps = Pick<GlobalActions, 'reset' | 'initApi'>;
|
||||
type DispatchProps = Pick<GlobalActions, 'reset' | 'initApi' | 'returnToAuthPhoneNumber' | 'goToAuthQrCode'>;
|
||||
|
||||
const Auth: FC<StateProps & DispatchProps> = ({ authState, reset, initApi }) => {
|
||||
const Auth: FC<StateProps & DispatchProps> = ({
|
||||
authState, reset, initApi, returnToAuthPhoneNumber, goToAuthQrCode,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
reset();
|
||||
initApi();
|
||||
}, [reset, initApi]);
|
||||
|
||||
const isMobile = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
||||
|
||||
const handleChangeAuthorizationMethod = () => {
|
||||
if (!isMobile) {
|
||||
goToAuthQrCode();
|
||||
} else {
|
||||
returnToAuthPhoneNumber();
|
||||
}
|
||||
};
|
||||
|
||||
useHistoryBack(
|
||||
(!isMobile && authState === 'authorizationStateWaitPhoneNumber')
|
||||
|| (isMobile && authState === 'authorizationStateWaitQrCode'), handleChangeAuthorizationMethod,
|
||||
);
|
||||
|
||||
switch (authState) {
|
||||
case 'authorizationStateWaitCode':
|
||||
return <UiLoader page="authCode" key="authCode"><AuthCode /></UiLoader>;
|
||||
@ -37,7 +55,7 @@ const Auth: FC<StateProps & DispatchProps> = ({ authState, reset, initApi }) =>
|
||||
case 'authorizationStateWaitQrCode':
|
||||
return <UiLoader page="authQrCode" key="authQrCode"><AuthQrCode /></UiLoader>;
|
||||
default:
|
||||
return PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android'
|
||||
return isMobile
|
||||
? <UiLoader page="authPhoneNumber" key="authPhoneNumber"><AuthPhoneNumber /></UiLoader>
|
||||
: <UiLoader page="authQrCode" key="authQrCode"><AuthQrCode /></UiLoader>;
|
||||
}
|
||||
@ -45,5 +63,5 @@ const Auth: FC<StateProps & DispatchProps> = ({ authState, reset, initApi }) =>
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => pick(global, ['authState']),
|
||||
(global, actions): DispatchProps => pick(actions, ['reset', 'initApi']),
|
||||
(global, actions): DispatchProps => pick(actions, ['reset', 'initApi', 'returnToAuthPhoneNumber', 'goToAuthQrCode']),
|
||||
)(Auth));
|
||||
|
||||
@ -7,19 +7,27 @@ import { GlobalState, GlobalActions } from '../../global/types';
|
||||
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import InputText from '../ui/InputText';
|
||||
import Loading from '../ui/Loading';
|
||||
import TrackingMonkey from '../common/TrackingMonkey';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
type StateProps = Pick<GlobalState, 'authPhoneNumber' | 'authIsCodeViaApp' | 'authIsLoading' | 'authError'>;
|
||||
type DispatchProps = Pick<GlobalActions, 'setAuthCode' | 'returnToAuthPhoneNumber' | 'clearAuthError'>;
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'setAuthCode' | 'returnToAuthPhoneNumber' | 'clearAuthError'
|
||||
)>;
|
||||
|
||||
const CODE_LENGTH = 5;
|
||||
|
||||
const AuthCode: FC<StateProps & DispatchProps> = ({
|
||||
authPhoneNumber, authIsCodeViaApp, authIsLoading, authError, setAuthCode, returnToAuthPhoneNumber, clearAuthError,
|
||||
authPhoneNumber,
|
||||
authIsCodeViaApp,
|
||||
authIsLoading,
|
||||
authError,
|
||||
setAuthCode,
|
||||
returnToAuthPhoneNumber,
|
||||
clearAuthError,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -34,7 +42,7 @@ const AuthCode: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
useHistoryBack(returnToAuthPhoneNumber);
|
||||
useHistoryBack(true, returnToAuthPhoneNumber);
|
||||
|
||||
const onCodeChange = useCallback((e: FormEvent<HTMLInputElement>) => {
|
||||
if (authError) {
|
||||
@ -119,5 +127,9 @@ const AuthCode: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => pick(global, ['authPhoneNumber', 'authIsCodeViaApp', 'authIsLoading', 'authError']),
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['setAuthCode', 'returnToAuthPhoneNumber', 'clearAuthError']),
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'setAuthCode',
|
||||
'returnToAuthPhoneNumber',
|
||||
'clearAuthError',
|
||||
]),
|
||||
)(AuthCode));
|
||||
|
||||
@ -8,7 +8,9 @@ import React, {
|
||||
FC, memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import {
|
||||
IS_SAFARI, IS_TOUCH_ENV,
|
||||
} from '../../util/environment';
|
||||
import { preloadImage } from '../../util/files';
|
||||
import preloadFonts from '../../util/fonts';
|
||||
import { pick } from '../../util/iteratees';
|
||||
|
||||
@ -9,7 +9,6 @@ import { pick } from '../../util/iteratees';
|
||||
|
||||
import Loading from '../ui/Loading';
|
||||
import Button from '../ui/Button';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
type StateProps = Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>;
|
||||
type DispatchProps = Pick<GlobalActions, 'returnToAuthPhoneNumber'>;
|
||||
@ -17,7 +16,10 @@ type DispatchProps = Pick<GlobalActions, 'returnToAuthPhoneNumber'>;
|
||||
const DATA_PREFIX = 'tg://login?token=';
|
||||
|
||||
const AuthCode: FC<StateProps & DispatchProps> = ({
|
||||
connectionState, authState, authQrCode, returnToAuthPhoneNumber,
|
||||
connectionState,
|
||||
authState,
|
||||
authQrCode,
|
||||
returnToAuthPhoneNumber,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const qrCodeRef = useRef<HTMLDivElement>(null);
|
||||
@ -41,8 +43,6 @@ const AuthCode: FC<StateProps & DispatchProps> = ({
|
||||
}, container);
|
||||
}, [connectionState, authQrCode]);
|
||||
|
||||
useHistoryBack(returnToAuthPhoneNumber);
|
||||
|
||||
const isAuthReady = authState === 'authorizationStateWaitQrCode';
|
||||
|
||||
return (
|
||||
|
||||
@ -28,7 +28,7 @@ type OwnProps = {
|
||||
children: any;
|
||||
};
|
||||
|
||||
type StateProps = Pick<GlobalState, 'uiReadyState'> & {
|
||||
type StateProps = Pick<GlobalState, 'uiReadyState' | 'shouldSkipHistoryAnimations'> & {
|
||||
hasCustomBackground?: boolean;
|
||||
hasCustomBackgroundColor: boolean;
|
||||
isRightColumnShown?: boolean;
|
||||
@ -82,6 +82,7 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
hasCustomBackground,
|
||||
hasCustomBackgroundColor,
|
||||
isRightColumnShown,
|
||||
shouldSkipHistoryAnimations,
|
||||
setIsUiReady,
|
||||
}) => {
|
||||
const [isReady, markReady] = useFlag();
|
||||
@ -126,7 +127,7 @@ const UiLoader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<div id="UiLoader">
|
||||
{children}
|
||||
{shouldRenderMask && (
|
||||
{shouldRenderMask && !shouldSkipHistoryAnimations && (
|
||||
<div className={buildClassName('mask', transitionClassNames)}>
|
||||
{page === 'main' ? (
|
||||
<>
|
||||
@ -156,6 +157,7 @@ export default withGlobal<OwnProps>(
|
||||
const { background, backgroundColor } = global.settings.themes[theme] || {};
|
||||
|
||||
return {
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
uiReadyState: global.uiReadyState,
|
||||
hasCustomBackground: Boolean(background),
|
||||
hasCustomBackgroundColor: Boolean(backgroundColor),
|
||||
|
||||
@ -1,20 +1,25 @@
|
||||
import React, { FC, memo } from '../../lib/teact/teact';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import ChatList from './main/ChatList';
|
||||
import { LeftColumnContent } from '../../types';
|
||||
|
||||
import './ArchivedChats.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isActive: boolean;
|
||||
onReset: () => void;
|
||||
onContentChange: (content: LeftColumnContent) => void;
|
||||
};
|
||||
|
||||
const ArchivedChats: FC<OwnProps> = ({ isActive, onReset }) => {
|
||||
const ArchivedChats: FC<OwnProps> = ({ isActive, onReset, onContentChange }) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onContentChange, LeftColumnContent.Archived);
|
||||
|
||||
return (
|
||||
<div className="ArchivedChats">
|
||||
<div className="left-header">
|
||||
|
||||
@ -22,6 +22,7 @@ type StateProps = {
|
||||
searchQuery?: string;
|
||||
searchDate?: number;
|
||||
activeChatFolder: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
@ -47,6 +48,7 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
searchQuery,
|
||||
searchDate,
|
||||
activeChatFolder,
|
||||
shouldSkipHistoryAnimations,
|
||||
setGlobalSearchQuery,
|
||||
setGlobalSearchChatId,
|
||||
resetChatCreation,
|
||||
@ -80,14 +82,20 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
|
||||
const handleReset = useCallback((forceReturnToChatList?: boolean) => {
|
||||
if (
|
||||
content === LeftColumnContent.NewGroupStep2
|
||||
if (content === LeftColumnContent.NewGroupStep2
|
||||
&& !forceReturnToChatList
|
||||
) {
|
||||
setContent(LeftColumnContent.NewGroupStep1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content === LeftColumnContent.NewChannelStep2
|
||||
&& !forceReturnToChatList
|
||||
) {
|
||||
setContent(LeftColumnContent.NewChannelStep1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (content === LeftColumnContent.NewGroupStep1) {
|
||||
const pickerSearchInput = document.getElementById('new-group-picker-search');
|
||||
if (pickerSearchInput) {
|
||||
@ -205,8 +213,8 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
setLastResetTime(Date.now());
|
||||
}, RESET_TRANSITION_DELAY_MS);
|
||||
}, [
|
||||
content, activeChatFolder, setGlobalSearchQuery, setGlobalSearchDate, setGlobalSearchChatId, resetChatCreation,
|
||||
settingsScreen,
|
||||
content, activeChatFolder, settingsScreen, setGlobalSearchQuery, setGlobalSearchDate, setGlobalSearchChatId,
|
||||
resetChatCreation,
|
||||
]);
|
||||
|
||||
const handleSearchQuery = useCallback((query: string) => {
|
||||
@ -220,7 +228,7 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
if (query !== searchQuery) {
|
||||
setGlobalSearchQuery({ query });
|
||||
}
|
||||
}, [content, setGlobalSearchQuery, searchQuery]);
|
||||
}, [content, searchQuery, setGlobalSearchQuery]);
|
||||
|
||||
useEffect(
|
||||
() => (content !== LeftColumnContent.ChatList || activeChatFolder === 0
|
||||
@ -237,10 +245,15 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [clearTwoFaError, loadPasswordInfo, settingsScreen]);
|
||||
|
||||
const handleSettingsScreenSelect = (screen: SettingsScreens) => {
|
||||
setContent(LeftColumnContent.Settings);
|
||||
setSettingsScreen(screen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition
|
||||
id="LeftColumn"
|
||||
name={LAYERS_ANIMATION_NAME}
|
||||
name={shouldSkipHistoryAnimations ? 'none' : LAYERS_ANIMATION_NAME}
|
||||
renderCount={RENDER_COUNT}
|
||||
activeKey={contentType}
|
||||
shouldCleanup
|
||||
@ -253,20 +266,24 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
<ArchivedChats
|
||||
isActive={isActive}
|
||||
onReset={handleReset}
|
||||
onContentChange={setContent}
|
||||
/>
|
||||
);
|
||||
case ContentType.Settings:
|
||||
return (
|
||||
<Settings
|
||||
isActive={isActive}
|
||||
currentScreen={settingsScreen}
|
||||
onScreenSelect={setSettingsScreen}
|
||||
onScreenSelect={handleSettingsScreenSelect}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
);
|
||||
case ContentType.NewChannel:
|
||||
return (
|
||||
<NewChat
|
||||
key={lastResetTime}
|
||||
isActive={isActive}
|
||||
isChannel
|
||||
content={content}
|
||||
onContentChange={setContent}
|
||||
@ -277,6 +294,7 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<NewChat
|
||||
key={lastResetTime}
|
||||
isActive={isActive}
|
||||
content={content}
|
||||
onContentChange={setContent}
|
||||
onReset={handleReset}
|
||||
@ -292,6 +310,7 @@ const LeftColumn: FC<StateProps & DispatchProps> = ({
|
||||
onContentChange={setContent}
|
||||
onSearchQuery={handleSearchQuery}
|
||||
onReset={handleReset}
|
||||
shouldSkipTransition={shouldSkipHistoryAnimations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -310,8 +329,11 @@ export default memo(withGlobal(
|
||||
chatFolders: {
|
||||
activeChatFolder,
|
||||
},
|
||||
shouldSkipHistoryAnimations,
|
||||
} = global;
|
||||
return { searchQuery: query, searchDate: date, activeChatFolder };
|
||||
return {
|
||||
searchQuery: query, searchDate: date, activeChatFolder, shouldSkipHistoryAnimations,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'setGlobalSearchQuery', 'setGlobalSearchChatId', 'resetChatCreation', 'setGlobalSearchDate',
|
||||
|
||||
@ -16,6 +16,7 @@ import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useThrottledMemo from '../../../hooks/useThrottledMemo';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
|
||||
import Transition from '../../ui/Transition';
|
||||
@ -144,6 +145,8 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}) : undefined), [activeChatFolder, setActiveChatFolder]);
|
||||
|
||||
useHistoryBack(activeChatFolder !== 0, () => setActiveChatFolder(0));
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.metaKey && e.code.startsWith('Digit') && folderTabs) {
|
||||
|
||||
@ -12,6 +12,7 @@ import searchWords from '../../../util/searchWords';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { getUserFullName, sortUserIds } from '../../../modules/helpers';
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
@ -20,6 +21,8 @@ import Loading from '../../ui/Loading';
|
||||
|
||||
export type OwnProps = {
|
||||
filter: string;
|
||||
isActive: boolean;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -33,6 +36,7 @@ type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'openChat'>;
|
||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||
|
||||
const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive, onReset,
|
||||
filter, usersById, contactIds, loadContactList, openChat, serverTimeOffset,
|
||||
}) => {
|
||||
// Due to the parent Transition, this component never gets unmounted,
|
||||
@ -43,6 +47,8 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
useHistoryBack(isActive, onReset);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(id: number) => {
|
||||
openChat({ id });
|
||||
|
||||
@ -31,6 +31,7 @@ type OwnProps = {
|
||||
searchQuery?: string;
|
||||
searchDate?: number;
|
||||
contactsFilter: string;
|
||||
shouldSkipTransition?: boolean;
|
||||
onSearchQuery: (query: string) => void;
|
||||
onContentChange: (content: LeftColumnContent) => void;
|
||||
onReset: () => void;
|
||||
@ -49,6 +50,7 @@ const LeftMain: FC<OwnProps & StateProps> = ({
|
||||
searchQuery,
|
||||
searchDate,
|
||||
contactsFilter,
|
||||
shouldSkipTransition,
|
||||
onSearchQuery,
|
||||
onContentChange,
|
||||
onReset,
|
||||
@ -140,12 +142,13 @@ const LeftMain: FC<OwnProps & StateProps> = ({
|
||||
onSelectContacts={handleSelectContacts}
|
||||
onSelectArchived={handleSelectArchived}
|
||||
onReset={onReset}
|
||||
shouldSkipTransition={shouldSkipTransition}
|
||||
/>
|
||||
<ShowTransition isOpen={isConnecting} isCustom className="connection-state-wrapper opacity-transition slow">
|
||||
{() => <ConnectionState />}
|
||||
</ShowTransition>
|
||||
<Transition
|
||||
name="zoom-fade"
|
||||
name={shouldSkipTransition ? 'none' : 'zoom-fade'}
|
||||
renderCount={TRANSITION_RENDER_COUNT}
|
||||
activeKey={content}
|
||||
shouldCleanup
|
||||
@ -166,7 +169,7 @@ const LeftMain: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
);
|
||||
case LeftColumnContent.Contacts:
|
||||
return <ContactList filter={contactsFilter} />;
|
||||
return <ContactList filter={contactsFilter} isActive={isActive} onReset={onReset} />;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -40,6 +40,14 @@
|
||||
transform: rotate(-45deg) scaleX(0.75) translate(0.375rem, 0.1875rem);
|
||||
}
|
||||
}
|
||||
|
||||
&.no-animation {
|
||||
transition: none;
|
||||
|
||||
&::before, &::after {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.archived-badge {
|
||||
|
||||
@ -31,6 +31,7 @@ import './LeftMainHeader.scss';
|
||||
type OwnProps = {
|
||||
content: LeftColumnContent;
|
||||
contactsFilter: string;
|
||||
shouldSkipTransition?: boolean;
|
||||
onSearchQuery: (query: string) => void;
|
||||
onSelectSettings: () => void;
|
||||
onSelectContacts: () => void;
|
||||
@ -71,6 +72,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onReset,
|
||||
searchQuery,
|
||||
isLoading,
|
||||
shouldSkipTransition,
|
||||
currentUserId,
|
||||
globalSearchChatId,
|
||||
searchDate,
|
||||
@ -118,10 +120,15 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onClick={hasMenu ? onTrigger : () => onReset()}
|
||||
ariaLabel={hasMenu ? lang('AccDescrOpenMenu2') : 'Return to chat list'}
|
||||
>
|
||||
<div className={buildClassName('animated-menu-icon', !hasMenu && 'state-back')} />
|
||||
<div className={buildClassName(
|
||||
'animated-menu-icon',
|
||||
!hasMenu && 'state-back',
|
||||
shouldSkipTransition && 'no-animation',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}, [hasMenu, lang, onReset]);
|
||||
}, [hasMenu, lang, onReset, shouldSkipTransition]);
|
||||
|
||||
const handleSearchFocus = useCallback(() => {
|
||||
if (!searchQuery) {
|
||||
|
||||
@ -13,6 +13,7 @@ import NewChatStep2 from './NewChatStep2';
|
||||
import './NewChat.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isActive: boolean;
|
||||
isChannel?: boolean;
|
||||
content: LeftColumnContent;
|
||||
onContentChange: (content: LeftColumnContent) => void;
|
||||
@ -22,6 +23,7 @@ export type OwnProps = {
|
||||
const RENDER_COUNT = Object.keys(LeftColumnContent).length / 2;
|
||||
|
||||
const NewChat: FC<OwnProps> = ({
|
||||
isActive,
|
||||
isChannel = false,
|
||||
content,
|
||||
onContentChange,
|
||||
@ -40,13 +42,14 @@ const NewChat: FC<OwnProps> = ({
|
||||
renderCount={RENDER_COUNT}
|
||||
activeKey={content}
|
||||
>
|
||||
{() => {
|
||||
{(isStepActive) => {
|
||||
switch (content) {
|
||||
case LeftColumnContent.NewChannelStep1:
|
||||
case LeftColumnContent.NewGroupStep1:
|
||||
return (
|
||||
<NewChatStep1
|
||||
isChannel={isChannel}
|
||||
isActive={isActive}
|
||||
selectedMemberIds={newChatMemberIds}
|
||||
onSelectedMemberIdsChange={setNewChatMemberIds}
|
||||
onNextStep={handleNextStep}
|
||||
@ -58,6 +61,7 @@ const NewChat: FC<OwnProps> = ({
|
||||
return (
|
||||
<NewChatStep2
|
||||
isChannel={isChannel}
|
||||
isActive={isStepActive && isActive}
|
||||
memberIds={newChatMemberIds}
|
||||
onReset={onReset}
|
||||
/>
|
||||
|
||||
@ -11,6 +11,7 @@ import { throttle } from '../../../util/schedulers';
|
||||
import searchWords from '../../../util/searchWords';
|
||||
import { getUserFullName, sortChatIds } from '../../../modules/helpers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import Picker from '../../common/Picker';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
@ -18,6 +19,7 @@ import Button from '../../ui/Button';
|
||||
|
||||
export type OwnProps = {
|
||||
isChannel?: boolean;
|
||||
isActive: boolean;
|
||||
selectedMemberIds: number[];
|
||||
onSelectedMemberIdsChange: (ids: number[]) => void;
|
||||
onNextStep: () => void;
|
||||
@ -41,6 +43,7 @@ const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||
|
||||
const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isChannel,
|
||||
isActive,
|
||||
selectedMemberIds,
|
||||
onSelectedMemberIdsChange,
|
||||
onNextStep,
|
||||
@ -64,6 +67,10 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset);
|
||||
|
||||
const handleFilterChange = useCallback((query: string) => {
|
||||
setGlobalSearchQuery({ query });
|
||||
}, [setGlobalSearchQuery]);
|
||||
@ -108,8 +115,6 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [selectedMemberIds.length, isChannel, setGlobalSearchQuery, onNextStep]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<div className="NewChat step-1">
|
||||
<div className="left-header">
|
||||
|
||||
@ -8,6 +8,7 @@ import { ChatCreationProgress } from '../../../types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import InputText from '../../ui/InputText';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
@ -19,6 +20,7 @@ import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
|
||||
export type OwnProps = {
|
||||
isChannel?: boolean;
|
||||
isActive: boolean;
|
||||
memberIds: number[];
|
||||
onReset: (forceReturnToChatList?: boolean) => void;
|
||||
};
|
||||
@ -35,6 +37,7 @@ const MAX_USERS_FOR_LEGACY_CHAT = 199; // Accounting for current user
|
||||
|
||||
const NewChatStep2: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isChannel,
|
||||
isActive,
|
||||
memberIds,
|
||||
onReset,
|
||||
creationProgress,
|
||||
@ -44,6 +47,8 @@ const NewChatStep2: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset);
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [about, setAbout] = useState('');
|
||||
const [photo, setPhoto] = useState<File | undefined>();
|
||||
|
||||
@ -10,6 +10,7 @@ import { pick } from '../../../util/iteratees';
|
||||
import { parseDateString } from '../../../util/dateFormat';
|
||||
import useKeyboardListNavigation from '../../../hooks/useKeyboardListNavigation';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import TabList from '../../ui/TabList';
|
||||
import Transition from '../../ui/Transition';
|
||||
@ -76,6 +77,8 @@ const LeftSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
setGlobalSearchDate({ date: value.getTime() / 1000 });
|
||||
}, [setGlobalSearchDate]);
|
||||
|
||||
useHistoryBack(isActive, onReset, undefined, undefined, true);
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const handleKeyDown = useKeyboardListNavigation(containerRef, isActive, undefined, '.ListItem-button', true);
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
background: var(--color-background);
|
||||
height: calc(100% - var(--header-height));
|
||||
overflow-y: auto;
|
||||
|
||||
|
||||
@ -28,16 +28,77 @@ import './Settings.scss';
|
||||
const TRANSITION_RENDER_COUNT = Object.keys(SettingsScreens).length / 2;
|
||||
const TRANSITION_DURATION = 200;
|
||||
|
||||
const TWO_FA_SCREENS = [
|
||||
SettingsScreens.TwoFaDisabled,
|
||||
SettingsScreens.TwoFaNewPassword,
|
||||
SettingsScreens.TwoFaNewPasswordConfirm,
|
||||
SettingsScreens.TwoFaNewPasswordHint,
|
||||
SettingsScreens.TwoFaNewPasswordEmail,
|
||||
SettingsScreens.TwoFaNewPasswordEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
SettingsScreens.TwoFaEnabled,
|
||||
SettingsScreens.TwoFaChangePasswordCurrent,
|
||||
SettingsScreens.TwoFaChangePasswordNew,
|
||||
SettingsScreens.TwoFaChangePasswordConfirm,
|
||||
SettingsScreens.TwoFaChangePasswordHint,
|
||||
SettingsScreens.TwoFaTurnOff,
|
||||
SettingsScreens.TwoFaRecoveryEmailCurrentPassword,
|
||||
SettingsScreens.TwoFaRecoveryEmail,
|
||||
SettingsScreens.TwoFaRecoveryEmailCode,
|
||||
];
|
||||
|
||||
const FOLDERS_SCREENS = [
|
||||
SettingsScreens.Folders,
|
||||
SettingsScreens.FoldersCreateFolder,
|
||||
SettingsScreens.FoldersEditFolder,
|
||||
SettingsScreens.FoldersIncludedChats,
|
||||
SettingsScreens.FoldersExcludedChats,
|
||||
];
|
||||
|
||||
const PRIVACY_SCREENS = [
|
||||
SettingsScreens.PrivacyBlockedUsers,
|
||||
SettingsScreens.PrivacyActiveSessions,
|
||||
];
|
||||
|
||||
const PRIVACY_PHONE_NUMBER_SCREENS = [
|
||||
SettingsScreens.PrivacyPhoneNumberAllowedContacts,
|
||||
SettingsScreens.PrivacyPhoneNumberDeniedContacts,
|
||||
];
|
||||
|
||||
const PRIVACY_LAST_SEEN_PHONE_SCREENS = [
|
||||
SettingsScreens.PrivacyLastSeenAllowedContacts,
|
||||
SettingsScreens.PrivacyLastSeenDeniedContacts,
|
||||
];
|
||||
|
||||
const PRIVACY_PROFILE_PHOTO_SCREENS = [
|
||||
SettingsScreens.PrivacyProfilePhotoAllowedContacts,
|
||||
SettingsScreens.PrivacyProfilePhotoDeniedContacts,
|
||||
];
|
||||
|
||||
const PRIVACY_FORWARDING_SCREENS = [
|
||||
SettingsScreens.PrivacyForwardingAllowedContacts,
|
||||
SettingsScreens.PrivacyForwardingDeniedContacts,
|
||||
];
|
||||
|
||||
const PRIVACY_GROUP_CHATS_SCREENS = [
|
||||
SettingsScreens.PrivacyGroupChatsAllowedContacts,
|
||||
SettingsScreens.PrivacyGroupChatsDeniedContacts,
|
||||
];
|
||||
|
||||
export type OwnProps = {
|
||||
isActive: boolean;
|
||||
currentScreen: SettingsScreens;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
shouldSkipTransition?: boolean;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
const Settings: FC<OwnProps> = ({
|
||||
isActive,
|
||||
currentScreen,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
shouldSkipTransition,
|
||||
}) => {
|
||||
const [foldersState, foldersDispatch] = useFoldersReducer();
|
||||
const [twoFaState, twoFaDispatch] = useTwoFaReducer();
|
||||
@ -75,47 +136,93 @@ const Settings: FC<OwnProps> = ({
|
||||
handleReset();
|
||||
}, [foldersDispatch, handleReset]);
|
||||
|
||||
function renderCurrentSectionContent() {
|
||||
function renderCurrentSectionContent(isScreenActive: boolean, screen: SettingsScreens) {
|
||||
const privacyAllowScreens: Record<number, boolean> = {
|
||||
[SettingsScreens.PrivacyPhoneNumber]: PRIVACY_PHONE_NUMBER_SCREENS.includes(screen),
|
||||
[SettingsScreens.PrivacyLastSeen]: PRIVACY_LAST_SEEN_PHONE_SCREENS.includes(screen),
|
||||
[SettingsScreens.PrivacyProfilePhoto]: PRIVACY_PROFILE_PHOTO_SCREENS.includes(screen),
|
||||
[SettingsScreens.PrivacyForwarding]: PRIVACY_FORWARDING_SCREENS.includes(screen),
|
||||
[SettingsScreens.PrivacyGroupChats]: PRIVACY_GROUP_CHATS_SCREENS.includes(screen),
|
||||
};
|
||||
|
||||
const isTwoFaScreen = TWO_FA_SCREENS.includes(screen);
|
||||
const isFoldersScreen = FOLDERS_SCREENS.includes(screen);
|
||||
const isPrivacyScreen = PRIVACY_SCREENS.includes(screen)
|
||||
|| isTwoFaScreen
|
||||
|| Object.keys(privacyAllowScreens).includes(screen.toString())
|
||||
|| Object.values(privacyAllowScreens).find((key) => key === true);
|
||||
|
||||
switch (currentScreen) {
|
||||
case SettingsScreens.Main:
|
||||
return (
|
||||
<SettingsMain onScreenSelect={onScreenSelect} />
|
||||
<SettingsMain onScreenSelect={onScreenSelect} isActive={isActive} onReset={handleReset} />
|
||||
);
|
||||
case SettingsScreens.EditProfile:
|
||||
return (
|
||||
<SettingsEditProfile />
|
||||
<SettingsEditProfile
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive && isScreenActive}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.General:
|
||||
return (
|
||||
<SettingsGeneral onScreenSelect={onScreenSelect} />
|
||||
<SettingsGeneral
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive
|
||||
|| screen === SettingsScreens.GeneralChatBackgroundColor
|
||||
|| screen === SettingsScreens.GeneralChatBackground
|
||||
|| isPrivacyScreen || isFoldersScreen}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.Notifications:
|
||||
return (
|
||||
<SettingsNotifications />
|
||||
<SettingsNotifications onScreenSelect={onScreenSelect} isActive={isScreenActive} onReset={handleReset} />
|
||||
);
|
||||
case SettingsScreens.Privacy:
|
||||
return (
|
||||
<SettingsPrivacy onScreenSelect={onScreenSelect} />
|
||||
<SettingsPrivacy
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive || isPrivacyScreen || isTwoFaScreen}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.Language:
|
||||
return (
|
||||
<SettingsLanguage />
|
||||
<SettingsLanguage onScreenSelect={onScreenSelect} isActive={isScreenActive} onReset={handleReset} />
|
||||
);
|
||||
case SettingsScreens.GeneralChatBackground:
|
||||
return (
|
||||
<SettingsGeneralBackground onScreenSelect={onScreenSelect} />
|
||||
<SettingsGeneralBackground
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive || screen === SettingsScreens.GeneralChatBackgroundColor}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.GeneralChatBackgroundColor:
|
||||
return (
|
||||
<SettingsGeneralBackgroundColor onScreenSelect={onScreenSelect} />
|
||||
<SettingsGeneralBackgroundColor
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.PrivacyActiveSessions:
|
||||
return (
|
||||
<SettingsPrivacyActiveSessions />
|
||||
<SettingsPrivacyActiveSessions
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.PrivacyBlockedUsers:
|
||||
return (
|
||||
<SettingsPrivacyBlockedUsers />
|
||||
<SettingsPrivacyBlockedUsers
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.PrivacyPhoneNumber:
|
||||
case SettingsScreens.PrivacyLastSeen:
|
||||
@ -123,7 +230,12 @@ const Settings: FC<OwnProps> = ({
|
||||
case SettingsScreens.PrivacyForwarding:
|
||||
case SettingsScreens.PrivacyGroupChats:
|
||||
return (
|
||||
<SettingsPrivacyVisibility screen={currentScreen} onScreenSelect={onScreenSelect} />
|
||||
<SettingsPrivacyVisibility
|
||||
screen={currentScreen}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive || privacyAllowScreens[currentScreen]}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.PrivacyPhoneNumberAllowedContacts:
|
||||
@ -136,6 +248,8 @@ const Settings: FC<OwnProps> = ({
|
||||
isAllowList
|
||||
screen={currentScreen}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive || privacyAllowScreens[currentScreen]}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -148,6 +262,8 @@ const Settings: FC<OwnProps> = ({
|
||||
<SettingsPrivacyVisibilityExceptionList
|
||||
screen={currentScreen}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isScreenActive}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -159,8 +275,10 @@ const Settings: FC<OwnProps> = ({
|
||||
return (
|
||||
<SettingsFolders
|
||||
currentScreen={currentScreen}
|
||||
shownScreen={screen}
|
||||
state={foldersState}
|
||||
dispatch={foldersDispatch}
|
||||
isActive={isScreenActive}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
@ -187,7 +305,10 @@ const Settings: FC<OwnProps> = ({
|
||||
currentScreen={currentScreen}
|
||||
state={twoFaState}
|
||||
dispatch={twoFaDispatch}
|
||||
shownScreen={screen}
|
||||
isActive={isScreenActive}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onReset={handleReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -196,7 +317,7 @@ const Settings: FC<OwnProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
function renderCurrentSection() {
|
||||
function renderCurrentSection(isScreenActive: boolean, isFrom: boolean, currentKey: SettingsScreens) {
|
||||
return (
|
||||
<>
|
||||
<SettingsHeader
|
||||
@ -205,7 +326,7 @@ const Settings: FC<OwnProps> = ({
|
||||
onSaveFilter={handleSaveFilter}
|
||||
editedFolderId={foldersState.folderId}
|
||||
/>
|
||||
{renderCurrentSectionContent()}
|
||||
{renderCurrentSectionContent(isScreenActive, currentKey)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -213,7 +334,7 @@ const Settings: FC<OwnProps> = ({
|
||||
return (
|
||||
<Transition
|
||||
id="Settings"
|
||||
name={LAYERS_ANIMATION_NAME}
|
||||
name={shouldSkipTransition ? 'none' : LAYERS_ANIMATION_NAME}
|
||||
activeKey={currentScreen}
|
||||
renderCount={TRANSITION_RENDER_COUNT}
|
||||
>
|
||||
|
||||
@ -6,7 +6,7 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { ApiMediaFormat } from '../../../api/types';
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { ProfileEditProgress } from '../../../types';
|
||||
import { ProfileEditProgress, SettingsScreens } from '../../../types';
|
||||
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
@ -21,6 +21,13 @@ import Spinner from '../../ui/Spinner';
|
||||
import InputText from '../../ui/InputText';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import UsernameInput from '../../common/UsernameInput';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
type OwnProps = {
|
||||
isActive: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
currentAvatarHash?: string;
|
||||
@ -43,7 +50,10 @@ const MAX_BIO_LENGTH = 70;
|
||||
const ERROR_FIRST_NAME_MISSING = 'Please provide your first name';
|
||||
const ERROR_BIO_TOO_LONG = 'Bio can\' be longer than 70 characters';
|
||||
|
||||
const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
const SettingsEditProfile: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
currentAvatarHash,
|
||||
currentFirstName,
|
||||
currentLastName,
|
||||
@ -55,6 +65,8 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
updateProfile,
|
||||
checkUsername,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const [isUsernameTouched, setIsUsernameTouched] = useState(false);
|
||||
const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
@ -78,6 +90,8 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
return Boolean(photo) || isProfileFieldsTouched || isUsernameAvailable === true;
|
||||
}, [photo, isProfileFieldsTouched, isUsernameError, isUsernameAvailable]);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.EditProfile);
|
||||
|
||||
// Due to the parent Transition, this component never gets unmounted,
|
||||
// that's why we use throttled API call on every update.
|
||||
useEffect(() => {
|
||||
@ -165,8 +179,6 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
updateProfile,
|
||||
]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<div className="settings-fab-wrapper">
|
||||
<div className="settings-content custom-scroll">
|
||||
@ -242,7 +254,7 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { currentUserId } = global;
|
||||
const { progress, isUsernameAvailable } = global.profileEdit || {};
|
||||
|
||||
@ -12,6 +12,7 @@ import { pick } from '../../../util/iteratees';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import RangeSlider from '../../ui/RangeSlider';
|
||||
@ -21,7 +22,9 @@ import SettingsStickerSet from './SettingsStickerSet';
|
||||
import StickerSetModal from '../../common/StickerSetModal.async';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = Pick<ISettings, (
|
||||
@ -52,7 +55,9 @@ const ANIMATION_LEVEL_OPTIONS = [
|
||||
];
|
||||
|
||||
const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
stickerSetIds,
|
||||
stickerSetsById,
|
||||
messageTextSize,
|
||||
@ -120,6 +125,8 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return stickerSetsById && stickerSetsById[id] && stickerSetsById[id].installedDate ? stickerSetsById[id] : false;
|
||||
}).filter<ApiStickerSet>(Boolean as any);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.General);
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
<div className="settings-item pt-3">
|
||||
|
||||
@ -14,6 +14,7 @@ import { openSystemFilesDialog } from '../../../util/systemFilesDialog';
|
||||
import { getAverageColor, getPatternColor, rgb2hex } from '../../../util/colors';
|
||||
import { selectTheme } from '../../../modules/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
@ -23,7 +24,9 @@ import WallpaperTile from './WallpaperTile';
|
||||
import './SettingsGeneralBackground.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -42,7 +45,9 @@ const SUPPORTED_TYPES = 'image/jpeg';
|
||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||
|
||||
const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
background,
|
||||
isBlurred,
|
||||
loadedWallpapers,
|
||||
@ -106,6 +111,8 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.GeneralChatBackground);
|
||||
|
||||
const isUploading = loadedWallpapers && loadedWallpapers[0] && loadedWallpapers[0].slug === UPLOADING_WALLPAPER_SLUG;
|
||||
|
||||
return (
|
||||
|
||||
@ -15,13 +15,16 @@ import { captureEvents, RealTouchEvent } from '../../../util/captureEvents';
|
||||
import { selectTheme } from '../../../modules/selectors';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import InputText from '../../ui/InputText';
|
||||
|
||||
import './SettingsGeneralBackgroundColor.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -51,6 +54,9 @@ const PREDEFINED_COLORS = [
|
||||
];
|
||||
|
||||
const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
theme,
|
||||
backgroundColor,
|
||||
setThemeSettings,
|
||||
@ -195,6 +201,8 @@ const SettingsGeneralBackground: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isDragging && 'is-dragging',
|
||||
);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.GeneralChatBackgroundColor);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={className}>
|
||||
<div className="settings-item pt-3">
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { ISettings } from '../../../types';
|
||||
import { ISettings, SettingsScreens } from '../../../types';
|
||||
import { ApiLanguage } from '../../../api/types';
|
||||
|
||||
import { setLanguage } from '../../../util/langProvider';
|
||||
@ -13,12 +13,22 @@ import { pick } from '../../../util/iteratees';
|
||||
import RadioGroup from '../../ui/RadioGroup';
|
||||
import Loading from '../../ui/Loading';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = Pick<ISettings, 'languages' | 'language'>;
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'loadLanguages' | 'setSettingOption'>;
|
||||
|
||||
const SettingsLanguage: FC<StateProps & DispatchProps> = ({
|
||||
const SettingsLanguage: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
languages,
|
||||
language,
|
||||
loadLanguages,
|
||||
@ -47,6 +57,8 @@ const SettingsLanguage: FC<StateProps & DispatchProps> = ({
|
||||
return languages ? buildOptions(languages) : undefined;
|
||||
}, [languages]);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Language);
|
||||
|
||||
return (
|
||||
<div className="settings-content settings-item settings-language custom-scroll">
|
||||
{options ? (
|
||||
@ -77,7 +89,7 @@ function buildOptions(languages: ApiLanguage[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export default memo(withGlobal(
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
return {
|
||||
languages: global.settings.byKey.languages,
|
||||
|
||||
@ -8,12 +8,15 @@ import { selectUser } from '../../../modules/selectors';
|
||||
import { getUserFullName } from '../../../modules/helpers';
|
||||
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Avatar from '../../common/Avatar';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -21,11 +24,15 @@ type StateProps = {
|
||||
};
|
||||
|
||||
const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
currentUser,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Main);
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
<div className="settings-main-menu">
|
||||
|
||||
@ -5,12 +5,20 @@ import React, {
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
hasPrivateChatsNotifications: boolean;
|
||||
hasPrivateChatsMessagePreview: boolean;
|
||||
@ -25,7 +33,10 @@ type DispatchProps = Pick<GlobalActions, (
|
||||
'loadNotificationSettings' | 'updateContactSignUpNotification' | 'updateNotificationSettings'
|
||||
)>;
|
||||
|
||||
const SettingsNotifications: FC<StateProps & DispatchProps> = ({
|
||||
const SettingsNotifications: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
hasPrivateChatsNotifications,
|
||||
hasPrivateChatsMessagePreview,
|
||||
hasGroupNotifications,
|
||||
@ -73,6 +84,8 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Notifications);
|
||||
|
||||
return (
|
||||
<div className="settings-content custom-scroll">
|
||||
<div className="settings-item">
|
||||
@ -145,7 +158,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal((global): StateProps => {
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
return {
|
||||
hasPrivateChatsNotifications: Boolean(global.settings.byKey.hasPrivateChatsNotifications),
|
||||
hasPrivateChatsMessagePreview: Boolean(global.settings.byKey.hasPrivateChatsMessagePreview),
|
||||
|
||||
@ -6,12 +6,15 @@ import { PrivacyVisibility, SettingsScreens } from '../../../types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -32,7 +35,9 @@ type DispatchProps = Pick<GlobalActions, (
|
||||
)>;
|
||||
|
||||
const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
hasPassword,
|
||||
blockedCount,
|
||||
sessionsCount,
|
||||
@ -58,6 +63,8 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Privacy);
|
||||
|
||||
function getVisibilityValue(visibility?: PrivacyVisibility) {
|
||||
switch (visibility) {
|
||||
case 'everybody':
|
||||
|
||||
@ -5,15 +5,23 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { ApiSession } from '../../../api/types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { formatPastTimeShort } from '../../../util/dateFormat';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
activeSessions: ApiSession[];
|
||||
};
|
||||
@ -22,7 +30,10 @@ type DispatchProps = Pick<GlobalActions, (
|
||||
'loadAuthorizations' | 'terminateAuthorization' | 'terminateAllAuthorizations'
|
||||
)>;
|
||||
|
||||
const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
|
||||
const SettingsPrivacyActiveSessions: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
activeSessions,
|
||||
loadAuthorizations,
|
||||
terminateAuthorization,
|
||||
@ -52,6 +63,8 @@ const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.PrivacyActiveSessions);
|
||||
|
||||
function renderCurrentSession(session: ApiSession) {
|
||||
return (
|
||||
<div className="settings-item">
|
||||
@ -140,7 +153,7 @@ function getDeviceEnvironment(session: ApiSession) {
|
||||
return `${session.deviceModel}${session.deviceModel ? ', ' : ''} ${session.platform} ${session.systemVersion}`;
|
||||
}
|
||||
|
||||
export default memo(withGlobal(
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
return {
|
||||
activeSessions: global.activeSessions,
|
||||
|
||||
@ -5,6 +5,7 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../global/types';
|
||||
import { ApiChat, ApiUser } from '../../../api/types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { CHAT_HEIGHT_PX } from '../../../config';
|
||||
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||
@ -15,12 +16,19 @@ import {
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
import Avatar from '../../common/Avatar';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chatsByIds: Record<number, ApiChat>;
|
||||
usersByIds: Record<number, ApiUser>;
|
||||
@ -29,7 +37,10 @@ type StateProps = {
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'unblockContact'>;
|
||||
|
||||
const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
|
||||
const SettingsPrivacyBlockedUsers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
chatsByIds,
|
||||
usersByIds,
|
||||
blockedIds,
|
||||
@ -41,6 +52,8 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.PrivacyBlockedUsers);
|
||||
|
||||
function renderContact(contactId: number, i: number, viewportOffset: number) {
|
||||
const isPrivate = isChatPrivate(contactId);
|
||||
const user = isPrivate ? usersByIds[contactId] : undefined;
|
||||
@ -118,7 +131,7 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
|
||||
};
|
||||
|
||||
|
||||
export default memo(withGlobal(
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
|
||||
@ -9,6 +9,7 @@ import { ApiPrivacySettings, SettingsScreens } from '../../../types';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import RadioGroup from '../../ui/RadioGroup';
|
||||
@ -16,7 +17,9 @@ import { getPrivacyKey } from './helper/privacy';
|
||||
|
||||
type OwnProps = {
|
||||
screen: SettingsScreens;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = Partial<ApiPrivacySettings> & {
|
||||
@ -28,7 +31,9 @@ type DispatchProps = Pick<GlobalActions, 'setPrivacyVisibility'>;
|
||||
|
||||
const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
screen,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
visibility,
|
||||
allowUserIds,
|
||||
allowChatIds,
|
||||
@ -81,6 +86,8 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [lang, screen]);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, screen);
|
||||
|
||||
const descriptionText = useMemo(() => {
|
||||
switch (screen) {
|
||||
case SettingsScreens.PrivacyLastSeen:
|
||||
|
||||
@ -14,6 +14,7 @@ import { getPrivacyKey } from './helper/privacy';
|
||||
import {
|
||||
getChatTitle, isChatGroup, isChatPrivate, prepareChatList,
|
||||
} from '../../../modules/helpers';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import Picker from '../../common/Picker';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
@ -21,7 +22,9 @@ import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
export type OwnProps = {
|
||||
isAllowList?: boolean;
|
||||
screen: SettingsScreens;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -47,7 +50,9 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps & Dispatc
|
||||
archivedListIds,
|
||||
archivedPinnedIds,
|
||||
setPrivacySettings,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
@ -122,6 +127,9 @@ const SettingsPrivacyVisibilityExceptionList: FC<OwnProps & StateProps & Dispatc
|
||||
onScreenSelect(SettingsScreens.Privacy);
|
||||
}, [isAllowList, newSelectedContactIds, onScreenSelect, screen, setPrivacySettings]);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, screen);
|
||||
|
||||
|
||||
return (
|
||||
<div className="NewChat-inner step-1">
|
||||
<Picker
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { FC, memo, useCallback } from '../../../../lib/teact/teact';
|
||||
import { ApiChatFolder } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { FoldersState, FolderEditDispatch } from '../../../../hooks/reducers/useFoldersReducer';
|
||||
import { FolderEditDispatch, FoldersState } from '../../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import SettingsFoldersMain from './SettingsFoldersMain';
|
||||
import SettingsFoldersEdit from './SettingsFoldersEdit';
|
||||
@ -15,16 +15,20 @@ const TRANSITION_DURATION = 200;
|
||||
|
||||
export type OwnProps = {
|
||||
currentScreen: SettingsScreens;
|
||||
shownScreen: SettingsScreens;
|
||||
state: FoldersState;
|
||||
dispatch: FolderEditDispatch;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
const SettingsFolders: FC<OwnProps> = ({
|
||||
currentScreen,
|
||||
shownScreen,
|
||||
state,
|
||||
dispatch,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
}) => {
|
||||
@ -82,6 +86,14 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
<SettingsFoldersMain
|
||||
onCreateFolder={handleCreateFolder}
|
||||
onEditFolder={handleEditFolder}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.FoldersCreateFolder,
|
||||
SettingsScreens.FoldersEditFolder,
|
||||
SettingsScreens.FoldersIncludedChats,
|
||||
SettingsScreens.FoldersExcludedChats,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.FoldersCreateFolder:
|
||||
@ -93,6 +105,12 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
onAddIncludedChats={handleAddIncludedChats}
|
||||
onAddExcludedChats={handleAddExcludedChats}
|
||||
onReset={handleReset}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.FoldersIncludedChats,
|
||||
SettingsScreens.FoldersExcludedChats,
|
||||
].includes(shownScreen)}
|
||||
onBack={onReset}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.FoldersIncludedChats:
|
||||
@ -101,6 +119,9 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
mode="included"
|
||||
state={state}
|
||||
dispatch={dispatch}
|
||||
onReset={handleReset}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.FoldersExcludedChats:
|
||||
@ -109,6 +130,9 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
mode="excluded"
|
||||
state={state}
|
||||
dispatch={dispatch}
|
||||
onReset={handleReset}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../../global/types';
|
||||
import { ApiChat } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import { pick } from '../../../../util/iteratees';
|
||||
@ -15,6 +16,7 @@ import {
|
||||
FolderEditDispatch,
|
||||
selectChatFilters,
|
||||
} from '../../../../hooks/reducers/useFoldersReducer';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import SettingsFoldersChatsPicker from './SettingsFoldersChatsPicker';
|
||||
|
||||
@ -24,6 +26,9 @@ type OwnProps = {
|
||||
mode: 'included' | 'excluded';
|
||||
state: FoldersState;
|
||||
dispatch: FolderEditDispatch;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -37,6 +42,9 @@ type StateProps = {
|
||||
type DispatchProps = Pick<GlobalActions, 'loadMoreChats'>;
|
||||
|
||||
const SettingsFoldersChatFilters: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
mode,
|
||||
state,
|
||||
dispatch,
|
||||
@ -132,6 +140,9 @@ const SettingsFoldersChatFilters: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [mode, selectedChatIds, dispatch]);
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect,
|
||||
mode === 'included' ? SettingsScreens.FoldersIncludedChats : SettingsScreens.FoldersExcludedChats);
|
||||
|
||||
if (!displayedIds) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
@ -1,22 +1,24 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useState, useEffect, useMemo,
|
||||
FC, memo, useCallback, useEffect, useMemo, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../../global/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||
import { pick, findIntersectionWithSet } from '../../../../util/iteratees';
|
||||
import { findIntersectionWithSet, pick } from '../../../../util/iteratees';
|
||||
import { isChatPrivate } from '../../../../modules/helpers';
|
||||
import getAnimationData from '../../../common/helpers/animatedAssets';
|
||||
import {
|
||||
FoldersState,
|
||||
FolderEditDispatch,
|
||||
INCLUDED_CHAT_TYPES,
|
||||
EXCLUDED_CHAT_TYPES,
|
||||
FolderEditDispatch,
|
||||
FoldersState,
|
||||
INCLUDED_CHAT_TYPES,
|
||||
selectChatFilters,
|
||||
} from '../../../../hooks/reducers/useFoldersReducer';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../../ui/ListItem';
|
||||
import AnimatedSticker from '../../../common/AnimatedSticker';
|
||||
@ -32,7 +34,10 @@ type OwnProps = {
|
||||
dispatch: FolderEditDispatch;
|
||||
onAddIncludedChats: () => void;
|
||||
onAddExcludedChats: () => void;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
onBack: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -54,7 +59,10 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
dispatch,
|
||||
onAddIncludedChats,
|
||||
onAddExcludedChats,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
onBack,
|
||||
loadedActiveChatIds,
|
||||
loadedArchivedChatIds,
|
||||
editChatFolder,
|
||||
@ -128,6 +136,10 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onBack, onScreenSelect, state.mode === 'edit'
|
||||
? SettingsScreens.FoldersEditFolder
|
||||
: SettingsScreens.FoldersCreateFolder);
|
||||
|
||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const { currentTarget } = event;
|
||||
dispatch({ type: 'setTitle', payload: currentTarget.value.trim() });
|
||||
|
||||
@ -5,7 +5,7 @@ import { withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../../../global/types';
|
||||
import { ApiChatFolder, ApiChat, ApiUser } from '../../../../api/types';
|
||||
import { NotifyException, NotifySettings } from '../../../../types';
|
||||
import { NotifyException, NotifySettings, SettingsScreens } from '../../../../types';
|
||||
|
||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||
import { pick } from '../../../../util/iteratees';
|
||||
@ -14,6 +14,7 @@ import { throttle } from '../../../../util/schedulers';
|
||||
import getAnimationData from '../../../common/helpers/animatedAssets';
|
||||
import { getFolderDescriptionText } from '../../../../modules/helpers';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../../ui/ListItem';
|
||||
import Button from '../../../ui/Button';
|
||||
@ -23,6 +24,9 @@ import AnimatedSticker from '../../../common/AnimatedSticker';
|
||||
type OwnProps = {
|
||||
onCreateFolder: () => void;
|
||||
onEditFolder: (folder: ApiChatFolder) => void;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -44,6 +48,9 @@ const MAX_ALLOWED_FOLDERS = 10;
|
||||
const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onCreateFolder,
|
||||
onEditFolder,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
chatsById,
|
||||
usersById,
|
||||
orderedFolderIds,
|
||||
@ -90,6 +97,8 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.Folders);
|
||||
|
||||
const userFolders = useMemo(() => {
|
||||
if (!orderedFolderIds) {
|
||||
return undefined;
|
||||
|
||||
@ -20,8 +20,11 @@ import SettingsTwoFaEmailCode from './SettingsTwoFaEmailCode';
|
||||
export type OwnProps = {
|
||||
state: TwoFaState;
|
||||
currentScreen: SettingsScreens;
|
||||
shownScreen: SettingsScreens;
|
||||
dispatch: TwoFaDispatch;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = GlobalState['twoFaSettings'];
|
||||
@ -33,13 +36,16 @@ type DispatchProps = Pick<GlobalActions, (
|
||||
|
||||
const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
currentScreen,
|
||||
shownScreen,
|
||||
state,
|
||||
hint,
|
||||
isLoading,
|
||||
error,
|
||||
waitingEmailCodeLength,
|
||||
dispatch,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
updatePassword,
|
||||
checkPassword,
|
||||
clearTwoFaError,
|
||||
@ -158,25 +164,54 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<SettingsTwoFaStart
|
||||
onStart={handleStartWizard}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaNewPassword,
|
||||
SettingsScreens.TwoFaNewPasswordConfirm,
|
||||
SettingsScreens.TwoFaNewPasswordHint,
|
||||
SettingsScreens.TwoFaNewPasswordEmail,
|
||||
SettingsScreens.TwoFaNewPasswordEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaNewPassword:
|
||||
return (
|
||||
<SettingsTwoFaPassword
|
||||
screen={currentScreen}
|
||||
placeholder={lang('EnterPassword')}
|
||||
submitLabel={lang('Continue')}
|
||||
onSubmit={handleNewPassword}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaNewPasswordConfirm,
|
||||
SettingsScreens.TwoFaNewPasswordHint,
|
||||
SettingsScreens.TwoFaNewPasswordEmail,
|
||||
SettingsScreens.TwoFaNewPasswordEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaNewPasswordConfirm:
|
||||
return (
|
||||
<SettingsTwoFaPassword
|
||||
screen={currentScreen}
|
||||
expectedPassword={state.password}
|
||||
placeholder={lang('PleaseReEnterPassword')}
|
||||
submitLabel={lang('Continue')}
|
||||
onSubmit={handleNewPasswordConfirm}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaNewPasswordHint,
|
||||
SettingsScreens.TwoFaNewPasswordEmail,
|
||||
SettingsScreens.TwoFaNewPasswordEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -186,6 +221,14 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
icon="hint"
|
||||
placeholder={lang('PasswordHintPlaceholder')}
|
||||
onSubmit={handleNewPasswordHint}
|
||||
screen={currentScreen}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaNewPasswordEmail,
|
||||
SettingsScreens.TwoFaNewPasswordEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -200,6 +243,13 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
placeholder={lang('RecoveryEmailTitle')}
|
||||
shouldConfirm
|
||||
onSubmit={handleNewPasswordEmail}
|
||||
screen={currentScreen}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaNewPasswordEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -210,6 +260,10 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
error={error}
|
||||
clearError={clearTwoFaError}
|
||||
onSubmit={handleEmailCode}
|
||||
screen={currentScreen}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || shownScreen === SettingsScreens.TwoFaCongratulations}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -217,6 +271,8 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<SettingsTwoFaCongratulations
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -224,34 +280,70 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<SettingsTwoFaEnabled
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaChangePasswordCurrent,
|
||||
SettingsScreens.TwoFaChangePasswordNew,
|
||||
SettingsScreens.TwoFaChangePasswordConfirm,
|
||||
SettingsScreens.TwoFaChangePasswordHint,
|
||||
SettingsScreens.TwoFaTurnOff,
|
||||
SettingsScreens.TwoFaRecoveryEmailCurrentPassword,
|
||||
SettingsScreens.TwoFaRecoveryEmail,
|
||||
SettingsScreens.TwoFaRecoveryEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaChangePasswordCurrent:
|
||||
return (
|
||||
<SettingsTwoFaPassword
|
||||
screen={currentScreen}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
clearError={clearTwoFaError}
|
||||
hint={hint}
|
||||
onSubmit={handleChangePasswordCurrent}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaChangePasswordNew,
|
||||
SettingsScreens.TwoFaChangePasswordConfirm,
|
||||
SettingsScreens.TwoFaChangePasswordHint,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaChangePasswordNew:
|
||||
return (
|
||||
<SettingsTwoFaPassword
|
||||
screen={currentScreen}
|
||||
placeholder={lang('PleaseEnterNewFirstPassword')}
|
||||
onSubmit={handleChangePasswordNew}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaChangePasswordConfirm,
|
||||
SettingsScreens.TwoFaChangePasswordHint,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaChangePasswordConfirm:
|
||||
return (
|
||||
<SettingsTwoFaPassword
|
||||
screen={currentScreen}
|
||||
expectedPassword={state.password}
|
||||
placeholder={lang('PleaseReEnterPassword')}
|
||||
onSubmit={handleChangePasswordConfirm}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaChangePasswordHint,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -264,6 +356,10 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
icon="hint"
|
||||
placeholder={lang('PasswordHintPlaceholder')}
|
||||
onSubmit={handleChangePasswordHint}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || shownScreen === SettingsScreens.TwoFaCongratulations}
|
||||
onReset={onReset}
|
||||
screen={currentScreen}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -275,37 +371,60 @@ const SettingsTwoFa: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
clearError={clearTwoFaError}
|
||||
hint={hint}
|
||||
onSubmit={handleTurnOff}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
onReset={onReset}
|
||||
screen={currentScreen}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaRecoveryEmailCurrentPassword:
|
||||
return (
|
||||
<SettingsTwoFaPassword
|
||||
screen={currentScreen}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
clearError={clearTwoFaError}
|
||||
hint={hint}
|
||||
onSubmit={handleRecoveryEmailCurrentPassword}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaRecoveryEmail,
|
||||
SettingsScreens.TwoFaRecoveryEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaRecoveryEmail:
|
||||
return (
|
||||
<SettingsTwoFaSkippableForm
|
||||
screen={currentScreen}
|
||||
icon="email"
|
||||
type="email"
|
||||
placeholder={lang('RecoveryEmailTitle')}
|
||||
onSubmit={handleRecoveryEmail}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
SettingsScreens.TwoFaRecoveryEmailCode,
|
||||
SettingsScreens.TwoFaCongratulations,
|
||||
].includes(shownScreen)}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
case SettingsScreens.TwoFaRecoveryEmailCode:
|
||||
return (
|
||||
<SettingsTwoFaEmailCode
|
||||
screen={currentScreen}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
clearError={clearTwoFaError}
|
||||
onSubmit={handleEmailCode}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || shownScreen === SettingsScreens.TwoFaCongratulations}
|
||||
onReset={onReset}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@ -6,25 +6,32 @@ import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { selectAnimatedEmoji } from '../../../../modules/selectors';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import Button from '../../../ui/Button';
|
||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
animatedEmoji: ApiSticker;
|
||||
};
|
||||
|
||||
const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({ animatedEmoji, onScreenSelect }) => {
|
||||
const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({
|
||||
isActive, onReset, animatedEmoji, onScreenSelect,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const handleClick = () => {
|
||||
onScreenSelect(SettingsScreens.Privacy);
|
||||
};
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaCongratulations);
|
||||
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
|
||||
@ -4,10 +4,12 @@ import React, {
|
||||
import { withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { ApiSticker } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
|
||||
import { selectAnimatedEmoji } from '../../../../modules/selectors';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
||||
import InputText from '../../../ui/InputText';
|
||||
@ -18,6 +20,10 @@ type OwnProps = {
|
||||
error?: string;
|
||||
clearError: NoneToVoidFunction;
|
||||
onSubmit: (hint: string) => void;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
screen: SettingsScreens;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -34,6 +40,10 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||
error,
|
||||
clearError,
|
||||
onSubmit,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
screen,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -50,6 +60,8 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, screen);
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (error && clearError) {
|
||||
clearError();
|
||||
|
||||
@ -6,22 +6,29 @@ import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { selectAnimatedEmoji } from '../../../../modules/selectors';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../../ui/ListItem';
|
||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
animatedEmoji: ApiSticker;
|
||||
};
|
||||
|
||||
const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({ animatedEmoji, onScreenSelect }) => {
|
||||
const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
|
||||
isActive, onReset, animatedEmoji, onScreenSelect,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaEnabled);
|
||||
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
|
||||
@ -2,12 +2,16 @@ import React, {
|
||||
FC, memo, useCallback, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import PasswordMonkey from '../../../common/PasswordMonkey';
|
||||
import PasswordForm from '../../../common/PasswordForm';
|
||||
|
||||
type OwnProps = {
|
||||
screen: SettingsScreens;
|
||||
error?: string;
|
||||
isLoading?: boolean;
|
||||
expectedPassword?: string;
|
||||
@ -16,11 +20,18 @@ type OwnProps = {
|
||||
submitLabel?: string;
|
||||
clearError?: NoneToVoidFunction;
|
||||
onSubmit: (password: string) => void;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
const EQUAL_PASSWORD_ERROR = 'Passwords Should Be Equal';
|
||||
|
||||
const SettingsTwoFaPassword: FC<OwnProps> = ({
|
||||
screen,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
error,
|
||||
isLoading,
|
||||
expectedPassword,
|
||||
@ -50,6 +61,8 @@ const SettingsTwoFaPassword: FC<OwnProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, screen);
|
||||
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
|
||||
@ -4,11 +4,13 @@ import React, {
|
||||
import { withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { ApiSticker } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
|
||||
import { selectAnimatedEmoji } from '../../../../modules/selectors';
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import Button from '../../../ui/Button';
|
||||
import Modal from '../../../ui/Modal';
|
||||
@ -25,6 +27,10 @@ type OwnProps = {
|
||||
shouldConfirm?: boolean;
|
||||
clearError?: NoneToVoidFunction;
|
||||
onSubmit: (value?: string) => void;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
screen: SettingsScreens;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -42,6 +48,10 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||
shouldConfirm,
|
||||
clearError,
|
||||
onSubmit,
|
||||
isActive,
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
screen,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -86,6 +96,8 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, screen);
|
||||
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
|
||||
@ -2,24 +2,33 @@ import React, { FC, memo } from '../../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../../lib/teact/teactn';
|
||||
|
||||
import { ApiSticker } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
|
||||
import { selectAnimatedEmoji } from '../../../../modules/selectors';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
|
||||
import Button from '../../../ui/Button';
|
||||
import AnimatedEmoji from '../../../common/AnimatedEmoji';
|
||||
|
||||
type OwnProps = {
|
||||
onStart: NoneToVoidFunction;
|
||||
isActive?: boolean;
|
||||
onScreenSelect: (screen: SettingsScreens) => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
animatedEmoji: ApiSticker;
|
||||
};
|
||||
|
||||
const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({ animatedEmoji, onStart }) => {
|
||||
const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
|
||||
isActive, onScreenSelect, onReset, animatedEmoji, onStart,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onReset, onScreenSelect, SettingsScreens.TwoFaDisabled);
|
||||
|
||||
return (
|
||||
<div className="settings-content two-fa custom-scroll">
|
||||
<div className="settings-content-header">
|
||||
|
||||
@ -87,6 +87,14 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#Main.history-animation-disabled & {
|
||||
transition: none;
|
||||
|
||||
&:after {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@ -104,6 +112,18 @@
|
||||
@media (max-width: 600px) {
|
||||
height: calc(var(--vh, 1vh) * 100 + 1px);
|
||||
}
|
||||
|
||||
#Main.history-animation-disabled & {
|
||||
transition: none;
|
||||
|
||||
.overlay-backdrop {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Main.history-animation-disabled .overlay-backdrop {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
#MiddleColumn {
|
||||
@ -155,6 +175,14 @@
|
||||
transform: translate3d(-20vw, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#Main.history-animation-disabled & {
|
||||
transition: none;
|
||||
|
||||
&:after {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.SymbolMenu {
|
||||
|
||||
@ -49,6 +49,7 @@ type StateProps = {
|
||||
audioMessage?: ApiMessage;
|
||||
safeLinkModalUrl?: string;
|
||||
isHistoryCalendarOpen: boolean;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
@ -75,6 +76,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
audioMessage,
|
||||
safeLinkModalUrl,
|
||||
isHistoryCalendarOpen,
|
||||
shouldSkipHistoryAnimations,
|
||||
loadAnimatedEmojis,
|
||||
loadNotificationSettings,
|
||||
loadNotificationExceptions,
|
||||
@ -98,15 +100,17 @@ const Main: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const {
|
||||
transitionClassNames: middleColumnTransitionClassNames,
|
||||
} = useShowTransition(!isLeftColumnShown, undefined, true);
|
||||
} = useShowTransition(!isLeftColumnShown, undefined, true, undefined, shouldSkipHistoryAnimations);
|
||||
|
||||
const {
|
||||
transitionClassNames: rightColumnTransitionClassNames,
|
||||
} = useShowTransition(isRightColumnShown, undefined, true);
|
||||
} = useShowTransition(isRightColumnShown, undefined, true, undefined, shouldSkipHistoryAnimations);
|
||||
|
||||
|
||||
const className = buildClassName(
|
||||
middleColumnTransitionClassNames.replace(/([\w-]+)/g, 'middle-column-$1'),
|
||||
rightColumnTransitionClassNames.replace(/([\w-]+)/g, 'right-column-$1'),
|
||||
shouldSkipHistoryAnimations && 'history-animation-disabled',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -230,6 +234,7 @@ export default memo(withGlobal(
|
||||
audioMessage,
|
||||
safeLinkModalUrl: global.safeLinkModalUrl,
|
||||
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
|
||||
@ -58,6 +58,7 @@ import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'
|
||||
import { renderMessageText } from '../common/helpers/renderMessageText';
|
||||
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import Spinner from '../ui/Spinner';
|
||||
import ShowTransition from '../ui/ShowTransition';
|
||||
@ -443,6 +444,14 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isOpen, closeMediaViewer, openMediaViewer, {
|
||||
chatId,
|
||||
threadId,
|
||||
messageId,
|
||||
origin,
|
||||
avatarOwnerId: avatarOwner && avatarOwner.id,
|
||||
});
|
||||
|
||||
function renderSlide(isActive: boolean) {
|
||||
if (isAvatar) {
|
||||
return (
|
||||
|
||||
@ -35,6 +35,7 @@ import {
|
||||
selectIsRightColumnShown,
|
||||
selectPinnedIds,
|
||||
selectTheme,
|
||||
selectThreadOriginChat,
|
||||
} from '../../modules/selectors';
|
||||
import { getCanPostInChat, getMessageSendingRestrictionReason, isChatPrivate } from '../../modules/helpers';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
@ -45,6 +46,7 @@ import useWindowSize from '../../hooks/useWindowSize';
|
||||
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
||||
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import MiddleHeader from './MiddleHeader';
|
||||
@ -64,6 +66,7 @@ type StateProps = {
|
||||
messageListType?: MessageListType;
|
||||
isPrivate?: boolean;
|
||||
isPinnedMessageList?: boolean;
|
||||
isScheduledMessageList?: boolean;
|
||||
canPost?: boolean;
|
||||
messageSendingRestrictionReason?: string;
|
||||
hasPinnedOrAudioMessage?: boolean;
|
||||
@ -78,9 +81,12 @@ type StateProps = {
|
||||
isMobileSearchActive?: boolean;
|
||||
isSelectModeActive?: boolean;
|
||||
animationLevel?: number;
|
||||
originChatId?: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'openChat' | 'unpinAllMessages' | 'loadUser'>;
|
||||
type DispatchProps = Pick<GlobalActions, 'openChat' | 'unpinAllMessages' | 'loadUser' |
|
||||
'closeLocalTextSearch' | 'exitMessageSelectMode'>;
|
||||
|
||||
const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined;
|
||||
|
||||
@ -94,6 +100,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
messageListType,
|
||||
isPrivate,
|
||||
isPinnedMessageList,
|
||||
isScheduledMessageList,
|
||||
canPost,
|
||||
messageSendingRestrictionReason,
|
||||
hasPinnedOrAudioMessage,
|
||||
@ -108,9 +115,13 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
isMobileSearchActive,
|
||||
isSelectModeActive,
|
||||
animationLevel,
|
||||
originChatId,
|
||||
shouldSkipHistoryAnimations,
|
||||
openChat,
|
||||
unpinAllMessages,
|
||||
loadUser,
|
||||
closeLocalTextSearch,
|
||||
exitMessageSelectMode,
|
||||
}) => {
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
|
||||
@ -223,6 +234,31 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
renderingCanPost && isNotchShown && !isSelectModeActive && 'with-notch',
|
||||
);
|
||||
|
||||
const closeChat = () => {
|
||||
if (renderingThreadId !== MAIN_THREAD_ID) {
|
||||
openChat({ id: originChatId, threadId: MAIN_THREAD_ID }, true);
|
||||
} else if (isPinnedMessageList || isScheduledMessageList) {
|
||||
openChat({ id: chatId, type: 'thread' });
|
||||
} else {
|
||||
openChat({ id: undefined }, true);
|
||||
}
|
||||
};
|
||||
|
||||
useHistoryBack(renderingChatId && renderingThreadId, closeChat, openChat, {
|
||||
id: chatId,
|
||||
threadId: MAIN_THREAD_ID,
|
||||
});
|
||||
|
||||
const isDiscussion = renderingChatId && renderingThreadId !== MAIN_THREAD_ID;
|
||||
|
||||
useHistoryBack(isDiscussion || isPinnedMessageList || isScheduledMessageList, closeChat, openChat, {
|
||||
id: chatId,
|
||||
threadId: renderingThreadId,
|
||||
});
|
||||
|
||||
useHistoryBack(isMobileSearchActive, closeLocalTextSearch);
|
||||
useHistoryBack(isSelectModeActive, exitMessageSelectMode);
|
||||
|
||||
return (
|
||||
<div
|
||||
id="MiddleColumn"
|
||||
@ -256,7 +292,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
messageListType={renderingMessageListType}
|
||||
/>
|
||||
<Transition
|
||||
name={animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
||||
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
||||
activeKey={renderingMessageListType === 'thread' && renderingThreadId === MAIN_THREAD_ID ? 1 : 2}
|
||||
shouldCleanup
|
||||
>
|
||||
@ -371,15 +407,19 @@ export default memo(withGlobal(
|
||||
const canPost = chat && getCanPostInChat(chat, threadId);
|
||||
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
||||
const isPinnedMessageList = messageListType === 'pinned';
|
||||
const isScheduledMessageList = messageListType === 'scheduled';
|
||||
const originChat = selectThreadOriginChat(global, chatId, threadId);
|
||||
|
||||
return {
|
||||
...state,
|
||||
chatId,
|
||||
threadId,
|
||||
messageListType,
|
||||
originChatId: originChat ? originChat.id : chatId,
|
||||
isPrivate: isChatPrivate(chatId),
|
||||
canPost: !isPinnedMessageList && (!chat || canPost) && (!isBotNotStarted || IS_SINGLE_COLUMN_LAYOUT),
|
||||
isPinnedMessageList,
|
||||
isScheduledMessageList,
|
||||
messageSendingRestrictionReason: chat && getMessageSendingRestrictionReason(chat),
|
||||
hasPinnedOrAudioMessage: (
|
||||
threadId !== MAIN_THREAD_ID
|
||||
@ -387,9 +427,10 @@ export default memo(withGlobal(
|
||||
|| Boolean(audioChatId && audioMessageId)
|
||||
),
|
||||
pinnedMessagesCount: pinnedIds ? pinnedIds.length : 0,
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
'openChat', 'unpinAllMessages', 'loadUser',
|
||||
'openChat', 'unpinAllMessages', 'loadUser', 'closeLocalTextSearch', 'exitMessageSelectMode',
|
||||
]),
|
||||
)(MiddleColumn));
|
||||
|
||||
@ -94,6 +94,7 @@ type StateProps = {
|
||||
lastSyncTime?: number;
|
||||
notifySettings: NotifySettings;
|
||||
notifyExceptions?: Record<number, NotifyException>;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
@ -123,6 +124,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
lastSyncTime,
|
||||
notifySettings,
|
||||
notifyExceptions,
|
||||
shouldSkipHistoryAnimations,
|
||||
openChatWithInfo,
|
||||
pinMessage,
|
||||
focusMessage,
|
||||
@ -384,7 +386,10 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
return (
|
||||
<div className="MiddleHeader" ref={componentRef}>
|
||||
<Transition name="slide-fade" activeKey={messageListType === 'thread' ? threadId : 1}>
|
||||
<Transition
|
||||
name={shouldSkipHistoryAnimations ? 'none' : 'slide-fade'}
|
||||
activeKey={messageListType === 'thread' ? threadId : 1}
|
||||
>
|
||||
{renderInfo}
|
||||
</Transition>
|
||||
|
||||
@ -421,7 +426,7 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, messageListType }): StateProps => {
|
||||
const { isLeftColumnShown, lastSyncTime } = global;
|
||||
const { isLeftColumnShown, lastSyncTime, shouldSkipHistoryAnimations } = global;
|
||||
const { byId: chatsById } = global.chats;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
@ -463,6 +468,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
lastSyncTime,
|
||||
notifySettings: selectNotifySettings(global),
|
||||
notifyExceptions: selectNotifyExceptions(global),
|
||||
shouldSkipHistoryAnimations,
|
||||
};
|
||||
|
||||
const messagesById = selectChatMessages(global, chatId);
|
||||
|
||||
@ -18,6 +18,7 @@ import { pick } from '../../util/iteratees';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import InfiniteScroll from '../ui/InfiniteScroll';
|
||||
import GifButton from '../common/GifButton';
|
||||
@ -25,6 +26,11 @@ import Loading from '../ui/Loading';
|
||||
|
||||
import './GifSearch.scss';
|
||||
|
||||
type OwnProps = {
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
query?: string;
|
||||
results?: ApiVideo[];
|
||||
@ -37,7 +43,9 @@ type DispatchProps = Pick<GlobalActions, 'searchMoreGifs' | 'sendMessage' | 'set
|
||||
const PRELOAD_BACKWARDS = 96; // GIF Search bot results are multiplied by 24
|
||||
const INTERSECTION_DEBOUNCE = 300;
|
||||
|
||||
const GifSearch: FC<StateProps & DispatchProps> = ({
|
||||
const GifSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onClose,
|
||||
isActive,
|
||||
query,
|
||||
results,
|
||||
chat,
|
||||
@ -67,6 +75,8 @@ const GifSearch: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
function renderContent() {
|
||||
if (query === undefined) {
|
||||
return undefined;
|
||||
|
||||
@ -6,24 +6,34 @@ import { selectChat, selectChatMessage } from '../../modules/selectors';
|
||||
import { buildCollectionByKey } from '../../util/iteratees';
|
||||
import { getMessagePoll } from '../../modules/helpers';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import PollAnswerResults from './PollAnswerResults';
|
||||
import Loading from '../ui/Loading';
|
||||
|
||||
import './PollResults.scss';
|
||||
|
||||
type OwnProps = {
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
message?: ApiMessage;
|
||||
lastSyncTime?: number;
|
||||
};
|
||||
|
||||
const PollResults: FC<StateProps> = ({
|
||||
const PollResults: FC<OwnProps & StateProps> = ({
|
||||
onClose,
|
||||
isActive,
|
||||
chat,
|
||||
message,
|
||||
lastSyncTime,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
if (!message || !chat) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps';
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import RightHeader from './RightHeader';
|
||||
import Profile from './Profile';
|
||||
@ -35,6 +36,7 @@ type StateProps = {
|
||||
threadId?: number;
|
||||
currentProfileUserId?: number;
|
||||
isChatSelected: boolean;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
@ -67,6 +69,7 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
setStickerSearchQuery,
|
||||
setGifSearchQuery,
|
||||
closePollResults,
|
||||
shouldSkipHistoryAnimations,
|
||||
}) => {
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
const [profileState, setProfileState] = useState<ProfileState>(ProfileState.Profile);
|
||||
@ -88,21 +91,21 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
const renderingContentKey = useCurrentOrPrev(contentKey, true, !isChatSelected) ?? -1;
|
||||
|
||||
const close = useCallback(() => {
|
||||
const close = useCallback((shouldScrollUp = true) => {
|
||||
switch (contentKey) {
|
||||
case RightColumnContent.ChatInfo:
|
||||
if (isScrolledDown) {
|
||||
if (isScrolledDown && shouldScrollUp) {
|
||||
setProfileState(ProfileState.Profile);
|
||||
break;
|
||||
}
|
||||
toggleChatInfo();
|
||||
toggleChatInfo(undefined, true);
|
||||
break;
|
||||
case RightColumnContent.UserInfo:
|
||||
if (isScrolledDown) {
|
||||
if (isScrolledDown && shouldScrollUp) {
|
||||
setProfileState(ProfileState.Profile);
|
||||
break;
|
||||
}
|
||||
openUserInfo({ id: undefined });
|
||||
openUserInfo({ id: undefined }, true);
|
||||
break;
|
||||
case RightColumnContent.Management: {
|
||||
switch (managementScreen) {
|
||||
@ -139,9 +142,11 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
break;
|
||||
}
|
||||
case RightColumnContent.StickerSearch:
|
||||
case RightColumnContent.GifSearch: {
|
||||
blurSearchInput();
|
||||
setStickerSearchQuery({ query: undefined });
|
||||
break;
|
||||
case RightColumnContent.GifSearch: {
|
||||
blurSearchInput();
|
||||
setGifSearchQuery({ query: undefined });
|
||||
break;
|
||||
}
|
||||
@ -187,8 +192,13 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
}, [contentKey, chatId]);
|
||||
|
||||
|
||||
useHistoryBack(isChatSelected && (contentKey === RightColumnContent.ChatInfo
|
||||
|| contentKey === RightColumnContent.UserInfo || contentKey === RightColumnContent.Management),
|
||||
() => close(false), toggleChatInfo);
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
function renderContent() {
|
||||
function renderContent(isActive: boolean) {
|
||||
if (renderingContentKey === -1) {
|
||||
return undefined;
|
||||
}
|
||||
@ -206,7 +216,7 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
/>
|
||||
);
|
||||
case RightColumnContent.Search:
|
||||
return <RightSearch chatId={chatId!} threadId={threadId!} />;
|
||||
return <RightSearch chatId={chatId!} threadId={threadId!} onClose={close} isActive={isOpen && isActive} />;
|
||||
case RightColumnContent.Management:
|
||||
return (
|
||||
<Management
|
||||
@ -216,14 +226,17 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
selectedChatMemberId={selectedChatMemberId}
|
||||
onScreenSelect={setManagementScreen}
|
||||
onChatMemberSelect={handleSelectChatMember}
|
||||
isActive={isOpen && isActive}
|
||||
onClose={close}
|
||||
/>
|
||||
);
|
||||
|
||||
case RightColumnContent.StickerSearch:
|
||||
return <StickerSearch />;
|
||||
return <StickerSearch onClose={close} isActive={isOpen && isActive} />;
|
||||
case RightColumnContent.GifSearch:
|
||||
return <GifSearch />;
|
||||
return <GifSearch onClose={close} isActive={isOpen && isActive} />;
|
||||
case RightColumnContent.PollResults:
|
||||
return <PollResults />;
|
||||
return <PollResults onClose={close} isActive={isOpen && isActive} />;
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,9 +261,10 @@ const RightColumn: FC<StateProps & DispatchProps> = ({
|
||||
profileState={profileState}
|
||||
managementScreen={managementScreen}
|
||||
onClose={close}
|
||||
shouldSkipAnimation={shouldSkipTransition || shouldSkipHistoryAnimations}
|
||||
/>
|
||||
<Transition
|
||||
name={shouldSkipTransition ? 'none' : 'zoom-fade'}
|
||||
name={(shouldSkipTransition || shouldSkipHistoryAnimations) ? 'none' : 'zoom-fade'}
|
||||
renderCount={MAIN_SCREENS_COUNT + MANAGEMENT_SCREENS_COUNT}
|
||||
activeKey={isManagement ? MAIN_SCREENS_COUNT + managementScreen : renderingContentKey}
|
||||
shouldCleanup
|
||||
@ -274,6 +288,7 @@ export default memo(withGlobal(
|
||||
threadId,
|
||||
currentProfileUserId: global.users.selectedId,
|
||||
isChatSelected: Boolean(chatId && areActiveChatsLoaded),
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
|
||||
@ -36,6 +36,7 @@ type OwnProps = {
|
||||
isStickerSearch?: boolean;
|
||||
isGifSearch?: boolean;
|
||||
isPollResults?: boolean;
|
||||
shouldSkipAnimation?: boolean;
|
||||
profileState?: ProfileState;
|
||||
managementScreen?: ManagementScreens;
|
||||
onClose: () => void;
|
||||
@ -102,6 +103,7 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
searchTextMessagesLocal,
|
||||
toggleManagement,
|
||||
openHistoryCalendar,
|
||||
shouldSkipAnimation,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const backButtonRef = useRef<HTMLDivElement>(null);
|
||||
@ -278,7 +280,7 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|
||||
const buttonClassName = buildClassName(
|
||||
'animated-close-icon',
|
||||
shouldSkipTransition && 'no-transition',
|
||||
(shouldSkipTransition || shouldSkipAnimation) && 'no-transition',
|
||||
);
|
||||
|
||||
// Add class in the next AF to synchronize with animation with Transition components
|
||||
@ -299,7 +301,7 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div ref={backButtonRef} className={buttonClassName} />
|
||||
</Button>
|
||||
<Transition
|
||||
name={shouldSkipTransition ? 'none' : 'slide-fade'}
|
||||
name={(shouldSkipTransition || shouldSkipAnimation) ? 'none' : 'slide-fade'}
|
||||
activeKey={renderingContentKey}
|
||||
>
|
||||
{renderHeaderContent}
|
||||
|
||||
@ -23,6 +23,7 @@ import useLang from '../../hooks/useLang';
|
||||
import { orderBy, pick } from '../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import InfiniteScroll from '../ui/InfiniteScroll';
|
||||
import ListItem from '../ui/ListItem';
|
||||
@ -34,6 +35,8 @@ import './RightSearch.scss';
|
||||
export type OwnProps = {
|
||||
chatId: number;
|
||||
threadId: number;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -56,6 +59,8 @@ interface Result {
|
||||
const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chatId,
|
||||
threadId,
|
||||
onClose,
|
||||
isActive,
|
||||
chat,
|
||||
messagesById,
|
||||
query,
|
||||
@ -125,6 +130,8 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const handleKeyDown = useKeyboardListNavigation(containerRef, true, (index) => {
|
||||
|
||||
@ -10,12 +10,18 @@ import { throttle } from '../../util/schedulers';
|
||||
import { selectCurrentStickerSearch } from '../../modules/selectors';
|
||||
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import Loading from '../ui/Loading';
|
||||
import StickerSetResult from './StickerSetResult';
|
||||
|
||||
import './StickerSearch.scss';
|
||||
|
||||
type OwnProps = {
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
query?: string;
|
||||
featuredIds?: string[];
|
||||
@ -28,7 +34,9 @@ const INTERSECTION_THROTTLE = 200;
|
||||
|
||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||
|
||||
const StickerSearch: FC<StateProps & DispatchProps> = ({
|
||||
const StickerSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onClose,
|
||||
isActive,
|
||||
query,
|
||||
featuredIds,
|
||||
resultIds,
|
||||
@ -53,6 +61,8 @@ const StickerSearch: FC<StateProps & DispatchProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
function renderContent() {
|
||||
if (query === undefined) {
|
||||
return undefined;
|
||||
|
||||
@ -22,12 +22,15 @@ import Spinner from '../../ui/Spinner';
|
||||
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import './Management.scss';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -56,6 +59,8 @@ const ManageChannel: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
leaveChannel,
|
||||
deleteChannel,
|
||||
openChat,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const currentTitle = chat ? (chat.title || '') : '';
|
||||
const currentAbout = chat && chat.fullInfo ? (chat.fullInfo.about || '') : '';
|
||||
@ -71,6 +76,8 @@ const ManageChannel: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
if (progress === ManagementProgress.Complete) {
|
||||
setIsProfileFieldsTouched(false);
|
||||
|
||||
@ -9,6 +9,7 @@ import { getUserFullName, isChatChannel } from '../../../modules/helpers';
|
||||
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
@ -17,6 +18,8 @@ type OwnProps = {
|
||||
chatId: number;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onChatMemberSelect: (memberId: number, isPromotedByCurrentUser?: boolean) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -33,9 +36,13 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
|
||||
usersById,
|
||||
onScreenSelect,
|
||||
onChatMemberSelect,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
function handleRecentActionsClick() {
|
||||
onScreenSelect(ManagementScreens.GroupRecentActions);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import { pick } from '../../../util/iteratees';
|
||||
import { isChatChannel } from '../../../modules/helpers';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
@ -26,6 +27,8 @@ type PrivacyType = 'private' | 'public';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -41,6 +44,8 @@ type DispatchProps = Pick<GlobalActions, (
|
||||
|
||||
const ManageChatPrivacyType: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chat,
|
||||
onClose,
|
||||
isActive,
|
||||
isChannel,
|
||||
progress,
|
||||
isUsernameAvailable,
|
||||
@ -60,6 +65,8 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
|| (privacyType === 'private' && isPublic)
|
||||
);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
if (privacyType && !privateLink) {
|
||||
updatePrivateLink();
|
||||
|
||||
@ -12,6 +12,7 @@ import { selectChat } from '../../../modules/selectors';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import getAnimationData from '../../common/helpers/animatedAssets';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
@ -26,6 +27,8 @@ import { isChatChannel } from '../../../modules/helpers';
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -40,6 +43,8 @@ type DispatchProps = Pick<GlobalActions, 'loadGroupsForDiscussion' | 'linkDiscus
|
||||
|
||||
const ManageDiscussion: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chat,
|
||||
onClose,
|
||||
isActive,
|
||||
chatId,
|
||||
chatsByIds,
|
||||
linkedChat,
|
||||
@ -59,6 +64,8 @@ const ManageDiscussion: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const lang = useLang();
|
||||
const linkedChatId = linkedChat && linkedChat.id;
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
loadGroupsForDiscussion();
|
||||
}, [loadGroupsForDiscussion]);
|
||||
|
||||
@ -16,6 +16,7 @@ import { selectChat } from '../../../modules/selectors';
|
||||
import { formatInteger } from '../../../util/textFormat';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import AvatarEditable from '../../ui/AvatarEditable';
|
||||
import InputText from '../../ui/InputText';
|
||||
@ -30,6 +31,8 @@ import './Management.scss';
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -68,6 +71,8 @@ const ManageGroup: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
deleteChannel,
|
||||
closeManagement,
|
||||
openChat,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||
const currentTitle = chat.title;
|
||||
@ -82,6 +87,8 @@ const ManageGroup: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const currentAvatarBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
useEffect(() => {
|
||||
if (progress === ManagementProgress.Complete) {
|
||||
setIsProfileFieldsTouched(false);
|
||||
|
||||
@ -12,6 +12,7 @@ import { selectChat } from '../../../modules/selectors';
|
||||
import { getUserFullName, isChatBasicGroup, isChatChannel } from '../../../modules/helpers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
@ -26,6 +27,8 @@ type OwnProps = {
|
||||
selectedChatMemberId?: number;
|
||||
isPromotedByCurrentUser?: boolean;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -49,6 +52,8 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isChannel,
|
||||
isFormFullyDisabled,
|
||||
updateChatAdmin,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const [permissions, setPermissions] = useState<ApiChatAdminRights>({});
|
||||
const [isTouched, setIsTouched] = useState(false);
|
||||
@ -57,6 +62,8 @@ const ManageGroupAdminRights: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const [customTitle, setCustomTitle] = useState('');
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const selectedChatMember = useMemo(() => {
|
||||
if (!chat.fullInfo || !chat.fullInfo.adminMembers) {
|
||||
return undefined;
|
||||
|
||||
@ -8,6 +8,7 @@ import { GlobalActions } from '../../../global/types';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
@ -15,6 +16,8 @@ import ListItem from '../../ui/ListItem';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -31,6 +34,8 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
usersById,
|
||||
isChannel,
|
||||
openUserInfo,
|
||||
onClose,
|
||||
isActive,
|
||||
serverTimeOffset,
|
||||
}) => {
|
||||
const memberIds = useMemo(() => {
|
||||
@ -45,6 +50,8 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
openUserInfo({ id });
|
||||
}, [openUserInfo]);
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
return (
|
||||
<div className="Management">
|
||||
<div className="custom-scroll">
|
||||
|
||||
@ -10,6 +10,7 @@ import { GlobalActions } from '../../../global/types';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
@ -21,6 +22,8 @@ type OwnProps = {
|
||||
chatId: number;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onChatMemberSelect: (memberId: number, isPromotedByCurrentUser?: boolean) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -61,12 +64,16 @@ const ManageGroupPermissions: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chat,
|
||||
currentUserId,
|
||||
updateChatDefaultBannedRights,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const [permissions, setPermissions] = useState<ApiChatBannedRights>({});
|
||||
const [havePermissionChanged, setHavePermissionChanged] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const handleRemovedUsersClick = useCallback(() => {
|
||||
onScreenSelect(ManagementScreens.GroupRemovedUsers);
|
||||
}, [onScreenSelect]);
|
||||
|
||||
@ -6,6 +6,7 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
||||
import { ApiChat, ApiChatMember } from '../../../api/types';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
@ -13,15 +14,19 @@ import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
};
|
||||
|
||||
const ManageGroupRecentActions: FC<OwnProps & StateProps> = ({ chat }) => {
|
||||
const ManageGroupRecentActions: FC<OwnProps & StateProps> = ({ chat, onClose, isActive }) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const adminMembers = useMemo(() => {
|
||||
if (!chat || !chat.fullInfo || !chat.fullInfo.adminMembers) {
|
||||
return [];
|
||||
|
||||
@ -10,12 +10,15 @@ import { selectChat } from '../../../modules/selectors';
|
||||
import { getUserFullName } from '../../../modules/helpers';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -29,9 +32,13 @@ const ManageGroupRemovedUsers: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chat,
|
||||
usersById,
|
||||
updateChatMemberBannedRights,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const removedMembers = useMemo(() => {
|
||||
if (!chat || !chat.fullInfo || !chat.fullInfo.kickedMembers) {
|
||||
return [];
|
||||
|
||||
@ -11,6 +11,7 @@ import { pick } from '../../../util/iteratees';
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
@ -24,6 +25,8 @@ type OwnProps = {
|
||||
selectedChatMemberId?: number;
|
||||
isPromotedByCurrentUser?: boolean;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -39,6 +42,8 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onScreenSelect,
|
||||
updateChatMemberBannedRights,
|
||||
isFormFullyDisabled,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const [permissions, setPermissions] = useState<ApiChatBannedRights>({});
|
||||
const [havePermissionChanged, setHavePermissionChanged] = useState(false);
|
||||
@ -46,6 +51,8 @@ const ManageGroupUserPermissions: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const [isBanConfirmationDialogOpen, openBanConfirmationDialog, closeBanConfirmationDialog] = useFlag();
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const selectedChatMember = useMemo(() => {
|
||||
if (!chat || !chat.fullInfo || !chat.fullInfo.members) {
|
||||
return undefined;
|
||||
|
||||
@ -8,6 +8,7 @@ import { ManagementScreens } from '../../../types';
|
||||
|
||||
import { selectChat } from '../../../modules/selectors';
|
||||
import { sortUserIds, isChatChannel } from '../../../modules/helpers';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
@ -17,6 +18,8 @@ type OwnProps = {
|
||||
chatId: number;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onChatMemberSelect: (memberId: number) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -32,8 +35,12 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
||||
isChannel,
|
||||
onScreenSelect,
|
||||
onChatMemberSelect,
|
||||
onClose,
|
||||
isActive,
|
||||
serverTimeOffset,
|
||||
}) => {
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const memberIds = useMemo(() => {
|
||||
if (!members || !usersById) {
|
||||
return undefined;
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
import { selectIsChatMuted } from '../../../modules/helpers';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
|
||||
import InputText from '../../ui/InputText';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
@ -28,6 +29,8 @@ import './Management.scss';
|
||||
|
||||
type OwnProps = {
|
||||
userId: number;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -54,12 +57,16 @@ const ManageUser: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
deleteHistory,
|
||||
closeManagement,
|
||||
openChat,
|
||||
onClose,
|
||||
isActive,
|
||||
}) => {
|
||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||
const [isProfileFieldsTouched, setIsProfileFieldsTouched] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const lang = useLang();
|
||||
|
||||
useHistoryBack(isActive, onClose);
|
||||
|
||||
const currentFirstName = user ? (user.firstName || '') : '';
|
||||
const currentLastName = user ? (user.lastName || '') : '';
|
||||
|
||||
|
||||
@ -26,6 +26,8 @@ export type OwnProps = {
|
||||
isPromotedByCurrentUser?: boolean;
|
||||
onScreenSelect: (screen: ManagementScreens) => void;
|
||||
onChatMemberSelect: (memberId: number, isPromotedByCurrentUser?: boolean) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -39,17 +41,59 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
isPromotedByCurrentUser,
|
||||
onScreenSelect,
|
||||
onChatMemberSelect,
|
||||
onClose,
|
||||
isActive,
|
||||
managementType,
|
||||
}) => {
|
||||
switch (currentScreen) {
|
||||
case ManagementScreens.Initial: {
|
||||
switch (managementType) {
|
||||
case 'user':
|
||||
return <ManageUser key={chatId} userId={chatId} />;
|
||||
return (
|
||||
<ManageUser
|
||||
key={chatId}
|
||||
userId={chatId}
|
||||
onClose={onClose}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
case 'group':
|
||||
return <ManageGroup key={chatId} chatId={chatId} onScreenSelect={onScreenSelect} />;
|
||||
return (
|
||||
<ManageGroup
|
||||
key={chatId}
|
||||
chatId={chatId}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onClose={onClose}
|
||||
isActive={isActive || [
|
||||
ManagementScreens.ChatPrivacyType,
|
||||
ManagementScreens.Discussion,
|
||||
ManagementScreens.GroupPermissions,
|
||||
ManagementScreens.ChatAdministrators,
|
||||
ManagementScreens.GroupRemovedUsers,
|
||||
ManagementScreens.GroupUserPermissionsCreate,
|
||||
ManagementScreens.GroupUserPermissions,
|
||||
ManagementScreens.ChatAdminRights,
|
||||
ManagementScreens.GroupRecentActions,
|
||||
].includes(currentScreen)}
|
||||
/>
|
||||
);
|
||||
case 'channel':
|
||||
return <ManageChannel key={chatId} chatId={chatId} onScreenSelect={onScreenSelect} />;
|
||||
return (
|
||||
<ManageChannel
|
||||
key={chatId}
|
||||
chatId={chatId}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onClose={onClose}
|
||||
isActive={isActive || [
|
||||
ManagementScreens.ChannelSubscribers,
|
||||
ManagementScreens.ChatAdministrators,
|
||||
ManagementScreens.Discussion,
|
||||
ManagementScreens.ChatPrivacyType,
|
||||
ManagementScreens.ChatAdminRights,
|
||||
ManagementScreens.GroupRecentActions,
|
||||
].includes(currentScreen)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -57,7 +101,11 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
|
||||
case ManagementScreens.ChatPrivacyType:
|
||||
return (
|
||||
<ManageChatPrivacyType chatId={chatId} />
|
||||
<ManageChatPrivacyType
|
||||
chatId={chatId}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.Discussion:
|
||||
@ -65,6 +113,8 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
<ManageDiscussion
|
||||
chatId={chatId}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -74,12 +124,22 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
chatId={chatId}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onChatMemberSelect={onChatMemberSelect}
|
||||
isActive={isActive || [
|
||||
ManagementScreens.GroupRemovedUsers,
|
||||
ManagementScreens.GroupUserPermissionsCreate,
|
||||
ManagementScreens.GroupUserPermissions,
|
||||
].includes(currentScreen)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.GroupRemovedUsers:
|
||||
return (
|
||||
<ManageGroupRemovedUsers chatId={chatId} />
|
||||
<ManageGroupRemovedUsers
|
||||
chatId={chatId}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.GroupUserPermissionsCreate:
|
||||
@ -88,6 +148,10 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
chatId={chatId}
|
||||
onChatMemberSelect={onChatMemberSelect}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive || [
|
||||
ManagementScreens.GroupUserPermissions,
|
||||
].includes(currentScreen)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -98,6 +162,8 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
selectedChatMemberId={selectedChatMemberId}
|
||||
isPromotedByCurrentUser={isPromotedByCurrentUser}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -107,6 +173,11 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
chatId={chatId}
|
||||
onScreenSelect={onScreenSelect}
|
||||
onChatMemberSelect={onChatMemberSelect}
|
||||
isActive={isActive || [
|
||||
ManagementScreens.ChatAdminRights,
|
||||
ManagementScreens.GroupRecentActions,
|
||||
].includes(currentScreen)}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -114,6 +185,8 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<ManageGroupRecentActions
|
||||
chatId={chatId}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -124,13 +197,19 @@ const Management: FC<OwnProps & StateProps> = ({
|
||||
selectedChatMemberId={selectedChatMemberId}
|
||||
isPromotedByCurrentUser={isPromotedByCurrentUser}
|
||||
onScreenSelect={onScreenSelect}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
|
||||
case ManagementScreens.ChannelSubscribers:
|
||||
case ManagementScreens.GroupMembers:
|
||||
return (
|
||||
<ManageGroupMembers chatId={chatId} />
|
||||
<ManageGroupMembers
|
||||
chatId={chatId}
|
||||
isActive={isActive}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,9 @@ type OwnProps = {
|
||||
positionX?: 'left' | 'right';
|
||||
positionY?: 'top' | 'bottom';
|
||||
footer?: string;
|
||||
forceOpen?: boolean;
|
||||
onOpen?: NoneToVoidFunction;
|
||||
onClose?: NoneToVoidFunction;
|
||||
children: any;
|
||||
};
|
||||
|
||||
@ -20,6 +23,9 @@ const DropdownMenu: FC<OwnProps> = ({
|
||||
positionX = 'left',
|
||||
positionY = 'top',
|
||||
footer,
|
||||
forceOpen,
|
||||
onOpen,
|
||||
onClose,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
@ -29,6 +35,9 @@ const DropdownMenu: FC<OwnProps> = ({
|
||||
|
||||
const toggleIsOpen = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (isOpen) {
|
||||
if (onClose) onClose();
|
||||
} else if (onOpen) onOpen();
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<any>) => {
|
||||
@ -48,6 +57,7 @@ const DropdownMenu: FC<OwnProps> = ({
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
if (onClose) onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
@ -61,13 +71,14 @@ const DropdownMenu: FC<OwnProps> = ({
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
containerRef={dropdownRef}
|
||||
isOpen={isOpen}
|
||||
isOpen={isOpen || !!forceOpen}
|
||||
className={className || ''}
|
||||
positionX={positionX}
|
||||
positionY={positionY}
|
||||
footer={footer}
|
||||
autoClose
|
||||
onClose={handleClose}
|
||||
shouldSkipTransition={forceOpen}
|
||||
>
|
||||
{children}
|
||||
</Menu>
|
||||
|
||||
@ -8,6 +8,7 @@ import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import './Menu.scss';
|
||||
|
||||
@ -20,6 +21,7 @@ type OwnProps = {
|
||||
positionX?: 'left' | 'right';
|
||||
positionY?: 'top' | 'bottom';
|
||||
autoClose?: boolean;
|
||||
shouldSkipTransition?: boolean;
|
||||
footer?: string;
|
||||
noCloseOnBackdrop?: boolean;
|
||||
onKeyDown?: (e: React.KeyboardEvent<any>) => void;
|
||||
@ -48,6 +50,7 @@ const Menu: FC<OwnProps> = ({
|
||||
onClose,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
shouldSkipTransition,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
let menuRef = useRef<HTMLDivElement>(null);
|
||||
@ -56,9 +59,22 @@ const Menu: FC<OwnProps> = ({
|
||||
}
|
||||
const backdropContainerRef = containerRef || menuRef;
|
||||
|
||||
const { transitionClassNames } = useShowTransition(isOpen, onCloseAnimationEnd);
|
||||
const {
|
||||
transitionClassNames,
|
||||
} = useShowTransition(
|
||||
isOpen,
|
||||
onCloseAnimationEnd,
|
||||
shouldSkipTransition,
|
||||
undefined,
|
||||
shouldSkipTransition,
|
||||
);
|
||||
|
||||
useEffect(() => (isOpen && onClose ? captureEscKeyListener(onClose) : undefined), [isOpen, onClose]);
|
||||
useEffect(
|
||||
() => (isOpen && onClose ? captureEscKeyListener(onClose) : undefined),
|
||||
[isOpen, onClose],
|
||||
);
|
||||
|
||||
useHistoryBack(isOpen, onClose, undefined, undefined, autoClose);
|
||||
|
||||
useEffectWithPrevDeps(([prevIsOpen]) => {
|
||||
if (prevIsOpen !== undefined) {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import React, { FC, useEffect, useRef } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
FC, useEffect, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||
import trapFocus from '../../util/trapFocus';
|
||||
@ -7,6 +9,7 @@ import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
|
||||
import Button from './Button';
|
||||
import Portal from './Portal';
|
||||
@ -28,20 +31,29 @@ type OwnProps = {
|
||||
onEnter?: () => void;
|
||||
};
|
||||
|
||||
const Modal: FC<OwnProps> = (props) => {
|
||||
type StateProps = {
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
};
|
||||
|
||||
const Modal: FC<OwnProps & StateProps> = ({
|
||||
title,
|
||||
className,
|
||||
isOpen,
|
||||
header,
|
||||
hasCloseButton,
|
||||
noBackdrop,
|
||||
children,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
onEnter,
|
||||
shouldSkipHistoryAnimations,
|
||||
}) => {
|
||||
const {
|
||||
title,
|
||||
className,
|
||||
isOpen,
|
||||
header,
|
||||
hasCloseButton,
|
||||
noBackdrop,
|
||||
children,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
onEnter,
|
||||
} = props;
|
||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, onCloseAnimationEnd);
|
||||
shouldRender,
|
||||
transitionClassNames,
|
||||
} = useShowTransition(
|
||||
isOpen, onCloseAnimationEnd, shouldSkipHistoryAnimations, undefined, shouldSkipHistoryAnimations,
|
||||
);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -50,6 +62,8 @@ const Modal: FC<OwnProps> = (props) => {
|
||||
: undefined), [isOpen, onClose, onEnter]);
|
||||
useEffect(() => (isOpen && modalRef.current ? trapFocus(modalRef.current) : undefined), [isOpen]);
|
||||
|
||||
useHistoryBack(isOpen, onClose);
|
||||
|
||||
useEffectWithPrevDeps(([prevIsOpen]) => {
|
||||
document.body.classList.toggle('has-open-dialog', isOpen);
|
||||
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.skip-slide-transition {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* scroll-slide
|
||||
*/
|
||||
|
||||
@ -13,7 +13,7 @@ import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'
|
||||
|
||||
import './Transition.scss';
|
||||
|
||||
type ChildrenFn = (isActive: boolean, isFrom: boolean) => any;
|
||||
type ChildrenFn = (isActive: boolean, isFrom: boolean, currentKey: number) => any;
|
||||
type OwnProps = {
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
activeKey: number;
|
||||
@ -239,7 +239,9 @@ const Transition: FC<OwnProps> = ({
|
||||
const render = renders[key];
|
||||
|
||||
return (
|
||||
typeof render === 'function' ? <div key={key}>{render(key === activeKey, key === prevActiveKey)}</div> : undefined
|
||||
typeof render === 'function'
|
||||
? <div key={key}>{render(key === activeKey, key === prevActiveKey, activeKey)}</div>
|
||||
: undefined
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -3,16 +3,10 @@ import { addReducer } from '../lib/teact/teactn';
|
||||
import { INITIAL_STATE } from './initial';
|
||||
import { initCache, loadCache } from './cache';
|
||||
import { cloneDeep } from '../util/iteratees';
|
||||
import { selectCurrentMessageList } from '../modules/selectors';
|
||||
|
||||
initCache();
|
||||
|
||||
addReducer('init', () => {
|
||||
const initial = cloneDeep(INITIAL_STATE);
|
||||
const newGlobal = loadCache(initial) || initial;
|
||||
|
||||
const currentMessageList = selectCurrentMessageList(newGlobal) || {};
|
||||
window.history.replaceState(currentMessageList, '');
|
||||
|
||||
return newGlobal;
|
||||
return loadCache(initial) || initial;
|
||||
});
|
||||
|
||||
@ -65,6 +65,7 @@ export type GlobalState = {
|
||||
isLeftColumnShown: boolean;
|
||||
isPollModalOpen?: boolean;
|
||||
uiReadyState: 0 | 1 | 2;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
connectionState?: ApiUpdateConnectionStateType;
|
||||
currentUserId?: number;
|
||||
lastSyncTime?: number;
|
||||
@ -403,7 +404,8 @@ export type ActionTypes = (
|
||||
'showNotification' | 'dismissNotification' | 'showDialog' | 'dismissDialog' |
|
||||
// ui
|
||||
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
|
||||
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' |
|
||||
'toggleSafeLinkModal' | 'disableHistoryAnimations' | 'openHistoryCalendar' | 'closeHistoryCalendar' |
|
||||
'disableContextMenuHint' |
|
||||
// auth
|
||||
'setAuthPhoneNumber' | 'setAuthCode' | 'setAuthPassword' | 'signUp' | 'returnToAuthPhoneNumber' | 'signOut' |
|
||||
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' |
|
||||
|
||||
@ -1,15 +1,137 @@
|
||||
// This is unsafe and can be not chained as `popstate` event is asynchronous
|
||||
import { useEffect, useRef } from '../lib/teact/teact';
|
||||
|
||||
export default function useHistoryBack(handler: NoneToVoidFunction) {
|
||||
function handlePopState() {
|
||||
handler();
|
||||
import { IS_IOS } from '../util/environment';
|
||||
import usePrevious from './usePrevious';
|
||||
import { getDispatch } from '../lib/teact/teactn';
|
||||
|
||||
// Carefully selected by swiping and observing visual changes
|
||||
// TODO: may be different on other devices such as iPad, maybe take dpi into account?
|
||||
const SAFARI_EDGE_BACK_GESTURE_LIMIT = 300;
|
||||
const SAFARI_EDGE_BACK_GESTURE_DURATION = 350;
|
||||
|
||||
let isEdge = false;
|
||||
|
||||
const handleTouchStart = (event: TouchEvent) => {
|
||||
const x = event.touches[0].pageX;
|
||||
|
||||
if (x <= SAFARI_EDGE_BACK_GESTURE_LIMIT || x >= window.innerWidth - SAFARI_EDGE_BACK_GESTURE_LIMIT) {
|
||||
isEdge = true;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
window.history.pushState({}, '');
|
||||
const handleTouchEnd = () => {
|
||||
if (isEdge) {
|
||||
setTimeout(() => {
|
||||
isEdge = false;
|
||||
}, SAFARI_EDGE_BACK_GESTURE_DURATION);
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('popstate', handlePopState);
|
||||
window.history.back();
|
||||
};
|
||||
if (IS_IOS) {
|
||||
window.addEventListener('touchstart', handleTouchStart);
|
||||
window.addEventListener('touchend', handleTouchEnd);
|
||||
window.addEventListener('popstate', handleTouchEnd);
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
let nextStateIndexToReplace = -1;
|
||||
let isHistoryAltered = false;
|
||||
const currentIndexes: number[] = [];
|
||||
|
||||
window.history.replaceState({ index: currentIndex }, '');
|
||||
|
||||
export default function useHistoryBack(
|
||||
isActive: boolean | undefined,
|
||||
onBack: ((noDisableAnimation: boolean) => void) | undefined,
|
||||
onForward?: (state: any) => void,
|
||||
currentState?: any,
|
||||
shouldReplaceNext = false,
|
||||
) {
|
||||
const indexRef = useRef(-1);
|
||||
const isForward = useRef(false);
|
||||
const prevIsActive = usePrevious(isActive);
|
||||
const isClosed = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = (event: PopStateEvent) => {
|
||||
if (isHistoryAltered) {
|
||||
setTimeout(() => {
|
||||
isHistoryAltered = false;
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
const { index: i } = event.state;
|
||||
const index = i || 0;
|
||||
|
||||
const prev = currentIndexes[currentIndexes.indexOf(indexRef.current) - 1];
|
||||
|
||||
if (!isClosed.current && (index === 0 || index === prev)) {
|
||||
currentIndexes.splice(currentIndexes.indexOf(indexRef.current), 1);
|
||||
|
||||
if (onBack) {
|
||||
if (isEdge) {
|
||||
getDispatch().disableHistoryAnimations();
|
||||
}
|
||||
onBack(!isEdge);
|
||||
isClosed.current = true;
|
||||
}
|
||||
} else if (index === indexRef.current && isClosed.current && onForward) {
|
||||
isForward.current = true;
|
||||
if (isEdge) {
|
||||
getDispatch().disableHistoryAnimations();
|
||||
}
|
||||
onForward(event.state.state);
|
||||
}
|
||||
};
|
||||
|
||||
if (prevIsActive !== isActive) {
|
||||
if (isActive) {
|
||||
isClosed.current = false;
|
||||
|
||||
if (isForward.current) {
|
||||
isForward.current = false;
|
||||
currentIndexes.push(indexRef.current);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
const index = ++currentIndex;
|
||||
|
||||
currentIndexes.push(index);
|
||||
|
||||
window.history[
|
||||
(currentIndexes.includes(nextStateIndexToReplace - 1)
|
||||
&& window.history.state.index !== 0
|
||||
&& nextStateIndexToReplace === index
|
||||
&& !shouldReplaceNext)
|
||||
? 'replaceState'
|
||||
: 'pushState'
|
||||
]({
|
||||
index,
|
||||
state: currentState,
|
||||
}, '');
|
||||
|
||||
indexRef.current = index;
|
||||
|
||||
if (shouldReplaceNext) {
|
||||
nextStateIndexToReplace = currentIndex + 1;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} else if (!isClosed.current) {
|
||||
if (indexRef.current === currentIndex || !shouldReplaceNext) {
|
||||
isHistoryAltered = true;
|
||||
window.history.back();
|
||||
|
||||
setTimeout(() => {
|
||||
nextStateIndexToReplace = -1;
|
||||
}, 400);
|
||||
}
|
||||
currentIndexes.splice(currentIndexes.indexOf(indexRef.current), 1);
|
||||
|
||||
isClosed.current = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, [currentState, isActive, onBack, onForward, prevIsActive, shouldReplaceNext]);
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ export default (
|
||||
// СSS class should be added in a separate tick to turn on CSS transition.
|
||||
const [hasOpenClassName, setHasOpenClassName] = useState(isOpen && noOpenTransition);
|
||||
|
||||
|
||||
if (isOpen) {
|
||||
setIsClosed(false);
|
||||
setHasOpenClassName(true);
|
||||
@ -39,12 +40,14 @@ export default (
|
||||
}
|
||||
}
|
||||
|
||||
// `noCloseTransition`, when set to true, should remove the open class immediately
|
||||
const shouldHaveOpenClassName = hasOpenClassName && !(noCloseTransition && !isOpen);
|
||||
const isClosing = Boolean(closeTimeoutRef.current);
|
||||
const shouldRender = isOpen || isClosing;
|
||||
const transitionClassNames = buildClassName(
|
||||
className && 'opacity-transition',
|
||||
className,
|
||||
hasOpenClassName && 'open',
|
||||
shouldHaveOpenClassName && 'open',
|
||||
shouldRender && 'shown',
|
||||
isClosing && 'closing',
|
||||
);
|
||||
|
||||
@ -8,26 +8,28 @@ const delegationRegistry: Record<string, Map<HTMLElement, Handler>> = {};
|
||||
const delegatedEventsByElement = new Map<HTMLElement, Set<string>>();
|
||||
const documentEventCounters: Record<string, number> = {};
|
||||
|
||||
export function addEventListener(element: HTMLElement, propName: string, handler: Handler) {
|
||||
export function addEventListener(element: HTMLElement, propName: string, handler: Handler, asCapture = false) {
|
||||
const eventName = resolveEventName(propName, element);
|
||||
if (canUseEventDelegation(eventName, element)) {
|
||||
if (canUseEventDelegation(eventName, element, asCapture)) {
|
||||
addDelegatedListener(eventName, element, handler);
|
||||
} else {
|
||||
element.addEventListener(eventName, handler);
|
||||
element.addEventListener(eventName, handler, asCapture);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeEventListener(element: HTMLElement, propName: string, handler: Handler) {
|
||||
export function removeEventListener(element: HTMLElement, propName: string, handler: Handler, asCapture = false) {
|
||||
const eventName = resolveEventName(propName, element);
|
||||
if (canUseEventDelegation(eventName, element)) {
|
||||
if (canUseEventDelegation(eventName, element, asCapture)) {
|
||||
removeDelegatedListener(eventName, element);
|
||||
} else {
|
||||
element.removeEventListener(eventName, handler);
|
||||
element.removeEventListener(eventName, handler, asCapture);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveEventName(propName: string, element: HTMLElement) {
|
||||
const eventName = propName.replace(/^on/, '').toLowerCase();
|
||||
const eventName = propName
|
||||
.replace(/^on/, '')
|
||||
.replace(/Capture$/, '').toLowerCase();
|
||||
|
||||
if (eventName === 'change' && element.tagName !== 'SELECT') {
|
||||
// React behavior repeated here.
|
||||
@ -51,9 +53,10 @@ function resolveEventName(propName: string, element: HTMLElement) {
|
||||
return eventName;
|
||||
}
|
||||
|
||||
function canUseEventDelegation(realEventName: string, element: HTMLElement) {
|
||||
function canUseEventDelegation(realEventName: string, element: HTMLElement, asCapture: boolean) {
|
||||
return (
|
||||
!NON_BUBBLEABLE_EVENTS.has(realEventName)
|
||||
!asCapture
|
||||
&& !NON_BUBBLEABLE_EVENTS.has(realEventName)
|
||||
&& element.tagName !== 'VIDEO'
|
||||
&& element.tagName !== 'IFRAME'
|
||||
);
|
||||
|
||||
@ -428,7 +428,7 @@ function addAttribute(element: HTMLElement, key: string, value: any) {
|
||||
} else if (key === 'style') {
|
||||
element.style.cssText = value;
|
||||
} else if (key.startsWith('on')) {
|
||||
addEventListener(element, key, value);
|
||||
addEventListener(element, key, value, key.endsWith('Capture'));
|
||||
} else if (key.startsWith('data-') || HTML_ATTRIBUTES.has(key)) {
|
||||
element.setAttribute(key, value);
|
||||
} else if (!FILTERED_ATTRIBUTES.has(key)) {
|
||||
@ -444,7 +444,7 @@ function removeAttribute(element: HTMLElement, key: string, value: any) {
|
||||
} else if (key === 'style') {
|
||||
element.style.cssText = '';
|
||||
} else if (key.startsWith('on')) {
|
||||
removeEventListener(element, key, value);
|
||||
removeEventListener(element, key, value, key.endsWith('Capture'));
|
||||
} else if (key.startsWith('data-') || HTML_ATTRIBUTES.has(key)) {
|
||||
element.removeAttribute(key);
|
||||
} else if (!FILTERED_ATTRIBUTES.has(key)) {
|
||||
|
||||
@ -46,10 +46,15 @@ function runCallbacks() {
|
||||
|
||||
const runCallbacksThrottled = throttleWithRaf(runCallbacks);
|
||||
|
||||
export function setGlobal(newGlobal?: GlobalState) {
|
||||
// `noThrottle = true` is used as a workaround for iOS gesture history navigation
|
||||
export function setGlobal(newGlobal?: GlobalState, noThrottle = false) {
|
||||
if (typeof newGlobal === 'object' && newGlobal !== currentGlobal) {
|
||||
currentGlobal = newGlobal;
|
||||
runCallbacksThrottled();
|
||||
if (!noThrottle) {
|
||||
runCallbacksThrottled();
|
||||
} else {
|
||||
runCallbacks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,12 +66,12 @@ export function getDispatch() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
function onDispatch(name: string, payload?: ActionPayload) {
|
||||
function onDispatch(name: string, payload?: ActionPayload, noThrottle?: boolean) {
|
||||
if (reducers[name]) {
|
||||
reducers[name].forEach((reducer) => {
|
||||
const newGlobal = reducer(currentGlobal, actions, payload);
|
||||
if (newGlobal) {
|
||||
setGlobal(newGlobal);
|
||||
setGlobal(newGlobal, noThrottle);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -139,8 +144,8 @@ export function addReducer(name: ActionTypes, reducer: Reducer) {
|
||||
if (!reducers[name]) {
|
||||
reducers[name] = [];
|
||||
|
||||
actions[name] = (payload?: ActionPayload) => {
|
||||
onDispatch(name, payload);
|
||||
actions[name] = (payload?: ActionPayload, noThrottle = false) => {
|
||||
onDispatch(name, payload, noThrottle);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,14 @@
|
||||
import { addReducer, getDispatch, setGlobal } from '../../../lib/teact/teactn';
|
||||
import { addReducer, setGlobal } from '../../../lib/teact/teactn';
|
||||
import {
|
||||
exitMessageSelectMode,
|
||||
updateCurrentMessageList,
|
||||
} from '../../reducers';
|
||||
import { selectCurrentMessageList } from '../../selectors';
|
||||
|
||||
window.addEventListener('popstate', (e) => {
|
||||
if (!e.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { chatId: id, threadId, messageListType: type } = e.state;
|
||||
|
||||
getDispatch().openChat({
|
||||
id, threadId, type, noPushState: true,
|
||||
});
|
||||
});
|
||||
import { closeLocalTextSearch } from './localSearch';
|
||||
|
||||
addReducer('openChat', (global, actions, payload) => {
|
||||
const {
|
||||
id, threadId = -1, type = 'thread', noPushState,
|
||||
id, threadId = -1, type = 'thread',
|
||||
} = payload!;
|
||||
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
@ -31,6 +20,7 @@ addReducer('openChat', (global, actions, payload) => {
|
||||
|| currentMessageList.type !== type
|
||||
)) {
|
||||
global = exitMessageSelectMode(global);
|
||||
global = closeLocalTextSearch(global);
|
||||
|
||||
global = {
|
||||
...global,
|
||||
@ -44,10 +34,6 @@ addReducer('openChat', (global, actions, payload) => {
|
||||
};
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
if (!noPushState) {
|
||||
window.history.pushState({ chatId: id, threadId, messageListType: type }, '');
|
||||
}
|
||||
}
|
||||
|
||||
return updateCurrentMessageList(global, id, threadId, type);
|
||||
|
||||
@ -8,6 +8,8 @@ import { setLanguage } from '../../../util/langProvider';
|
||||
import switchTheme from '../../../util/switchTheme';
|
||||
import { selectTheme } from '../../selectors';
|
||||
|
||||
const HISTORY_ANIMATION_DURATION = 450;
|
||||
|
||||
subscribeToSystemThemeChange();
|
||||
|
||||
addReducer('init', (global) => {
|
||||
@ -68,6 +70,21 @@ addReducer('clearAuthError', (global) => {
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('disableHistoryAnimations', () => {
|
||||
setTimeout(() => {
|
||||
setGlobal({
|
||||
...getGlobal(),
|
||||
shouldSkipHistoryAnimations: false,
|
||||
});
|
||||
document.body.classList.remove('no-animate');
|
||||
}, HISTORY_ANIMATION_DURATION);
|
||||
|
||||
setGlobal({
|
||||
...getGlobal(),
|
||||
shouldSkipHistoryAnimations: true,
|
||||
}, true);
|
||||
});
|
||||
|
||||
function subscribeToSystemThemeChange() {
|
||||
function handleSystemThemeChange() {
|
||||
const currentThemeMatch = document.documentElement.className.match(/theme-(\w+)/);
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { selectCurrentMessageList } from '../../selectors';
|
||||
import { buildChatThreadKey } from '../../helpers';
|
||||
import { GlobalState } from '../../../global/types';
|
||||
|
||||
addReducer('openLocalTextSearch', (global) => {
|
||||
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
||||
@ -18,16 +19,7 @@ addReducer('openLocalTextSearch', (global) => {
|
||||
return updateLocalTextSearch(global, chatId, threadId, true);
|
||||
});
|
||||
|
||||
addReducer('closeLocalTextSearch', (global) => {
|
||||
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
||||
if (!chatId || !threadId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
global = updateLocalTextSearch(global, chatId, threadId, false);
|
||||
global = replaceLocalTextSearchResults(global, chatId, threadId, undefined);
|
||||
return global;
|
||||
});
|
||||
addReducer('closeLocalTextSearch', closeLocalTextSearch);
|
||||
|
||||
addReducer('setLocalTextSearchQuery', (global, actions, payload) => {
|
||||
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
||||
@ -57,3 +49,14 @@ addReducer('setLocalMediaSearchType', (global, actions, payload) => {
|
||||
const { mediaType } = payload!;
|
||||
return updateLocalMediaSearchType(global, chatId, mediaType);
|
||||
});
|
||||
|
||||
export function closeLocalTextSearch(global: GlobalState): GlobalState {
|
||||
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
||||
if (!chatId || !threadId) {
|
||||
return global;
|
||||
}
|
||||
|
||||
global = updateLocalTextSearch(global, chatId, threadId, false);
|
||||
global = replaceLocalTextSearchResults(global, chatId, threadId, undefined);
|
||||
return global;
|
||||
}
|
||||
|
||||
@ -65,6 +65,7 @@
|
||||
|
||||
// Used by ChatList and ContactList components
|
||||
.chat-list {
|
||||
background: var(--color-background);
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: .5rem .125rem .5rem .4375rem;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user