Dynamic Resize (#2291)
This commit is contained in:
parent
9596bb1695
commit
3dab7609e3
10
src/App.tsx
10
src/App.tsx
@ -21,6 +21,7 @@ import AppInactive from './components/main/AppInactive';
|
||||
import Transition from './components/ui/Transition';
|
||||
import UiLoader from './components/common/UiLoader';
|
||||
import { parseInitialLocationHash } from './util/routing';
|
||||
import useAppLayout from './hooks/useAppLayout';
|
||||
// import Test from './components/test/TestNoRedundancy';
|
||||
|
||||
type StateProps = {
|
||||
@ -46,7 +47,8 @@ const App: FC<StateProps> = ({
|
||||
const { disconnect } = getActions();
|
||||
|
||||
const [isInactive, markInactive] = useFlag(false);
|
||||
const isMobile = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
||||
const { isMobile } = useAppLayout();
|
||||
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
||||
|
||||
useEffect(() => {
|
||||
updateSizes();
|
||||
@ -129,7 +131,7 @@ const App: FC<StateProps> = ({
|
||||
} else if (hasPasscode) {
|
||||
activeKey = AppScreens.lock;
|
||||
} else {
|
||||
page = isMobile ? 'authPhoneNumber' : 'authQrCode';
|
||||
page = isMobileOs ? 'authPhoneNumber' : 'authQrCode';
|
||||
activeKey = AppScreens.auth;
|
||||
}
|
||||
|
||||
@ -150,7 +152,7 @@ const App: FC<StateProps> = ({
|
||||
case AppScreens.auth:
|
||||
return <Auth isActive={isActive} />;
|
||||
case AppScreens.main:
|
||||
return <Main />;
|
||||
return <Main isMobile={isMobile} />;
|
||||
case AppScreens.lock:
|
||||
return <LockScreen isLocked={isScreenLocked} />;
|
||||
case AppScreens.inactive:
|
||||
@ -159,7 +161,7 @@ const App: FC<StateProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<UiLoader key="Loader" page={page}>
|
||||
<UiLoader key="Loader" page={page} isMobile={isMobile}>
|
||||
<Transition
|
||||
name="fade"
|
||||
activeKey={activeKey}
|
||||
|
||||
@ -7,7 +7,6 @@ import type { GlobalState } from '../../global/types';
|
||||
import '../../global/actions/initial';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { PLATFORM_ENV } from '../../util/environment';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
|
||||
@ -56,15 +55,6 @@ const Auth: FC<OwnProps & StateProps> = ({
|
||||
onBack: handleChangeAuthorizationMethod,
|
||||
});
|
||||
|
||||
// Prevent refresh when rotating device
|
||||
useEffect(() => {
|
||||
windowSize.disableRefresh();
|
||||
|
||||
return () => {
|
||||
windowSize.enableRefresh();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// For animation purposes
|
||||
const renderingAuthState = useCurrentOrPrev(
|
||||
authState !== 'authorizationStateReady' ? authState : undefined,
|
||||
|
||||
@ -17,7 +17,6 @@ import {
|
||||
IS_ANDROID,
|
||||
IS_IOS,
|
||||
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
||||
IS_SINGLE_COLUMN_LAYOUT,
|
||||
} from '../../../util/environment';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
@ -28,6 +27,7 @@ import {
|
||||
} from '../../../global/selectors/calls';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Loading from '../../ui/Loading';
|
||||
import Button from '../../ui/Button';
|
||||
@ -84,12 +84,13 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
const lang = useLang();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const { isMobile, isLandscape } = useAppLayout();
|
||||
|
||||
const [isLeaving, setIsLeaving] = useState(false);
|
||||
const [isFullscreen, openFullscreen, closeFullscreen] = useFlag();
|
||||
const [isSidebarOpen, openSidebar, closeSidebar] = useFlag(true);
|
||||
const hasVideoParticipants = Object.values(participants).some(({ video, presentation }) => video || presentation);
|
||||
const isLandscape = isFullscreen && !IS_SINGLE_COLUMN_LAYOUT && hasVideoParticipants;
|
||||
const isLandscapeLayout = isFullscreen && (!isMobile || isLandscape) && hasVideoParticipants;
|
||||
|
||||
const [participantMenu, setParticipantMenu] = useState<{
|
||||
participant: TypeGroupCallParticipant;
|
||||
@ -254,8 +255,8 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
onClose={toggleGroupCallPanel}
|
||||
className={buildClassName(
|
||||
'GroupCall',
|
||||
IS_SINGLE_COLUMN_LAYOUT && 'single-column',
|
||||
isLandscape && 'landscape',
|
||||
(isMobile && !isLandscape) && 'single-column',
|
||||
isLandscapeLayout && 'landscape',
|
||||
!isSidebarOpen && 'no-sidebar',
|
||||
)}
|
||||
dialogRef={containerRef}
|
||||
@ -274,7 +275,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
<i className={isFullscreen ? 'icon-smallscreen' : 'icon-fullscreen'} />
|
||||
</Button>
|
||||
)}
|
||||
{isLandscape && (
|
||||
{isLandscapeLayout && (
|
||||
<Button
|
||||
round
|
||||
size="smaller"
|
||||
@ -321,7 +322,7 @@ const GroupCall: FC<OwnProps & StateProps> = ({
|
||||
<div className="scrollable custom-scroll">
|
||||
<GroupCallParticipantStreams onDoubleClick={handleStreamsDoubleClick} />
|
||||
|
||||
{(!isLandscape || isSidebarOpen)
|
||||
{(!isLandscapeLayout || isSidebarOpen)
|
||||
&& <GroupCallParticipantList openParticipantMenu={handleOpenParticipantMenu} />}
|
||||
</div>
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
IS_ANDROID,
|
||||
IS_IOS,
|
||||
IS_REQUEST_FULLSCREEN_SUPPORTED,
|
||||
IS_SINGLE_COLUMN_LAYOUT,
|
||||
} from '../../../util/environment';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
@ -26,6 +25,7 @@ import {
|
||||
} from '../../../lib/secret-sauce';
|
||||
import useInterval from '../../../hooks/useInterval';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Modal from '../../ui/Modal';
|
||||
import Avatar from '../../common/Avatar';
|
||||
@ -58,6 +58,7 @@ const PhoneCall: FC<StateProps> = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [isFullscreen, openFullscreen, closeFullscreen] = useFlag();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
if (isFullscreen) {
|
||||
@ -230,7 +231,7 @@ const PhoneCall: FC<StateProps> = ({
|
||||
onClose={handleClose}
|
||||
className={buildClassName(
|
||||
styles.root,
|
||||
IS_SINGLE_COLUMN_LAYOUT && styles.singleColumn,
|
||||
isMobile && styles.singleColumn,
|
||||
)}
|
||||
dialogRef={containerRef}
|
||||
>
|
||||
|
||||
@ -8,11 +8,12 @@ import React, {
|
||||
import { fastRaf } from '../../util/schedulers';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
import generateIdFor from '../../util/generateIdFor';
|
||||
|
||||
import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
|
||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||
import useOnChange from '../../hooks/useOnChange';
|
||||
import generateIdFor from '../../util/generateIdFor';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
export type OwnProps = {
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
@ -88,6 +89,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|
||||
const containerId = useMemo(() => generateIdFor(ID_STORE, true), []);
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [animation, setAnimation] = useState<RLottieInstance>();
|
||||
const wasPlaying = useRef(false);
|
||||
const isFrozen = useRef(false);
|
||||
@ -138,6 +140,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
quality,
|
||||
isLowPriority,
|
||||
coords: sharedCanvasCoords,
|
||||
isMobile,
|
||||
},
|
||||
color,
|
||||
onEnded,
|
||||
@ -164,7 +167,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
}
|
||||
}, [
|
||||
animation, animationId, tgsUrl, color, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded, onLoop,
|
||||
containerId, sharedCanvas, sharedCanvasCoords,
|
||||
containerId, sharedCanvas, sharedCanvasCoords, isMobile,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -232,11 +235,14 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
}
|
||||
}, [noLoop, animation]);
|
||||
|
||||
useOnChange(([prevSharedCanvasCoords]) => {
|
||||
if (prevSharedCanvasCoords !== undefined && sharedCanvasCoords !== prevSharedCanvasCoords) {
|
||||
animation?.setSharedCanvasCoords(containerId, sharedCanvasCoords);
|
||||
useOnChange(([prevSharedCanvasCoords, prevIsMobile]) => {
|
||||
if (
|
||||
(prevSharedCanvasCoords !== undefined && sharedCanvasCoords !== prevSharedCanvasCoords)
|
||||
|| (prevIsMobile !== undefined && isMobile !== prevIsMobile)
|
||||
) {
|
||||
animation?.setSharedCanvasCoords(containerId, sharedCanvasCoords, isMobile);
|
||||
}
|
||||
}, [sharedCanvasCoords, containerId, animation]);
|
||||
}, [sharedCanvasCoords, isMobile, containerId, animation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!animation) {
|
||||
|
||||
@ -8,9 +8,11 @@ import type { ApiAudio, ApiMessage, ApiVoice } from '../../api/types';
|
||||
import { ApiMediaFormat } from '../../api/types';
|
||||
import type { ISettings } from '../../types';
|
||||
import { AudioOrigin } from '../../types';
|
||||
import type { LangFn } from '../../hooks/useLang';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
|
||||
import { MAX_EMPTY_WAVEFORM_POINTS, renderWaveform } from './helpers/waveform';
|
||||
import renderText from './helpers/renderText';
|
||||
import { getFileSizeString } from './helpers/documentInfo';
|
||||
import {
|
||||
getMediaDuration,
|
||||
getMediaTransferState,
|
||||
@ -19,22 +21,20 @@ import {
|
||||
isMessageLocal,
|
||||
isOwnMessage,
|
||||
} from '../../global/helpers';
|
||||
import { MAX_EMPTY_WAVEFORM_POINTS, renderWaveform } from './helpers/waveform';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import renderText from './helpers/renderText';
|
||||
import { getFileSizeString } from './helpers/documentInfo';
|
||||
import { formatMediaDateTime, formatMediaDuration, formatPastTimeShort } from '../../util/dateFormat';
|
||||
import { decodeWaveform, interpolateArray } from '../../util/waveform';
|
||||
import { makeTrackId } from '../../util/audioPlayer';
|
||||
import { getTranslation } from '../../util/langProvider';
|
||||
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||
import useBuffering from '../../hooks/useBuffering';
|
||||
import useAudioPlayer from '../../hooks/useAudioPlayer';
|
||||
import type { LangFn } from '../../hooks/useLang';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { captureEvents } from '../../util/captureEvents';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import { makeTrackId } from '../../util/audioPlayer';
|
||||
import { getTranslation } from '../../util/langProvider';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||
@ -111,6 +111,7 @@ const Audio: FC<OwnProps> = ({
|
||||
const lang = useLang();
|
||||
const { isRtl } = lang;
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [isActivated, setIsActivated] = useState(false);
|
||||
const shouldLoad = (isActivated || PRELOAD) && lastSyncTime;
|
||||
const coverHash = getMessageMediaHash(message, 'pictogram');
|
||||
@ -159,7 +160,7 @@ const Audio: FC<OwnProps> = ({
|
||||
|
||||
const isOwn = isOwnMessage(message);
|
||||
const waveformCanvasRef = useWaveformCanvas(
|
||||
theme, voice, (isMediaUnread && !isOwn) ? 1 : playProgress, isOwn, !noAvatars,
|
||||
theme, voice, (isMediaUnread && !isOwn) ? 1 : playProgress, isOwn, !noAvatars, isMobile,
|
||||
);
|
||||
|
||||
const withSeekline = isPlaying || (playProgress > 0 && playProgress < 1);
|
||||
@ -345,7 +346,7 @@ const Audio: FC<OwnProps> = ({
|
||||
)}
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color={coverBlobUrl ? 'translucent-white' : 'primary'}
|
||||
className={buttonClassNames.join(' ')}
|
||||
@ -414,10 +415,10 @@ const Audio: FC<OwnProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
function getSeeklineSpikeAmounts(withAvatar?: boolean) {
|
||||
function getSeeklineSpikeAmounts(isMobile?: boolean, withAvatar?: boolean) {
|
||||
return {
|
||||
MIN_SPIKES: IS_SINGLE_COLUMN_LAYOUT ? (TINY_SCREEN_WIDTH_MQL.matches ? 16 : 20) : 25,
|
||||
MAX_SPIKES: IS_SINGLE_COLUMN_LAYOUT
|
||||
MIN_SPIKES: isMobile ? (TINY_SCREEN_WIDTH_MQL.matches ? 16 : 20) : 25,
|
||||
MAX_SPIKES: isMobile
|
||||
? (TINY_SCREEN_WIDTH_MQL.matches
|
||||
? 35
|
||||
: (withAvatar && WITH_AVATAR_TINY_SCREEN_WIDTH_MQL.matches ? 40 : 45))
|
||||
@ -540,6 +541,7 @@ function useWaveformCanvas(
|
||||
playProgress = 0,
|
||||
isOwn = false,
|
||||
withAvatar = false,
|
||||
isMobile = false,
|
||||
) {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
@ -557,13 +559,13 @@ function useWaveformCanvas(
|
||||
};
|
||||
}
|
||||
|
||||
const { MIN_SPIKES, MAX_SPIKES } = getSeeklineSpikeAmounts(withAvatar);
|
||||
const { MIN_SPIKES, MAX_SPIKES } = getSeeklineSpikeAmounts(isMobile, withAvatar);
|
||||
const durationFactor = Math.min(duration / AVG_VOICE_DURATION, 1);
|
||||
const spikesCount = Math.round(MIN_SPIKES + (MAX_SPIKES - MIN_SPIKES) * durationFactor);
|
||||
const decodedWaveform = decodeWaveform(new Uint8Array(waveform));
|
||||
|
||||
return interpolateArray(decodedWaveform, spikesCount);
|
||||
}, [voice, withAvatar]) || {};
|
||||
}, [isMobile, voice, withAvatar]) || {};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
@ -2,16 +2,17 @@ import type { RefObject } from 'react';
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useRef, useState } from '../../lib/teact/teact';
|
||||
|
||||
import { IS_CANVAS_FILTER_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/environment';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { formatMediaDateTime, formatPastTimeShort } from '../../util/dateFormat';
|
||||
import { getColorFromExtension, getFileSizeString } from './helpers/documentInfo';
|
||||
import { getDocumentThumbnailDimensions } from './helpers/mediaDimensions';
|
||||
import renderText from './helpers/renderText';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useCanvasBlur from '../../hooks/useCanvasBlur';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||
import Link from '../ui/Link';
|
||||
@ -66,9 +67,10 @@ const File: FC<OwnProps> = ({
|
||||
elementRef = ref;
|
||||
}
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [withThumb] = useState(!previewData);
|
||||
const noThumb = Boolean(previewData);
|
||||
const thumbRef = useCanvasBlur(thumbnailDataUri, noThumb, IS_SINGLE_COLUMN_LAYOUT && !IS_CANVAS_FILTER_SUPPORTED);
|
||||
const thumbRef = useCanvasBlur(thumbnailDataUri, noThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED);
|
||||
const thumbClassNames = useMediaTransition(!noThumb);
|
||||
|
||||
const {
|
||||
|
||||
@ -5,11 +5,12 @@ import React, {
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import { MIN_PASSWORD_LENGTH } from '../../config';
|
||||
import { IS_TOUCH_ENV, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useTimeout from '../../hooks/useTimeout';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
|
||||
@ -31,8 +32,6 @@ type OwnProps = {
|
||||
onSubmit?: (password: string) => void;
|
||||
};
|
||||
|
||||
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
||||
|
||||
const PasswordForm: FC<OwnProps> = ({
|
||||
isLoading = false,
|
||||
isPasswordVisible,
|
||||
@ -54,8 +53,10 @@ const PasswordForm: FC<OwnProps> = ({
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const lang = useLang();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [password, setPassword] = useState('');
|
||||
const [canSubmit, setCanSubmit] = useState(false);
|
||||
const focusDelayTimeoutMs = isMobile ? 550 : 400;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldResetValue) {
|
||||
@ -67,7 +68,7 @@ const PasswordForm: FC<OwnProps> = ({
|
||||
if (!IS_TOUCH_ENV) {
|
||||
inputRef.current!.focus();
|
||||
}
|
||||
}, FOCUS_DELAY_TIMEOUT_MS);
|
||||
}, focusDelayTimeoutMs);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
|
||||
@ -2,13 +2,13 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import React, { useCallback, memo } from '../../lib/teact/teact';
|
||||
|
||||
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||
|
||||
import AnimatedSticker from './AnimatedSticker';
|
||||
|
||||
import useTimeout from '../../hooks/useTimeout';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import './PasswordMonkey.scss';
|
||||
|
||||
@ -21,11 +21,13 @@ const PEEK_MONKEY_SHOW_DELAY = 2000;
|
||||
const SEGMENT_COVER_EYES: [number, number] = [0, 50];
|
||||
const SEGMENT_UNCOVER_EYE: [number, number] = [0, 20];
|
||||
const SEGMENT_COVER_EYE: [number, number] = [20, 0];
|
||||
const STICKER_SIZE = IS_SINGLE_COLUMN_LAYOUT ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
|
||||
|
||||
const PasswordMonkey: FC<OwnProps> = ({ isPasswordVisible, isBig }) => {
|
||||
const [isFirstMonkeyLoaded, markFirstMonkeyLoaded] = useFlag(false);
|
||||
const [isPeekShown, markPeekShown] = useFlag(false);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const stikerSize = isMobile ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
|
||||
|
||||
useTimeout(markPeekShown, PEEK_MONKEY_SHOW_DELAY);
|
||||
const handleFirstMonkeyLoad = useCallback(markFirstMonkeyLoaded, [markFirstMonkeyLoaded]);
|
||||
@ -36,7 +38,7 @@ const PasswordMonkey: FC<OwnProps> = ({ isPasswordVisible, isBig }) => {
|
||||
<div className="monkey-preview" />
|
||||
)}
|
||||
<AnimatedSticker
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : stikerSize}
|
||||
className={isPeekShown ? 'hidden' : 'shown'}
|
||||
tgsUrl={LOCAL_TGS_URLS.MonkeyClose}
|
||||
playSegment={SEGMENT_COVER_EYES}
|
||||
@ -44,7 +46,7 @@ const PasswordMonkey: FC<OwnProps> = ({ isPasswordVisible, isBig }) => {
|
||||
onLoad={handleFirstMonkeyLoad}
|
||||
/>
|
||||
<AnimatedSticker
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : stikerSize}
|
||||
className={isPeekShown ? 'shown' : 'hidden'}
|
||||
tgsUrl={LOCAL_TGS_URLS.MonkeyPeek}
|
||||
playSegment={isPasswordVisible ? SEGMENT_UNCOVER_EYE : SEGMENT_COVER_EYE}
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
||||
|
||||
import { IS_CANVAS_FILTER_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/environment';
|
||||
import {
|
||||
getChatAvatarHash,
|
||||
getChatTitle,
|
||||
@ -21,6 +21,7 @@ import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||
import useCanvasBlur from '../../hooks/useCanvasBlur';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Spinner from '../ui/Spinner';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
@ -50,6 +51,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const isDeleted = user && isDeletedUser(user);
|
||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||
@ -73,7 +75,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
||||
const transitionClassNames = useMediaTransition(isFullMediaReady);
|
||||
const isBlurredThumb = canHaveMedia && !isFullMediaReady && !avatarBlobUrl && currentPhoto?.thumbnail?.dataUri;
|
||||
const blurredThumbCanvasRef = useCanvasBlur(
|
||||
currentPhoto?.thumbnail?.dataUri, !isBlurredThumb, IS_SINGLE_COLUMN_LAYOUT && !IS_CANVAS_FILTER_SUPPORTED,
|
||||
currentPhoto?.thumbnail?.dataUri, !isBlurredThumb, isMobile && !IS_CANVAS_FILTER_SUPPORTED,
|
||||
);
|
||||
const hasMedia = currentPhoto || avatarBlobUrl || isBlurredThumb;
|
||||
|
||||
|
||||
@ -2,9 +2,10 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import React, { useState, useCallback, memo } from '../../lib/teact/teact';
|
||||
|
||||
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
|
||||
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import AnimatedSticker from './AnimatedSticker';
|
||||
|
||||
import './PasswordMonkey.scss';
|
||||
@ -19,7 +20,6 @@ type OwnProps = {
|
||||
|
||||
const TRACKING_START_FRAME = 15;
|
||||
const TRACKING_END_FRAME = 180;
|
||||
const STICKER_SIZE = IS_SINGLE_COLUMN_LAYOUT ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
|
||||
|
||||
const TrackingMonkey: FC<OwnProps> = ({
|
||||
code,
|
||||
@ -29,17 +29,19 @@ const TrackingMonkey: FC<OwnProps> = ({
|
||||
isBig,
|
||||
}) => {
|
||||
const [isFirstMonkeyLoaded, setIsFirstMonkeyLoaded] = useState(false);
|
||||
const TRACKING_FRAMES_PER_SYMBOL = (TRACKING_END_FRAME - TRACKING_START_FRAME) / codeLength;
|
||||
const { isMobile } = useAppLayout();
|
||||
const trackningFramesPerSymbol = (TRACKING_END_FRAME - TRACKING_START_FRAME) / codeLength;
|
||||
const stickerSize = isMobile ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
|
||||
|
||||
const handleFirstMonkeyLoad = useCallback(() => setIsFirstMonkeyLoaded(true), []);
|
||||
|
||||
function getTrackingFrames(): [number, number] {
|
||||
const startFrame = (code && code.length > 1) || trackingDirection < 0
|
||||
? TRACKING_START_FRAME + TRACKING_FRAMES_PER_SYMBOL * (code.length - 1)
|
||||
? TRACKING_START_FRAME + trackningFramesPerSymbol * (code.length - 1)
|
||||
: 0;
|
||||
const endFrame = code.length === codeLength
|
||||
? TRACKING_END_FRAME
|
||||
: TRACKING_START_FRAME + TRACKING_FRAMES_PER_SYMBOL * code.length;
|
||||
: TRACKING_START_FRAME + trackningFramesPerSymbol * code.length;
|
||||
|
||||
if (trackingDirection < 1) {
|
||||
return [
|
||||
@ -60,14 +62,14 @@ const TrackingMonkey: FC<OwnProps> = ({
|
||||
<div className="monkey-preview" />
|
||||
)}
|
||||
<AnimatedSticker
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : stickerSize}
|
||||
className={isTracking ? 'hidden' : undefined}
|
||||
tgsUrl={LOCAL_TGS_URLS.MonkeyIdle}
|
||||
play={!isTracking}
|
||||
onLoad={handleFirstMonkeyLoad}
|
||||
/>
|
||||
<AnimatedSticker
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
|
||||
size={isBig ? STICKER_SIZE_TWO_FA : stickerSize}
|
||||
className={!isTracking ? 'hidden' : 'shown'}
|
||||
tgsUrl={LOCAL_TGS_URLS.MonkeyTracking}
|
||||
playSegment={isTracking ? getTrackingFrames() : undefined}
|
||||
|
||||
@ -38,6 +38,7 @@ export type UiLoaderPage =
|
||||
type OwnProps = {
|
||||
page?: UiLoaderPage;
|
||||
children: React.ReactNode;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = Pick<GlobalState, 'uiReadyState' | 'shouldSkipHistoryAnimations'> & {
|
||||
@ -173,13 +174,13 @@ const UiLoader: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
(global, { isMobile }): StateProps => {
|
||||
const theme = selectTheme(global);
|
||||
|
||||
return {
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
uiReadyState: global.uiReadyState,
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
||||
leftColumnWidth: global.leftColumnWidth,
|
||||
theme,
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import type {
|
||||
} from '../../../api/types';
|
||||
|
||||
import { STICKER_SIZE_INLINE_DESKTOP_FACTOR, STICKER_SIZE_INLINE_MOBILE_FACTOR } from '../../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import windowSize from '../../../util/windowSize';
|
||||
import { getPhotoInlineDimensions, getVideoDimensions } from '../../../global/helpers';
|
||||
|
||||
@ -25,9 +25,9 @@ let cachedMaxWidthOwn: number | undefined;
|
||||
let cachedMaxWidth: number | undefined;
|
||||
let cachedMaxWidthNoAvatar: number | undefined;
|
||||
|
||||
function getMaxMessageWidthRem(fromOwnMessage: boolean, noAvatars?: boolean) {
|
||||
function getMaxMessageWidthRem(fromOwnMessage: boolean, noAvatars?: boolean, isMobile?: boolean) {
|
||||
const regularMaxWidth = fromOwnMessage ? MESSAGE_OWN_MAX_WIDTH_REM : MESSAGE_MAX_WIDTH_REM;
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (!isMobile) {
|
||||
return regularMaxWidth;
|
||||
}
|
||||
|
||||
@ -63,9 +63,10 @@ export function getAvailableWidth(
|
||||
asForwarded?: boolean,
|
||||
isWebPageMedia?: boolean,
|
||||
noAvatars?: boolean,
|
||||
isMobile?: boolean,
|
||||
) {
|
||||
const extraPaddingRem = asForwarded && isWebPageMedia ? 2.25 : (asForwarded || isWebPageMedia ? 1.625 : 0);
|
||||
const availableWidthRem = getMaxMessageWidthRem(fromOwnMessage, noAvatars) - extraPaddingRem;
|
||||
const availableWidthRem = getMaxMessageWidthRem(fromOwnMessage, noAvatars, isMobile) - extraPaddingRem;
|
||||
|
||||
return availableWidthRem * REM;
|
||||
}
|
||||
@ -89,6 +90,7 @@ export function calculateDimensionsForMessageMedia({
|
||||
isWebPageMedia,
|
||||
isGif,
|
||||
noAvatars,
|
||||
isMobile,
|
||||
}: {
|
||||
width: number;
|
||||
height: number;
|
||||
@ -97,9 +99,10 @@ export function calculateDimensionsForMessageMedia({
|
||||
isWebPageMedia?: boolean;
|
||||
isGif?: boolean;
|
||||
noAvatars?: boolean;
|
||||
isMobile?: boolean;
|
||||
}): ApiDimensions {
|
||||
const aspectRatio = height / width;
|
||||
const availableWidth = getAvailableWidth(fromOwnMessage, asForwarded, isWebPageMedia, noAvatars);
|
||||
const availableWidth = getAvailableWidth(fromOwnMessage, asForwarded, isWebPageMedia, noAvatars, isMobile);
|
||||
const availableHeight = getAvailableHeight(isGif, aspectRatio);
|
||||
const mediaWidth = isGif ? Math.max(GIF_MIN_WIDTH, width) : width;
|
||||
const mediaHeight = isGif ? height * (mediaWidth / width) : height;
|
||||
@ -126,6 +129,7 @@ export function calculateInlineImageDimensions(
|
||||
asForwarded?: boolean,
|
||||
isWebPageMedia?: boolean,
|
||||
noAvatars?: boolean,
|
||||
isMobile?: boolean,
|
||||
) {
|
||||
const { width, height } = getPhotoInlineDimensions(photo) || DEFAULT_MEDIA_DIMENSIONS;
|
||||
|
||||
@ -136,6 +140,7 @@ export function calculateInlineImageDimensions(
|
||||
asForwarded,
|
||||
isWebPageMedia,
|
||||
noAvatars,
|
||||
isMobile,
|
||||
});
|
||||
}
|
||||
|
||||
@ -145,6 +150,7 @@ export function calculateVideoDimensions(
|
||||
asForwarded?: boolean,
|
||||
isWebPageMedia?: boolean,
|
||||
noAvatars?: boolean,
|
||||
isMobile?: boolean,
|
||||
) {
|
||||
const { width, height } = getVideoDimensions(video) || DEFAULT_MEDIA_DIMENSIONS;
|
||||
|
||||
@ -156,6 +162,7 @@ export function calculateVideoDimensions(
|
||||
isWebPageMedia,
|
||||
isGif: video.isGif,
|
||||
noAvatars,
|
||||
isMobile,
|
||||
});
|
||||
}
|
||||
|
||||
@ -180,7 +187,7 @@ export function getDocumentThumbnailDimensions(smaller?: boolean): ApiDimensions
|
||||
};
|
||||
}
|
||||
|
||||
export function getStickerDimensions(sticker: ApiSticker): ApiDimensions {
|
||||
export function getStickerDimensions(sticker: ApiSticker, isMobile?: boolean): ApiDimensions {
|
||||
const { width } = sticker;
|
||||
let { height } = sticker;
|
||||
|
||||
@ -191,7 +198,7 @@ export function getStickerDimensions(sticker: ApiSticker): ApiDimensions {
|
||||
|
||||
const aspectRatio = (height && width) && height / width;
|
||||
const baseWidth = REM * (
|
||||
IS_SINGLE_COLUMN_LAYOUT
|
||||
isMobile
|
||||
? STICKER_SIZE_INLINE_MOBILE_FACTOR
|
||||
: STICKER_SIZE_INLINE_DESKTOP_FACTOR
|
||||
);
|
||||
|
||||
@ -17,7 +17,6 @@ import type { AnimationLevel } from '../../../types';
|
||||
import type { ChatAnimationTypes } from './hooks';
|
||||
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import {
|
||||
isUserId,
|
||||
getPrivateChatUserId,
|
||||
@ -43,6 +42,7 @@ import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useChatListEntry from './hooks/useChatListEntry';
|
||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Avatar from '../../common/Avatar';
|
||||
@ -128,6 +128,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
openForumPanel,
|
||||
} = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
|
||||
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
|
||||
@ -225,7 +226,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
ref={ref}
|
||||
className={className}
|
||||
style={`top: ${offsetTop}px`}
|
||||
ripple={!isForum && !IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isForum && !isMobile}
|
||||
contextActions={contextActions}
|
||||
onClick={handleClick}
|
||||
onDragEnter={handleDragEnter}
|
||||
@ -245,7 +246,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
<AvatarBadge chatId={chatId} />
|
||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||
<ChatCallStatus isSelected={isSelected} isActive={animationLevel !== 0} />
|
||||
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={animationLevel !== 0} />
|
||||
)}
|
||||
</div>
|
||||
<div className="info">
|
||||
@ -255,7 +256,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
withEmojiStatus
|
||||
isSavedMessages={chatId === user?.id && user?.isSelf}
|
||||
observeIntersection={observeIntersection}
|
||||
key={!IS_SINGLE_COLUMN_LAYOUT && isEmojiStatusColored ? `${isSelected}` : undefined}
|
||||
key={!isMobile && isEmojiStatusColored ? `${isSelected}` : undefined}
|
||||
/>
|
||||
{isMuted && <i className="icon-muted" />}
|
||||
<div className="separator" />
|
||||
|
||||
@ -2,24 +2,24 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo } from '../../../lib/teact/teact';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
|
||||
import './ChatCallStatus.scss';
|
||||
|
||||
type OwnProps = {
|
||||
isSelected?: boolean;
|
||||
isActive?: boolean;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
const ChatCallStatus: FC<OwnProps> = ({
|
||||
isSelected,
|
||||
isActive,
|
||||
isMobile,
|
||||
}) => {
|
||||
return (
|
||||
<div className={buildClassName(
|
||||
'ChatCallStatus',
|
||||
isActive && 'active',
|
||||
isSelected && !IS_SINGLE_COLUMN_LAYOUT && 'selected',
|
||||
isSelected && !isMobile && 'selected',
|
||||
)}
|
||||
>
|
||||
<div className="indicator">
|
||||
|
||||
@ -4,11 +4,11 @@ import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiUser, ApiUserStatus } from '../../../api/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { filterUsersByName, sortUserIds } from '../../../global/helpers';
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
@ -42,6 +42,7 @@ const ContactList: FC<OwnProps & StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useHistoryBack({
|
||||
isActive,
|
||||
@ -73,7 +74,7 @@ const ContactList: FC<OwnProps & StateProps> = ({
|
||||
className="chat-item-clickable"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleClick(id)}
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
>
|
||||
<PrivateChatInfo userId={id} forceShowSelf avatarSize="large" />
|
||||
</ListItem>
|
||||
|
||||
@ -6,9 +6,9 @@ import type { ApiChatFolder, ApiSticker } from '../../../api/types';
|
||||
import { SettingsScreens } from '../../../types';
|
||||
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { selectAnimatedEmoji, selectChatFolder } from '../../../global/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
||||
@ -33,6 +33,7 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
|
||||
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleEditFolder = useCallback(() => {
|
||||
foldersDispatch!({ type: 'editFolder', payload: chatFolder });
|
||||
@ -50,7 +51,7 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
|
||||
</p>
|
||||
{chatFolder && foldersDispatch && onScreenSelect && (
|
||||
<Button
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
fluid
|
||||
pill
|
||||
onClick={handleEditFolder}
|
||||
|
||||
@ -4,12 +4,12 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiSticker } from '../../../api/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
import { selectAnimatedEmoji, selectChat } from '../../../global/selectors';
|
||||
import { getHasAdminRight } from '../../../global/helpers';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
||||
@ -31,7 +31,9 @@ const EmptyForum: FC<OwnProps & StateProps> = ({
|
||||
chatId, animatedEmoji, canManageTopics,
|
||||
}) => {
|
||||
const { openCreateTopicPanel } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleCreateTopic = useCallback(() => {
|
||||
openCreateTopicPanel({ chatId });
|
||||
@ -48,7 +50,7 @@ const EmptyForum: FC<OwnProps & StateProps> = ({
|
||||
</p>
|
||||
{canManageTopics && (
|
||||
<Button
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
fluid
|
||||
onClick={handleCreateTopic}
|
||||
size="smaller"
|
||||
|
||||
@ -26,6 +26,7 @@ import useLang from '../../../hooks/useLang';
|
||||
import usePrevious from '../../../hooks/usePrevious';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import { dispatchHeavyAnimationEvent } from '../../../hooks/useHeavyAnimationCheck';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import GroupChatInfo from '../../common/GroupChatInfo';
|
||||
import Button from '../../ui/Button';
|
||||
@ -73,6 +74,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const scrollTopHandlerRef = useRef<HTMLDivElement>(null);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useEffect(() => {
|
||||
if (lastSyncTime && chat && !chat.topics) {
|
||||
@ -223,6 +225,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
||||
messageListType="thread"
|
||||
canExpandActions={false}
|
||||
withForumActions
|
||||
isMobile={isMobile}
|
||||
onTopicSearch={onTopicSearch}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
IS_TEST,
|
||||
PRODUCTION_HOSTNAME,
|
||||
} from '../../../config';
|
||||
import { IS_PWA, IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { IS_PWA } from '../../../util/environment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatDateToString } from '../../../util/dateFormat';
|
||||
import switchTheme from '../../../util/switchTheme';
|
||||
@ -33,6 +33,7 @@ import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||
import { getPromptInstall } from '../../../util/installPrompt';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import DropdownMenu from '../../ui/DropdownMenu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
@ -119,6 +120,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
const hasMenu = content === LeftColumnContent.ChatList;
|
||||
const clearedDateSearchParam = { date: undefined };
|
||||
const clearedChatSearchParam = { id: undefined };
|
||||
@ -168,7 +170,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
return ({ onTrigger, isOpen }) => (
|
||||
<Button
|
||||
round
|
||||
ripple={hasMenu && !IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={hasMenu && !isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
className={isOpen ? 'active' : ''}
|
||||
@ -184,7 +186,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}, [hasMenu, lang, onReset, shouldSkipTransition]);
|
||||
}, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]);
|
||||
|
||||
const handleSearchFocus = useCallback(() => {
|
||||
if (!searchQuery) {
|
||||
@ -431,7 +433,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
{hasPasscode && (
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
ariaLabel={`${lang('ShortcutsController.Others.LockByPasscode')} (Ctrl+Shift+L)`}
|
||||
|
||||
@ -6,8 +6,8 @@ import type {
|
||||
ApiChat, ApiUser, ApiMessage, ApiMessageOutgoingStatus,
|
||||
} from '../../../api/types';
|
||||
import type { AnimationLevel } from '../../../types';
|
||||
import type { LangFn } from '../../../hooks/useLang';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import {
|
||||
getPrivateChatUserId,
|
||||
getMessageMediaHash,
|
||||
@ -23,9 +23,9 @@ import { formatPastTimeShort } from '../../../util/dateFormat';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import type { LangFn } from '../../../hooks/useLang';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
@ -59,6 +59,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const { focusMessage } = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const mediaThumbnail = !getMessageSticker(message) ? getMessageMediaThumbDataUri(message) : undefined;
|
||||
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'micro'));
|
||||
const isRoundVideo = Boolean(getMessageRoundVideo(message));
|
||||
@ -78,7 +79,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<ListItem
|
||||
className="ChatMessage chat-item-clickable"
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
onClick={handleClick}
|
||||
buttonRef={buttonRef}
|
||||
>
|
||||
|
||||
@ -8,8 +8,8 @@ import { LoadMoreDirection } from '../../../types';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
@ -54,6 +54,8 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
||||
const { searchMessagesGlobal, openChat } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
@ -70,11 +72,11 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
||||
(id: number) => {
|
||||
openChat({ id: searchChatId, threadId: id, shouldReplaceHistory: true });
|
||||
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (!isMobile) {
|
||||
onReset();
|
||||
}
|
||||
},
|
||||
[openChat, searchChatId, onReset],
|
||||
[openChat, searchChatId, isMobile, onReset],
|
||||
);
|
||||
|
||||
const foundMessages = useMemo(() => {
|
||||
|
||||
@ -7,7 +7,6 @@ import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
import type { ApiChat, ApiMessage } from '../../../api/types';
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import {
|
||||
sortChatIds,
|
||||
@ -15,9 +14,10 @@ import {
|
||||
} from '../../../global/helpers';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import LeftSearchResultChat from './LeftSearchResultChat';
|
||||
@ -72,6 +72,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [shouldShowMoreLocal, setShouldShowMoreLocal] = useState<boolean>(false);
|
||||
const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false);
|
||||
|
||||
@ -94,11 +95,11 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
addRecentlyFoundChatId({ id });
|
||||
}
|
||||
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (!isMobile) {
|
||||
onReset();
|
||||
}
|
||||
},
|
||||
[currentUserId, openChat, addRecentlyFoundChatId, onReset],
|
||||
[openChat, currentUserId, isMobile, addRecentlyFoundChatId, onReset],
|
||||
);
|
||||
|
||||
const handlePickerItemClick = useCallback((id: string) => {
|
||||
|
||||
@ -14,7 +14,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import TabList from '../../ui/TabList';
|
||||
import Transition from '../../ui/Transition';
|
||||
import ChatResults from './ChatResults';
|
||||
import UserChatResults from './ChatMessageResults';
|
||||
import ChatMessageResults from './ChatMessageResults';
|
||||
import MediaResults from './MediaResults';
|
||||
import LinkResults from './LinkResults';
|
||||
import FileResults from './FileResults';
|
||||
@ -99,7 +99,7 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||
case GlobalSearchContent.ChatList:
|
||||
if (chatId) {
|
||||
return (
|
||||
<UserChatResults
|
||||
<ChatMessageResults
|
||||
searchQuery={searchQuery}
|
||||
dateSearchQuery={dateSearchQuery}
|
||||
onReset={onReset}
|
||||
|
||||
@ -6,9 +6,9 @@ import { getActions } from '../../../global';
|
||||
|
||||
import { SettingsScreens } from '../../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useMultiClick from '../../../hooks/useMultiClick';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import DropdownMenu from '../../ui/DropdownMenu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
@ -35,6 +35,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
openDeleteChatFolderModal,
|
||||
} = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [isSignOutDialogOpen, setIsSignOutDialogOpen] = useState(false);
|
||||
|
||||
const handleMultiClick = useMultiClick(5, () => {
|
||||
@ -64,7 +65,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
return ({ onTrigger, isOpen }) => (
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
className={isOpen ? 'active' : ''}
|
||||
@ -74,7 +75,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
<i className="icon-more" />
|
||||
</Button>
|
||||
);
|
||||
}, []);
|
||||
}, [isMobile]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -240,7 +241,7 @@ const SettingsHeader: FC<OwnProps> = ({
|
||||
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
|
||||
@ -6,10 +6,11 @@ import { withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiSticker } from '../../../../api/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../../../util/environment';
|
||||
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||
|
||||
import InputText from '../../../ui/InputText';
|
||||
import Loading from '../../../ui/Loading';
|
||||
@ -29,7 +30,6 @@ type StateProps = {
|
||||
codeLength: number;
|
||||
};
|
||||
|
||||
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
||||
const ICON_SIZE = 160;
|
||||
|
||||
const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||
@ -44,6 +44,8 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { isMobile } = useAppLayout();
|
||||
const focusDelayTimeoutMs = isMobile ? 550 : 400;
|
||||
|
||||
const [value, setValue] = useState<string>('');
|
||||
|
||||
@ -51,9 +53,9 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
|
||||
if (!IS_TOUCH_ENV) {
|
||||
setTimeout(() => {
|
||||
inputRef.current!.focus();
|
||||
}, FOCUS_DELAY_TIMEOUT_MS);
|
||||
}, focusDelayTimeoutMs);
|
||||
}
|
||||
}, []);
|
||||
}, [focusDelayTimeoutMs]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
|
||||
@ -6,12 +6,13 @@ import { withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiSticker } from '../../../../api/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../../../util/environment';
|
||||
import { selectAnimatedEmoji } from '../../../../global/selectors';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
import useFlag from '../../../../hooks/useFlag';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../../../ui/Button';
|
||||
import Modal from '../../../ui/Modal';
|
||||
@ -35,7 +36,6 @@ type StateProps = {
|
||||
animatedEmoji: ApiSticker;
|
||||
};
|
||||
|
||||
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
|
||||
const ICON_SIZE = 160;
|
||||
|
||||
const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||
@ -52,7 +52,9 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const focusDelayTimeoutMs = isMobile ? 550 : 400;
|
||||
const [value, setValue] = useState<string>('');
|
||||
const [isConfirmShown, markIsConfirmShown, unmarkIsConfirmShown] = useFlag(false);
|
||||
|
||||
@ -60,9 +62,9 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
|
||||
if (!IS_TOUCH_ENV) {
|
||||
setTimeout(() => {
|
||||
inputRef.current!.focus();
|
||||
}, FOCUS_DELAY_TIMEOUT_MS);
|
||||
}, focusDelayTimeoutMs);
|
||||
}
|
||||
}, []);
|
||||
}, [focusDelayTimeoutMs]);
|
||||
|
||||
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (error && clearError) {
|
||||
|
||||
@ -4,13 +4,13 @@ import { withGlobal } from '../../global';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
|
||||
import useWindowSize from '../../hooks/useWindowSize';
|
||||
import useOnChange from '../../hooks/useOnChange';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import styles from './ConfettiContainer.module.scss';
|
||||
|
||||
@ -37,7 +37,6 @@ interface Confetti {
|
||||
}
|
||||
|
||||
const CONFETTI_FADEOUT_TIMEOUT = 10000;
|
||||
const DEFAULT_CONFETTI_AMOUNT = IS_SINGLE_COLUMN_LAYOUT ? 50 : 100;
|
||||
const DEFAULT_CONFETTI_SIZE = 10;
|
||||
const CONFETTI_COLORS = ['#E8BC2C', '#D0049E', '#02CBFE', '#5723FD', '#FE8C27', '#6CB859'];
|
||||
|
||||
@ -48,12 +47,14 @@ const ConfettiContainer: FC<StateProps> = ({ confetti }) => {
|
||||
const isRafStartedRef = useRef(false);
|
||||
const windowSize = useWindowSize();
|
||||
const forceUpdate = useForceUpdate();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const defaultConfettiAmount = isMobile ? 50 : 100;
|
||||
const {
|
||||
lastConfettiTime, top, width, left, height,
|
||||
} = confetti || {};
|
||||
|
||||
function generateConfetti(w: number, h: number, amount = DEFAULT_CONFETTI_AMOUNT) {
|
||||
function generateConfetti(w: number, h: number, amount = defaultConfettiAmount) {
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const leftSide = i % 2;
|
||||
const pos = {
|
||||
|
||||
@ -8,7 +8,6 @@ import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import { withGlobal } from '../../global';
|
||||
import { selectChat } from '../../global/selectors';
|
||||
import { getCanPostInChat } from '../../global/helpers';
|
||||
import windowSize from '../../util/windowSize';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useSendMessageAction from '../../hooks/useSendMessageAction';
|
||||
@ -68,17 +67,6 @@ const GameModal: FC<OwnProps & StateProps> = ({ openedGame, gameTitle, canPost }
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [handleMessage]);
|
||||
|
||||
// Prevent refresh when rotating device
|
||||
useEffect(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
windowSize.disableRefresh();
|
||||
|
||||
return () => {
|
||||
windowSize.enableRefresh();
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="GameModal"
|
||||
|
||||
@ -2,12 +2,15 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import type { OwnProps } from './Main';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const MainAsync: FC = () => {
|
||||
const MainAsync: FC<OwnProps> = (props) => {
|
||||
const Main = useModuleLoader(Bundles.Main, 'Main');
|
||||
|
||||
return Main ? <Main /> : undefined;
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return Main ? <Main {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(MainAsync);
|
||||
|
||||
@ -107,6 +107,7 @@
|
||||
|
||||
#Main.left-column-open & {
|
||||
transform: translate3d(0, 0, 0);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#Main.history-animation-disabled & {
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
import { IS_ANDROID } from '../../util/environment';
|
||||
import {
|
||||
selectChatMessage,
|
||||
selectCurrentMessageList,
|
||||
selectIsForwardModalOpen,
|
||||
selectIsMediaViewerOpen,
|
||||
selectIsRightColumnShown,
|
||||
@ -27,8 +28,8 @@ import {
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
||||
import { processDeepLink } from '../../util/deeplink';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import { getAllNotificationsCount } from '../../util/folderManager';
|
||||
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
|
||||
import { fastRaf } from '../../util/schedulers';
|
||||
|
||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||
@ -40,7 +41,7 @@ import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||
import useInterval from '../../hooks/useInterval';
|
||||
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import StickerSetModal from '../common/StickerSetModal.async';
|
||||
import UnreadCount from '../common/UnreadCounter';
|
||||
@ -78,10 +79,15 @@ import AttachBotRecipientPicker from './AttachBotRecipientPicker.async';
|
||||
|
||||
import './Main.scss';
|
||||
|
||||
export interface OwnProps {
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
lastSyncTime?: number;
|
||||
isLeftColumnOpen: boolean;
|
||||
isMiddleColumnOpen: boolean;
|
||||
isRightColumnOpen: boolean;
|
||||
isMediaViewerOpen: boolean;
|
||||
isForwardModalOpen: boolean;
|
||||
@ -129,9 +135,11 @@ let notificationInterval: number | undefined;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
let DEBUG_isLogged = false;
|
||||
|
||||
const Main: FC<StateProps> = ({
|
||||
const Main: FC<OwnProps & StateProps> = ({
|
||||
lastSyncTime,
|
||||
isMobile,
|
||||
isLeftColumnOpen,
|
||||
isMiddleColumnOpen,
|
||||
isRightColumnOpen,
|
||||
isMediaViewerOpen,
|
||||
isForwardModalOpen,
|
||||
@ -198,6 +206,7 @@ const Main: FC<StateProps> = ({
|
||||
clearReceipt,
|
||||
checkAppVersion,
|
||||
openChat,
|
||||
toggleLeftColumn,
|
||||
} = getActions();
|
||||
|
||||
if (DEBUG && !DEBUG_isLogged) {
|
||||
@ -206,6 +215,15 @@ const Main: FC<StateProps> = ({
|
||||
console.log('>>> RENDER MAIN');
|
||||
}
|
||||
|
||||
// If you open the chat in the mobile version, switch to the desktop version, close the chat and
|
||||
// switch back to the mobile version, you get a blank screen
|
||||
const { isDesktop } = useAppLayout();
|
||||
useEffect(() => {
|
||||
if (!isMiddleColumnOpen && !isLeftColumnOpen && !isDesktop) {
|
||||
toggleLeftColumn();
|
||||
}
|
||||
}, [isDesktop, isLeftColumnOpen, isMiddleColumnOpen, toggleLeftColumn]);
|
||||
|
||||
useInterval(checkAppVersion, APP_OUTDATED_TIMEOUT_MS, true);
|
||||
|
||||
// Initial API calls
|
||||
@ -303,19 +321,6 @@ const Main: FC<StateProps> = ({
|
||||
}
|
||||
}, [lastSyncTime] as const);
|
||||
|
||||
// Prevent refresh by accidentally rotating device when listening to a voice chat
|
||||
useEffect(() => {
|
||||
if (!activeGroupCallId && !isPhoneCallActive) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
windowSize.disableRefresh();
|
||||
|
||||
return () => {
|
||||
windowSize.enableRefresh();
|
||||
};
|
||||
}, [activeGroupCallId, isPhoneCallActive]);
|
||||
|
||||
const leftColumnTransition = useShowTransition(
|
||||
isLeftColumnOpen, undefined, true, undefined, shouldSkipHistoryAnimations,
|
||||
);
|
||||
@ -442,8 +447,8 @@ const Main: FC<StateProps> = ({
|
||||
return (
|
||||
<div id="Main" className={className}>
|
||||
<LeftColumn />
|
||||
<MiddleColumn />
|
||||
<RightColumn />
|
||||
<MiddleColumn isMobile={isMobile} />
|
||||
<RightColumn isMobile={isMobile} />
|
||||
<MediaViewer isOpen={isMediaViewerOpen} />
|
||||
<ForwardRecipientPicker isOpen={isForwardModalOpen} />
|
||||
<DraftRecipientPicker requestedDraft={requestedDraft} />
|
||||
@ -510,8 +515,8 @@ function updatePageTitle(nextTitle: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => {
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { isMobile }): StateProps => {
|
||||
const {
|
||||
settings: {
|
||||
byKey: {
|
||||
@ -538,11 +543,13 @@ export default memo(withGlobal(
|
||||
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
||||
const gameTitle = gameMessage?.content.game?.title;
|
||||
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||
const { chatId } = selectCurrentMessageList(global) || {};
|
||||
|
||||
return {
|
||||
lastSyncTime,
|
||||
isLeftColumnOpen: global.isLeftColumnShown,
|
||||
isRightColumnOpen: selectIsRightColumnShown(global),
|
||||
isMiddleColumnOpen: Boolean(chatId),
|
||||
isRightColumnOpen: selectIsRightColumnShown(global, isMobile),
|
||||
isMediaViewerOpen: selectIsMediaViewerOpen(global),
|
||||
isForwardModalOpen: selectIsForwardModalOpen(global),
|
||||
hasNotifications: Boolean(global.notifications.length),
|
||||
|
||||
@ -9,8 +9,6 @@ import type { GlobalState } from '../../global/types';
|
||||
import type { ThemeKey } from '../../types';
|
||||
import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame';
|
||||
|
||||
import windowSize from '../../util/windowSize';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { TME_LINK_PREFIX } from '../../config';
|
||||
import { selectCurrentChat, selectTheme, selectUser } from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
@ -22,6 +20,7 @@ import useOnChange from '../../hooks/useOnChange';
|
||||
import useWebAppFrame from './hooks/useWebAppFrame';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import Button from '../ui/Button';
|
||||
@ -101,6 +100,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
const [confirmClose, setConfirmClose] = useState(false);
|
||||
const [isCloseModalOpen, openCloseModal, closeCloseModal] = useFlag(false);
|
||||
const [popupParams, setPopupParams] = useState<PopupOptions | undefined>();
|
||||
const { isMobile } = useAppLayout();
|
||||
const prevPopupParams = usePrevious(popupParams);
|
||||
const renderingPopupParams = popupParams || prevPopupParams;
|
||||
|
||||
@ -268,16 +268,6 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
}, ANIMATION_WAIT);
|
||||
}, [theme, sendTheme]);
|
||||
|
||||
// Prevent refresh when rotating device
|
||||
useEffect(() => {
|
||||
if (!isOpen) return undefined;
|
||||
windowSize.disableRefresh();
|
||||
|
||||
return () => {
|
||||
windowSize.enableRefresh();
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
useOnChange(([prevIsPaymentModalOpen]) => {
|
||||
if (isPaymentModalOpen === prevIsPaymentModalOpen) return;
|
||||
if (webApp?.slug && !isPaymentModalOpen && paymentStatus) {
|
||||
@ -330,7 +320,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
className={isMenuOpen ? 'active' : ''}
|
||||
@ -340,7 +330,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
<i className="icon-more" />
|
||||
</Button>
|
||||
);
|
||||
}, []);
|
||||
}, [isMobile]);
|
||||
|
||||
const backButtonClassName = buildClassName(
|
||||
'animated-close-icon',
|
||||
|
||||
@ -27,10 +27,9 @@ import {
|
||||
} from '../../global/selectors';
|
||||
import { stopCurrentAudio } from '../../util/audioPlayer';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { ANIMATION_END_DELAY } from '../../config';
|
||||
import { MEDIA_VIEWER_MEDIA_QUERY } from '../common/helpers/mediaDimensions';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
|
||||
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
||||
import { renderMessageText } from '../common/helpers/renderMessageText';
|
||||
@ -42,6 +41,7 @@ import { exitPictureInPictureIfNeeded } from '../../hooks/usePictureInPicture';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import { useMediaProps } from './hooks/useMediaProps';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import ReportModal from '../common/ReportModal';
|
||||
import Button from '../ui/Button';
|
||||
@ -97,6 +97,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const isOpen = Boolean(avatarOwner || mediaId);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
/* Animation */
|
||||
const animationKey = useRef<number>();
|
||||
@ -163,14 +164,14 @@ const MediaViewer: FC<StateProps> = ({
|
||||
}, [isVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
document.body.classList.toggle('is-media-viewer-open', isOpen);
|
||||
}
|
||||
// Disable user selection if media viewer is open, to prevent accidental text selection
|
||||
if (IS_TOUCH_ENV) {
|
||||
document.body.classList.toggle('no-selection', isOpen);
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [isMobile, isOpen]);
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
useEffect(() => {
|
||||
@ -220,7 +221,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
const handleFooterClick = useCallback(() => {
|
||||
handleClose();
|
||||
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
setTimeout(() => {
|
||||
toggleChatInfo(false, { forceSyncOnIOs: true });
|
||||
focusMessage({ chatId, threadId, mediaId });
|
||||
@ -228,7 +229,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
} else {
|
||||
focusMessage({ chatId, threadId, mediaId });
|
||||
}
|
||||
}, [handleClose, chatId, threadId, focusMessage, toggleChatInfo, mediaId]);
|
||||
}, [handleClose, isMobile, chatId, threadId, focusMessage, toggleChatInfo, mediaId]);
|
||||
|
||||
const handleForward = useCallback(() => {
|
||||
openForwardMenu({
|
||||
@ -259,18 +260,6 @@ const MediaViewer: FC<StateProps> = ({
|
||||
}
|
||||
}, [isGif, isVideo]);
|
||||
|
||||
// Prevent refresh when rotating device to watch a video
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return undefined;
|
||||
}
|
||||
windowSize.disableRefresh();
|
||||
|
||||
return () => {
|
||||
windowSize.enableRefresh();
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const getMediaId = useCallback((fromId?: number, direction?: number): number | undefined => {
|
||||
if (fromId === undefined) return undefined;
|
||||
const index = mediaIds.indexOf(fromId);
|
||||
@ -317,7 +306,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
noCloseTransition={shouldSkipHistoryAnimations}
|
||||
>
|
||||
<div className="media-viewer-head" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{IS_SINGLE_COLUMN_LAYOUT && (
|
||||
{isMobile && (
|
||||
<Button
|
||||
className="media-viewer-close"
|
||||
round
|
||||
|
||||
@ -19,12 +19,12 @@ import {
|
||||
selectCurrentMessageList,
|
||||
selectIsChatProtected,
|
||||
} from '../../global/selectors';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { getMessageMediaFormat, getMessageMediaHash, isUserId } from '../../global/helpers';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import DropdownMenu from '../ui/DropdownMenu';
|
||||
@ -87,6 +87,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
setZoomLevelChange,
|
||||
}) => {
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(false);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const {
|
||||
downloadMessageMedia,
|
||||
@ -201,7 +202,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
const menuItems: MenuItemProps[] = [];
|
||||
if (!message?.isForwardingAllowed && !isChatProtected) {
|
||||
menuItems.push({
|
||||
|
||||
@ -8,7 +8,7 @@ import type {
|
||||
import type { AnimationLevel } from '../../types';
|
||||
import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
||||
import {
|
||||
selectChat, selectChatMessage, selectIsMessageProtected, selectScheduledMessage, selectUser,
|
||||
} from '../../global/selectors';
|
||||
@ -17,6 +17,7 @@ import { renderMessageText } from '../common/helpers/renderMessageText';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { useMediaProps } from './hooks/useMediaProps';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Spinner from '../ui/Spinner';
|
||||
import MediaViewerFooter from './MediaViewerFooter';
|
||||
@ -94,6 +95,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
||||
});
|
||||
|
||||
const isOpen = Boolean(avatarOwner || mediaId);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const toggleControls = useCallback((isVisible) => {
|
||||
setControlsVisible?.(isVisible);
|
||||
@ -106,7 +108,7 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
||||
{renderPhoto(
|
||||
bestData,
|
||||
calculateMediaViewerDimensions(dimensions, false),
|
||||
!IS_SINGLE_COLUMN_LAYOUT && !isProtected,
|
||||
!isMobile && !isProtected,
|
||||
isProtected,
|
||||
)}
|
||||
</div>
|
||||
@ -150,13 +152,13 @@ const MediaViewerContent: FC<OwnProps & StateProps> = (props) => {
|
||||
{isPhoto && renderPhoto(
|
||||
bestData,
|
||||
message && calculateMediaViewerDimensions(dimensions!, hasFooter),
|
||||
!IS_SINGLE_COLUMN_LAYOUT && !isProtected,
|
||||
!isMobile && !isProtected,
|
||||
isProtected,
|
||||
)}
|
||||
{isVideo && (!isActive ? renderVideoPreview(
|
||||
bestImageData,
|
||||
message && calculateMediaViewerDimensions(dimensions!, hasFooter, true),
|
||||
!IS_SINGLE_COLUMN_LAYOUT && !isProtected,
|
||||
!isMobile && !isProtected,
|
||||
isProtected,
|
||||
) : (
|
||||
<VideoPlayer
|
||||
|
||||
@ -3,10 +3,10 @@ import React, { useEffect, useState } from '../../lib/teact/teact';
|
||||
|
||||
import type { TextPart } from '../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { REM } from '../common/helpers/mediaDimensions';
|
||||
import { throttle } from '../../util/schedulers';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { REM } from '../common/helpers/mediaDimensions';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import './MediaViewerFooter.scss';
|
||||
|
||||
@ -24,6 +24,8 @@ const MediaViewerFooter: FC<OwnProps> = ({
|
||||
text = '', isHidden, isForVideo, onClick, isProtected,
|
||||
}) => {
|
||||
const [isMultiline, setIsMultiline] = useState(false);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useEffect(() => {
|
||||
const footerContent = document.querySelector('.MediaViewerFooter .media-text') as HTMLDivElement | null;
|
||||
|
||||
@ -61,7 +63,7 @@ const MediaViewerFooter: FC<OwnProps> = ({
|
||||
return (
|
||||
<div className={classNames} onClick={stopEvent}>
|
||||
{Boolean(text) && (
|
||||
<div className="media-viewer-footer-content" onClick={!IS_SINGLE_COLUMN_LAYOUT ? onClick : undefined}>
|
||||
<div className="media-viewer-footer-content" onClick={!isMobile ? onClick : undefined}>
|
||||
<p className={`media-text custom-scroll ${isMultiline ? 'multiline' : ''}`} dir="auto">{text}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -5,7 +5,6 @@ import { getActions, withGlobal } from '../../global';
|
||||
import type { ApiChat, ApiMessage, ApiUser } from '../../api/types';
|
||||
import type { AnimationLevel } from '../../types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { getSenderTitle, isUserId } from '../../global/helpers';
|
||||
import { formatMediaDateTime } from '../../util/dateFormat';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
@ -16,6 +15,7 @@ import {
|
||||
selectUser,
|
||||
} from '../../global/selectors';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Avatar from '../common/Avatar';
|
||||
|
||||
@ -49,10 +49,12 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
||||
toggleChatInfo,
|
||||
} = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleFocusMessage = useCallback(() => {
|
||||
closeMediaViewer();
|
||||
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
setTimeout(() => {
|
||||
toggleChatInfo(false, { forceSyncOnIOs: true });
|
||||
focusMessage({ chatId, messageId });
|
||||
@ -60,7 +62,7 @@ const SenderInfo: FC<OwnProps & StateProps> = ({
|
||||
} else {
|
||||
focusMessage({ chatId, messageId });
|
||||
}
|
||||
}, [chatId, focusMessage, toggleChatInfo, messageId, closeMediaViewer]);
|
||||
}, [chatId, isMobile, focusMessage, toggleChatInfo, messageId, closeMediaViewer]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
|
||||
@ -6,16 +6,15 @@ import { getActions } from '../../global';
|
||||
|
||||
import type { ApiDimensions } from '../../api/types';
|
||||
|
||||
import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/environment';
|
||||
import safePlay from '../../util/safePlay';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import useBuffering from '../../hooks/useBuffering';
|
||||
import useFullscreen from '../../hooks/useFullscreen';
|
||||
import usePictureInPicture from '../../hooks/usePictureInPicture';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useVideoCleanup from '../../hooks/useVideoCleanup';
|
||||
import {
|
||||
IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV, IS_YA_BROWSER,
|
||||
} from '../../util/environment';
|
||||
import safePlay from '../../util/safePlay';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||
@ -77,6 +76,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
||||
const [isPlaying, setIsPlaying] = useState(!IS_TOUCH_ENV || !IS_IOS);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreen(videoRef, setIsPlaying);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleEnterFullscreen = useCallback(() => {
|
||||
// Yandex browser doesn't support PIP when video is hidden
|
||||
@ -159,7 +159,10 @@ const VideoPlayer: FC<OwnProps> = ({
|
||||
}, [isPlaying]);
|
||||
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLVideoElement, MouseEvent>) => {
|
||||
if (isClickDisabled) return;
|
||||
if (isClickDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldCloseOnClick) {
|
||||
onClose(e);
|
||||
} else {
|
||||
@ -239,7 +242,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
||||
<div
|
||||
onContextMenu={stopEvent}
|
||||
onDoubleClick={!IS_TOUCH_ENV ? handleFullscreenChange : undefined}
|
||||
onClick={!IS_SINGLE_COLUMN_LAYOUT ? togglePlayState : undefined}
|
||||
onClick={!isMobile ? togglePlayState : undefined}
|
||||
className="protector"
|
||||
/>
|
||||
)}
|
||||
@ -255,7 +258,7 @@ const VideoPlayer: FC<OwnProps> = ({
|
||||
style={videoStyle}
|
||||
onPlay={() => setIsPlaying(true)}
|
||||
onEnded={handleEnded}
|
||||
onClick={!IS_SINGLE_COLUMN_LAYOUT && !isFullscreen ? handleClick : undefined}
|
||||
onClick={!isMobile && !isFullscreen ? handleClick : undefined}
|
||||
onDoubleClick={!IS_TOUCH_ENV ? handleFullscreenChange : undefined}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...bufferingHandlers}
|
||||
|
||||
@ -2,15 +2,17 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
useEffect, useRef, useCallback, useMemo,
|
||||
} from '../../lib/teact/teact';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||
|
||||
import { IS_IOS, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { formatMediaDuration } from '../../util/dateFormat';
|
||||
import { formatFileSize } from '../../util/textFormat';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||
import { captureEvents } from '../../util/captureEvents';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import RangeSlider from '../ui/RangeSlider';
|
||||
@ -89,6 +91,8 @@ const VideoPlayerControls: FC<OwnProps> = ({
|
||||
const isSeekingRef = useRef<boolean>(false);
|
||||
const isSeeking = isSeekingRef.current;
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useEffect(() => {
|
||||
if (!IS_TOUCH_ENV) return undefined;
|
||||
let timeout: number | undefined;
|
||||
@ -170,7 +174,7 @@ const VideoPlayerControls: FC<OwnProps> = ({
|
||||
<Button
|
||||
ariaLabel={lang('AccActionPlay')}
|
||||
size="tiny"
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
color="translucent-white"
|
||||
className="play"
|
||||
round
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import React, {
|
||||
useCallback, useEffect, useMemo, useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import React, { useCallback, useMemo, useRef } from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
@ -9,7 +7,7 @@ import type {
|
||||
ApiAudio, ApiChat, ApiMessage, ApiUser,
|
||||
} from '../../api/types';
|
||||
|
||||
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { IS_IOS, IS_TOUCH_ENV } from '../../util/environment';
|
||||
import { PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION } from '../../config';
|
||||
|
||||
import * as mediaLoader from '../../util/mediaLoader';
|
||||
@ -20,10 +18,10 @@ import { selectChat, selectSender } from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { makeTrackId } from '../../util/audioPlayer';
|
||||
import { clearMediaSession } from '../../util/mediaSession';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
import useAudioPlayer from '../../hooks/useAudioPlayer';
|
||||
import useMessageMediaMetadata from '../../hooks/useMessageMediaMetadata';
|
||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||
@ -80,6 +78,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
const { audio, voice, video } = getMessageContent(message);
|
||||
const isVoice = Boolean(voice || video);
|
||||
const shouldRenderPlaybackButton = isVoice || (audio?.duration || 0) > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION;
|
||||
@ -113,20 +112,6 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
true,
|
||||
);
|
||||
|
||||
// Prevent refresh by accidentally rotating device when listening to a voice message
|
||||
const isVoicePlaying = isVoice && isPlaying;
|
||||
useEffect(() => {
|
||||
if (!isVoicePlaying) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
windowSize.disableRefresh();
|
||||
|
||||
return () => {
|
||||
windowSize.enableRefresh();
|
||||
};
|
||||
}, [isVoicePlaying]);
|
||||
|
||||
const {
|
||||
isContextMenuOpen,
|
||||
handleBeforeContextMenu, handleContextMenu,
|
||||
@ -178,7 +163,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel="Playback Rate"
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
onClick={handlePlaybackClick}
|
||||
onMouseDown={handleBeforeContextMenu}
|
||||
onContextMenu={handleContextMenu}
|
||||
@ -188,7 +173,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
}, [handleBeforeContextMenu, handleContextMenu, handlePlaybackClick, playbackRate]);
|
||||
}, [handleBeforeContextMenu, handleContextMenu, handlePlaybackClick, isMobile, playbackRate]);
|
||||
|
||||
const volumeIcon = useMemo(() => {
|
||||
if (volume === 0 || isMuted) return 'icon-muted';
|
||||
@ -210,7 +195,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
className="player-button"
|
||||
@ -222,7 +207,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
</Button>
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
className={buildClassName('toggle-play', 'player-button', isPlaying ? 'pause' : 'play')}
|
||||
@ -234,7 +219,7 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
|
||||
</Button>
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
className="player-button"
|
||||
|
||||
@ -13,9 +13,7 @@ import type { IAnchorPosition } from '../../types';
|
||||
import { ManagementScreens } from '../../types';
|
||||
|
||||
import { ANIMATION_LEVEL_MIN } from '../../config';
|
||||
import {
|
||||
ARE_CALLS_SUPPORTED, IS_PWA, IS_SINGLE_COLUMN_LAYOUT,
|
||||
} from '../../util/environment';
|
||||
import { ARE_CALLS_SUPPORTED, IS_PWA } from '../../util/environment';
|
||||
import {
|
||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||
} from '../../global/helpers';
|
||||
@ -40,6 +38,7 @@ interface OwnProps {
|
||||
messageListType: MessageListType;
|
||||
canExpandActions: boolean;
|
||||
withForumActions?: boolean;
|
||||
isMobile?: boolean;
|
||||
onTopicSearch?: NoneToVoidFunction;
|
||||
}
|
||||
|
||||
@ -70,6 +69,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
threadId,
|
||||
noMenu,
|
||||
isMobile,
|
||||
isChannel,
|
||||
canStartBot,
|
||||
canRestartBot,
|
||||
@ -149,7 +149,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
|
||||
openLocalTextSearch();
|
||||
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
// iOS requires synchronous focus on user event.
|
||||
const searchInput = document.querySelector<HTMLInputElement>('#MobileSearch input')!;
|
||||
searchInput.focus();
|
||||
@ -161,7 +161,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
} else {
|
||||
setTimeout(setFocusInSearchInput, SEARCH_FOCUS_DELAY_MS);
|
||||
}
|
||||
}, [noAnimation, onTopicSearch, openLocalTextSearch, withForumActions]);
|
||||
}, [isMobile, noAnimation, onTopicSearch, openLocalTextSearch, withForumActions]);
|
||||
|
||||
const handleAsMessagesClick = useCallback(() => {
|
||||
openChat({ id: chatId, threadId: MAIN_THREAD_ID });
|
||||
@ -186,7 +186,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return (
|
||||
<div className="HeaderActions">
|
||||
{!IS_SINGLE_COLUMN_LAYOUT && (
|
||||
{!isMobile && (
|
||||
<>
|
||||
{canExpandActions && !shouldSendJoinRequest && (canSubscribe || shouldJoinToSend) && (
|
||||
<Button
|
||||
@ -272,7 +272,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
ref={menuButtonRef}
|
||||
className={isMenuOpen ? 'active' : ''}
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
disabled={noMenu}
|
||||
@ -287,7 +287,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
threadId={threadId}
|
||||
isOpen={isMenuOpen}
|
||||
anchor={menuPosition}
|
||||
withExtraActions={IS_SINGLE_COLUMN_LAYOUT || !canExpandActions}
|
||||
withExtraActions={isMobile || !canExpandActions}
|
||||
isChannel={isChannel}
|
||||
canStartBot={canStartBot}
|
||||
canRestartBot={canRestartBot}
|
||||
@ -314,7 +314,9 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, messageListType }): StateProps => {
|
||||
(global, {
|
||||
chatId, threadId, messageListType, isMobile,
|
||||
}): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const isChannel = Boolean(chat && isChatChannel(chat));
|
||||
|
||||
@ -328,7 +330,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
|
||||
const isDiscussionThread = messageListType === 'thread' && threadId !== MAIN_THREAD_ID;
|
||||
const isRightColumnShown = selectIsRightColumnShown(global);
|
||||
const isRightColumnShown = selectIsRightColumnShown(global, isMobile);
|
||||
|
||||
const canRestartBot = Boolean(bot && selectIsUserBlocked(global, bot.id));
|
||||
const canStartBot = !canRestartBot && Boolean(selectIsChatBotNotStarted(global, chatId));
|
||||
|
||||
@ -9,7 +9,6 @@ import type { IAnchorPosition } from '../../types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import { REPLIES_USER_ID } from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { disableScrolling, enableScrolling } from '../../util/scrollLock';
|
||||
import {
|
||||
selectChat,
|
||||
@ -34,6 +33,7 @@ import {
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Portal from '../ui/Portal';
|
||||
import Menu from '../ui/Menu';
|
||||
@ -163,6 +163,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
openChat,
|
||||
} = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
||||
@ -361,7 +362,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
{lang('lng_forum_topic_edit')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT && !withForumActions && isForum && !isTopic && (
|
||||
{isMobile && !withForumActions && isForum && !isTopic && (
|
||||
<MenuItem
|
||||
icon="forums"
|
||||
onClick={handleViewAsTopicsClick}
|
||||
@ -418,7 +419,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
{lang('AddContact')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT && canCall && (
|
||||
{isMobile && canCall && (
|
||||
<MenuItem
|
||||
icon="phone"
|
||||
onClick={handleCall}
|
||||
@ -434,7 +435,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
{lang('VideoCall')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT && canSearch && (
|
||||
{isMobile && canSearch && (
|
||||
<MenuItem
|
||||
icon="search"
|
||||
onClick={handleSearch}
|
||||
|
||||
@ -25,11 +25,7 @@ import {
|
||||
ANIMATION_LEVEL_MIN,
|
||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||
} from '../../config';
|
||||
import {
|
||||
IS_SINGLE_COLUMN_LAYOUT,
|
||||
IS_TABLET_COLUMN_LAYOUT,
|
||||
MASK_IMAGE_DISABLED,
|
||||
} from '../../util/environment';
|
||||
import { MASK_IMAGE_DISABLED } from '../../util/environment';
|
||||
import { DropAreaState } from './composer/DropArea';
|
||||
import {
|
||||
selectChat,
|
||||
@ -53,6 +49,7 @@ import {
|
||||
isChatSuperGroup,
|
||||
isUserId,
|
||||
} from '../../global/helpers';
|
||||
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import useCustomBackground from '../../hooks/useCustomBackground';
|
||||
@ -63,7 +60,7 @@ import useHistoryBack from '../../hooks/useHistoryBack';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import useOnChange from '../../hooks/useOnChange';
|
||||
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import MiddleHeader from './MiddleHeader';
|
||||
@ -82,6 +79,10 @@ import GiftPremiumModal from '../main/premium/GiftPremiumModal.async';
|
||||
import './MiddleColumn.scss';
|
||||
import styles from './MiddleColumn.module.scss';
|
||||
|
||||
interface OwnProps {
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
type StateProps = {
|
||||
chatId?: string;
|
||||
threadId?: number;
|
||||
@ -103,7 +104,7 @@ type StateProps = {
|
||||
isLeftColumnShown?: boolean;
|
||||
isRightColumnShown?: boolean;
|
||||
isBackgroundBlurred?: boolean;
|
||||
isMobileSearchActive?: boolean;
|
||||
hasCurrentTextSearch?: boolean;
|
||||
isSelectModeActive?: boolean;
|
||||
isSeenByModalOpen: boolean;
|
||||
isReactorListModalOpen: boolean;
|
||||
@ -123,16 +124,15 @@ type StateProps = {
|
||||
lastSyncTime?: number;
|
||||
};
|
||||
|
||||
const CLOSE_ANIMATION_DURATION = IS_SINGLE_COLUMN_LAYOUT ? 450 + ANIMATION_END_DELAY : undefined;
|
||||
|
||||
function isImage(item: DataTransferItem) {
|
||||
return item.kind === 'file' && item.type && SUPPORTED_IMAGE_CONTENT_TYPES.has(item.type);
|
||||
}
|
||||
|
||||
const MiddleColumn: FC<StateProps> = ({
|
||||
const MiddleColumn: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
threadId,
|
||||
messageListType,
|
||||
isMobile,
|
||||
chat,
|
||||
replyingToId,
|
||||
isPrivate,
|
||||
@ -149,7 +149,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
isLeftColumnShown,
|
||||
isRightColumnShown,
|
||||
isBackgroundBlurred,
|
||||
isMobileSearchActive,
|
||||
hasCurrentTextSearch,
|
||||
isSelectModeActive,
|
||||
isSeenByModalOpen,
|
||||
isReactorListModalOpen,
|
||||
@ -184,6 +184,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
const { isTablet } = useAppLayout();
|
||||
|
||||
const lang = useLang();
|
||||
const [dropAreaState, setDropAreaState] = useState(DropAreaState.None);
|
||||
@ -191,6 +192,8 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
const [isNotchShown, setIsNotchShown] = useState<boolean | undefined>();
|
||||
const [isUnpinModalOpen, setIsUnpinModalOpen] = useState(false);
|
||||
|
||||
const isMobileSearchActive = isMobile && hasCurrentTextSearch;
|
||||
const closeAnimationDuration = isMobile ? 450 + ANIMATION_END_DELAY : undefined;
|
||||
const hasTools = hasPinnedOrAudioPlayer && (
|
||||
windowWidth < MOBILE_SCREEN_MAX_WIDTH
|
||||
|| (
|
||||
@ -202,19 +205,19 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
)
|
||||
);
|
||||
|
||||
const renderingChatId = usePrevDuringAnimation(chatId, CLOSE_ANIMATION_DURATION);
|
||||
const renderingThreadId = usePrevDuringAnimation(threadId, CLOSE_ANIMATION_DURATION);
|
||||
const renderingMessageListType = usePrevDuringAnimation(messageListType, CLOSE_ANIMATION_DURATION);
|
||||
const renderingCanSubscribe = usePrevDuringAnimation(canSubscribe, CLOSE_ANIMATION_DURATION);
|
||||
const renderingCanStartBot = usePrevDuringAnimation(canStartBot, CLOSE_ANIMATION_DURATION);
|
||||
const renderingCanRestartBot = usePrevDuringAnimation(canRestartBot, CLOSE_ANIMATION_DURATION);
|
||||
const renderingCanPost = usePrevDuringAnimation(canPost, CLOSE_ANIMATION_DURATION)
|
||||
const renderingChatId = usePrevDuringAnimation(chatId, closeAnimationDuration);
|
||||
const renderingThreadId = usePrevDuringAnimation(threadId, closeAnimationDuration);
|
||||
const renderingMessageListType = usePrevDuringAnimation(messageListType, closeAnimationDuration);
|
||||
const renderingCanSubscribe = usePrevDuringAnimation(canSubscribe, closeAnimationDuration);
|
||||
const renderingCanStartBot = usePrevDuringAnimation(canStartBot, closeAnimationDuration);
|
||||
const renderingCanRestartBot = usePrevDuringAnimation(canRestartBot, closeAnimationDuration);
|
||||
const renderingCanPost = usePrevDuringAnimation(canPost, closeAnimationDuration)
|
||||
&& !renderingCanRestartBot && !renderingCanStartBot && !renderingCanSubscribe;
|
||||
const renderingHasTools = usePrevDuringAnimation(hasTools, CLOSE_ANIMATION_DURATION);
|
||||
const renderingIsFabShown = usePrevDuringAnimation(isFabShown, CLOSE_ANIMATION_DURATION);
|
||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, CLOSE_ANIMATION_DURATION);
|
||||
const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, CLOSE_ANIMATION_DURATION);
|
||||
const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, CLOSE_ANIMATION_DURATION);
|
||||
const renderingHasTools = usePrevDuringAnimation(hasTools, closeAnimationDuration);
|
||||
const renderingIsFabShown = usePrevDuringAnimation(isFabShown, closeAnimationDuration);
|
||||
const renderingIsChannel = usePrevDuringAnimation(isChannel, closeAnimationDuration);
|
||||
const renderingShouldJoinToSend = usePrevDuringAnimation(shouldJoinToSend, closeAnimationDuration);
|
||||
const renderingShouldSendJoinRequest = usePrevDuringAnimation(shouldSendJoinRequest, closeAnimationDuration);
|
||||
|
||||
const prevTransitionKey = usePrevious(currentTransitionKey);
|
||||
|
||||
@ -227,6 +230,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
currentTransitionKey,
|
||||
prevTransitionKey,
|
||||
chatId,
|
||||
isMobile,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -414,7 +418,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
--theme-background-color:
|
||||
${backgroundColor || (theme === 'dark' ? DARK_THEME_BG_COLOR : LIGHT_THEME_BG_COLOR)};
|
||||
`}
|
||||
onClick={(IS_TABLET_COLUMN_LAYOUT && isLeftColumnShown) ? handleTabletFocus : undefined}
|
||||
onClick={(isTablet && isLeftColumnShown) ? handleTabletFocus : undefined}
|
||||
>
|
||||
<div
|
||||
className={bgClassName}
|
||||
@ -429,6 +433,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
threadId={renderingThreadId}
|
||||
messageListType={renderingMessageListType}
|
||||
isReady={isReady}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
<Transition
|
||||
name={shouldSkipHistoryAnimations ? 'none' : animationLevel === ANIMATION_LEVEL_MAX ? 'slide' : 'fade'}
|
||||
@ -459,6 +464,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
dropAreaState={dropAreaState}
|
||||
onDropHide={handleHideDropArea}
|
||||
isReady={isReady}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
)}
|
||||
{isPinnedMessageList && (
|
||||
@ -484,7 +490,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT
|
||||
{isMobile
|
||||
&& (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest)) && (
|
||||
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
@ -498,7 +504,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT && renderingShouldSendJoinRequest && (
|
||||
{isMobile && renderingShouldSendJoinRequest && (
|
||||
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
size="tiny"
|
||||
@ -511,7 +517,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT && renderingCanStartBot && (
|
||||
{isMobile && renderingCanStartBot && (
|
||||
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
size="tiny"
|
||||
@ -524,7 +530,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT && renderingCanRestartBot && (
|
||||
{isMobile && renderingCanRestartBot && (
|
||||
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
size="tiny"
|
||||
@ -553,7 +559,7 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
withExtraShift={withExtraShift}
|
||||
/>
|
||||
</div>
|
||||
{IS_SINGLE_COLUMN_LAYOUT && <MobileSearch isActive={Boolean(isMobileSearchActive)} />}
|
||||
{isMobile && <MobileSearch isActive={Boolean(isMobileSearchActive)} />}
|
||||
</>
|
||||
)}
|
||||
{chatId && (
|
||||
@ -579,8 +585,8 @@ const MiddleColumn: FC<StateProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => {
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { isMobile }): StateProps => {
|
||||
const theme = selectTheme(global);
|
||||
const {
|
||||
isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor,
|
||||
@ -598,9 +604,9 @@ export default memo(withGlobal(
|
||||
backgroundColor,
|
||||
patternColor,
|
||||
isLeftColumnShown,
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
||||
isBackgroundBlurred,
|
||||
isMobileSearchActive: Boolean(IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global)),
|
||||
hasCurrentTextSearch: Boolean(selectCurrentTextSearch(global)),
|
||||
isSelectModeActive: selectIsInSelectMode(global),
|
||||
isSeenByModalOpen: Boolean(global.seenByModal),
|
||||
isReactorListModalOpen: Boolean(global.reactorModal),
|
||||
@ -681,8 +687,9 @@ function useIsReady(
|
||||
currentTransitionKey?: number,
|
||||
prevTransitionKey?: number,
|
||||
chatId?: string,
|
||||
isMobile?: boolean,
|
||||
) {
|
||||
const [isReady, setIsReady] = useState(!IS_SINGLE_COLUMN_LAYOUT);
|
||||
const [isReady, setIsReady] = useState(!isMobile);
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const willSwitchMessageList = prevTransitionKey !== undefined && prevTransitionKey !== currentTransitionKey;
|
||||
|
||||
@ -20,7 +20,6 @@ import {
|
||||
SAFE_SCREEN_WIDTH_FOR_CHAT_INFO,
|
||||
SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN,
|
||||
} from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import {
|
||||
getChatTitle, getMessageKey, getSenderTitle, isChatChannel, isChatSuperGroup, isUserId,
|
||||
} from '../../global/helpers';
|
||||
@ -50,6 +49,7 @@ import buildClassName from '../../util/buildClassName';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useConnectionStatus from '../../hooks/useConnectionStatus';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||
import GroupChatInfo from '../common/GroupChatInfo';
|
||||
@ -73,6 +73,7 @@ type OwnProps = {
|
||||
threadId: number;
|
||||
messageListType: MessageListType;
|
||||
isReady?: boolean;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -101,6 +102,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
threadId,
|
||||
messageListType,
|
||||
isReady,
|
||||
isMobile,
|
||||
pinnedMessageIds,
|
||||
messagesById,
|
||||
canUnpin,
|
||||
@ -133,6 +135,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const lang = useLang();
|
||||
const isBackButtonActive = useRef(true);
|
||||
const { isTablet } = useAppLayout();
|
||||
|
||||
const [pinnedMessageIndex, setPinnedMessageIndex] = useState(0);
|
||||
const pinnedMessageId = Array.isArray(pinnedMessageIds) ? pinnedMessageIds[pinnedMessageIndex] : pinnedMessageIds;
|
||||
@ -160,7 +163,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
|
||||
const isLeftColumnHideable = windowWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN;
|
||||
const shouldShowCloseButton = IS_TABLET_COLUMN_LAYOUT && isLeftColumnShown;
|
||||
const shouldShowCloseButton = isTablet && isLeftColumnShown;
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const componentRef = useRef<HTMLDivElement>(null);
|
||||
@ -198,7 +201,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
|
||||
// Workaround for missing UI when quickly clicking the Back button
|
||||
isBackButtonActive.current = false;
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||
messageInput?.blur();
|
||||
}
|
||||
@ -210,7 +213,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
if (messageListType === 'thread' && currentTransitionKey === 0) {
|
||||
if (IS_SINGLE_COLUMN_LAYOUT || shouldShowCloseButton) {
|
||||
if (isMobile || shouldShowCloseButton) {
|
||||
e.stopPropagation(); // Stop propagation to prevent chat re-opening on tablets
|
||||
openChat({ id: undefined }, { forceOnHeavyAnimation: true });
|
||||
} else {
|
||||
@ -226,7 +229,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
setBackButtonActive();
|
||||
}, [
|
||||
messageListType, currentTransitionKey, isSelectModeActive, openPreviousChat, shouldShowCloseButton,
|
||||
openChat, toggleLeftColumn, exitMessageSelectMode, setBackButtonActive,
|
||||
openChat, toggleLeftColumn, exitMessageSelectMode, setBackButtonActive, isMobile,
|
||||
]);
|
||||
|
||||
const canToolsCollideWithChatInfo = (
|
||||
@ -386,7 +389,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const isAudioPlayerRendered = Boolean(shouldRenderAudioPlayer && renderingAudioMessage);
|
||||
const isPinnedMessagesFullWidth = isAudioPlayerRendered
|
||||
|| (!IS_SINGLE_COLUMN_LAYOUT && hasButtonInHeader && windowWidth < MAX_SCREEN_WIDTH_FOR_EXPAND_PINNED_MESSAGES);
|
||||
|| (!isMobile && hasButtonInHeader && windowWidth < MAX_SCREEN_WIDTH_FOR_EXPAND_PINNED_MESSAGES);
|
||||
|
||||
return (
|
||||
<div className="MiddleHeader" ref={componentRef}>
|
||||
@ -442,6 +445,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
chatId={chatId}
|
||||
threadId={threadId}
|
||||
messageListType={messageListType}
|
||||
isMobile={isMobile}
|
||||
canExpandActions={!isAudioPlayerRendered}
|
||||
/>
|
||||
</div>
|
||||
@ -450,7 +454,9 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, messageListType }): StateProps => {
|
||||
(global, {
|
||||
chatId, threadId, messageListType, isMobile,
|
||||
}): StateProps => {
|
||||
const { isLeftColumnShown, lastSyncTime, shouldSkipHistoryAnimations } = global;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
@ -484,7 +490,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const state: StateProps = {
|
||||
typingStatus,
|
||||
isLeftColumnShown,
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
||||
isSelectModeActive: selectIsInSelectMode(global),
|
||||
audioMessage,
|
||||
chat,
|
||||
|
||||
@ -14,7 +14,6 @@ import {
|
||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||
} from '../../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems';
|
||||
import { getHtmlTextLength } from './helpers/getHtmlTextLength';
|
||||
@ -117,6 +116,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
const hideTimeoutRef = useRef<number>();
|
||||
const prevAttachments = usePrevious(attachments);
|
||||
const renderingAttachments = attachments.length ? attachments : prevAttachments;
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const [shouldSendCompressed, setShouldSendCompressed] = useState(
|
||||
shouldSuggestCompression ?? attachmentSettings.shouldCompress,
|
||||
@ -312,7 +312,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
className={isMenuOpen ? 'active' : ''}
|
||||
@ -322,7 +322,7 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
||||
<i className="icon-more" />
|
||||
</Button>
|
||||
);
|
||||
}, []);
|
||||
}, [isMobile]);
|
||||
|
||||
const leftChars = useMemo(() => {
|
||||
const captionLeftBeforeLimit = captionLimit - getHtmlTextLength(caption);
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useCallback } from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiBotCommand } from '../../../api/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import useMouseInside from '../../../hooks/useMouseInside';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Menu from '../../ui/Menu';
|
||||
import BotCommand from './BotCommand';
|
||||
|
||||
import './BotCommandMenu.scss';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
@ -22,8 +23,9 @@ const BotCommandMenu: FC<OwnProps> = ({
|
||||
isOpen, botCommands, onClose,
|
||||
}) => {
|
||||
const { sendBotCommand } = getActions();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, IS_SINGLE_COLUMN_LAYOUT);
|
||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile);
|
||||
|
||||
const handleClick = useCallback((botCommand: ApiBotCommand) => {
|
||||
sendBotCommand({
|
||||
|
||||
@ -30,7 +30,7 @@ import {
|
||||
SEND_MESSAGE_ACTION_INTERVAL,
|
||||
EDITABLE_INPUT_CSS_SELECTOR, MAX_UPLOAD_FILEPART_SIZE,
|
||||
} from '../../../config';
|
||||
import { IS_VOICE_RECORDING_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, IS_IOS } from '../../../util/environment';
|
||||
import { IS_VOICE_RECORDING_SUPPORTED, IS_IOS } from '../../../util/environment';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import {
|
||||
selectChat,
|
||||
@ -132,6 +132,7 @@ type OwnProps = {
|
||||
messageListType: MessageListType;
|
||||
dropAreaState: string;
|
||||
isReady: boolean;
|
||||
isMobile?: boolean;
|
||||
onDropHide: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
@ -213,6 +214,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
shouldSchedule,
|
||||
canScheduleUntilOnline,
|
||||
isReady,
|
||||
isMobile,
|
||||
onDropHide,
|
||||
editingMessage,
|
||||
chatId,
|
||||
@ -548,13 +550,16 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
closeMentionTooltip();
|
||||
closeEmojiTooltip();
|
||||
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
// @optimization
|
||||
setTimeout(() => closeSymbolMenu(), SENDING_ANIMATION_DURATION);
|
||||
} else {
|
||||
closeSymbolMenu();
|
||||
}
|
||||
}, [closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip, closeSymbolMenu, setHtml]);
|
||||
}, [
|
||||
closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip,
|
||||
closeSymbolMenu, setHtml, isMobile,
|
||||
]);
|
||||
|
||||
// Handle chat change (ref is used to avoid redundant effect calls)
|
||||
const stopRecordingVoiceRef = useRef<typeof stopRecordingVoice>();
|
||||
@ -1015,7 +1020,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const handleSymbolMenuOpen = useCallback(() => {
|
||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT || messageInput !== document.activeElement) {
|
||||
if (!isMobile || messageInput !== document.activeElement) {
|
||||
openSymbolMenu();
|
||||
return;
|
||||
}
|
||||
@ -1025,12 +1030,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
closeBotCommandMenu();
|
||||
openSymbolMenu();
|
||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||
}, [openSymbolMenu, closeBotCommandMenu]);
|
||||
}, [openSymbolMenu, closeBotCommandMenu, isMobile]);
|
||||
|
||||
const handleSendAsMenuOpen = useCallback(() => {
|
||||
const messageInput = document.querySelector<HTMLDivElement>(EDITABLE_INPUT_CSS_SELECTOR);
|
||||
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT || messageInput !== document.activeElement) {
|
||||
if (!isMobile || messageInput !== document.activeElement) {
|
||||
closeBotCommandMenu();
|
||||
closeSymbolMenu();
|
||||
openSendAsMenu();
|
||||
@ -1043,17 +1048,17 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
closeSymbolMenu();
|
||||
openSendAsMenu();
|
||||
}, MOBILE_KEYBOARD_HIDE_DELAY_MS);
|
||||
}, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu]);
|
||||
}, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]);
|
||||
|
||||
const handleAllScheduledClick = useCallback(() => {
|
||||
openChat({ id: chatId, threadId, type: 'scheduled' });
|
||||
}, [openChat, chatId, threadId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRightColumnShown && IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isRightColumnShown && isMobile) {
|
||||
closeSymbolMenu();
|
||||
}
|
||||
}, [isRightColumnShown, closeSymbolMenu]);
|
||||
}, [isRightColumnShown, closeSymbolMenu, isMobile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady) return;
|
||||
@ -1292,7 +1297,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{IS_SINGLE_COLUMN_LAYOUT ? (
|
||||
{isMobile ? (
|
||||
<Button
|
||||
className={symbolMenuButtonClassName}
|
||||
round
|
||||
@ -1329,7 +1334,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
forcedPlaceholder={inlineBotHelp}
|
||||
canAutoFocus={isReady && !attachments.length}
|
||||
noFocusInterception={attachments.length > 0}
|
||||
shouldSuppressFocus={IS_SINGLE_COLUMN_LAYOUT && isSymbolMenuOpen}
|
||||
shouldSuppressFocus={isMobile && isSymbolMenuOpen}
|
||||
shouldSuppressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen || isInlineBotTooltipOpen}
|
||||
onUpdate={setHtml}
|
||||
onSend={onSend}
|
||||
@ -1479,7 +1484,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, messageListType }): StateProps => {
|
||||
(global, {
|
||||
chatId, threadId, messageListType, isMobile,
|
||||
}): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined;
|
||||
const isChatWithBot = Boolean(chatBot);
|
||||
@ -1521,7 +1528,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isForCurrentMessageList,
|
||||
canScheduleUntilOnline: selectCanScheduleUntilOnline(global, chatId),
|
||||
isChannel: chat ? isChatChannel(chat) : undefined,
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
||||
isSelectModeActive: selectIsInSelectMode(global),
|
||||
withScheduledButton: (
|
||||
messageListType === 'thread'
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
|
||||
import { RECENT_SYMBOL_SET_ID } from '../../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import windowSize from '../../../util/windowSize';
|
||||
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import { useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import EmojiButton from './EmojiButton';
|
||||
|
||||
@ -38,8 +39,9 @@ const EmojiCategory: FC<OwnProps> = ({
|
||||
const transitionClassNames = useMediaTransition(shouldRender);
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const emojisPerRow = IS_SINGLE_COLUMN_LAYOUT
|
||||
const emojisPerRow = isMobile
|
||||
? Math.floor((windowSize.get().width - MOBILE_CONTAINER_PADDING) / (EMOJI_SIZE + EMOJI_MARGIN))
|
||||
: EMOJIS_PER_ROW_ON_DESKTOP;
|
||||
const height = Math.ceil(category.emojis.length / emojisPerRow) * (EMOJI_SIZE + EMOJI_MARGIN);
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
useState, useEffect, memo, useRef, useMemo, useCallback,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
|
||||
import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import type {
|
||||
EmojiModule,
|
||||
EmojiRawData,
|
||||
EmojiData,
|
||||
} from '../../../util/emoji';
|
||||
import {
|
||||
uncompressEmoji,
|
||||
} from '../../../util/emoji';
|
||||
|
||||
import { MENU_TRANSITION_DURATION, RECENT_SYMBOL_SET_ID } from '../../../config';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { uncompressEmoji } from '../../../util/emoji';
|
||||
import fastSmoothScroll from '../../../util/fastSmoothScroll';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
@ -25,6 +23,7 @@ import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import Loading from '../../ui/Loading';
|
||||
@ -79,6 +78,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
const [categories, setCategories] = useState<EmojiCategoryData[]>();
|
||||
const [emojis, setEmojis] = useState<AllEmojis>();
|
||||
const [activeCategoryIndex, setActiveCategoryIndex] = useState(0);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const { observe: observeIntersection } = useIntersectionObserver({
|
||||
rootRef: containerRef,
|
||||
@ -105,7 +105,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
setActiveCategoryIndex(intersectingWithIndexes[Math.floor(intersectingWithIndexes.length / 2)].index);
|
||||
});
|
||||
|
||||
useHorizontalScroll(headerRef.current, !IS_SINGLE_COLUMN_LAYOUT);
|
||||
useHorizontalScroll(headerRef.current, !isMobile);
|
||||
|
||||
// Scroll header when active set updates
|
||||
useEffect(() => {
|
||||
|
||||
@ -8,13 +8,13 @@ import { getActions, withGlobal } from '../../../global';
|
||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
||||
|
||||
import { EDITABLE_INPUT_ID } from '../../../config';
|
||||
import {
|
||||
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_TOUCH_ENV,
|
||||
} from '../../../util/environment';
|
||||
import { selectIsInSelectMode, selectReplyingToId } from '../../../global/selectors';
|
||||
import { debounce } from '../../../util/schedulers';
|
||||
import focusEditableElement from '../../../util/focusEditableElement';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import {
|
||||
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV,
|
||||
} from '../../../util/environment';
|
||||
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
||||
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
|
||||
import parseEmojiOnlyString from '../../../util/parseEmojiOnlyString';
|
||||
@ -26,6 +26,7 @@ import useFlag from '../../../hooks/useFlag';
|
||||
import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useInputCustomEmojis from './hooks/useInputCustomEmojis';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import TextFormatter from './TextFormatter';
|
||||
|
||||
@ -62,7 +63,6 @@ type StateProps = {
|
||||
messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
|
||||
};
|
||||
|
||||
const MAX_INPUT_HEIGHT = IS_SINGLE_COLUMN_LAYOUT ? 256 : 416;
|
||||
const MAX_ATTACHMENT_MODAL_INPUT_HEIGHT = 160;
|
||||
const TAB_INDEX_PRIORITY_TIMEOUT = 2000;
|
||||
// Heuristics allowing the user to make a triple click
|
||||
@ -134,14 +134,16 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
|
||||
const [selectedRange, setSelectedRange] = useState<Range>();
|
||||
const [isTextFormatterDisabled, setIsTextFormatterDisabled] = useState<boolean>(false);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useInputCustomEmojis(html, inputRef, sharedCanvasRef, sharedCanvasHqRef, absoluteContainerRef);
|
||||
|
||||
const maxInputHeight = isMobile ? 256 : 416;
|
||||
const updateInputHeight = useCallback((willSend = false) => {
|
||||
const scroller = inputRef.current!.closest<HTMLDivElement>(`.${SCROLLER_CLASS}`)!;
|
||||
const clone = scrollerCloneRef.current!;
|
||||
const currentHeight = Number(scroller.style.height.replace('px', ''));
|
||||
const maxHeight = isAttachmentModalInput ? MAX_ATTACHMENT_MODAL_INPUT_HEIGHT : MAX_INPUT_HEIGHT;
|
||||
const maxHeight = isAttachmentModalInput ? MAX_ATTACHMENT_MODAL_INPUT_HEIGHT : maxInputHeight;
|
||||
const newHeight = Math.min(clone.scrollHeight, maxHeight);
|
||||
if (newHeight === currentHeight) {
|
||||
return;
|
||||
@ -163,7 +165,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
} else {
|
||||
exec();
|
||||
}
|
||||
}, [isAttachmentModalInput]);
|
||||
}, [isAttachmentModalInput, maxInputHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAttachmentModalInput) return;
|
||||
@ -391,7 +393,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
!chatId
|
||||
|| editableInputId !== EDITABLE_INPUT_ID
|
||||
|| noFocusInterception
|
||||
|| (IS_TOUCH_ENV && IS_SINGLE_COLUMN_LAYOUT)
|
||||
|| (IS_TOUCH_ENV && isMobile)
|
||||
|| isSelectModeActive
|
||||
) {
|
||||
return undefined;
|
||||
@ -438,7 +440,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleDocumentKeyDown, true);
|
||||
};
|
||||
}, [chatId, editableInputId, isSelectModeActive, noFocusInterception]);
|
||||
}, [chatId, editableInputId, isMobile, isSelectModeActive, noFocusInterception]);
|
||||
|
||||
useEffect(() => {
|
||||
const captureFirstTab = debounce((e: KeyboardEvent) => {
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
DEFAULT_TOPIC_ICON_STICKER_ID,
|
||||
EMOJI_SIZE_PICKER, FAVORITE_SYMBOL_SET_ID, RECENT_SYMBOL_SET_ID, STICKER_SIZE_PICKER,
|
||||
} from '../../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { selectIsAlwaysHighPriorityEmoji, selectIsSetPremium } from '../../../global/selectors';
|
||||
|
||||
@ -21,6 +20,7 @@ import useFlag from '../../../hooks/useFlag';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import StickerButton from '../../common/StickerButton';
|
||||
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||
@ -46,10 +46,6 @@ type OwnProps = {
|
||||
onStickerRemoveRecent?: (sticker: ApiSticker) => void;
|
||||
};
|
||||
|
||||
const STICKER_MARGIN = IS_SINGLE_COLUMN_LAYOUT ? 8 : 16;
|
||||
const EMOJI_MARGIN = IS_SINGLE_COLUMN_LAYOUT ? 8 : 10;
|
||||
const CONTAINER_PADDING = IS_SINGLE_COLUMN_LAYOUT ? 8 : 0;
|
||||
|
||||
const ITEMS_PER_ROW_FALLBACK = 8;
|
||||
|
||||
const StickerSet: FC<OwnProps> = ({
|
||||
@ -85,8 +81,9 @@ const StickerSet: FC<OwnProps> = ({
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const sharedCanvasHqRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
|
||||
const lang = useLang();
|
||||
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useFlag();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const [itemsPerRow, setItemsPerRow] = useState(ITEMS_PER_ROW_FALLBACK);
|
||||
|
||||
@ -94,6 +91,9 @@ const StickerSet: FC<OwnProps> = ({
|
||||
|
||||
const transitionClassNames = useMediaTransition(shouldRender);
|
||||
|
||||
const stickerMarginPx = isMobile ? 8 : 16;
|
||||
const emojiMarginPx = isMobile ? 8 : 10;
|
||||
const containerPaddingPx = isMobile ? 8 : 0;
|
||||
const isRecent = stickerSet.id === RECENT_SYMBOL_SET_ID;
|
||||
const isFavorite = stickerSet.id === FAVORITE_SYMBOL_SET_ID;
|
||||
const isEmoji = stickerSet.isEmoji;
|
||||
@ -132,13 +132,13 @@ const StickerSet: FC<OwnProps> = ({
|
||||
}, [onStickerSelect]);
|
||||
|
||||
const itemSize = isEmoji ? EMOJI_SIZE_PICKER : STICKER_SIZE_PICKER;
|
||||
const margin = isEmoji ? EMOJI_MARGIN : STICKER_MARGIN;
|
||||
const margin = isEmoji ? emojiMarginPx : stickerMarginPx;
|
||||
|
||||
const calculateItemsPerRow = useCallback((width: number) => {
|
||||
if (!width) return ITEMS_PER_ROW_FALLBACK;
|
||||
|
||||
return Math.floor((width - CONTAINER_PADDING) / (itemSize + margin));
|
||||
}, [itemSize, margin]);
|
||||
return Math.floor((width - containerPaddingPx) / (itemSize + margin));
|
||||
}, [containerPaddingPx, itemSize, margin]);
|
||||
|
||||
const handleResize = useCallback((entry: ResizeObserverEntry) => {
|
||||
setItemsPerRow(calculateItemsPerRow(entry.contentRect.width));
|
||||
|
||||
@ -7,7 +7,7 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
||||
import type { GlobalActions } from '../../../global/types';
|
||||
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { fastRaf } from '../../../util/schedulers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||
@ -15,6 +15,7 @@ import { selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useMouseInside from '../../../hooks/useMouseInside';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import Menu from '../../ui/Menu';
|
||||
@ -86,8 +87,9 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
const [recentEmojis, setRecentEmojis] = useState<string[]>([]);
|
||||
const [recentCustomEmojis, setRecentCustomEmojis] = useState<string[]>([]);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, IS_SINGLE_COLUMN_LAYOUT);
|
||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile);
|
||||
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, onClose, false, false);
|
||||
|
||||
if (!isActivated && isOpen) {
|
||||
@ -107,7 +109,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
}, [isCurrentUserPremium, lastSyncTime, loadFeaturedEmojiStickers, loadPremiumSetStickers]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (!isMobile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -126,7 +128,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [isOpen]);
|
||||
}, [isMobile, isOpen]);
|
||||
|
||||
const recentEmojisRef = useRef(recentEmojis);
|
||||
recentEmojisRef.current = recentEmojis;
|
||||
@ -239,7 +241,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
{IS_SINGLE_COLUMN_LAYOUT && (
|
||||
{isMobile && (
|
||||
<Button
|
||||
round
|
||||
faded
|
||||
@ -261,7 +263,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
||||
</>
|
||||
);
|
||||
|
||||
if (IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (isMobile) {
|
||||
if (!shouldRender) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import { REM } from '../../../common/helpers/mediaDimensions';
|
||||
|
||||
import useResizeObserver from '../../../../hooks/useResizeObserver';
|
||||
import useBackgroundMode from '../../../../hooks/useBackgroundMode';
|
||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||
|
||||
const SIZE = 1.25 * REM;
|
||||
|
||||
@ -26,7 +27,7 @@ type CustomEmojiPlayer = {
|
||||
play: () => void;
|
||||
pause: () => void;
|
||||
destroy: () => void;
|
||||
updatePosition: (x: number, y: number) => void;
|
||||
updatePosition: (x: number, y: number, isMobile?: boolean) => void;
|
||||
};
|
||||
|
||||
export default function useInputCustomEmojis(
|
||||
@ -38,6 +39,8 @@ export default function useInputCustomEmojis(
|
||||
) {
|
||||
const mapRef = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const removeContainers = useCallback((ids: string[]) => {
|
||||
ids.forEach((id) => {
|
||||
const player = mapRef.current.get(id);
|
||||
@ -74,7 +77,7 @@ export default function useInputCustomEmojis(
|
||||
|
||||
if (mapRef.current.has(id)) {
|
||||
const player = mapRef.current.get(id)!;
|
||||
player.updatePosition(x, y);
|
||||
player.updatePosition(x, y, isMobile);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -93,6 +96,7 @@ export default function useInputCustomEmojis(
|
||||
mediaUrl,
|
||||
isHq,
|
||||
position: { x, y },
|
||||
isMobile,
|
||||
});
|
||||
animation.play();
|
||||
|
||||
@ -100,7 +104,7 @@ export default function useInputCustomEmojis(
|
||||
});
|
||||
|
||||
removeContainers(Array.from(removedContainers));
|
||||
}, [absoluteContainerRef, inputRef, removeContainers, sharedCanvasHqRef, sharedCanvasRef]);
|
||||
}, [absoluteContainerRef, inputRef, isMobile, removeContainers, sharedCanvasHqRef, sharedCanvasRef]);
|
||||
|
||||
useEffect(() => {
|
||||
addCustomEmojiInputRenderCallback(synchronizeElements);
|
||||
@ -152,6 +156,7 @@ function createPlayer({
|
||||
mediaUrl,
|
||||
position,
|
||||
isHq,
|
||||
isMobile,
|
||||
} : {
|
||||
customEmoji: ApiSticker;
|
||||
sharedCanvasRef: React.RefObject<HTMLCanvasElement>;
|
||||
@ -161,6 +166,7 @@ function createPlayer({
|
||||
mediaUrl: string;
|
||||
position: { x: number; y: number };
|
||||
isHq?: boolean;
|
||||
isMobile?: boolean;
|
||||
}): CustomEmojiPlayer {
|
||||
if (customEmoji.isLottie) {
|
||||
const lottie = RLottie.init(
|
||||
@ -173,13 +179,16 @@ function createPlayer({
|
||||
size: SIZE,
|
||||
coords: position,
|
||||
isLowPriority: !isHq,
|
||||
isMobile,
|
||||
},
|
||||
);
|
||||
return {
|
||||
play: () => lottie.play(),
|
||||
pause: () => lottie.pause(),
|
||||
destroy: () => lottie.removeContainer(uniqueId),
|
||||
updatePosition: (x: number, y: number) => lottie.setSharedCanvasCoords(uniqueId, { x, y }),
|
||||
updatePosition: (x: number, y: number, isMobileNew?: boolean) => {
|
||||
return lottie.setSharedCanvasCoords(uniqueId, { x, y }, isMobileNew);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,13 +3,13 @@ import { getActions } from '../../../global';
|
||||
|
||||
import type { MessageListType } from '../../../global/types';
|
||||
|
||||
import { IS_ANDROID, IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { IS_ANDROID } from '../../../util/environment';
|
||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||
import useBackgroundMode from '../../../hooks/useBackgroundMode';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
const INTERSECTION_THROTTLE_FOR_READING = 150;
|
||||
const INTERSECTION_THROTTLE_FOR_MEDIA = IS_ANDROID ? 1000 : 350;
|
||||
const INTERSECTION_MARGIN_FOR_LOADING = IS_SINGLE_COLUMN_LAYOUT ? 300 : 500;
|
||||
|
||||
export default function useMessageObservers(
|
||||
type: MessageListType,
|
||||
@ -18,6 +18,9 @@ export default function useMessageObservers(
|
||||
) {
|
||||
const { markMessageListRead, markMentionsRead, animateUnreadReaction } = getActions();
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const INTERSECTION_MARGIN_FOR_LOADING = isMobile ? 300 : 500;
|
||||
|
||||
const {
|
||||
observe: observeIntersectionForReading, freeze: freezeForReading, unfreeze: unfreezeForReading,
|
||||
} = useIntersectionObserver({
|
||||
|
||||
@ -26,12 +26,9 @@ import type {
|
||||
import type {
|
||||
AnimationLevel, FocusDirection, IAlbum, ISettings,
|
||||
} from '../../../types';
|
||||
import {
|
||||
AudioOrigin,
|
||||
} from '../../../types';
|
||||
import {
|
||||
MAIN_THREAD_ID,
|
||||
} from '../../../api/types';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import { AudioOrigin } from '../../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { IS_ANDROID, IS_TOUCH_ENV } from '../../../util/environment';
|
||||
import { EMOJI_STATUS_LOOP_LIMIT } from '../../../config';
|
||||
@ -88,8 +85,6 @@ import {
|
||||
isChatGroup,
|
||||
} from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||
import {
|
||||
calculateDimensionsForMessageMedia,
|
||||
REM,
|
||||
@ -100,9 +95,13 @@ import { getMinMediaWidth, calculateMediaDimensions } from './helpers/mediaDimen
|
||||
import { calculateAlbumLayout } from './helpers/calculateAlbumLayout';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import calculateAuthorWidth from './helpers/calculateAuthorWidth';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import { isAnimatingScroll } from '../../../util/fastSmoothScroll';
|
||||
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { isElementInViewport } from '../../../util/isElementInViewport';
|
||||
import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
|
||||
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||
import { useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
@ -110,10 +109,8 @@ import useFlag from '../../../hooks/useFlag';
|
||||
import useFocusMessage from './hooks/useFocusMessage';
|
||||
import useOuterHandlers from './hooks/useOuterHandlers';
|
||||
import useInnerHandlers from './hooks/useInnerHandlers';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { isElementInViewport } from '../../../util/isElementInViewport';
|
||||
import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
|
||||
import useResizeObserver from '../../../hooks/useResizeObserver';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
import Avatar from '../../common/Avatar';
|
||||
@ -349,6 +346,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const [isTranscriptionHidden, setTranscriptionHidden] = useState(false);
|
||||
const [hasActiveStickerEffect, startStickerEffect, stopStickerEffect] = useFlag();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useOnIntersect(bottomMarkerRef, observeIntersectionForBottom);
|
||||
|
||||
@ -626,19 +624,21 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
let calculatedWidth;
|
||||
let noMediaCorners = false;
|
||||
const albumLayout = useMemo(() => {
|
||||
return isAlbum ? calculateAlbumLayout(isOwn, Boolean(asForwarded), Boolean(noAvatars), album!) : undefined;
|
||||
}, [isAlbum, isOwn, asForwarded, noAvatars, album]);
|
||||
return isAlbum
|
||||
? calculateAlbumLayout(isOwn, Boolean(asForwarded), Boolean(noAvatars), album!, isMobile)
|
||||
: undefined;
|
||||
}, [isAlbum, isOwn, asForwarded, noAvatars, album, isMobile]);
|
||||
|
||||
const extraPadding = asForwarded ? 28 : 0;
|
||||
if (!isAlbum && (photo || video || invoice?.extendedMedia)) {
|
||||
let width: number | undefined;
|
||||
if (photo) {
|
||||
width = calculateMediaDimensions(message, asForwarded, noAvatars).width;
|
||||
width = calculateMediaDimensions(message, asForwarded, noAvatars, isMobile).width;
|
||||
} else if (video) {
|
||||
if (video.isRound) {
|
||||
width = ROUND_VIDEO_DIMENSIONS_PX;
|
||||
} else {
|
||||
width = calculateMediaDimensions(message, asForwarded, noAvatars).width;
|
||||
width = calculateMediaDimensions(message, asForwarded, noAvatars, isMobile).width;
|
||||
}
|
||||
} else if (invoice?.extendedMedia && (
|
||||
invoice.extendedMedia.width && invoice.extendedMedia.height
|
||||
@ -650,6 +650,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
fromOwnMessage: isOwn,
|
||||
asForwarded,
|
||||
noAvatars,
|
||||
isMobile,
|
||||
}).width;
|
||||
}
|
||||
|
||||
|
||||
@ -13,12 +13,12 @@ import { getMessageCopyOptions } from './helpers/copyOptions';
|
||||
import { disableScrolling, enableScrolling } from '../../../util/scrollLock';
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useContextMenuPosition from '../../../hooks/useContextMenuPosition';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
@ -170,6 +170,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
const messageId = !isSponsoredMessage ? message.id : '';
|
||||
|
||||
const [isReady, markIsReady, unmarkIsReady] = useFlag();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleAfterCopy = useCallback(() => {
|
||||
showNotification({
|
||||
@ -217,11 +218,11 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
);
|
||||
|
||||
const getLayout = useCallback(() => {
|
||||
const extraHeightAudioPlayer = (IS_SINGLE_COLUMN_LAYOUT
|
||||
const extraHeightAudioPlayer = (isMobile
|
||||
&& (document.querySelector<HTMLElement>('.AudioPlayer-content'))?.offsetHeight) || 0;
|
||||
const pinnedElement = document.querySelector<HTMLElement>('.HeaderPinnedMessage-wrapper');
|
||||
const extraHeightPinned = (((IS_SINGLE_COLUMN_LAYOUT && !extraHeightAudioPlayer)
|
||||
|| (!IS_SINGLE_COLUMN_LAYOUT && pinnedElement?.classList.contains('full-width')))
|
||||
const extraHeightPinned = (((isMobile && !extraHeightAudioPlayer)
|
||||
|| (!isMobile && pinnedElement?.classList.contains('full-width')))
|
||||
&& pinnedElement?.offsetHeight) || 0;
|
||||
|
||||
return {
|
||||
@ -230,7 +231,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
marginSides: withReactions ? REACTION_BUBBLE_EXTRA_WIDTH : undefined,
|
||||
extraMarginTop: extraHeightPinned + extraHeightAudioPlayer,
|
||||
};
|
||||
}, [withReactions]);
|
||||
}, [isMobile, withReactions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
|
||||
@ -30,6 +30,7 @@ import usePrevious from '../../../hooks/usePrevious';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevDeps';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||
import MediaSpoiler from '../../common/MediaSpoiler';
|
||||
@ -83,6 +84,7 @@ const Photo: FC<OwnProps> = ({
|
||||
|
||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
||||
const shouldLoad = isLoadAllowed && isIntersecting;
|
||||
const {
|
||||
@ -161,7 +163,7 @@ const Photo: FC<OwnProps> = ({
|
||||
}
|
||||
}, [shouldAffectAppendix, fullMediaData, isOwn, isInSelectMode, isSelected, theme] as const);
|
||||
|
||||
const { width, height, isSmall } = dimensions || calculateMediaDimensions(message, asForwarded, noAvatars);
|
||||
const { width, height, isSmall } = dimensions || calculateMediaDimensions(message, asForwarded, noAvatars, isMobile);
|
||||
|
||||
const className = buildClassName(
|
||||
'media-inner',
|
||||
|
||||
@ -55,7 +55,7 @@ const ReactionSelector: FC<OwnProps> = ({
|
||||
if (!itemsScrollRef) return;
|
||||
const deltaY = 'deltaY' in e ? e.deltaY : getTouchY(e);
|
||||
|
||||
if (deltaY) {
|
||||
if (deltaY && e.cancelable) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useRef } from '../../../lib/teact/teact'
|
||||
|
||||
import type { ApiMessage } from '../../../api/types';
|
||||
import { ApiMediaFormat } from '../../../api/types';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
|
||||
import { getStickerDimensions } from '../../common/helpers/mediaDimensions';
|
||||
import { getMessageMediaHash } from '../../../global/helpers';
|
||||
@ -10,11 +11,11 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import { IS_WEBM_SUPPORTED } from '../../../util/environment';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import StickerView from '../../common/StickerView';
|
||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
||||
@ -42,6 +43,7 @@ const Sticker: FC<OwnProps> = ({
|
||||
const { showNotification, openStickerSet } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@ -101,7 +103,7 @@ const Sticker: FC<OwnProps> = ({
|
||||
}, [hasEffect, isPlayingEffect, lang, onPlayEffect, openModal, showNotification, startPlayingEffect]);
|
||||
|
||||
const isMemojiSticker = 'isMissing' in stickerSetInfo;
|
||||
const { width, height } = getStickerDimensions(sticker);
|
||||
const { width, height } = getStickerDimensions(sticker, isMobile);
|
||||
const className = buildClassName(
|
||||
'Sticker media-inner',
|
||||
isMemojiSticker && 'inactive',
|
||||
|
||||
@ -4,6 +4,7 @@ import { getActions } from '../../../global';
|
||||
|
||||
import type { ApiMessage } from '../../../api/types';
|
||||
import type { IMediaDimensions } from './helpers/calculateAlbumLayout';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
|
||||
import { formatMediaDuration } from '../../../util/dateFormat';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
@ -17,7 +18,6 @@ import {
|
||||
getMessageWebPageVideo,
|
||||
isOwnMessage,
|
||||
} from '../../../global/helpers';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import * as mediaLoader from '../../../util/mediaLoader';
|
||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
|
||||
@ -27,6 +27,7 @@ import usePrevious from '../../../hooks/usePrevious';
|
||||
import useMediaTransition from '../../../hooks/useMediaTransition';
|
||||
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import ProgressSpinner from '../../ui/ProgressSpinner';
|
||||
import OptimizedVideo from '../../ui/OptimizedVideo';
|
||||
@ -87,6 +88,7 @@ const Video: FC<OwnProps> = ({
|
||||
wasIntersectedRef.current = true;
|
||||
}
|
||||
|
||||
const { isMobile } = useAppLayout();
|
||||
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
||||
const shouldLoad = Boolean(isLoadAllowed && isIntersectingForLoading && lastSyncTime);
|
||||
const [isPlayAllowed, setIsPlayAllowed] = useState(canAutoPlay && !isSpoilerShown);
|
||||
@ -147,7 +149,7 @@ const Video: FC<OwnProps> = ({
|
||||
const isWebPageVideo = Boolean(getMessageWebPageVideo(message));
|
||||
const {
|
||||
width, height,
|
||||
} = dimensions || calculateVideoDimensions(video, isOwn, asForwarded, isWebPageVideo, noAvatars);
|
||||
} = dimensions || calculateVideoDimensions(video, isOwn, asForwarded, isWebPageVideo, noAvatars, isMobile);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (isUploading) {
|
||||
|
||||
@ -10,6 +10,7 @@ import { calculateMediaDimensions } from './helpers/mediaDimensions';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
import trimText from '../../../util/trimText';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import Photo from './Photo';
|
||||
@ -51,6 +52,7 @@ const WebPage: FC<OwnProps> = ({
|
||||
onCancelMediaTransfer,
|
||||
}) => {
|
||||
const webPage = getMessageWebPage(message);
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleMediaClick = useCallback(() => {
|
||||
onMediaClick!();
|
||||
@ -73,7 +75,7 @@ const WebPage: FC<OwnProps> = ({
|
||||
const isArticle = Boolean(truncatedDescription || title || siteName);
|
||||
let isSquarePhoto = false;
|
||||
if (isArticle && webPage?.photo && !webPage.video) {
|
||||
const { width, height } = calculateMediaDimensions(message);
|
||||
const { width, height } = calculateMediaDimensions(message, undefined, undefined, isMobile);
|
||||
isSquarePhoto = width === height;
|
||||
}
|
||||
const isMediaInteractive = (photo || video) && onMediaClick && !isSquarePhoto;
|
||||
|
||||
@ -46,10 +46,10 @@ export type IAlbumLayout = {
|
||||
containerStyle: ApiDimensions;
|
||||
};
|
||||
|
||||
function getRatios(messages: ApiMessage[]) {
|
||||
function getRatios(messages: ApiMessage[], isMobile?: boolean) {
|
||||
return messages.map(
|
||||
(message) => {
|
||||
const dimensions = calculateMediaDimensions(message) as ApiDimensions;
|
||||
const dimensions = calculateMediaDimensions(message, undefined, undefined, isMobile) as ApiDimensions;
|
||||
|
||||
return dimensions.width / dimensions.height;
|
||||
},
|
||||
@ -96,14 +96,15 @@ export function calculateAlbumLayout(
|
||||
asForwarded: boolean,
|
||||
noAvatars: boolean,
|
||||
album: IAlbum,
|
||||
isMobile?: boolean,
|
||||
): IAlbumLayout {
|
||||
const spacing = 2;
|
||||
const ratios = getRatios(album.messages);
|
||||
const ratios = getRatios(album.messages, isMobile);
|
||||
const proportions = getProportions(ratios);
|
||||
const averageRatio = getAverageRatio(ratios);
|
||||
const albumCount = ratios.length;
|
||||
const forceCalc = ratios.some((ratio) => ratio > 2);
|
||||
const maxWidth = getAvailableWidth(isOwn, asForwarded, false, noAvatars) - (asForwarded ? 2.5 : 0) * REM;
|
||||
const maxWidth = getAvailableWidth(isOwn, asForwarded, false, noAvatars, isMobile) - (asForwarded ? 2.5 : 0) * REM;
|
||||
const maxHeight = maxWidth;
|
||||
|
||||
let layout;
|
||||
|
||||
@ -22,7 +22,9 @@ export function getMinMediaWidth(hasText?: boolean, hasCommentButton?: boolean)
|
||||
: (hasCommentButton ? MIN_MEDIA_WIDTH_WITH_COMMENTS : MIN_MEDIA_WIDTH);
|
||||
}
|
||||
|
||||
export function calculateMediaDimensions(message: ApiMessage, asForwarded?: boolean, noAvatars?: boolean) {
|
||||
export function calculateMediaDimensions(
|
||||
message: ApiMessage, asForwarded?: boolean, noAvatars?: boolean, isMobile?: boolean,
|
||||
) {
|
||||
const isOwn = isOwnMessage(message);
|
||||
const photo = getMessagePhoto(message) || getMessageWebPagePhoto(message);
|
||||
const video = getMessageVideo(message);
|
||||
@ -30,8 +32,8 @@ export function calculateMediaDimensions(message: ApiMessage, asForwarded?: bool
|
||||
const isWebPagePhoto = Boolean(getMessageWebPagePhoto(message));
|
||||
const isWebPageVideo = Boolean(getMessageWebPageVideo(message));
|
||||
const { width, height } = photo
|
||||
? calculateInlineImageDimensions(photo, isOwn, asForwarded, isWebPagePhoto, noAvatars)
|
||||
: calculateVideoDimensions(video!, isOwn, asForwarded, isWebPageVideo, noAvatars);
|
||||
? calculateInlineImageDimensions(photo, isOwn, asForwarded, isWebPagePhoto, noAvatars, isMobile)
|
||||
: calculateVideoDimensions(video!, isOwn, asForwarded, isWebPageVideo, noAvatars, isMobile);
|
||||
|
||||
const hasText = Boolean(getMessageText(message));
|
||||
const minMediaWidth = getMinMediaWidth(hasText);
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import type { ApiMessage } from '../../../../api/types';
|
||||
|
||||
import { IS_CANVAS_FILTER_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT } from '../../../../util/environment';
|
||||
import { IS_CANVAS_FILTER_SUPPORTED } from '../../../../util/environment';
|
||||
import { getMessageMediaThumbDataUri } from '../../../../global/helpers';
|
||||
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
|
||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||
|
||||
export default function useBlurredMediaThumbRef(message: ApiMessage, isDisabled?: boolean | string) {
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
return useCanvasBlur(
|
||||
getMessageMediaThumbDataUri(message),
|
||||
Boolean(isDisabled),
|
||||
IS_SINGLE_COLUMN_LAYOUT && !IS_CANVAS_FILTER_SUPPORTED,
|
||||
isMobile && !IS_CANVAS_FILTER_SUPPORTED,
|
||||
);
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ type OwnProps = {
|
||||
chatId: string;
|
||||
topicId?: number;
|
||||
profileState: ProfileState;
|
||||
isMobile?: boolean;
|
||||
onProfileStateChange: (state: ProfileState) => void;
|
||||
};
|
||||
|
||||
@ -532,7 +533,7 @@ function buildInfiniteScrollItemSelector(resultType: string) {
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, topicId }): StateProps => {
|
||||
(global, { chatId, topicId, isMobile }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const messagesById = selectChatMessages(global, chatId);
|
||||
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
||||
@ -577,7 +578,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canAddMembers,
|
||||
canDeleteMembers,
|
||||
currentUserId: global.currentUserId,
|
||||
isRightColumnShown: selectIsRightColumnShown(global),
|
||||
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
|
||||
isRestricted: chat?.isRestricted,
|
||||
lastSyncTime: global.lastSyncTime,
|
||||
activeDownloadIds,
|
||||
|
||||
@ -38,6 +38,10 @@ import EditTopic from './EditTopic.async';
|
||||
|
||||
import './RightColumn.scss';
|
||||
|
||||
interface OwnProps {
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
type StateProps = {
|
||||
contentKey?: RightColumnContent;
|
||||
chatId?: string;
|
||||
@ -59,10 +63,11 @@ function blurSearchInput() {
|
||||
}
|
||||
}
|
||||
|
||||
const RightColumn: FC<StateProps> = ({
|
||||
const RightColumn: FC<OwnProps & StateProps> = ({
|
||||
contentKey,
|
||||
chatId,
|
||||
threadId,
|
||||
isMobile,
|
||||
isInsideTopic,
|
||||
isChatSelected,
|
||||
shouldSkipHistoryAnimations,
|
||||
@ -274,6 +279,7 @@ const RightColumn: FC<StateProps> = ({
|
||||
chatId={chatId!}
|
||||
topicId={isInsideTopic ? threadId : undefined}
|
||||
profileState={profileState}
|
||||
isMobile={isMobile}
|
||||
onProfileStateChange={setProfileState}
|
||||
/>
|
||||
);
|
||||
@ -359,8 +365,8 @@ const RightColumn: FC<StateProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal(
|
||||
(global): StateProps => {
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { isMobile }): StateProps => {
|
||||
const { chatId, threadId } = selectCurrentMessageList(global) || {};
|
||||
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
|
||||
const nextManagementScreen = chatId ? global.management.byChatId[chatId]?.nextScreen : undefined;
|
||||
@ -368,7 +374,7 @@ export default memo(withGlobal(
|
||||
const isInsideTopic = isForum && Boolean(threadId && threadId !== MAIN_THREAD_ID);
|
||||
|
||||
return {
|
||||
contentKey: selectRightColumnContentKey(global),
|
||||
contentKey: selectRightColumnContentKey(global, isMobile),
|
||||
chatId,
|
||||
threadId,
|
||||
isInsideTopic,
|
||||
|
||||
@ -9,7 +9,6 @@ import { ManagementScreens, ProfileState } from '../../types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import { ANIMATION_END_DELAY } from '../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { debounce } from '../../util/schedulers';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import {
|
||||
@ -23,10 +22,11 @@ import {
|
||||
import {
|
||||
getCanAddContact, getCanManageTopic, isChatAdmin, isChatChannel, isUserBot, isUserId,
|
||||
} from '../../global/helpers';
|
||||
import { getDayStartAt } from '../../util/dateFormat';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import { getDayStartAt } from '../../util/dateFormat';
|
||||
import useAppLayout from '../../hooks/useAppLayout';
|
||||
|
||||
import SearchInput from '../ui/SearchInput';
|
||||
import Button from '../ui/Button';
|
||||
@ -161,6 +161,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
const handleEditInviteClick = useCallback(() => {
|
||||
setEditingExportedInvite({ chatId: chatId!, invite: currentInviteInfo! });
|
||||
@ -490,7 +491,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
const isBackButton = (
|
||||
IS_SINGLE_COLUMN_LAYOUT
|
||||
isMobile
|
||||
|| contentKey === HeaderContent.SharedMedia
|
||||
|| contentKey === HeaderContent.MemberList
|
||||
|| contentKey === HeaderContent.AddingMembers
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { ApiChat, ApiExportedInvite } from '../../../api/types';
|
||||
import { ManagementScreens } from '../../../types';
|
||||
|
||||
import { STICKER_SIZE_INVITES, TME_LINK_PREFIX } from '../../../config';
|
||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import { formatCountdown, MILLISECONDS_IN_DAY } from '../../../util/dateFormat';
|
||||
import useInterval from '../../../hooks/useInterval';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import { getMainUsername, isChatChannel } from '../../../global/helpers';
|
||||
import { selectChat } from '../../../global/selectors';
|
||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useInterval from '../../../hooks/useInterval';
|
||||
import useForceUpdate from '../../../hooks/useForceUpdate';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import { getMainUsername, isChatChannel } from '../../../global/helpers';
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
@ -79,6 +79,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
const [revokingInvite, setRevokingInvite] = useState<ApiExportedInvite | undefined>();
|
||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||
const [deletingInvite, setDeletingInvite] = useState<ApiExportedInvite | undefined>();
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useHistoryBack({
|
||||
isActive,
|
||||
@ -280,7 +281,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
return ({ onTrigger, isOpen }) => (
|
||||
<Button
|
||||
round
|
||||
ripple={!IS_SINGLE_COLUMN_LAYOUT}
|
||||
ripple={!isMobile}
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
className={isOpen ? 'active' : ''}
|
||||
@ -290,7 +291,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
|
||||
<i className="icon-more" />
|
||||
</Button>
|
||||
);
|
||||
}, []);
|
||||
}, [isMobile]);
|
||||
|
||||
return (
|
||||
<div className="Management ManageInvites">
|
||||
|
||||
@ -3,6 +3,7 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import type { ApiMessage } from '../../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
import { FocusDirection } from '../../../types';
|
||||
import type { GlobalState } from '../../types';
|
||||
|
||||
import {
|
||||
ANIMATION_END_DELAY,
|
||||
@ -45,8 +46,8 @@ import parseMessageInput from '../../../util/parseMessageInput';
|
||||
import { getMessageSummaryText, getSenderTitle } from '../../helpers';
|
||||
import * as langProvider from '../../../util/langProvider';
|
||||
import { copyHtmlToClipboard } from '../../../util/clipboard';
|
||||
import type { GlobalState } from '../../types';
|
||||
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
|
||||
import { getIsMobile } from '../../../hooks/useAppLayout';
|
||||
|
||||
const FOCUS_DURATION = 1500;
|
||||
const FOCUS_NO_HIGHLIGHT_DURATION = FAST_SMOOTH_MAX_DURATION + ANIMATION_END_DELAY;
|
||||
@ -251,7 +252,7 @@ addActionHandler('closeAudioPlayer', (global) => {
|
||||
addActionHandler('openPollResults', (global, actions, payload) => {
|
||||
const { chatId, messageId } = payload!;
|
||||
|
||||
const shouldOpenInstantly = selectIsRightColumnShown(global);
|
||||
const shouldOpenInstantly = selectIsRightColumnShown(global, getIsMobile());
|
||||
|
||||
if (!shouldOpenInstantly) {
|
||||
window.setTimeout(() => {
|
||||
|
||||
@ -4,13 +4,13 @@ import type { ApiError, ApiNotification } from '../../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { APP_VERSION, DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT } from '../../../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment';
|
||||
import getReadableErrorText from '../../../util/getReadableErrorText';
|
||||
import {
|
||||
selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectIsTrustedBot,
|
||||
} from '../../selectors';
|
||||
import generateIdFor from '../../../util/generateIdFor';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import { getIsMobile, getIsTablet } from '../../../hooks/useAppLayout';
|
||||
|
||||
export const APP_VERSION_URL = 'version.txt';
|
||||
const MAX_STORED_EMOJIS = 8 * 4; // Represents four rows of recent emojis
|
||||
@ -104,7 +104,7 @@ addActionHandler('closeManagement', (global) => {
|
||||
});
|
||||
|
||||
addActionHandler('openChat', (global) => {
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT && !IS_TABLET_COLUMN_LAYOUT) {
|
||||
if (!getIsMobile() && !getIsTablet()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@ import {
|
||||
DEFAULT_PATTERN_COLOR,
|
||||
DEFAULT_LIMITS,
|
||||
} from '../config';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../util/environment';
|
||||
import { isHeavyAnimating } from '../hooks/useHeavyAnimationCheck';
|
||||
import { pick, pickTruthy, unique } from '../util/iteratees';
|
||||
import {
|
||||
@ -39,6 +38,7 @@ import { isUserId } from './helpers';
|
||||
import { getOrderedIds } from '../util/folderManager';
|
||||
import { clearGlobalForLockScreen } from './reducers';
|
||||
import { encryptSession } from '../util/passcode';
|
||||
import { getIsMobile } from '../hooks/useAppLayout';
|
||||
|
||||
const UPDATE_THROTTLE = 5000;
|
||||
|
||||
@ -130,7 +130,7 @@ function readCache(initialState: GlobalState): GlobalState {
|
||||
...cached,
|
||||
};
|
||||
|
||||
const parsedMessageList = !IS_SINGLE_COLUMN_LAYOUT ? parseLocationHash() : undefined;
|
||||
const parsedMessageList = !getIsMobile() ? parseLocationHash() : undefined;
|
||||
|
||||
return {
|
||||
...newState,
|
||||
|
||||
@ -6,11 +6,11 @@ import {
|
||||
MIN_LEFT_COLUMN_WIDTH,
|
||||
SIDE_COLUMN_MAX_WIDTH,
|
||||
} from '../../components/middle/helpers/calculateMiddleFooterTransforms';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import windowSize from '../../util/windowSize';
|
||||
import { updateChat } from './chats';
|
||||
import { isSameReaction, isReactionChosen } from '../helpers';
|
||||
import { updateChatMessage } from './messages';
|
||||
import { getIsMobile } from '../../hooks/useAppLayout';
|
||||
|
||||
function getLeftColumnWidth(windowWidth: number) {
|
||||
if (windowWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN) {
|
||||
@ -31,7 +31,7 @@ function getLeftColumnWidth(windowWidth: number) {
|
||||
}
|
||||
|
||||
export function subtractXForEmojiInteraction(global: GlobalState, x: number) {
|
||||
return x - ((global.isLeftColumnShown && !IS_SINGLE_COLUMN_LAYOUT)
|
||||
return x - ((global.isLeftColumnShown && !getIsMobile())
|
||||
? global.leftColumnWidth || getLeftColumnWidth(windowSize.get().width)
|
||||
: 0);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { GlobalState } from '../types';
|
||||
import { NewChatMembersProgress, RightColumnContent } from '../../types';
|
||||
|
||||
import { getSystemTheme, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||
import { getSystemTheme } from '../../util/environment';
|
||||
import {
|
||||
selectCurrentMessageList, selectIsCreateTopicPanelOpen, selectIsEditTopicPanelOpen, selectIsPollResultsOpen,
|
||||
} from './messages';
|
||||
@ -15,14 +15,14 @@ export function selectIsMediaViewerOpen(global: GlobalState) {
|
||||
return Boolean(mediaViewer.mediaId || mediaViewer.avatarOwnerId);
|
||||
}
|
||||
|
||||
export function selectRightColumnContentKey(global: GlobalState) {
|
||||
export function selectRightColumnContentKey(global: GlobalState, isMobile?: boolean) {
|
||||
return selectIsEditTopicPanelOpen(global) ? (
|
||||
RightColumnContent.EditTopic
|
||||
) : selectIsCreateTopicPanelOpen(global) ? (
|
||||
RightColumnContent.CreateTopic
|
||||
) : selectIsPollResultsOpen(global) ? (
|
||||
RightColumnContent.PollResults
|
||||
) : !IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global) ? (
|
||||
) : !isMobile && selectCurrentTextSearch(global) ? (
|
||||
RightColumnContent.Search
|
||||
) : selectCurrentManagement(global) ? (
|
||||
RightColumnContent.Management
|
||||
@ -41,8 +41,8 @@ export function selectRightColumnContentKey(global: GlobalState) {
|
||||
) : undefined;
|
||||
}
|
||||
|
||||
export function selectIsRightColumnShown(global: GlobalState) {
|
||||
return selectRightColumnContentKey(global) !== undefined;
|
||||
export function selectIsRightColumnShown(global: GlobalState, isMobile?: boolean) {
|
||||
return selectRightColumnContentKey(global, isMobile) !== undefined;
|
||||
}
|
||||
|
||||
export function selectTheme(global: GlobalState) {
|
||||
|
||||
75
src/hooks/useAppLayout.ts
Normal file
75
src/hooks/useAppLayout.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN,
|
||||
MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT,
|
||||
MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH,
|
||||
MOBILE_SCREEN_MAX_WIDTH,
|
||||
} from '../config';
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
|
||||
import { IS_IOS } from '../util/environment';
|
||||
import { createCallbackManager } from '../util/callbacks';
|
||||
import { updateSizes } from '../util/windowSize';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
|
||||
type MediaQueryCacheKey = 'mobile' | 'tablet' | 'landscape';
|
||||
|
||||
const mediaQueryCache = new Map<MediaQueryCacheKey, MediaQueryList>();
|
||||
const callbacks = createCallbackManager();
|
||||
|
||||
let isMobile: boolean | undefined;
|
||||
let isTablet: boolean | undefined;
|
||||
let isLandscape: boolean | undefined;
|
||||
|
||||
export function getIsMobile() {
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
export function getIsTablet() {
|
||||
return isTablet;
|
||||
}
|
||||
|
||||
function handleMediaQueryChange() {
|
||||
isMobile = mediaQueryCache.get('mobile')?.matches || false;
|
||||
isTablet = !isMobile && (mediaQueryCache.get('tablet')?.matches || false);
|
||||
isLandscape = mediaQueryCache.get('landscape')?.matches || false;
|
||||
updateSizes();
|
||||
callbacks.runCallbacks();
|
||||
}
|
||||
|
||||
function initMediaQueryCache() {
|
||||
const mobileQuery = window.matchMedia(`(max-width: ${MOBILE_SCREEN_MAX_WIDTH}px), \
|
||||
(max-width: ${MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH}px and max-height: ${MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT}px)`);
|
||||
mediaQueryCache.set('mobile', mobileQuery);
|
||||
mobileQuery.addEventListener('change', handleMediaQueryChange);
|
||||
|
||||
const tabletQuery = window.matchMedia(`(max-width: ${MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN}px)`);
|
||||
mediaQueryCache.set('tablet', tabletQuery);
|
||||
tabletQuery.addEventListener('change', handleMediaQueryChange);
|
||||
|
||||
const landscapeQuery = window.matchMedia(
|
||||
IS_IOS
|
||||
? '(orientation: landscape)'
|
||||
// Source: https://web.archive.org/web/20160509220835/http://blog.abouthalf.com/development/orientation-media-query-challenges-in-android-browsers/
|
||||
// Feature is marked as deprecated now, but it is still supported
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/device-aspect-ratio#browser_compatibility
|
||||
: 'screen and (min-device-aspect-ratio: 1/1) and (orientation: landscape)',
|
||||
);
|
||||
mediaQueryCache.set('landscape', landscapeQuery);
|
||||
landscapeQuery.addEventListener('change', handleMediaQueryChange);
|
||||
}
|
||||
|
||||
initMediaQueryCache();
|
||||
handleMediaQueryChange();
|
||||
|
||||
export default function useAppLayout() {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
useEffect(() => callbacks.addCallback(forceUpdate), [forceUpdate]);
|
||||
|
||||
return {
|
||||
isMobile,
|
||||
isTablet,
|
||||
isLandscape,
|
||||
isDesktop: !isMobile && !isTablet,
|
||||
};
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../util/environment';
|
||||
import useAppLayout from './useAppLayout';
|
||||
|
||||
// Focus slows down animation, also it breaks transition layout in Chrome
|
||||
const FOCUS_DELAY_MS = 500;
|
||||
@ -11,9 +11,11 @@ export default function useInputFocusOnOpen(
|
||||
isOpen?: boolean,
|
||||
onClose?: NoneToVoidFunction,
|
||||
) {
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
||||
if (!isMobile) {
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (inputRef.current?.isConnected) {
|
||||
@ -31,5 +33,5 @@ export default function useInputFocusOnOpen(
|
||||
setTimeout(onClose, MODAL_HIDE_DELAY_MS);
|
||||
}
|
||||
}
|
||||
}, [inputRef, isOpen, onClose]);
|
||||
}, [inputRef, isMobile, isOpen, onClose]);
|
||||
}
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
import {
|
||||
DPR,
|
||||
IS_SINGLE_COLUMN_LAYOUT,
|
||||
IS_SAFARI,
|
||||
IS_ANDROID,
|
||||
} from '../../util/environment';
|
||||
import { DPR, IS_SAFARI, IS_ANDROID } from '../../util/environment';
|
||||
import WorkerConnector from '../../util/WorkerConnector';
|
||||
import { animate } from '../../util/animation';
|
||||
import cycleRestrict from '../../util/cycleRestrict';
|
||||
@ -14,6 +9,7 @@ interface Params {
|
||||
size?: number;
|
||||
quality?: number;
|
||||
isLowPriority?: boolean;
|
||||
isMobile?: boolean;
|
||||
coords?: { x: number; y: number };
|
||||
}
|
||||
|
||||
@ -24,7 +20,8 @@ type Frame =
|
||||
| ImageBitmap;
|
||||
|
||||
const MAX_WORKERS = 4;
|
||||
const HIGH_PRIORITY_QUALITY = IS_SINGLE_COLUMN_LAYOUT ? 0.75 : 1;
|
||||
const HIGH_PRIORITY_QUALITY_MOBILE = 0.75;
|
||||
const HIGH_PRIORITY_QUALITY_DESKTOP = 1;
|
||||
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
||||
const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24;
|
||||
const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4;
|
||||
@ -99,7 +96,7 @@ class RLottie {
|
||||
instance = new RLottie(...args);
|
||||
instancesById.set(id, instance);
|
||||
} else {
|
||||
instance.addContainer(container, canvas, onLoad, params?.coords);
|
||||
instance.addContainer(container, canvas, onLoad, params?.coords, params?.isMobile);
|
||||
}
|
||||
|
||||
return instance;
|
||||
@ -116,7 +113,7 @@ class RLottie {
|
||||
private onEnded?: (isDestroyed?: boolean) => void,
|
||||
private onLoop?: () => void,
|
||||
) {
|
||||
this.addContainer(containerId, container, onLoad, params.coords);
|
||||
this.addContainer(containerId, container, onLoad, params.coords, params.isMobile);
|
||||
this.initConfig();
|
||||
this.initRenderer();
|
||||
}
|
||||
@ -203,13 +200,18 @@ class RLottie {
|
||||
this.params.noLoop = noLoop;
|
||||
}
|
||||
|
||||
setSharedCanvasCoords(containerId: string, newCoords: Params['coords']) {
|
||||
setIsMobile(isMobile?: Params['isMobile']) {
|
||||
this.params.isMobile = isMobile;
|
||||
}
|
||||
|
||||
setSharedCanvasCoords(containerId: string, newCoords: Params['coords'], isMobile?: Params['isMobile']) {
|
||||
const containerInfo = this.containers.get(containerId)!;
|
||||
const {
|
||||
canvas, ctx,
|
||||
} = containerInfo;
|
||||
|
||||
if (!canvas.dataset.isJustCleaned || canvas.dataset.isJustCleaned === 'false') {
|
||||
this.setIsMobile(isMobile);
|
||||
const sizeFactor = this.calcSizeFactor();
|
||||
ensureCanvasSize(canvas, sizeFactor);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
@ -236,7 +238,9 @@ class RLottie {
|
||||
container: HTMLDivElement | HTMLCanvasElement,
|
||||
onLoad?: NoneToVoidFunction,
|
||||
coords?: Params['coords'],
|
||||
isMobile?: Params['isMobile'],
|
||||
) {
|
||||
this.setIsMobile(isMobile);
|
||||
const sizeFactor = this.calcSizeFactor();
|
||||
|
||||
let imgSize: number;
|
||||
@ -314,10 +318,11 @@ class RLottie {
|
||||
const {
|
||||
isLowPriority,
|
||||
size,
|
||||
isMobile,
|
||||
// Reduced quality only looks acceptable on big enough images
|
||||
quality = isLowPriority && (!size || size > LOW_PRIORITY_QUALITY_SIZE_THRESHOLD)
|
||||
? LOW_PRIORITY_QUALITY
|
||||
: HIGH_PRIORITY_QUALITY,
|
||||
: (isMobile ? HIGH_PRIORITY_QUALITY_MOBILE : HIGH_PRIORITY_QUALITY_DESKTOP),
|
||||
} = this.params;
|
||||
|
||||
// Reduced quality only looks acceptable on high DPR screens
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN,
|
||||
MOBILE_SCREEN_MAX_WIDTH,
|
||||
MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT,
|
||||
MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH,
|
||||
IS_TEST,
|
||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||
VIDEO_MOV_TYPE,
|
||||
@ -53,14 +49,6 @@ export const IS_PWA = (
|
||||
);
|
||||
|
||||
export const IS_TOUCH_ENV = window.matchMedia('(pointer: coarse)').matches;
|
||||
// Keep in mind the landscape orientation
|
||||
export const IS_SINGLE_COLUMN_LAYOUT = window.innerWidth <= MOBILE_SCREEN_MAX_WIDTH || (
|
||||
window.innerWidth <= MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH && window.innerHeight <= MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT
|
||||
);
|
||||
// Special layout, 1 column while chat opened, 2 columns while collapsed
|
||||
export const IS_TABLET_COLUMN_LAYOUT = !IS_SINGLE_COLUMN_LAYOUT && (
|
||||
window.innerWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN
|
||||
);
|
||||
export const IS_VOICE_RECORDING_SUPPORTED = Boolean(
|
||||
window.navigator.mediaDevices && 'getUserMedia' in window.navigator.mediaDevices && (
|
||||
window.AudioContext || (window as any).webkitAudioContext
|
||||
|
||||
@ -1,40 +1,19 @@
|
||||
import { throttle } from './schedulers';
|
||||
import {
|
||||
MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT,
|
||||
MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH,
|
||||
MOBILE_SCREEN_MAX_WIDTH,
|
||||
} from '../config';
|
||||
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT } from './environment';
|
||||
import { IS_IOS } from './environment';
|
||||
|
||||
type IDimensions = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
const IS_LANDSCAPE = IS_SINGLE_COLUMN_LAYOUT && isLandscape();
|
||||
const WINDOW_RESIZE_THROTTLE_MS = 250;
|
||||
|
||||
const initialHeight = window.innerHeight;
|
||||
let currentWindowSize = updateSizes();
|
||||
let isRefreshDisabled = false;
|
||||
|
||||
function disableRefresh() {
|
||||
isRefreshDisabled = true;
|
||||
}
|
||||
|
||||
function enableRefresh() {
|
||||
isRefreshDisabled = false;
|
||||
}
|
||||
|
||||
const handleResize = throttle(() => {
|
||||
currentWindowSize = updateSizes();
|
||||
|
||||
if (!isRefreshDisabled && (
|
||||
isMobileScreen() !== IS_SINGLE_COLUMN_LAYOUT
|
||||
|| (IS_SINGLE_COLUMN_LAYOUT && IS_LANDSCAPE !== isLandscape())
|
||||
)) {
|
||||
window.location.reload();
|
||||
}
|
||||
}, 250, true);
|
||||
}, WINDOW_RESIZE_THROTTLE_MS, true);
|
||||
|
||||
window.addEventListener('orientationchange', handleResize);
|
||||
if (IS_IOS) {
|
||||
@ -60,29 +39,9 @@ export function updateSizes(): IDimensions {
|
||||
};
|
||||
}
|
||||
|
||||
function isMobileScreen() {
|
||||
return currentWindowSize.width <= MOBILE_SCREEN_MAX_WIDTH || (
|
||||
currentWindowSize.width <= MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH
|
||||
&& currentWindowSize.height <= MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT
|
||||
);
|
||||
}
|
||||
|
||||
function isLandscape() {
|
||||
if (IS_IOS) {
|
||||
return window.matchMedia('(orientation: landscape)').matches;
|
||||
}
|
||||
|
||||
// Source: https://web.archive.org/web/20160509220835/http://blog.abouthalf.com/development/orientation-media-query-challenges-in-android-browsers/
|
||||
// Feature is marked as deprecated now, but it is still supported
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/@media/device-aspect-ratio#browser_compatibility
|
||||
return window.matchMedia('screen and (min-device-aspect-ratio: 1/1) and (orientation: landscape)').matches;
|
||||
}
|
||||
|
||||
const windowSize = {
|
||||
get: () => currentWindowSize,
|
||||
getIsKeyboardVisible: () => initialHeight > currentWindowSize.height,
|
||||
disableRefresh,
|
||||
enableRefresh,
|
||||
};
|
||||
|
||||
export default windowSize;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user