ChatList: Display unconfirmed sessions (#3887)
This commit is contained in:
parent
a21019c340
commit
0d843112fa
@ -44,6 +44,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
default_emoji_statuses_stickerset_id: string;
|
||||
hidden_members_group_size_min: number;
|
||||
autoarchive_setting_available: boolean;
|
||||
authorization_autoconfirm_period: number;
|
||||
// Forums
|
||||
topics_pinned_limit: number;
|
||||
// Stories
|
||||
|
||||
@ -43,6 +43,7 @@ export function buildApiSession(session: GramJs.Authorization): ApiSession {
|
||||
hash: String(session.hash),
|
||||
areCallsEnabled: !session.callRequestsDisabled,
|
||||
areSecretChatsEnabled: !session.encryptedRequestsDisabled,
|
||||
isUnconfirmed: session.unconfirmed,
|
||||
...pick(session, [
|
||||
'deviceModel', 'platform', 'systemVersion', 'appName', 'appVersion', 'dateCreated', 'dateActive',
|
||||
'ip', 'country', 'region',
|
||||
|
||||
@ -46,14 +46,15 @@ export async function reportProfilePhoto({
|
||||
}
|
||||
|
||||
export async function changeSessionSettings({
|
||||
hash, areCallsEnabled, areSecretChatsEnabled,
|
||||
hash, areCallsEnabled, areSecretChatsEnabled, isConfirmed,
|
||||
}: {
|
||||
hash: string; areCallsEnabled?: boolean; areSecretChatsEnabled?: boolean;
|
||||
hash: string; areCallsEnabled?: boolean; areSecretChatsEnabled?: boolean; isConfirmed?: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.account.ChangeAuthorizationSettings({
|
||||
hash: BigInt(hash),
|
||||
...(areCallsEnabled !== undefined ? { callRequestsDisabled: !areCallsEnabled } : undefined),
|
||||
...(areSecretChatsEnabled !== undefined ? { encryptedRequestsDisabled: !areSecretChatsEnabled } : undefined),
|
||||
...(isConfirmed && { confirmed: isConfirmed }),
|
||||
}));
|
||||
|
||||
return result;
|
||||
|
||||
@ -1108,6 +1108,15 @@ export function updater(update: Update) {
|
||||
onUpdate({
|
||||
'@type': 'updateAttachMenuBots',
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateNewAuthorization) {
|
||||
onUpdate({
|
||||
'@type': 'updateNewAuthorization',
|
||||
hash: update.hash.toString(),
|
||||
date: update.date,
|
||||
device: update.device,
|
||||
location: update.location,
|
||||
isUnconfirmed: update.unconfirmed,
|
||||
});
|
||||
} else if (DEBUG) {
|
||||
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
||||
log('UNEXPECTED UPDATE', params);
|
||||
|
||||
@ -76,6 +76,7 @@ export interface ApiSession {
|
||||
region: string;
|
||||
areCallsEnabled: boolean;
|
||||
areSecretChatsEnabled: boolean;
|
||||
isUnconfirmed?: true;
|
||||
}
|
||||
|
||||
export interface ApiWebSession {
|
||||
|
||||
@ -665,6 +665,15 @@ export type ApiUpdateAttachMenuBots = {
|
||||
'@type': 'updateAttachMenuBots';
|
||||
};
|
||||
|
||||
export type ApiUpdateNewAuthorization = {
|
||||
'@type': 'updateNewAuthorization';
|
||||
hash: string;
|
||||
isUnconfirmed?: true;
|
||||
date?: number;
|
||||
device?: string;
|
||||
location?: string;
|
||||
};
|
||||
|
||||
export type ApiUpdate = (
|
||||
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
|
||||
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
||||
@ -693,7 +702,7 @@ export type ApiUpdate = (
|
||||
ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses |
|
||||
ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction |
|
||||
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
|
||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots
|
||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization
|
||||
);
|
||||
|
||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiChatFolder, ApiChatlistExportedInvite } from '../../../api/types';
|
||||
import type { ApiChatFolder, ApiChatlistExportedInvite, ApiSession } from '../../../api/types';
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||
import type { LeftColumnContent, SettingsScreens } from '../../../types';
|
||||
@ -53,6 +53,7 @@ type StateProps = {
|
||||
hasArchivedStories?: boolean;
|
||||
archiveSettings: GlobalState['archiveSettings'];
|
||||
isStoryRibbonShown?: boolean;
|
||||
sessions?: Record<string, ApiSession>;
|
||||
};
|
||||
|
||||
const SAVED_MESSAGES_HOTKEY = '0';
|
||||
@ -77,6 +78,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
hasArchivedStories,
|
||||
archiveSettings,
|
||||
isStoryRibbonShown,
|
||||
sessions,
|
||||
}) => {
|
||||
const {
|
||||
loadChatFolders,
|
||||
@ -299,6 +301,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
onLeftColumnContentChange={onLeftColumnContentChange}
|
||||
canDisplayArchive={(hasArchivedChats || hasArchivedStories) && !archiveSettings.isHidden}
|
||||
archiveSettings={archiveSettings}
|
||||
sessions={sessions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -356,6 +359,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
archived: archivedStories,
|
||||
},
|
||||
},
|
||||
activeSessions: {
|
||||
byHash: sessions,
|
||||
},
|
||||
currentUserId,
|
||||
archiveSettings,
|
||||
} = global;
|
||||
@ -376,6 +382,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
maxChatLists: selectCurrentLimit(global, 'chatlistJoined'),
|
||||
archiveSettings,
|
||||
isStoryRibbonShown,
|
||||
sessions,
|
||||
};
|
||||
},
|
||||
)(ChatFolders));
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useRef,
|
||||
memo, useEffect, useMemo, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiSession } from '../../../api/types';
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||
import type { SettingsScreens } from '../../../types';
|
||||
@ -15,9 +16,11 @@ import {
|
||||
ARCHIVED_FOLDER_ID,
|
||||
CHAT_HEIGHT_PX,
|
||||
CHAT_LIST_SLICE,
|
||||
FRESH_AUTH_PERIOD,
|
||||
} from '../../../config';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { IS_APP, IS_MAC_OS } from '../../../util/windowEnvironment';
|
||||
|
||||
import useUserStoriesPolling from '../../../hooks/polling/useUserStoriesPolling';
|
||||
@ -35,6 +38,7 @@ import Loading from '../../ui/Loading';
|
||||
import Archive from './Archive';
|
||||
import Chat from './Chat';
|
||||
import EmptyFolder from './EmptyFolder';
|
||||
import UnconfirmedSession from './UnconfirmedSession';
|
||||
|
||||
type OwnProps = {
|
||||
folderType: 'all' | 'archived' | 'folder';
|
||||
@ -43,7 +47,7 @@ type OwnProps = {
|
||||
canDisplayArchive?: boolean;
|
||||
archiveSettings: GlobalState['archiveSettings'];
|
||||
isForumPanelOpen?: boolean;
|
||||
className?: string;
|
||||
sessions?: Record<string, ApiSession>;
|
||||
foldersDispatch: FolderEditDispatch;
|
||||
onSettingsScreenSelect: (screen: SettingsScreens) => void;
|
||||
onLeftColumnContentChange: (content: LeftColumnContent) => void;
|
||||
@ -60,6 +64,7 @@ const ChatList: FC<OwnProps> = ({
|
||||
isForumPanelOpen,
|
||||
canDisplayArchive,
|
||||
archiveSettings,
|
||||
sessions,
|
||||
foldersDispatch,
|
||||
onSettingsScreenSelect,
|
||||
onLeftColumnContentChange,
|
||||
@ -73,13 +78,15 @@ const ChatList: FC<OwnProps> = ({
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const shouldIgnoreDragRef = useRef(false);
|
||||
const [unconfirmedSessionHeight, setUnconfirmedSessionHeight] = useState(0);
|
||||
|
||||
const isArchived = folderType === 'archived';
|
||||
const isAllFolder = folderType === 'all';
|
||||
const resolvedFolderId = (
|
||||
folderType === 'all' ? ALL_FOLDER_ID : isArchived ? ARCHIVED_FOLDER_ID : folderId!
|
||||
isAllFolder ? ALL_FOLDER_ID : isArchived ? ARCHIVED_FOLDER_ID : folderId!
|
||||
);
|
||||
|
||||
const shouldDisplayArchive = folderType === 'all' && canDisplayArchive;
|
||||
const shouldDisplayArchive = isAllFolder && canDisplayArchive;
|
||||
|
||||
const orderedIds = useFolderManagerForOrderedIds(resolvedFolderId);
|
||||
useUserStoriesPolling(orderedIds);
|
||||
@ -92,6 +99,18 @@ const ChatList: FC<OwnProps> = ({
|
||||
|
||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
|
||||
|
||||
const shouldShowUnconfirmedSessions = useMemo(() => {
|
||||
const sessionsArray = Object.values(sessions || {});
|
||||
const current = sessionsArray.find((session) => session.isCurrent);
|
||||
if (!current || getServerTime() - current.dateCreated < FRESH_AUTH_PERIOD) return false;
|
||||
|
||||
return isAllFolder && sessionsArray.some((session) => session.isUnconfirmed);
|
||||
}, [isAllFolder, sessions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldShowUnconfirmedSessions) setUnconfirmedSessionHeight(0);
|
||||
}, [shouldShowUnconfirmedSessions]);
|
||||
|
||||
// Support <Alt>+<Up/Down> to navigate between chats
|
||||
useHotkeys(isActive && orderedIds?.length ? {
|
||||
'Alt+ArrowUp': (e: KeyboardEvent) => {
|
||||
@ -189,7 +208,7 @@ const ChatList: FC<OwnProps> = ({
|
||||
|
||||
return viewportIds!.map((id, i) => {
|
||||
const isPinned = viewportOffset + i < pinnedCount;
|
||||
const offsetTop = archiveHeight + (viewportOffset + i) * CHAT_HEIGHT_PX;
|
||||
const offsetTop = unconfirmedSessionHeight + archiveHeight + (viewportOffset + i) * CHAT_HEIGHT_PX;
|
||||
|
||||
return (
|
||||
<Chat
|
||||
@ -217,10 +236,17 @@ const ChatList: FC<OwnProps> = ({
|
||||
preloadBackwards={CHAT_LIST_SLICE}
|
||||
withAbsolutePositioning
|
||||
beforeChildren={renderedOverflowTrigger}
|
||||
maxHeight={chatsHeight + archiveHeight}
|
||||
maxHeight={chatsHeight + archiveHeight + unconfirmedSessionHeight}
|
||||
onLoadMore={getMore}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
{shouldShowUnconfirmedSessions && (
|
||||
<UnconfirmedSession
|
||||
key="unconfirmed"
|
||||
sessions={sessions!}
|
||||
onHeightChange={setUnconfirmedSessionHeight}
|
||||
/>
|
||||
)}
|
||||
{shouldDisplayArchive && (
|
||||
<Archive
|
||||
key="archive"
|
||||
|
||||
32
src/components/left/main/UnconfirmedSession.module.scss
Normal file
32
src/components/left/main/UnconfirmedSession.module.scss
Normal file
@ -0,0 +1,32 @@
|
||||
/* stylelint-disable-next-line */
|
||||
@value minimized from "./Archive.module.scss";
|
||||
|
||||
.root {
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
|
||||
& + :global(.minimized) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
72
src/components/left/main/UnconfirmedSession.tsx
Normal file
72
src/components/left/main/UnconfirmedSession.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiSession } from '../../../api/types';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
|
||||
import styles from './UnconfirmedSession.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
sessions: Record<string, ApiSession>;
|
||||
onHeightChange: (height: number) => void;
|
||||
};
|
||||
|
||||
const UnconfirmedSession = ({ sessions, onHeightChange } : OwnProps) => {
|
||||
const { changeSessionSettings, terminateAuthorization, showNotification } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const lang = useLang();
|
||||
|
||||
useResizeObserver(ref, (entry) => {
|
||||
const height = entry.borderBoxSize?.[0]?.blockSize || entry.contentRect.height;
|
||||
onHeightChange(height);
|
||||
});
|
||||
|
||||
const firstUnconfirmed = useMemo(() => {
|
||||
return Object.values(sessions).sort((a, b) => b.dateCreated - a.dateCreated)
|
||||
.find((session) => session.isUnconfirmed)!;
|
||||
}, [sessions]);
|
||||
|
||||
const locationString = useMemo(() => {
|
||||
return [firstUnconfirmed.deviceModel, firstUnconfirmed.region, firstUnconfirmed.country].filter(Boolean).join(', ');
|
||||
}, [firstUnconfirmed]);
|
||||
|
||||
const handleAccept = useLastCallback(() => {
|
||||
changeSessionSettings({
|
||||
hash: firstUnconfirmed.hash,
|
||||
isConfirmed: true,
|
||||
});
|
||||
});
|
||||
|
||||
const handleReject = useLastCallback(() => {
|
||||
terminateAuthorization({ hash: firstUnconfirmed.hash });
|
||||
showNotification({
|
||||
title: lang('UnconfirmedAuthDeniedTitle', 1),
|
||||
message: lang('UnconfirmedAuthDeniedMessageSingle', locationString),
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.root} ref={ref}>
|
||||
<h2 className={styles.title}>{lang('UnconfirmedAuthTitle')}</h2>
|
||||
<p className={styles.info}>
|
||||
{lang('UnconfirmedAuthSingle', locationString)}
|
||||
</p>
|
||||
<div className={styles.buttons}>
|
||||
<Button fluid isText size="smaller" className={styles.button} onClick={handleAccept}>
|
||||
{lang('UnconfirmedAuthConfirm')}
|
||||
</Button>
|
||||
<Button fluid isText size="smaller" color="danger" onClick={handleReject} className={styles.button}>
|
||||
{lang('UnconfirmedAuthDeny')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(UnconfirmedSession);
|
||||
@ -40,7 +40,6 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const {
|
||||
loadProfilePhotos,
|
||||
loadAuthorizations,
|
||||
openPremiumModal,
|
||||
openSupportChat,
|
||||
openUrl,
|
||||
@ -61,10 +60,6 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
onBack: onReset,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadAuthorizations();
|
||||
}, []);
|
||||
|
||||
const handleOpenSupport = useLastCallback(() => {
|
||||
openSupportChat();
|
||||
closeSupportDialog();
|
||||
|
||||
@ -68,7 +68,6 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
loadPrivacySettings,
|
||||
loadBlockedUsers,
|
||||
loadAuthorizations,
|
||||
loadContentSettings,
|
||||
updateContentSettings,
|
||||
loadGlobalPrivacySettings,
|
||||
@ -80,7 +79,6 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
loadBlockedUsers();
|
||||
loadAuthorizations();
|
||||
loadPrivacySettings();
|
||||
loadContentSettings();
|
||||
loadWebAuthorizations();
|
||||
|
||||
@ -254,6 +254,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
loadFeaturedEmojiStickers,
|
||||
setIsElectronUpdateAvailable,
|
||||
loadPremiumSetStickers,
|
||||
loadAuthorizations,
|
||||
} = getActions();
|
||||
|
||||
if (DEBUG && !DEBUG_isLogged) {
|
||||
@ -327,6 +328,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
loadTopReactions();
|
||||
loadRecentReactions();
|
||||
loadFeaturedEmojiStickers();
|
||||
loadAuthorizations();
|
||||
}
|
||||
}, [isMasterTab, isSynced]);
|
||||
|
||||
|
||||
@ -51,7 +51,7 @@ export const CUSTOM_EMOJI_PREVIEW_CACHE_DISABLED = false;
|
||||
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v23';
|
||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v24';
|
||||
export const ASSET_CACHE_NAME = 'tt-assets';
|
||||
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
||||
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
||||
@ -301,6 +301,7 @@ export const MINI_APP_TOS_URL = 'https://telegram.org/tos/mini-apps';
|
||||
export const GENERAL_TOPIC_ID = 1;
|
||||
export const STORY_EXPIRE_PERIOD = 86400; // 1 day
|
||||
export const STORY_VIEWERS_EXPIRE_PERIOD = 86400; // 1 day
|
||||
export const FRESH_AUTH_PERIOD = 86400; // 1 day
|
||||
|
||||
export const LIGHT_THEME_BG_COLOR = '#99BA92';
|
||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||
|
||||
@ -135,11 +135,14 @@ addActionHandler('terminateAllAuthorizations', async (global): Promise<void> =>
|
||||
});
|
||||
|
||||
addActionHandler('changeSessionSettings', async (global, actions, payload): Promise<void> => {
|
||||
const { hash, areCallsEnabled, areSecretChatsEnabled } = payload;
|
||||
const {
|
||||
hash, areCallsEnabled, areSecretChatsEnabled, isConfirmed,
|
||||
} = payload;
|
||||
const result = await callApi('changeSessionSettings', {
|
||||
hash,
|
||||
areCallsEnabled,
|
||||
areSecretChatsEnabled,
|
||||
isConfirmed,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
@ -157,6 +160,7 @@ addActionHandler('changeSessionSettings', async (global, actions, payload): Prom
|
||||
...global.activeSessions.byHash[hash],
|
||||
...(areCallsEnabled !== undefined ? { areCallsEnabled } : undefined),
|
||||
...(areSecretChatsEnabled !== undefined ? { areSecretChatsEnabled } : undefined),
|
||||
...(isConfirmed && { isUnconfirmed: undefined }),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -41,6 +41,12 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
actions.loadConfig();
|
||||
break;
|
||||
|
||||
case 'updateNewAuthorization': {
|
||||
// Load more info about this session
|
||||
actions.loadAuthorizations();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateFavoriteStickers':
|
||||
actions.loadFavoriteStickers();
|
||||
break;
|
||||
|
||||
@ -1682,6 +1682,7 @@ export interface ActionPayloads {
|
||||
hash: string;
|
||||
areCallsEnabled?: boolean;
|
||||
areSecretChatsEnabled?: boolean;
|
||||
isConfirmed?: boolean;
|
||||
};
|
||||
changeSessionTtl: {
|
||||
days: number;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user