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