From 3dab7609e3068b4b0221759cd927010d0d0569b4 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Tue, 24 Jan 2023 00:21:55 +0100 Subject: [PATCH] Dynamic Resize (#2291) --- src/App.tsx | 10 ++- src/components/auth/Auth.tsx | 10 --- src/components/calls/group/GroupCall.tsx | 13 ++-- src/components/calls/phone/PhoneCall.tsx | 5 +- src/components/common/AnimatedSticker.tsx | 18 +++-- src/components/common/Audio.tsx | 32 ++++---- src/components/common/File.tsx | 10 ++- src/components/common/PasswordForm.tsx | 9 ++- src/components/common/PasswordMonkey.tsx | 10 ++- src/components/common/ProfilePhoto.tsx | 6 +- src/components/common/TrackingMonkey.tsx | 16 ++-- src/components/common/UiLoader.tsx | 5 +- .../common/helpers/mediaDimensions.ts | 21 ++++-- src/components/left/main/Chat.tsx | 9 ++- src/components/left/main/ChatCallStatus.tsx | 6 +- src/components/left/main/ContactList.tsx | 5 +- src/components/left/main/EmptyFolder.tsx | 5 +- src/components/left/main/EmptyForum.tsx | 8 +- src/components/left/main/ForumPanel.tsx | 3 + src/components/left/main/LeftMainHeader.tsx | 10 ++- src/components/left/search/ChatMessage.tsx | 7 +- .../left/search/ChatMessageResults.tsx | 8 +- src/components/left/search/ChatResults.tsx | 9 ++- src/components/left/search/LeftSearch.tsx | 4 +- .../left/settings/SettingsHeader.tsx | 9 ++- .../settings/twoFa/SettingsTwoFaEmailCode.tsx | 10 ++- .../twoFa/SettingsTwoFaSkippableForm.tsx | 10 ++- src/components/main/ConfettiContainer.tsx | 7 +- src/components/main/GameModal.tsx | 12 --- src/components/main/Main.async.tsx | 7 +- src/components/main/Main.scss | 1 + src/components/main/Main.tsx | 49 ++++++------ src/components/main/WebAppModal.tsx | 18 +---- src/components/mediaViewer/MediaViewer.tsx | 27 ++----- .../mediaViewer/MediaViewerActions.tsx | 5 +- .../mediaViewer/MediaViewerContent.tsx | 10 ++- .../mediaViewer/MediaViewerFooter.tsx | 8 +- src/components/mediaViewer/SenderInfo.tsx | 8 +- src/components/mediaViewer/VideoPlayer.tsx | 19 +++-- .../mediaViewer/VideoPlayerControls.tsx | 16 ++-- src/components/middle/AudioPlayer.tsx | 35 +++------ src/components/middle/HeaderActions.tsx | 22 +++--- src/components/middle/HeaderMenuContainer.tsx | 9 ++- src/components/middle/MiddleColumn.tsx | 75 ++++++++++--------- src/components/middle/MiddleHeader.tsx | 22 ++++-- .../middle/composer/AttachmentModal.tsx | 6 +- .../middle/composer/BotCommandMenu.tsx | 10 ++- src/components/middle/composer/Composer.tsx | 33 ++++---- .../middle/composer/EmojiCategory.tsx | 10 ++- .../middle/composer/EmojiPicker.tsx | 18 ++--- .../middle/composer/MessageInput.tsx | 18 +++-- src/components/middle/composer/StickerSet.tsx | 18 ++--- src/components/middle/composer/SymbolMenu.tsx | 14 ++-- .../composer/hooks/useInputCustomEmojis.ts | 17 ++++- .../middle/hooks/useMessageObservers.ts | 7 +- src/components/middle/message/Message.tsx | 33 ++++---- .../middle/message/MessageContextMenu.tsx | 11 +-- src/components/middle/message/Photo.tsx | 4 +- .../middle/message/ReactionSelector.tsx | 2 +- src/components/middle/message/Sticker.tsx | 6 +- src/components/middle/message/Video.tsx | 6 +- src/components/middle/message/WebPage.tsx | 4 +- .../message/helpers/calculateAlbumLayout.ts | 9 ++- .../middle/message/helpers/mediaDimensions.ts | 8 +- .../message/hooks/useBlurredMediaThumbRef.ts | 7 +- src/components/right/Profile.tsx | 5 +- src/components/right/RightColumn.tsx | 14 +++- src/components/right/RightHeader.tsx | 7 +- .../right/management/ManageInvites.tsx | 19 ++--- src/global/actions/ui/messages.ts | 5 +- src/global/actions/ui/misc.ts | 4 +- src/global/cache.ts | 4 +- src/global/reducers/reactions.ts | 4 +- src/global/selectors/ui.ts | 10 +-- src/hooks/useAppLayout.ts | 75 +++++++++++++++++++ src/hooks/useInputFocusOnOpen.ts | 8 +- src/lib/rlottie/RLottie.ts | 27 ++++--- src/util/environment.ts | 12 --- src/util/windowSize.ts | 47 +----------- 79 files changed, 607 insertions(+), 493 deletions(-) create mode 100644 src/hooks/useAppLayout.ts diff --git a/src/App.tsx b/src/App.tsx index cb4ddfaff..fc1586096 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import AppInactive from './components/main/AppInactive'; import Transition from './components/ui/Transition'; import UiLoader from './components/common/UiLoader'; import { parseInitialLocationHash } from './util/routing'; +import useAppLayout from './hooks/useAppLayout'; // import Test from './components/test/TestNoRedundancy'; type StateProps = { @@ -46,7 +47,8 @@ const App: FC = ({ const { disconnect } = getActions(); const [isInactive, markInactive] = useFlag(false); - const isMobile = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android'; + const { isMobile } = useAppLayout(); + const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android'; useEffect(() => { updateSizes(); @@ -129,7 +131,7 @@ const App: FC = ({ } else if (hasPasscode) { activeKey = AppScreens.lock; } else { - page = isMobile ? 'authPhoneNumber' : 'authQrCode'; + page = isMobileOs ? 'authPhoneNumber' : 'authQrCode'; activeKey = AppScreens.auth; } @@ -150,7 +152,7 @@ const App: FC = ({ case AppScreens.auth: return ; case AppScreens.main: - return
; + return
; case AppScreens.lock: return ; case AppScreens.inactive: @@ -159,7 +161,7 @@ const App: FC = ({ } return ( - + = ({ onBack: handleChangeAuthorizationMethod, }); - // Prevent refresh when rotating device - useEffect(() => { - windowSize.disableRefresh(); - - return () => { - windowSize.enableRefresh(); - }; - }, []); - // For animation purposes const renderingAuthState = useCurrentOrPrev( authState !== 'authorizationStateReady' ? authState : undefined, diff --git a/src/components/calls/group/GroupCall.tsx b/src/components/calls/group/GroupCall.tsx index c48cd79ff..dcbde871d 100644 --- a/src/components/calls/group/GroupCall.tsx +++ b/src/components/calls/group/GroupCall.tsx @@ -17,7 +17,6 @@ import { IS_ANDROID, IS_IOS, IS_REQUEST_FULLSCREEN_SUPPORTED, - IS_SINGLE_COLUMN_LAYOUT, } from '../../../util/environment'; import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import buildClassName from '../../../util/buildClassName'; @@ -28,6 +27,7 @@ import { } from '../../../global/selectors/calls'; import useFlag from '../../../hooks/useFlag'; import useLang from '../../../hooks/useLang'; +import useAppLayout from '../../../hooks/useAppLayout'; import Loading from '../../ui/Loading'; import Button from '../../ui/Button'; @@ -84,12 +84,13 @@ const GroupCall: FC = ({ const lang = useLang(); // eslint-disable-next-line no-null/no-null const containerRef = useRef(null); + const { isMobile, isLandscape } = useAppLayout(); const [isLeaving, setIsLeaving] = useState(false); const [isFullscreen, openFullscreen, closeFullscreen] = useFlag(); const [isSidebarOpen, openSidebar, closeSidebar] = useFlag(true); const hasVideoParticipants = Object.values(participants).some(({ video, presentation }) => video || presentation); - const isLandscape = isFullscreen && !IS_SINGLE_COLUMN_LAYOUT && hasVideoParticipants; + const isLandscapeLayout = isFullscreen && (!isMobile || isLandscape) && hasVideoParticipants; const [participantMenu, setParticipantMenu] = useState<{ participant: TypeGroupCallParticipant; @@ -254,8 +255,8 @@ const GroupCall: FC = ({ onClose={toggleGroupCallPanel} className={buildClassName( 'GroupCall', - IS_SINGLE_COLUMN_LAYOUT && 'single-column', - isLandscape && 'landscape', + (isMobile && !isLandscape) && 'single-column', + isLandscapeLayout && 'landscape', !isSidebarOpen && 'no-sidebar', )} dialogRef={containerRef} @@ -274,7 +275,7 @@ const GroupCall: FC = ({ )} - {isLandscape && ( + {isLandscapeLayout && ( ); - }, [hasMenu, lang, onReset, shouldSkipTransition]); + }, [hasMenu, isMobile, lang, onReset, shouldSkipTransition]); const handleSearchFocus = useCallback(() => { if (!searchQuery) { @@ -431,7 +433,7 @@ const LeftMainHeader: FC = ({ {hasPasscode && ( ); - }, []); + }, [isMobile]); const lang = useLang(); @@ -240,7 +241,7 @@ const SettingsHeader: FC = ({ ); - }, []); + }, [isMobile]); const backButtonClassName = buildClassName( 'animated-close-icon', diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx index 6d4341797..4cc08f2fc 100644 --- a/src/components/mediaViewer/MediaViewer.tsx +++ b/src/components/mediaViewer/MediaViewer.tsx @@ -27,10 +27,9 @@ import { } from '../../global/selectors'; import { stopCurrentAudio } from '../../util/audioPlayer'; import captureEscKeyListener from '../../util/captureEscKeyListener'; -import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment'; +import { IS_TOUCH_ENV } from '../../util/environment'; import { ANIMATION_END_DELAY } from '../../config'; import { MEDIA_VIEWER_MEDIA_QUERY } from '../common/helpers/mediaDimensions'; -import windowSize from '../../util/windowSize'; import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager'; import { animateClosing, animateOpening } from './helpers/ghostAnimation'; import { renderMessageText } from '../common/helpers/renderMessageText'; @@ -42,6 +41,7 @@ import { exitPictureInPictureIfNeeded } from '../../hooks/usePictureInPicture'; import useLang from '../../hooks/useLang'; import usePrevious from '../../hooks/usePrevious'; import { useMediaProps } from './hooks/useMediaProps'; +import useAppLayout from '../../hooks/useAppLayout'; import ReportModal from '../common/ReportModal'; import Button from '../ui/Button'; @@ -97,6 +97,7 @@ const MediaViewer: FC = ({ } = getActions(); const isOpen = Boolean(avatarOwner || mediaId); + const { isMobile } = useAppLayout(); /* Animation */ const animationKey = useRef(); @@ -163,14 +164,14 @@ const MediaViewer: FC = ({ }, [isVisible]); useEffect(() => { - if (IS_SINGLE_COLUMN_LAYOUT) { + if (isMobile) { document.body.classList.toggle('is-media-viewer-open', isOpen); } // Disable user selection if media viewer is open, to prevent accidental text selection if (IS_TOUCH_ENV) { document.body.classList.toggle('no-selection', isOpen); } - }, [isOpen]); + }, [isMobile, isOpen]); const forceUpdate = useForceUpdate(); useEffect(() => { @@ -220,7 +221,7 @@ const MediaViewer: FC = ({ const handleFooterClick = useCallback(() => { handleClose(); - if (IS_SINGLE_COLUMN_LAYOUT) { + if (isMobile) { setTimeout(() => { toggleChatInfo(false, { forceSyncOnIOs: true }); focusMessage({ chatId, threadId, mediaId }); @@ -228,7 +229,7 @@ const MediaViewer: FC = ({ } else { focusMessage({ chatId, threadId, mediaId }); } - }, [handleClose, chatId, threadId, focusMessage, toggleChatInfo, mediaId]); + }, [handleClose, isMobile, chatId, threadId, focusMessage, toggleChatInfo, mediaId]); const handleForward = useCallback(() => { openForwardMenu({ @@ -259,18 +260,6 @@ const MediaViewer: FC = ({ } }, [isGif, isVideo]); - // Prevent refresh when rotating device to watch a video - useEffect(() => { - if (!isOpen) { - return undefined; - } - windowSize.disableRefresh(); - - return () => { - windowSize.enableRefresh(); - }; - }, [isOpen]); - const getMediaId = useCallback((fromId?: number, direction?: number): number | undefined => { if (fromId === undefined) return undefined; const index = mediaIds.indexOf(fromId); @@ -317,7 +306,7 @@ const MediaViewer: FC = ({ noCloseTransition={shouldSkipHistoryAnimations} >
- {IS_SINGLE_COLUMN_LAYOUT && ( + {isMobile && (
@@ -150,13 +152,13 @@ const MediaViewerContent: FC = (props) => { {isPhoto && renderPhoto( bestData, message && calculateMediaViewerDimensions(dimensions!, hasFooter), - !IS_SINGLE_COLUMN_LAYOUT && !isProtected, + !isMobile && !isProtected, isProtected, )} {isVideo && (!isActive ? renderVideoPreview( bestImageData, message && calculateMediaViewerDimensions(dimensions!, hasFooter, true), - !IS_SINGLE_COLUMN_LAYOUT && !isProtected, + !isMobile && !isProtected, isProtected, ) : ( = ({ text = '', isHidden, isForVideo, onClick, isProtected, }) => { const [isMultiline, setIsMultiline] = useState(false); + const { isMobile } = useAppLayout(); + useEffect(() => { const footerContent = document.querySelector('.MediaViewerFooter .media-text') as HTMLDivElement | null; @@ -61,7 +63,7 @@ const MediaViewerFooter: FC = ({ return (
{Boolean(text) && ( -
+

{text}

)} diff --git a/src/components/mediaViewer/SenderInfo.tsx b/src/components/mediaViewer/SenderInfo.tsx index ccdb01b82..aeeadb2b4 100644 --- a/src/components/mediaViewer/SenderInfo.tsx +++ b/src/components/mediaViewer/SenderInfo.tsx @@ -5,7 +5,6 @@ import { getActions, withGlobal } from '../../global'; import type { ApiChat, ApiMessage, ApiUser } from '../../api/types'; import type { AnimationLevel } from '../../types'; -import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { getSenderTitle, isUserId } from '../../global/helpers'; import { formatMediaDateTime } from '../../util/dateFormat'; import renderText from '../common/helpers/renderText'; @@ -16,6 +15,7 @@ import { selectUser, } from '../../global/selectors'; import useLang from '../../hooks/useLang'; +import useAppLayout from '../../hooks/useAppLayout'; import Avatar from '../common/Avatar'; @@ -49,10 +49,12 @@ const SenderInfo: FC = ({ toggleChatInfo, } = getActions(); + const { isMobile } = useAppLayout(); + const handleFocusMessage = useCallback(() => { closeMediaViewer(); - if (IS_SINGLE_COLUMN_LAYOUT) { + if (isMobile) { setTimeout(() => { toggleChatInfo(false, { forceSyncOnIOs: true }); focusMessage({ chatId, messageId }); @@ -60,7 +62,7 @@ const SenderInfo: FC = ({ } else { focusMessage({ chatId, messageId }); } - }, [chatId, focusMessage, toggleChatInfo, messageId, closeMediaViewer]); + }, [chatId, isMobile, focusMessage, toggleChatInfo, messageId, closeMediaViewer]); const lang = useLang(); diff --git a/src/components/mediaViewer/VideoPlayer.tsx b/src/components/mediaViewer/VideoPlayer.tsx index 21712eb4d..2b4fc1046 100644 --- a/src/components/mediaViewer/VideoPlayer.tsx +++ b/src/components/mediaViewer/VideoPlayer.tsx @@ -6,16 +6,15 @@ import { getActions } from '../../global'; import type { ApiDimensions } from '../../api/types'; +import { IS_IOS, IS_TOUCH_ENV, IS_YA_BROWSER } from '../../util/environment'; +import safePlay from '../../util/safePlay'; +import stopEvent from '../../util/stopEvent'; import useBuffering from '../../hooks/useBuffering'; import useFullscreen from '../../hooks/useFullscreen'; import usePictureInPicture from '../../hooks/usePictureInPicture'; import useShowTransition from '../../hooks/useShowTransition'; import useVideoCleanup from '../../hooks/useVideoCleanup'; -import { - IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV, IS_YA_BROWSER, -} from '../../util/environment'; -import safePlay from '../../util/safePlay'; -import stopEvent from '../../util/stopEvent'; +import useAppLayout from '../../hooks/useAppLayout'; import Button from '../ui/Button'; import ProgressSpinner from '../ui/ProgressSpinner'; @@ -77,6 +76,7 @@ const VideoPlayer: FC = ({ const [isPlaying, setIsPlaying] = useState(!IS_TOUCH_ENV || !IS_IOS); const [currentTime, setCurrentTime] = useState(0); const [isFullscreen, setFullscreen, exitFullscreen] = useFullscreen(videoRef, setIsPlaying); + const { isMobile } = useAppLayout(); const handleEnterFullscreen = useCallback(() => { // Yandex browser doesn't support PIP when video is hidden @@ -159,7 +159,10 @@ const VideoPlayer: FC = ({ }, [isPlaying]); const handleClick = useCallback((e: React.MouseEvent) => { - if (isClickDisabled) return; + if (isClickDisabled) { + return; + } + if (shouldCloseOnClick) { onClose(e); } else { @@ -239,7 +242,7 @@ const VideoPlayer: FC = ({
)} @@ -255,7 +258,7 @@ const VideoPlayer: FC = ({ style={videoStyle} onPlay={() => setIsPlaying(true)} onEnded={handleEnded} - onClick={!IS_SINGLE_COLUMN_LAYOUT && !isFullscreen ? handleClick : undefined} + onClick={!isMobile && !isFullscreen ? handleClick : undefined} onDoubleClick={!IS_TOUCH_ENV ? handleFullscreenChange : undefined} // eslint-disable-next-line react/jsx-props-no-spreading {...bufferingHandlers} diff --git a/src/components/mediaViewer/VideoPlayerControls.tsx b/src/components/mediaViewer/VideoPlayerControls.tsx index 92af742ab..3b198ef50 100644 --- a/src/components/mediaViewer/VideoPlayerControls.tsx +++ b/src/components/mediaViewer/VideoPlayerControls.tsx @@ -2,15 +2,17 @@ import type { FC } from '../../lib/teact/teact'; import React, { useEffect, useRef, useCallback, useMemo, } from '../../lib/teact/teact'; -import buildClassName from '../../util/buildClassName'; -import useFlag from '../../hooks/useFlag'; -import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment'; +import type { BufferedRange } from '../../hooks/useBuffering'; + +import { IS_IOS, IS_TOUCH_ENV } from '../../util/environment'; +import buildClassName from '../../util/buildClassName'; import { formatMediaDuration } from '../../util/dateFormat'; import { formatFileSize } from '../../util/textFormat'; -import useLang from '../../hooks/useLang'; -import type { BufferedRange } from '../../hooks/useBuffering'; import { captureEvents } from '../../util/captureEvents'; +import useLang from '../../hooks/useLang'; +import useFlag from '../../hooks/useFlag'; +import useAppLayout from '../../hooks/useAppLayout'; import Button from '../ui/Button'; import RangeSlider from '../ui/RangeSlider'; @@ -89,6 +91,8 @@ const VideoPlayerControls: FC = ({ const isSeekingRef = useRef(false); const isSeeking = isSeekingRef.current; + const { isMobile } = useAppLayout(); + useEffect(() => { if (!IS_TOUCH_ENV) return undefined; let timeout: number | undefined; @@ -170,7 +174,7 @@ const VideoPlayerControls: FC = ({ ); - }, [handleBeforeContextMenu, handleContextMenu, handlePlaybackClick, playbackRate]); + }, [handleBeforeContextMenu, handleContextMenu, handlePlaybackClick, isMobile, playbackRate]); const volumeIcon = useMemo(() => { if (volume === 0 || isMuted) return 'icon-muted'; @@ -210,7 +195,7 @@ const AudioPlayer: FC = ({
)} - {IS_SINGLE_COLUMN_LAYOUT + {isMobile && (renderingCanSubscribe || (renderingShouldJoinToSend && !renderingShouldSendJoinRequest)) && (
)} - {IS_SINGLE_COLUMN_LAYOUT && renderingShouldSendJoinRequest && ( + {isMobile && renderingShouldSendJoinRequest && (
)} - {IS_SINGLE_COLUMN_LAYOUT && renderingCanStartBot && ( + {isMobile && renderingCanStartBot && (
)} - {IS_SINGLE_COLUMN_LAYOUT && renderingCanRestartBot && ( + {isMobile && renderingCanRestartBot && (
- {IS_SINGLE_COLUMN_LAYOUT && } + {isMobile && } )} {chatId && ( @@ -579,8 +585,8 @@ const MiddleColumn: FC = ({ ); }; -export default memo(withGlobal( - (global): StateProps => { +export default memo(withGlobal( + (global, { isMobile }): StateProps => { const theme = selectTheme(global); const { isBlurred: isBackgroundBlurred, background: customBackground, backgroundColor, patternColor, @@ -598,9 +604,9 @@ export default memo(withGlobal( backgroundColor, patternColor, isLeftColumnShown, - isRightColumnShown: selectIsRightColumnShown(global), + isRightColumnShown: selectIsRightColumnShown(global, isMobile), isBackgroundBlurred, - isMobileSearchActive: Boolean(IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global)), + hasCurrentTextSearch: Boolean(selectCurrentTextSearch(global)), isSelectModeActive: selectIsInSelectMode(global), isSeenByModalOpen: Boolean(global.seenByModal), isReactorListModalOpen: Boolean(global.reactorModal), @@ -681,8 +687,9 @@ function useIsReady( currentTransitionKey?: number, prevTransitionKey?: number, chatId?: string, + isMobile?: boolean, ) { - const [isReady, setIsReady] = useState(!IS_SINGLE_COLUMN_LAYOUT); + const [isReady, setIsReady] = useState(!isMobile); const forceUpdate = useForceUpdate(); const willSwitchMessageList = prevTransitionKey !== undefined && prevTransitionKey !== currentTransitionKey; diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 1fdac5d48..15f42599a 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -20,7 +20,6 @@ import { SAFE_SCREEN_WIDTH_FOR_CHAT_INFO, SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, } from '../../config'; -import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../util/environment'; import { getChatTitle, getMessageKey, getSenderTitle, isChatChannel, isChatSuperGroup, isUserId, } from '../../global/helpers'; @@ -50,6 +49,7 @@ import buildClassName from '../../util/buildClassName'; import useLang from '../../hooks/useLang'; import useConnectionStatus from '../../hooks/useConnectionStatus'; import usePrevious from '../../hooks/usePrevious'; +import useAppLayout from '../../hooks/useAppLayout'; import PrivateChatInfo from '../common/PrivateChatInfo'; import GroupChatInfo from '../common/GroupChatInfo'; @@ -73,6 +73,7 @@ type OwnProps = { threadId: number; messageListType: MessageListType; isReady?: boolean; + isMobile?: boolean; }; type StateProps = { @@ -101,6 +102,7 @@ const MiddleHeader: FC = ({ threadId, messageListType, isReady, + isMobile, pinnedMessageIds, messagesById, canUnpin, @@ -133,6 +135,7 @@ const MiddleHeader: FC = ({ const lang = useLang(); const isBackButtonActive = useRef(true); + const { isTablet } = useAppLayout(); const [pinnedMessageIndex, setPinnedMessageIndex] = useState(0); const pinnedMessageId = Array.isArray(pinnedMessageIds) ? pinnedMessageIds[pinnedMessageIndex] : pinnedMessageIds; @@ -160,7 +163,7 @@ const MiddleHeader: FC = ({ const { width: windowWidth } = useWindowSize(); const isLeftColumnHideable = windowWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN; - const shouldShowCloseButton = IS_TABLET_COLUMN_LAYOUT && isLeftColumnShown; + const shouldShowCloseButton = isTablet && isLeftColumnShown; // eslint-disable-next-line no-null/no-null const componentRef = useRef(null); @@ -198,7 +201,7 @@ const MiddleHeader: FC = ({ // Workaround for missing UI when quickly clicking the Back button isBackButtonActive.current = false; - if (IS_SINGLE_COLUMN_LAYOUT) { + if (isMobile) { const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); messageInput?.blur(); } @@ -210,7 +213,7 @@ const MiddleHeader: FC = ({ } if (messageListType === 'thread' && currentTransitionKey === 0) { - if (IS_SINGLE_COLUMN_LAYOUT || shouldShowCloseButton) { + if (isMobile || shouldShowCloseButton) { e.stopPropagation(); // Stop propagation to prevent chat re-opening on tablets openChat({ id: undefined }, { forceOnHeavyAnimation: true }); } else { @@ -226,7 +229,7 @@ const MiddleHeader: FC = ({ setBackButtonActive(); }, [ messageListType, currentTransitionKey, isSelectModeActive, openPreviousChat, shouldShowCloseButton, - openChat, toggleLeftColumn, exitMessageSelectMode, setBackButtonActive, + openChat, toggleLeftColumn, exitMessageSelectMode, setBackButtonActive, isMobile, ]); const canToolsCollideWithChatInfo = ( @@ -386,7 +389,7 @@ const MiddleHeader: FC = ({ const isAudioPlayerRendered = Boolean(shouldRenderAudioPlayer && renderingAudioMessage); const isPinnedMessagesFullWidth = isAudioPlayerRendered - || (!IS_SINGLE_COLUMN_LAYOUT && hasButtonInHeader && windowWidth < MAX_SCREEN_WIDTH_FOR_EXPAND_PINNED_MESSAGES); + || (!isMobile && hasButtonInHeader && windowWidth < MAX_SCREEN_WIDTH_FOR_EXPAND_PINNED_MESSAGES); return (
@@ -442,6 +445,7 @@ const MiddleHeader: FC = ({ chatId={chatId} threadId={threadId} messageListType={messageListType} + isMobile={isMobile} canExpandActions={!isAudioPlayerRendered} />
@@ -450,7 +454,9 @@ const MiddleHeader: FC = ({ }; export default memo(withGlobal( - (global, { chatId, threadId, messageListType }): StateProps => { + (global, { + chatId, threadId, messageListType, isMobile, + }): StateProps => { const { isLeftColumnShown, lastSyncTime, shouldSkipHistoryAnimations } = global; const chat = selectChat(global, chatId); @@ -484,7 +490,7 @@ export default memo(withGlobal( const state: StateProps = { typingStatus, isLeftColumnShown, - isRightColumnShown: selectIsRightColumnShown(global), + isRightColumnShown: selectIsRightColumnShown(global, isMobile), isSelectModeActive: selectIsInSelectMode(global), audioMessage, chat, diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index ffaa99015..12ed112b2 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -14,7 +14,6 @@ import { SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, } from '../../../config'; -import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import getFilesFromDataTransferItems from './helpers/getFilesFromDataTransferItems'; import { getHtmlTextLength } from './helpers/getHtmlTextLength'; @@ -117,6 +116,7 @@ const AttachmentModal: FC = ({ const hideTimeoutRef = useRef(); const prevAttachments = usePrevious(attachments); const renderingAttachments = attachments.length ? attachments : prevAttachments; + const { isMobile } = useAppLayout(); const [shouldSendCompressed, setShouldSendCompressed] = useState( shouldSuggestCompression ?? attachmentSettings.shouldCompress, @@ -312,7 +312,7 @@ const AttachmentModal: FC = ({ return ({ onTrigger, isOpen: isMenuOpen }) => ( ); - }, []); + }, [isMobile]); const leftChars = useMemo(() => { const captionLeftBeforeLimit = captionLimit - getHtmlTextLength(caption); diff --git a/src/components/middle/composer/BotCommandMenu.tsx b/src/components/middle/composer/BotCommandMenu.tsx index c5b0f5a76..af33b3838 100644 --- a/src/components/middle/composer/BotCommandMenu.tsx +++ b/src/components/middle/composer/BotCommandMenu.tsx @@ -1,16 +1,17 @@ -import type { FC } from '../../../lib/teact/teact'; import React, { memo, useCallback } from '../../../lib/teact/teact'; +import { getActions } from '../../../global'; +import type { FC } from '../../../lib/teact/teact'; import type { ApiBotCommand } from '../../../api/types'; -import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../util/environment'; +import { IS_TOUCH_ENV } from '../../../util/environment'; import useMouseInside from '../../../hooks/useMouseInside'; +import useAppLayout from '../../../hooks/useAppLayout'; import Menu from '../../ui/Menu'; import BotCommand from './BotCommand'; import './BotCommandMenu.scss'; -import { getActions } from '../../../global'; export type OwnProps = { isOpen: boolean; @@ -22,8 +23,9 @@ const BotCommandMenu: FC = ({ isOpen, botCommands, onClose, }) => { const { sendBotCommand } = getActions(); + const { isMobile } = useAppLayout(); - const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, IS_SINGLE_COLUMN_LAYOUT); + const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile); const handleClick = useCallback((botCommand: ApiBotCommand) => { sendBotCommand({ diff --git a/src/components/middle/composer/Composer.tsx b/src/components/middle/composer/Composer.tsx index cf6d28e5d..28e351d71 100644 --- a/src/components/middle/composer/Composer.tsx +++ b/src/components/middle/composer/Composer.tsx @@ -30,7 +30,7 @@ import { SEND_MESSAGE_ACTION_INTERVAL, EDITABLE_INPUT_CSS_SELECTOR, MAX_UPLOAD_FILEPART_SIZE, } from '../../../config'; -import { IS_VOICE_RECORDING_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, IS_IOS } from '../../../util/environment'; +import { IS_VOICE_RECORDING_SUPPORTED, IS_IOS } from '../../../util/environment'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { selectChat, @@ -132,6 +132,7 @@ type OwnProps = { messageListType: MessageListType; dropAreaState: string; isReady: boolean; + isMobile?: boolean; onDropHide: NoneToVoidFunction; }; @@ -213,6 +214,7 @@ const Composer: FC = ({ shouldSchedule, canScheduleUntilOnline, isReady, + isMobile, onDropHide, editingMessage, chatId, @@ -548,13 +550,16 @@ const Composer: FC = ({ closeMentionTooltip(); closeEmojiTooltip(); - if (IS_SINGLE_COLUMN_LAYOUT) { + if (isMobile) { // @optimization setTimeout(() => closeSymbolMenu(), SENDING_ANIMATION_DURATION); } else { closeSymbolMenu(); } - }, [closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip, closeSymbolMenu, setHtml]); + }, [ + closeStickerTooltip, closeCustomEmojiTooltip, closeMentionTooltip, closeEmojiTooltip, + closeSymbolMenu, setHtml, isMobile, + ]); // Handle chat change (ref is used to avoid redundant effect calls) const stopRecordingVoiceRef = useRef(); @@ -1015,7 +1020,7 @@ const Composer: FC = ({ const handleSymbolMenuOpen = useCallback(() => { const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); - if (!IS_SINGLE_COLUMN_LAYOUT || messageInput !== document.activeElement) { + if (!isMobile || messageInput !== document.activeElement) { openSymbolMenu(); return; } @@ -1025,12 +1030,12 @@ const Composer: FC = ({ closeBotCommandMenu(); openSymbolMenu(); }, MOBILE_KEYBOARD_HIDE_DELAY_MS); - }, [openSymbolMenu, closeBotCommandMenu]); + }, [openSymbolMenu, closeBotCommandMenu, isMobile]); const handleSendAsMenuOpen = useCallback(() => { const messageInput = document.querySelector(EDITABLE_INPUT_CSS_SELECTOR); - if (!IS_SINGLE_COLUMN_LAYOUT || messageInput !== document.activeElement) { + if (!isMobile || messageInput !== document.activeElement) { closeBotCommandMenu(); closeSymbolMenu(); openSendAsMenu(); @@ -1043,17 +1048,17 @@ const Composer: FC = ({ closeSymbolMenu(); openSendAsMenu(); }, MOBILE_KEYBOARD_HIDE_DELAY_MS); - }, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu]); + }, [closeBotCommandMenu, closeSymbolMenu, openSendAsMenu, isMobile]); const handleAllScheduledClick = useCallback(() => { openChat({ id: chatId, threadId, type: 'scheduled' }); }, [openChat, chatId, threadId]); useEffect(() => { - if (isRightColumnShown && IS_SINGLE_COLUMN_LAYOUT) { + if (isRightColumnShown && isMobile) { closeSymbolMenu(); } - }, [isRightColumnShown, closeSymbolMenu]); + }, [isRightColumnShown, closeSymbolMenu, isMobile]); useEffect(() => { if (!isReady) return; @@ -1292,7 +1297,7 @@ const Composer: FC = ({ /> )} - {IS_SINGLE_COLUMN_LAYOUT ? ( + {isMobile ? (
- {IS_SINGLE_COLUMN_LAYOUT && ( + {isMobile && ( ); - }, []); + }, [isMobile]); return (
diff --git a/src/global/actions/ui/messages.ts b/src/global/actions/ui/messages.ts index ad001b05d..29dbc7ddd 100644 --- a/src/global/actions/ui/messages.ts +++ b/src/global/actions/ui/messages.ts @@ -3,6 +3,7 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index'; import type { ApiMessage } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types'; import { FocusDirection } from '../../../types'; +import type { GlobalState } from '../../types'; import { ANIMATION_END_DELAY, @@ -45,8 +46,8 @@ import parseMessageInput from '../../../util/parseMessageInput'; import { getMessageSummaryText, getSenderTitle } from '../../helpers'; import * as langProvider from '../../../util/langProvider'; import { copyHtmlToClipboard } from '../../../util/clipboard'; -import type { GlobalState } from '../../types'; import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml'; +import { getIsMobile } from '../../../hooks/useAppLayout'; const FOCUS_DURATION = 1500; const FOCUS_NO_HIGHLIGHT_DURATION = FAST_SMOOTH_MAX_DURATION + ANIMATION_END_DELAY; @@ -251,7 +252,7 @@ addActionHandler('closeAudioPlayer', (global) => { addActionHandler('openPollResults', (global, actions, payload) => { const { chatId, messageId } = payload!; - const shouldOpenInstantly = selectIsRightColumnShown(global); + const shouldOpenInstantly = selectIsRightColumnShown(global, getIsMobile()); if (!shouldOpenInstantly) { window.setTimeout(() => { diff --git a/src/global/actions/ui/misc.ts b/src/global/actions/ui/misc.ts index e6527a4a3..bb5776460 100644 --- a/src/global/actions/ui/misc.ts +++ b/src/global/actions/ui/misc.ts @@ -4,13 +4,13 @@ import type { ApiError, ApiNotification } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types'; import { APP_VERSION, DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT } from '../../../config'; -import { IS_SINGLE_COLUMN_LAYOUT, IS_TABLET_COLUMN_LAYOUT } from '../../../util/environment'; import getReadableErrorText from '../../../util/getReadableErrorText'; import { selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectIsTrustedBot, } from '../../selectors'; import generateIdFor from '../../../util/generateIdFor'; import { unique } from '../../../util/iteratees'; +import { getIsMobile, getIsTablet } from '../../../hooks/useAppLayout'; export const APP_VERSION_URL = 'version.txt'; const MAX_STORED_EMOJIS = 8 * 4; // Represents four rows of recent emojis @@ -104,7 +104,7 @@ addActionHandler('closeManagement', (global) => { }); addActionHandler('openChat', (global) => { - if (!IS_SINGLE_COLUMN_LAYOUT && !IS_TABLET_COLUMN_LAYOUT) { + if (!getIsMobile() && !getIsTablet()) { return undefined; } diff --git a/src/global/cache.ts b/src/global/cache.ts index e671692ce..76f6a9f2d 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -23,7 +23,6 @@ import { DEFAULT_PATTERN_COLOR, DEFAULT_LIMITS, } from '../config'; -import { IS_SINGLE_COLUMN_LAYOUT } from '../util/environment'; import { isHeavyAnimating } from '../hooks/useHeavyAnimationCheck'; import { pick, pickTruthy, unique } from '../util/iteratees'; import { @@ -39,6 +38,7 @@ import { isUserId } from './helpers'; import { getOrderedIds } from '../util/folderManager'; import { clearGlobalForLockScreen } from './reducers'; import { encryptSession } from '../util/passcode'; +import { getIsMobile } from '../hooks/useAppLayout'; const UPDATE_THROTTLE = 5000; @@ -130,7 +130,7 @@ function readCache(initialState: GlobalState): GlobalState { ...cached, }; - const parsedMessageList = !IS_SINGLE_COLUMN_LAYOUT ? parseLocationHash() : undefined; + const parsedMessageList = !getIsMobile() ? parseLocationHash() : undefined; return { ...newState, diff --git a/src/global/reducers/reactions.ts b/src/global/reducers/reactions.ts index 9579aff4d..11470741a 100644 --- a/src/global/reducers/reactions.ts +++ b/src/global/reducers/reactions.ts @@ -6,11 +6,11 @@ import { MIN_LEFT_COLUMN_WIDTH, SIDE_COLUMN_MAX_WIDTH, } from '../../components/middle/helpers/calculateMiddleFooterTransforms'; -import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import windowSize from '../../util/windowSize'; import { updateChat } from './chats'; import { isSameReaction, isReactionChosen } from '../helpers'; import { updateChatMessage } from './messages'; +import { getIsMobile } from '../../hooks/useAppLayout'; function getLeftColumnWidth(windowWidth: number) { if (windowWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN) { @@ -31,7 +31,7 @@ function getLeftColumnWidth(windowWidth: number) { } export function subtractXForEmojiInteraction(global: GlobalState, x: number) { - return x - ((global.isLeftColumnShown && !IS_SINGLE_COLUMN_LAYOUT) + return x - ((global.isLeftColumnShown && !getIsMobile()) ? global.leftColumnWidth || getLeftColumnWidth(windowSize.get().width) : 0); } diff --git a/src/global/selectors/ui.ts b/src/global/selectors/ui.ts index f7823795d..c987b3b2d 100644 --- a/src/global/selectors/ui.ts +++ b/src/global/selectors/ui.ts @@ -1,7 +1,7 @@ import type { GlobalState } from '../types'; import { NewChatMembersProgress, RightColumnContent } from '../../types'; -import { getSystemTheme, IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; +import { getSystemTheme } from '../../util/environment'; import { selectCurrentMessageList, selectIsCreateTopicPanelOpen, selectIsEditTopicPanelOpen, selectIsPollResultsOpen, } from './messages'; @@ -15,14 +15,14 @@ export function selectIsMediaViewerOpen(global: GlobalState) { return Boolean(mediaViewer.mediaId || mediaViewer.avatarOwnerId); } -export function selectRightColumnContentKey(global: GlobalState) { +export function selectRightColumnContentKey(global: GlobalState, isMobile?: boolean) { return selectIsEditTopicPanelOpen(global) ? ( RightColumnContent.EditTopic ) : selectIsCreateTopicPanelOpen(global) ? ( RightColumnContent.CreateTopic ) : selectIsPollResultsOpen(global) ? ( RightColumnContent.PollResults - ) : !IS_SINGLE_COLUMN_LAYOUT && selectCurrentTextSearch(global) ? ( + ) : !isMobile && selectCurrentTextSearch(global) ? ( RightColumnContent.Search ) : selectCurrentManagement(global) ? ( RightColumnContent.Management @@ -41,8 +41,8 @@ export function selectRightColumnContentKey(global: GlobalState) { ) : undefined; } -export function selectIsRightColumnShown(global: GlobalState) { - return selectRightColumnContentKey(global) !== undefined; +export function selectIsRightColumnShown(global: GlobalState, isMobile?: boolean) { + return selectRightColumnContentKey(global, isMobile) !== undefined; } export function selectTheme(global: GlobalState) { diff --git a/src/hooks/useAppLayout.ts b/src/hooks/useAppLayout.ts new file mode 100644 index 000000000..5367436ac --- /dev/null +++ b/src/hooks/useAppLayout.ts @@ -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(); +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, + }; +} diff --git a/src/hooks/useInputFocusOnOpen.ts b/src/hooks/useInputFocusOnOpen.ts index 7fd4c70b6..f56575c5b 100644 --- a/src/hooks/useInputFocusOnOpen.ts +++ b/src/hooks/useInputFocusOnOpen.ts @@ -1,6 +1,6 @@ import type { RefObject } from 'react'; import { useEffect } from '../lib/teact/teact'; -import { IS_SINGLE_COLUMN_LAYOUT } from '../util/environment'; +import useAppLayout from './useAppLayout'; // Focus slows down animation, also it breaks transition layout in Chrome const FOCUS_DELAY_MS = 500; @@ -11,9 +11,11 @@ export default function useInputFocusOnOpen( isOpen?: boolean, onClose?: NoneToVoidFunction, ) { + const { isMobile } = useAppLayout(); + useEffect(() => { if (isOpen) { - if (!IS_SINGLE_COLUMN_LAYOUT) { + if (!isMobile) { setTimeout(() => { requestAnimationFrame(() => { if (inputRef.current?.isConnected) { @@ -31,5 +33,5 @@ export default function useInputFocusOnOpen( setTimeout(onClose, MODAL_HIDE_DELAY_MS); } } - }, [inputRef, isOpen, onClose]); + }, [inputRef, isMobile, isOpen, onClose]); } diff --git a/src/lib/rlottie/RLottie.ts b/src/lib/rlottie/RLottie.ts index d8995208e..46342849f 100644 --- a/src/lib/rlottie/RLottie.ts +++ b/src/lib/rlottie/RLottie.ts @@ -1,9 +1,4 @@ -import { - DPR, - IS_SINGLE_COLUMN_LAYOUT, - IS_SAFARI, - IS_ANDROID, -} from '../../util/environment'; +import { DPR, IS_SAFARI, IS_ANDROID } from '../../util/environment'; import WorkerConnector from '../../util/WorkerConnector'; import { animate } from '../../util/animation'; import cycleRestrict from '../../util/cycleRestrict'; @@ -14,6 +9,7 @@ interface Params { size?: number; quality?: number; isLowPriority?: boolean; + isMobile?: boolean; coords?: { x: number; y: number }; } @@ -24,7 +20,8 @@ type Frame = | ImageBitmap; const MAX_WORKERS = 4; -const HIGH_PRIORITY_QUALITY = IS_SINGLE_COLUMN_LAYOUT ? 0.75 : 1; +const HIGH_PRIORITY_QUALITY_MOBILE = 0.75; +const HIGH_PRIORITY_QUALITY_DESKTOP = 1; const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75; const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24; const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4; @@ -99,7 +96,7 @@ class RLottie { instance = new RLottie(...args); instancesById.set(id, instance); } else { - instance.addContainer(container, canvas, onLoad, params?.coords); + instance.addContainer(container, canvas, onLoad, params?.coords, params?.isMobile); } return instance; @@ -116,7 +113,7 @@ class RLottie { private onEnded?: (isDestroyed?: boolean) => void, private onLoop?: () => void, ) { - this.addContainer(containerId, container, onLoad, params.coords); + this.addContainer(containerId, container, onLoad, params.coords, params.isMobile); this.initConfig(); this.initRenderer(); } @@ -203,13 +200,18 @@ class RLottie { this.params.noLoop = noLoop; } - setSharedCanvasCoords(containerId: string, newCoords: Params['coords']) { + setIsMobile(isMobile?: Params['isMobile']) { + this.params.isMobile = isMobile; + } + + setSharedCanvasCoords(containerId: string, newCoords: Params['coords'], isMobile?: Params['isMobile']) { const containerInfo = this.containers.get(containerId)!; const { canvas, ctx, } = containerInfo; if (!canvas.dataset.isJustCleaned || canvas.dataset.isJustCleaned === 'false') { + this.setIsMobile(isMobile); const sizeFactor = this.calcSizeFactor(); ensureCanvasSize(canvas, sizeFactor); ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -236,7 +238,9 @@ class RLottie { container: HTMLDivElement | HTMLCanvasElement, onLoad?: NoneToVoidFunction, coords?: Params['coords'], + isMobile?: Params['isMobile'], ) { + this.setIsMobile(isMobile); const sizeFactor = this.calcSizeFactor(); let imgSize: number; @@ -314,10 +318,11 @@ class RLottie { const { isLowPriority, size, + isMobile, // Reduced quality only looks acceptable on big enough images quality = isLowPriority && (!size || size > LOW_PRIORITY_QUALITY_SIZE_THRESHOLD) ? LOW_PRIORITY_QUALITY - : HIGH_PRIORITY_QUALITY, + : (isMobile ? HIGH_PRIORITY_QUALITY_MOBILE : HIGH_PRIORITY_QUALITY_DESKTOP), } = this.params; // Reduced quality only looks acceptable on high DPR screens diff --git a/src/util/environment.ts b/src/util/environment.ts index d6cfbe202..d96ff6461 100644 --- a/src/util/environment.ts +++ b/src/util/environment.ts @@ -1,8 +1,4 @@ import { - MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, - MOBILE_SCREEN_MAX_WIDTH, - MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT, - MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH, IS_TEST, SUPPORTED_VIDEO_CONTENT_TYPES, VIDEO_MOV_TYPE, @@ -53,14 +49,6 @@ export const IS_PWA = ( ); export const IS_TOUCH_ENV = window.matchMedia('(pointer: coarse)').matches; -// Keep in mind the landscape orientation -export const IS_SINGLE_COLUMN_LAYOUT = window.innerWidth <= MOBILE_SCREEN_MAX_WIDTH || ( - window.innerWidth <= MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH && window.innerHeight <= MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT -); -// Special layout, 1 column while chat opened, 2 columns while collapsed -export const IS_TABLET_COLUMN_LAYOUT = !IS_SINGLE_COLUMN_LAYOUT && ( - window.innerWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN -); export const IS_VOICE_RECORDING_SUPPORTED = Boolean( window.navigator.mediaDevices && 'getUserMedia' in window.navigator.mediaDevices && ( window.AudioContext || (window as any).webkitAudioContext diff --git a/src/util/windowSize.ts b/src/util/windowSize.ts index 9bf30791e..b6135f0fe 100644 --- a/src/util/windowSize.ts +++ b/src/util/windowSize.ts @@ -1,40 +1,19 @@ import { throttle } from './schedulers'; -import { - MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT, - MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH, - MOBILE_SCREEN_MAX_WIDTH, -} from '../config'; -import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT } from './environment'; +import { IS_IOS } from './environment'; type IDimensions = { width: number; height: number; }; -const IS_LANDSCAPE = IS_SINGLE_COLUMN_LAYOUT && isLandscape(); +const WINDOW_RESIZE_THROTTLE_MS = 250; const initialHeight = window.innerHeight; let currentWindowSize = updateSizes(); -let isRefreshDisabled = false; - -function disableRefresh() { - isRefreshDisabled = true; -} - -function enableRefresh() { - isRefreshDisabled = false; -} const handleResize = throttle(() => { currentWindowSize = updateSizes(); - - if (!isRefreshDisabled && ( - isMobileScreen() !== IS_SINGLE_COLUMN_LAYOUT - || (IS_SINGLE_COLUMN_LAYOUT && IS_LANDSCAPE !== isLandscape()) - )) { - window.location.reload(); - } -}, 250, true); +}, WINDOW_RESIZE_THROTTLE_MS, true); window.addEventListener('orientationchange', handleResize); if (IS_IOS) { @@ -60,29 +39,9 @@ export function updateSizes(): IDimensions { }; } -function isMobileScreen() { - return currentWindowSize.width <= MOBILE_SCREEN_MAX_WIDTH || ( - currentWindowSize.width <= MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH - && currentWindowSize.height <= MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT - ); -} - -function isLandscape() { - if (IS_IOS) { - return window.matchMedia('(orientation: landscape)').matches; - } - - // Source: https://web.archive.org/web/20160509220835/http://blog.abouthalf.com/development/orientation-media-query-challenges-in-android-browsers/ - // Feature is marked as deprecated now, but it is still supported - // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/device-aspect-ratio#browser_compatibility - return window.matchMedia('screen and (min-device-aspect-ratio: 1/1) and (orientation: landscape)').matches; -} - const windowSize = { get: () => currentWindowSize, getIsKeyboardVisible: () => initialHeight > currentWindowSize.height, - disableRefresh, - enableRefresh, }; export default windowSize;