diff --git a/src/components/calls/group/GroupCall.tsx b/src/components/calls/group/GroupCall.tsx index b5cc51081..5952c525e 100644 --- a/src/components/calls/group/GroupCall.tsx +++ b/src/components/calls/group/GroupCall.tsx @@ -24,10 +24,10 @@ import { compact } from '../../../util/iteratees'; import useAppLayout from '../../../hooks/useAppLayout'; import useFlag from '../../../hooks/useFlag'; -import { useFullscreenStatus } from '../../../hooks/useFullscreen'; import { useIntersectionObserver, useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; +import { useFullscreenStatus } from '../../../hooks/window/useFullscreen'; import useGroupCallVideoLayout from './hooks/useGroupCallVideoLayout'; import Button from '../../ui/Button'; diff --git a/src/components/common/AnimatedSticker.tsx b/src/components/common/AnimatedSticker.tsx index b2e41a21c..8fec37014 100644 --- a/src/components/common/AnimatedSticker.tsx +++ b/src/components/common/AnimatedSticker.tsx @@ -15,7 +15,6 @@ import { hexToRgb } from '../../util/switchTheme'; import { IS_ELECTRON } from '../../util/windowEnvironment'; import useColorFilter from '../../hooks/stickers/useColorFilter'; -import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/useBackgroundMode'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck'; import useLastCallback from '../../hooks/useLastCallback'; @@ -25,6 +24,7 @@ import { useStateRef } from '../../hooks/useStateRef'; import useSyncEffect from '../../hooks/useSyncEffect'; import useThrottledCallback from '../../hooks/useThrottledCallback'; import useUniqueId from '../../hooks/useUniqueId'; +import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/window/useBackgroundMode'; export type OwnProps = { ref?: RefObject; diff --git a/src/components/common/AvatarStoryCircle.tsx b/src/components/common/AvatarStoryCircle.tsx index 1d5eda837..0dbdf0b6e 100644 --- a/src/components/common/AvatarStoryCircle.tsx +++ b/src/components/common/AvatarStoryCircle.tsx @@ -8,9 +8,10 @@ import type { AvatarSize } from './Avatar'; import { selectPeerStories, selectTheme, selectUser } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; -import { DPR } from '../../util/windowEnvironment'; import { REM } from './helpers/mediaDimensions'; +import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio'; + interface OwnProps { // eslint-disable-next-line react/no-unused-prop-types peerId: string; @@ -27,15 +28,15 @@ interface StateProps { } const SIZES: Record = { - micro: 1.125 * DPR * REM, - tiny: 2.125 * DPR * REM, - mini: 1.625 * DPR * REM, - small: 2.25 * DPR * REM, - 'small-mobile': 2.625 * DPR * REM, - medium: 2.875 * DPR * REM, - large: 3.5 * DPR * REM, - giant: 5.125 * DPR * REM, - jumbo: 7.625 * DPR * REM, + micro: 1.125 * REM, + tiny: 2.125 * REM, + mini: 1.625 * REM, + small: 2.25 * REM, + 'small-mobile': 2.625 * REM, + medium: 2.875 * REM, + large: 3.5 * REM, + giant: 5.125 * REM, + jumbo: 7.625 * REM, }; const BLUE = ['#34C578', '#3CA3F3']; @@ -43,8 +44,8 @@ const GREEN = ['#C9EB38', '#09C167']; const PURPLE = ['#A667FF', '#55A5FF']; const GRAY = '#C4C9CC'; const DARK_GRAY = '#737373'; -const STROKE_WIDTH = 0.125 * DPR * REM; -const STROKE_WIDTH_READ = 0.0625 * DPR * REM; +const STROKE_WIDTH = 0.125 * REM; +const STROKE_WIDTH_READ = 0.0625 * REM; const GAP_PERCENT = 2; const SEGMENTS_MAX = 45; // More than this breaks rendering in Safari and Chrome @@ -66,6 +67,8 @@ function AvatarStoryCircle({ // eslint-disable-next-line no-null/no-null const ref = useRef(null); + const dpr = useDevicePixelRatio(); + const values = useMemo(() => { return (storyIds || []).reduce((acc, id) => { acc.total += 1; @@ -84,20 +87,21 @@ function AvatarStoryCircle({ drawGradientCircle({ canvas: ref.current, - size: SIZES[size], + size: SIZES[size] * dpr, segmentsCount: values.total, color: isCloseFriend ? 'green' : 'blue', readSegmentsCount: values.read, withExtraGap, readSegmentColor: appTheme === 'dark' ? DARK_GRAY : GRAY, + dpr, }); - }, [appTheme, isCloseFriend, size, values.read, values.total, withExtraGap]); + }, [appTheme, isCloseFriend, size, values.read, values.total, withExtraGap, dpr]); if (!values.total) { return undefined; } - const maxSize = SIZES[size] / DPR; + const maxSize = SIZES[size]; return ( SEGMENTS_MAX) { readSegmentsCount = Math.round(readSegmentsCount * (SEGMENTS_MAX / segmentsCount)); @@ -144,7 +150,7 @@ export function drawGradientCircle({ segmentsCount = SEGMENTS_MAX; } - const strokeModifier = Math.max(Math.max(size - SIZES.large, 0) / DPR / REM / 1.5, 1); + const strokeModifier = Math.max(Math.max(size - SIZES.large * dpr, 0) / REM / 1.5, 1); const ctx = canvas.getContext('2d'); if (!ctx) { diff --git a/src/components/common/StickerSet.tsx b/src/components/common/StickerSet.tsx index cab96d28d..e3841ada4 100644 --- a/src/components/common/StickerSet.tsx +++ b/src/components/common/StickerSet.tsx @@ -28,7 +28,7 @@ import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import useMediaTransition from '../../hooks/useMediaTransition'; import useResizeObserver from '../../hooks/useResizeObserver'; -import useWindowSize from '../../hooks/useWindowSize'; +import useWindowSize from '../../hooks/window/useWindowSize'; import Button from '../ui/Button'; import ConfirmDialog from '../ui/ConfirmDialog'; diff --git a/src/components/common/StickerView.tsx b/src/components/common/StickerView.tsx index a7fdfea69..aaa49d577 100644 --- a/src/components/common/StickerView.tsx +++ b/src/components/common/StickerView.tsx @@ -20,6 +20,7 @@ import useMedia from '../../hooks/useMedia'; import useMediaTransition from '../../hooks/useMediaTransition'; import useThumbnail from '../../hooks/useThumbnail'; import useUniqueId from '../../hooks/useUniqueId'; +import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio'; import OptimizedVideo from '../ui/OptimizedVideo'; import AnimatedSticker from './AnimatedSticker'; @@ -87,6 +88,8 @@ const StickerView: FC = ({ const isStatic = !isLottie && !isVideo; const previewMediaHash = getStickerPreviewHash(sticker.id); + const dpr = useDevicePixelRatio(); + const filterStyle = useColorFilter(customColor); const isIntersectingForLoading = useIsIntersecting(containerRef, observeIntersectionForLoading); @@ -133,6 +136,7 @@ const StickerView: FC = ({ id, size, (withSharedAnimation ? customColor : undefined), + dpr, ].filter(Boolean).join('_'); return ( diff --git a/src/components/common/code/CodeOverlay.tsx b/src/components/common/code/CodeOverlay.tsx index 7d9674394..a40714390 100644 --- a/src/components/common/code/CodeOverlay.tsx +++ b/src/components/common/code/CodeOverlay.tsx @@ -9,7 +9,7 @@ import { copyTextToClipboard } from '../../../util/clipboard'; import { areLinesWrapping } from '../helpers/renderText'; import useLang from '../../../hooks/useLang'; -import useWindowSize from '../../../hooks/useWindowSize'; +import useWindowSize from '../../../hooks/window/useWindowSize'; import styles from './CodeOverlay.module.scss'; diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 616d9c8f0..fee74a56f 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -29,10 +29,10 @@ import useAppLayout from '../../../hooks/useAppLayout'; import useConnectionStatus from '../../../hooks/useConnectionStatus'; import useElectronDrag from '../../../hooks/useElectronDrag'; import useFlag from '../../../hooks/useFlag'; -import { useFullscreenStatus } from '../../../hooks/useFullscreen'; import { useHotkeys } from '../../../hooks/useHotkeys'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; +import { useFullscreenStatus } from '../../../hooks/window/useFullscreen'; import useLeftHeaderButtonRtlForumTransition from './hooks/useLeftHeaderButtonRtlForumTransition'; import PickerSelectedItem from '../../common/PickerSelectedItem'; diff --git a/src/components/main/ConfettiContainer.tsx b/src/components/main/ConfettiContainer.tsx index b6f782a39..1d39ac11c 100644 --- a/src/components/main/ConfettiContainer.tsx +++ b/src/components/main/ConfettiContainer.tsx @@ -12,7 +12,7 @@ import { pick } from '../../util/iteratees'; import useAppLayout from '../../hooks/useAppLayout'; import useForceUpdate from '../../hooks/useForceUpdate'; import useSyncEffect from '../../hooks/useSyncEffect'; -import useWindowSize from '../../hooks/useWindowSize'; +import useWindowSize from '../../hooks/window/useWindowSize'; import styles from './ConfettiContainer.module.scss'; diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 07747e51e..5fd7a821f 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -50,10 +50,7 @@ import updateIcon from '../../util/updateIcon'; import { IS_ANDROID, IS_ELECTRON } from '../../util/windowEnvironment'; import useAppLayout from '../../hooks/useAppLayout'; -import useBackgroundMode from '../../hooks/useBackgroundMode'; -import useBeforeUnload from '../../hooks/useBeforeUnload'; import useForceUpdate from '../../hooks/useForceUpdate'; -import { useFullscreenStatus } from '../../hooks/useFullscreen'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import useInterval from '../../hooks/useInterval'; import useLastCallback from '../../hooks/useLastCallback'; @@ -61,6 +58,9 @@ import usePreventPinchZoomGesture from '../../hooks/usePreventPinchZoomGesture'; import useShowTransition from '../../hooks/useShowTransition'; import useSyncEffect from '../../hooks/useSyncEffect'; import useTimeout from '../../hooks/useTimeout'; +import useBackgroundMode from '../../hooks/window/useBackgroundMode'; +import useBeforeUnload from '../../hooks/window/useBeforeUnload'; +import { useFullscreenStatus } from '../../hooks/window/useFullscreen'; import ActiveCallHeader from '../calls/ActiveCallHeader.async'; import GroupCall from '../calls/group/GroupCall.async'; diff --git a/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx b/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx index 7c3e9b428..4049cc486 100644 --- a/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx +++ b/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx @@ -5,11 +5,11 @@ import type { ApiUser } from '../../../../api/types'; import { selectUser } from '../../../../global/selectors'; import buildClassName from '../../../../util/buildClassName'; -import { DPR } from '../../../../util/windowEnvironment'; import { REM } from '../../../common/helpers/mediaDimensions'; import useLang from '../../../../hooks/useLang'; import useScrolledState from '../../../../hooks/useScrolledState'; +import useDevicePixelRatio from '../../../../hooks/window/useDevicePixelRatio'; import Avatar from '../../../common/Avatar'; import { drawGradientCircle } from '../../../common/AvatarStoryCircle'; @@ -53,7 +53,7 @@ const STORY_FEATURE_ICONS = { const STORY_FEATURE_ORDER = Object.keys(STORY_FEATURE_TITLES) as (keyof typeof STORY_FEATURE_TITLES)[]; -const CIRCLE_SIZE = 5.25 * DPR * REM; +const CIRCLE_SIZE = 5.25 * REM; const CIRCLE_SEGMENTS = 8; const CIRCLE_READ_SEGMENTS = 0; @@ -65,6 +65,8 @@ const PremiumFeaturePreviewVideo = ({ const lang = useLang(); + const dpr = useDevicePixelRatio(); + useLayoutEffect(() => { if (!circleRef.current) { return; @@ -72,17 +74,18 @@ const PremiumFeaturePreviewVideo = ({ drawGradientCircle({ canvas: circleRef.current, - size: CIRCLE_SIZE, + size: CIRCLE_SIZE * dpr, segmentsCount: CIRCLE_SEGMENTS, color: 'purple', readSegmentsCount: CIRCLE_READ_SEGMENTS, readSegmentColor: 'transparent', + dpr, }); - }, []); + }, [dpr]); const { handleScroll, isAtBeginning } = useScrolledState(); - const maxSize = CIRCLE_SIZE / DPR; + const maxSize = CIRCLE_SIZE; return (
diff --git a/src/components/mediaViewer/MediaViewer.tsx b/src/components/mediaViewer/MediaViewer.tsx index 379b9d106..8902c7f09 100644 --- a/src/components/mediaViewer/MediaViewer.tsx +++ b/src/components/mediaViewer/MediaViewer.tsx @@ -186,18 +186,10 @@ const MediaViewer: FC = ({ const forceUpdate = useForceUpdate(); useEffect(() => { const mql = window.matchMedia(MEDIA_VIEWER_MEDIA_QUERY); - if (typeof mql.addEventListener === 'function') { - mql.addEventListener('change', forceUpdate); - } else if (typeof mql.addListener === 'function') { - mql.addListener(forceUpdate); - } + mql.addEventListener('change', forceUpdate); return () => { - if (typeof mql.removeEventListener === 'function') { - mql.removeEventListener('change', forceUpdate); - } else if (typeof mql.removeListener === 'function') { - mql.removeListener(forceUpdate); - } + mql.removeEventListener('change', forceUpdate); }; }, [forceUpdate]); diff --git a/src/components/mediaViewer/MediaViewerSlides.tsx b/src/components/mediaViewer/MediaViewerSlides.tsx index e64c1c235..9432101fc 100644 --- a/src/components/mediaViewer/MediaViewerSlides.tsx +++ b/src/components/mediaViewer/MediaViewerSlides.tsx @@ -20,14 +20,14 @@ import { IS_IOS, IS_TOUCH_ENV } from '../../util/windowEnvironment'; import useDebouncedCallback from '../../hooks/useDebouncedCallback'; import useDerivedState from '../../hooks/useDerivedState'; -import { useFullscreenStatus } from '../../hooks/useFullscreen'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import useSignal from '../../hooks/useSignal'; import { useSignalRef } from '../../hooks/useSignalRef'; import useTimeout from '../../hooks/useTimeout'; -import useWindowSize from '../../hooks/useWindowSize'; +import { useFullscreenStatus } from '../../hooks/window/useFullscreen'; +import useWindowSize from '../../hooks/window/useWindowSize'; import useControlsSignal from './hooks/useControlsSignal'; import useZoomChange from './hooks/useZoomChangeSignal'; diff --git a/src/components/mediaViewer/VideoPlayer.tsx b/src/components/mediaViewer/VideoPlayer.tsx index 89850fa95..bdd5961fa 100644 --- a/src/components/mediaViewer/VideoPlayer.tsx +++ b/src/components/mediaViewer/VideoPlayer.tsx @@ -15,11 +15,11 @@ import useUnsupportedMedia from '../../hooks/media/useUnsupportedMedia'; import useAppLayout from '../../hooks/useAppLayout'; import useBuffering from '../../hooks/useBuffering'; import useCurrentTimeSignal from '../../hooks/useCurrentTimeSignal'; -import useFullscreen from '../../hooks/useFullscreen'; import useLastCallback from '../../hooks/useLastCallback'; import usePictureInPicture from '../../hooks/usePictureInPicture'; import useShowTransition from '../../hooks/useShowTransition'; import useVideoCleanup from '../../hooks/useVideoCleanup'; +import useFullscreen from '../../hooks/window/useFullscreen'; import useControlsSignal from './hooks/useControlsSignal'; import useVideoWaitingSignal from './hooks/useVideoWaitingSignal'; diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 5f924df5b..a6cda0844 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -58,7 +58,6 @@ import { debounce, onTickEnd } from '../../util/schedulers'; import { groupMessages } from './helpers/groupMessages'; import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; -import { isBackgroundModeActive } from '../../hooks/useBackgroundMode'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import useInterval from '../../hooks/useInterval'; @@ -67,6 +66,7 @@ import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps import useNativeCopySelectedMessages from '../../hooks/useNativeCopySelectedMessages'; import { useStateRef } from '../../hooks/useStateRef'; import useSyncEffect from '../../hooks/useSyncEffect'; +import { isBackgroundModeActive } from '../../hooks/window/useBackgroundMode'; import useContainerHeight from './hooks/useContainerHeight'; import useStickyDates from './hooks/useStickyDates'; diff --git a/src/components/middle/MessageListBotInfo.tsx b/src/components/middle/MessageListBotInfo.tsx index 269ae0e94..ed378b919 100644 --- a/src/components/middle/MessageListBotInfo.tsx +++ b/src/components/middle/MessageListBotInfo.tsx @@ -13,11 +13,11 @@ import { import { selectBot, selectUserFullInfo } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import buildStyle from '../../util/buildStyle'; -import { DPR } from '../../util/windowEnvironment'; import renderText from '../common/helpers/renderText'; import useLang from '../../hooks/useLang'; import useMedia from '../../hooks/useMedia'; +import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio'; import OptimizedVideo from '../ui/OptimizedVideo'; import Skeleton from '../ui/placeholder/Skeleton'; @@ -40,14 +40,15 @@ const MessageListBotInfo: FC = ({ isInMessageList, }) => { const lang = useLang(); + const dpr = useDevicePixelRatio(); const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined); const botInfoGifUrl = useMedia(botInfo?.gif ? getDocumentMediaHash(botInfo.gif) : undefined); const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif ? getVideoDimensions(botInfo.gif) : undefined; const botInfoRealDimensions = botInfoDimensions && { - width: botInfoDimensions.width / DPR, - height: botInfoDimensions.height / DPR, + width: botInfoDimensions.width / dpr, + height: botInfoDimensions.height / dpr, }; const isBotInfoEmpty = botInfo && !botInfo.description && !botInfo.gif && !botInfo.photo; diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index cb97124b4..bddc14f5d 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -72,7 +72,7 @@ import usePrevDuringAnimation from '../../hooks/usePrevDuringAnimation'; import usePrevious from '../../hooks/usePrevious'; import { useResize } from '../../hooks/useResize'; import useSyncEffect from '../../hooks/useSyncEffect'; -import useWindowSize from '../../hooks/useWindowSize'; +import useWindowSize from '../../hooks/window/useWindowSize'; import usePinnedMessage from './hooks/usePinnedMessage'; import Composer from '../common/Composer'; diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index c605ec7cd..044212911 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -62,7 +62,7 @@ import useLang from '../../hooks/useLang'; import useLastCallback from '../../hooks/useLastCallback'; import usePrevious from '../../hooks/usePrevious'; import useShowTransition from '../../hooks/useShowTransition'; -import useWindowSize from '../../hooks/useWindowSize'; +import useWindowSize from '../../hooks/window/useWindowSize'; import GroupCallTopPane from '../calls/group/GroupCallTopPane'; import GroupChatInfo from '../common/GroupChatInfo'; diff --git a/src/components/middle/composer/hooks/useDraft.ts b/src/components/middle/composer/hooks/useDraft.ts index 8e583aff5..d314928c9 100644 --- a/src/components/middle/composer/hooks/useDraft.ts +++ b/src/components/middle/composer/hooks/useDraft.ts @@ -15,12 +15,12 @@ import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText' import { IS_TOUCH_ENV } from '../../../../util/windowEnvironment'; import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities'; -import useBackgroundMode from '../../../../hooks/useBackgroundMode'; -import useBeforeUnload from '../../../../hooks/useBeforeUnload'; import useLastCallback from '../../../../hooks/useLastCallback'; import useLayoutEffectWithPrevDeps from '../../../../hooks/useLayoutEffectWithPrevDeps'; import useRunDebounced from '../../../../hooks/useRunDebounced'; import { useStateRef } from '../../../../hooks/useStateRef'; +import useBackgroundMode from '../../../../hooks/window/useBackgroundMode'; +import useBeforeUnload from '../../../../hooks/window/useBeforeUnload'; let isFrozen = false; diff --git a/src/components/middle/composer/hooks/useEditing.ts b/src/components/middle/composer/hooks/useEditing.ts index 3bdc9b0b2..5f1635988 100644 --- a/src/components/middle/composer/hooks/useEditing.ts +++ b/src/components/middle/composer/hooks/useEditing.ts @@ -14,11 +14,11 @@ import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText' import { getTextWithEntitiesAsHtml } from '../../../common/helpers/renderTextWithEntities'; import { useDebouncedResolver } from '../../../../hooks/useAsyncResolvers'; -import useBackgroundMode from '../../../../hooks/useBackgroundMode'; -import useBeforeUnload from '../../../../hooks/useBeforeUnload'; import useDerivedSignal from '../../../../hooks/useDerivedSignal'; import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps'; import useLastCallback from '../../../../hooks/useLastCallback'; +import useBackgroundMode from '../../../../hooks/window/useBackgroundMode'; +import useBeforeUnload from '../../../../hooks/window/useBeforeUnload'; const URL_ENTITIES = new Set([ApiMessageEntityTypes.TextUrl, ApiMessageEntityTypes.Url]); const DEBOUNCE_MS = 300; diff --git a/src/components/middle/composer/hooks/useInputCustomEmojis.ts b/src/components/middle/composer/hooks/useInputCustomEmojis.ts index 9c881f20c..5982b53d3 100644 --- a/src/components/middle/composer/hooks/useInputCustomEmojis.ts +++ b/src/components/middle/composer/hooks/useInputCustomEmojis.ts @@ -20,11 +20,12 @@ import { REM } from '../../../common/helpers/mediaDimensions'; import useColorFilter from '../../../../hooks/stickers/useColorFilter'; import useDynamicColorListener from '../../../../hooks/stickers/useDynamicColorListener'; -import useBackgroundMode from '../../../../hooks/useBackgroundMode'; import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps'; import useLastCallback from '../../../../hooks/useLastCallback'; import useResizeObserver from '../../../../hooks/useResizeObserver'; import useThrottledCallback from '../../../../hooks/useThrottledCallback'; +import useBackgroundMode from '../../../../hooks/window/useBackgroundMode'; +import useDevicePixelRatio from '../../../../hooks/window/useDevicePixelRatio'; const SIZE = 1.25 * REM; const THROTTLE_MS = 300; @@ -49,6 +50,7 @@ export default function useInputCustomEmojis( ) { const customColor = useDynamicColorListener(inputRef, !isReady); const colorFilter = useColorFilter(customColor, true); + const dpr = useDevicePixelRatio(); const playersById = useRef>(new Map()); const clearPlayers = useLastCallback((ids: string[]) => { @@ -99,7 +101,7 @@ export default function useInputCustomEmojis( } const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(global, customEmoji.stickerSetInfo); const renderId = [ - prefixId, documentId, customColor, + prefixId, documentId, customColor, dpr, ].filter(Boolean).join('_'); createPlayer({ @@ -159,6 +161,12 @@ export default function useInputCustomEmojis( false, ); useResizeObserver(sharedCanvasRef, throttledSynchronizeElements); + useEffectWithPrevDeps(([prevDpr]) => { + if (dpr !== prevDpr) { + clearPlayers(Array.from(playersById.current.keys())); + synchronizeElements(); + } + }, [dpr, synchronizeElements]); const freezeAnimation = useLastCallback(() => { playersById.current.forEach((player) => { diff --git a/src/components/middle/hooks/useMessageObservers.ts b/src/components/middle/hooks/useMessageObservers.ts index f4c9e2385..b0251187d 100644 --- a/src/components/middle/hooks/useMessageObservers.ts +++ b/src/components/middle/hooks/useMessageObservers.ts @@ -7,8 +7,8 @@ import type { PinnedIntersectionChangedCallback } from './usePinnedMessage'; import { IS_ANDROID } from '../../../util/windowEnvironment'; import useAppLayout from '../../../hooks/useAppLayout'; -import useBackgroundMode, { isBackgroundModeActive } from '../../../hooks/useBackgroundMode'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; +import useBackgroundMode, { isBackgroundModeActive } from '../../../hooks/window/useBackgroundMode'; const INTERSECTION_THROTTLE_FOR_READING = 150; const INTERSECTION_THROTTLE_FOR_MEDIA = IS_ANDROID ? 1000 : 350; diff --git a/src/components/middle/message/ReactionPickerLimited.tsx b/src/components/middle/message/ReactionPickerLimited.tsx index a69b17ed1..db97df75e 100644 --- a/src/components/middle/message/ReactionPickerLimited.tsx +++ b/src/components/middle/message/ReactionPickerLimited.tsx @@ -10,7 +10,7 @@ import buildClassName from '../../../util/buildClassName'; import { REM } from '../../common/helpers/mediaDimensions'; import useAppLayout from '../../../hooks/useAppLayout'; -import useWindowSize from '../../../hooks/useWindowSize'; +import useWindowSize from '../../../hooks/window/useWindowSize'; import ReactionEmoji from '../../common/ReactionEmoji'; diff --git a/src/components/middle/message/hooks/useVideoAutoPause.ts b/src/components/middle/message/hooks/useVideoAutoPause.ts index 4e1dcb77b..0369871ab 100644 --- a/src/components/middle/message/hooks/useVideoAutoPause.ts +++ b/src/components/middle/message/hooks/useVideoAutoPause.ts @@ -2,10 +2,10 @@ import { useEffect, useRef } from '../../../../lib/teact/teact'; import { requestMeasure } from '../../../../lib/fasterdom/fasterdom'; -import useBackgroundMode, { isBackgroundModeActive } from '../../../../hooks/useBackgroundMode'; import useHeavyAnimationCheck, { isHeavyAnimating } from '../../../../hooks/useHeavyAnimationCheck'; import useLastCallback from '../../../../hooks/useLastCallback'; import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../../../hooks/usePriorityPlaybackCheck'; +import useBackgroundMode, { isBackgroundModeActive } from '../../../../hooks/window/useBackgroundMode'; export default function useVideoAutoPause( playerRef: { current: HTMLVideoElement | null }, canPlay: boolean, isPriority?: boolean, diff --git a/src/components/modals/webApp/hooks/useWebAppFrame.ts b/src/components/modals/webApp/hooks/useWebAppFrame.ts index de3c44694..66960fca6 100644 --- a/src/components/modals/webApp/hooks/useWebAppFrame.ts +++ b/src/components/modals/webApp/hooks/useWebAppFrame.ts @@ -6,7 +6,7 @@ import type { WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/ import { extractCurrentThemeParams } from '../../../../util/themeStyle'; import useLastCallback from '../../../../hooks/useLastCallback'; -import useWindowSize from '../../../../hooks/useWindowSize'; +import useWindowSize from '../../../../hooks/window/useWindowSize'; const SCROLLBAR_STYLE = `* { scrollbar-width: thin; diff --git a/src/components/right/RightColumn.tsx b/src/components/right/RightColumn.tsx index 3df28551b..79d6c2fbe 100644 --- a/src/components/right/RightColumn.tsx +++ b/src/components/right/RightColumn.tsx @@ -18,7 +18,7 @@ import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useHistoryBack from '../../hooks/useHistoryBack'; import useLastCallback from '../../hooks/useLastCallback'; import useLayoutEffectWithPrevDeps from '../../hooks/useLayoutEffectWithPrevDeps'; -import useWindowSize from '../../hooks/useWindowSize'; +import useWindowSize from '../../hooks/window/useWindowSize'; import Transition from '../ui/Transition'; import AddChatMembers from './AddChatMembers'; diff --git a/src/components/story/Story.tsx b/src/components/story/Story.tsx index 444cb6b2b..22e7bcfa9 100644 --- a/src/components/story/Story.tsx +++ b/src/components/story/Story.tsx @@ -34,7 +34,6 @@ import { PRIMARY_VIDEO_MIME, SECONDARY_VIDEO_MIME } from './helpers/videoFormats import useUnsupportedMedia from '../../hooks/media/useUnsupportedMedia'; import useAppLayout, { getIsMobile } from '../../hooks/useAppLayout'; -import useBackgroundMode from '../../hooks/useBackgroundMode'; import useCanvasBlur from '../../hooks/useCanvasBlur'; import useCurrentOrPrev from '../../hooks/useCurrentOrPrev'; import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps'; @@ -45,6 +44,7 @@ import useLongPress from '../../hooks/useLongPress'; import useMediaTransition from '../../hooks/useMediaTransition'; import useShowTransition from '../../hooks/useShowTransition'; import { useStreaming } from '../../hooks/useStreaming'; +import useBackgroundMode from '../../hooks/window/useBackgroundMode'; import useStoryPreloader from './hooks/useStoryPreloader'; import useStoryProps from './hooks/useStoryProps'; diff --git a/src/components/story/StorySlides.tsx b/src/components/story/StorySlides.tsx index 91559df19..cb6c3ddc0 100644 --- a/src/components/story/StorySlides.tsx +++ b/src/components/story/StorySlides.tsx @@ -36,7 +36,7 @@ import useHistoryBack from '../../hooks/useHistoryBack'; import useLastCallback from '../../hooks/useLastCallback'; import usePrevious from '../../hooks/usePrevious'; import useSignal from '../../hooks/useSignal'; -import useWindowSize from '../../hooks/useWindowSize'; +import useWindowSize from '../../hooks/window/useWindowSize'; import useSlideSizes from './hooks/useSlideSizes'; import Story from './Story'; diff --git a/src/components/story/hooks/useSlideSizes.ts b/src/components/story/hooks/useSlideSizes.ts index 33487fac4..2a1062748 100644 --- a/src/components/story/hooks/useSlideSizes.ts +++ b/src/components/story/hooks/useSlideSizes.ts @@ -2,7 +2,7 @@ import { useMemo } from '../../../lib/teact/teact'; import { calculateSlideSizes } from '../helpers/dimensions'; -import useWindowSize from '../../../hooks/useWindowSize'; +import useWindowSize from '../../../hooks/window/useWindowSize'; export default function useSlideSizes() { const { width: windowWidth, height: windowHeight } = useWindowSize(); diff --git a/src/components/story/mediaArea/MediaAreaOverlay.tsx b/src/components/story/mediaArea/MediaAreaOverlay.tsx index 8da60dce3..79c25428b 100644 --- a/src/components/story/mediaArea/MediaAreaOverlay.tsx +++ b/src/components/story/mediaArea/MediaAreaOverlay.tsx @@ -8,7 +8,7 @@ import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import buildClassName from '../../../util/buildClassName'; import buildStyle from '../../../util/buildStyle'; -import useWindowSize from '../../../hooks/useWindowSize'; +import useWindowSize from '../../../hooks/window/useWindowSize'; import MediaAreaSuggestedReaction from './MediaAreaSuggestedReaction'; diff --git a/src/components/ui/ProgressSpinner.tsx b/src/components/ui/ProgressSpinner.tsx index 9299b2a1a..764c3fd1c 100644 --- a/src/components/ui/ProgressSpinner.tsx +++ b/src/components/ui/ProgressSpinner.tsx @@ -4,18 +4,18 @@ import React, { memo, useEffect, useRef } from '../../lib/teact/teact'; import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { animate, timingFunctions } from '../../util/animation'; import buildClassName from '../../util/buildClassName'; -import { DPR } from '../../util/windowEnvironment'; import { useStateRef } from '../../hooks/useStateRef'; +import useDevicePixelRatio from '../../hooks/window/useDevicePixelRatio'; import './ProgressSpinner.scss'; const SIZES = { s: 42, m: 48, l: 54, xl: 52, }; -const STROKE_WIDTH = 2 * DPR; -const STROKE_WIDTH_XL = 3 * DPR; -const PADDING = 2 * DPR; +const STROKE_WIDTH = 2; +const STROKE_WIDTH_XL = 3; +const PADDING = 2; const MIN_PROGRESS = 0.05; const MAX_PROGRESS = 1; const GROW_DURATION = 600; // 0.6 s @@ -41,6 +41,8 @@ const ProgressSpinner: FC<{ const width = SIZES[size]; const progressRef = useStateRef(progress); + const dpr = useDevicePixelRatio(); + useEffect(() => { let isFirst = true; let growFrom = MIN_PROGRESS; @@ -65,10 +67,11 @@ const ProgressSpinner: FC<{ drawSpinnerArc( canvasRef.current, - width * DPR, - size === 'xl' ? STROKE_WIDTH_XL : STROKE_WIDTH, + width * dpr, + (size === 'xl' ? STROKE_WIDTH_XL : STROKE_WIDTH) * dpr, 'white', currentProgress, + dpr, isFirst, ); @@ -76,7 +79,7 @@ const ProgressSpinner: FC<{ return currentProgress < 1; }, requestMutation); - }, [progressRef, size, width]); + }, [progressRef, size, width, dpr]); const className = buildClassName( `ProgressSpinner size-${size}`, @@ -101,10 +104,11 @@ function drawSpinnerArc( strokeWidth: number, color: string, progress: number, + dpr: number, shouldInit = false, ) { const centerCoordinate = size / 2; - const radius = (size - strokeWidth) / 2 - PADDING; + const radius = (size - strokeWidth) / 2 - PADDING * dpr; const rotationOffset = (Date.now() % ROTATE_DURATION) / ROTATE_DURATION; const startAngle = (2 * Math.PI) * rotationOffset; const endAngle = startAngle + (2 * Math.PI) * progress; diff --git a/src/hooks/useConnectionStatus.ts b/src/hooks/useConnectionStatus.ts index dffa8a3f2..f8f419068 100644 --- a/src/hooks/useConnectionStatus.ts +++ b/src/hooks/useConnectionStatus.ts @@ -1,7 +1,7 @@ import type { GlobalState } from '../global/types'; import type { LangFn } from './useLang'; -import useBrowserOnline from './useBrowserOnline'; +import useBrowserOnline from './window/useBrowserOnline'; export enum ConnectionStatus { waitingForNetwork, diff --git a/src/hooks/useBackgroundMode.ts b/src/hooks/window/useBackgroundMode.ts similarity index 86% rename from src/hooks/useBackgroundMode.ts rename to src/hooks/window/useBackgroundMode.ts index e17569b0d..639fa4d99 100644 --- a/src/hooks/useBackgroundMode.ts +++ b/src/hooks/window/useBackgroundMode.ts @@ -1,7 +1,7 @@ -import { useEffect } from '../lib/teact/teact'; +import { useEffect } from '../../lib/teact/teact'; -import { createCallbackManager } from '../util/callbacks'; -import useLastCallback from './useLastCallback'; +import { createCallbackManager } from '../../util/callbacks'; +import useLastCallback from '../useLastCallback'; const blurCallbacks = createCallbackManager(); const focusCallbacks = createCallbackManager(); diff --git a/src/hooks/useBeforeUnload.ts b/src/hooks/window/useBeforeUnload.ts similarity index 54% rename from src/hooks/useBeforeUnload.ts rename to src/hooks/window/useBeforeUnload.ts index 89914ef2c..3834d1050 100644 --- a/src/hooks/useBeforeUnload.ts +++ b/src/hooks/window/useBeforeUnload.ts @@ -1,7 +1,7 @@ -import { useEffect } from '../lib/teact/teact'; +import { useEffect } from '../../lib/teact/teact'; -import { onBeforeUnload } from '../util/schedulers'; -import useLastCallback from './useLastCallback'; +import { onBeforeUnload } from '../../util/schedulers'; +import useLastCallback from '../useLastCallback'; export default function useBeforeUnload(callback: AnyToVoidFunction) { const lastCallback = useLastCallback(callback); diff --git a/src/hooks/useBrowserOnline.ts b/src/hooks/window/useBrowserOnline.ts similarity index 89% rename from src/hooks/useBrowserOnline.ts rename to src/hooks/window/useBrowserOnline.ts index afcdf8c4d..84b0c2bb7 100644 --- a/src/hooks/useBrowserOnline.ts +++ b/src/hooks/window/useBrowserOnline.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from '../lib/teact/teact'; +import { useEffect, useState } from '../../lib/teact/teact'; export default function useBrowserOnline() { const [isOnline, setIsOnline] = useState(window.navigator.onLine); diff --git a/src/hooks/window/useDevicePixelRatio.ts b/src/hooks/window/useDevicePixelRatio.ts new file mode 100644 index 000000000..7b4f544b9 --- /dev/null +++ b/src/hooks/window/useDevicePixelRatio.ts @@ -0,0 +1,28 @@ +import { useState } from '../../lib/teact/teact'; + +import { createCallbackManager } from '../../util/callbacks'; +import useEffectOnce from '../useEffectOnce'; + +const callbacks = createCallbackManager(); + +function createListener() { + window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`) + .addEventListener('change', callbacks.runCallbacks, { once: true }); +} + +export default function useDevicePixelRatio() { + const [dpr, setDpr] = useState(window.devicePixelRatio); + + useEffectOnce(() => { + callbacks.addCallback(() => { + setDpr(window.devicePixelRatio); + }); + }); + + return dpr; +} + +createListener(); + +// Set up new listener for the next `devicePixelRatio` change +callbacks.addCallback(createListener); diff --git a/src/hooks/useFullscreen.ts b/src/hooks/window/useFullscreen.ts similarity index 95% rename from src/hooks/useFullscreen.ts rename to src/hooks/window/useFullscreen.ts index d5bb1907e..631e4e865 100644 --- a/src/hooks/useFullscreen.ts +++ b/src/hooks/window/useFullscreen.ts @@ -1,8 +1,8 @@ -import { useEffect, useLayoutEffect, useState } from '../lib/teact/teact'; +import { useEffect, useLayoutEffect, useState } from '../../lib/teact/teact'; -import { ElectronEvent } from '../types/electron'; +import { ElectronEvent } from '../../types/electron'; -import { IS_IOS } from '../util/windowEnvironment'; +import { IS_IOS } from '../../util/windowEnvironment'; type RefType = { current: HTMLVideoElement | null; diff --git a/src/hooks/useWindowSize.ts b/src/hooks/window/useWindowSize.ts similarity index 83% rename from src/hooks/useWindowSize.ts rename to src/hooks/window/useWindowSize.ts index a26253c83..e802cc594 100644 --- a/src/hooks/useWindowSize.ts +++ b/src/hooks/window/useWindowSize.ts @@ -1,8 +1,8 @@ -import { useEffect, useMemo, useState } from '../lib/teact/teact'; +import { useEffect, useMemo, useState } from '../../lib/teact/teact'; -import { throttle } from '../util/schedulers'; -import windowSize from '../util/windowSize'; -import useDebouncedCallback from './useDebouncedCallback'; +import { throttle } from '../../util/schedulers'; +import windowSize from '../../util/windowSize'; +import useDebouncedCallback from '../useDebouncedCallback'; const THROTTLE = 250; diff --git a/src/lib/rlottie/RLottie.ts b/src/lib/rlottie/RLottie.ts index 076b3b014..f61a7c5e9 100644 --- a/src/lib/rlottie/RLottie.ts +++ b/src/lib/rlottie/RLottie.ts @@ -4,8 +4,7 @@ import Deferred from '../../util/Deferred'; import generateUniqueId from '../../util/generateUniqueId'; import launchMediaWorkers, { MAX_WORKERS } from '../../util/launchMediaWorkers'; import { - DPR, IS_ANDROID, IS_IOS, - IS_SAFARI, + IS_ANDROID, IS_IOS, IS_SAFARI, } from '../../util/windowEnvironment'; import { requestMeasure, requestMutation } from '../fasterdom/fasterdom'; @@ -338,7 +337,7 @@ class RLottie { } = this.params; // Reduced quality only looks acceptable on high DPR screens - return Math.max(DPR * quality, 1); + return Math.max(window.devicePixelRatio * quality, 1); } private destroy() { diff --git a/src/util/systemTheme.ts b/src/util/systemTheme.ts index 28e676600..911934271 100644 --- a/src/util/systemTheme.ts +++ b/src/util/systemTheme.ts @@ -20,9 +20,5 @@ export function setSystemThemeChangeCallback(callback: (newTheme: ThemeKey) => v themeChangeCallback = callback; } -const mql = window.matchMedia('(prefers-color-scheme: dark)'); -if (typeof mql.addEventListener === 'function') { - mql.addEventListener('change', handleSystemThemeChange); -} else if (typeof mql.addListener === 'function') { - mql.addListener(handleSystemThemeChange); -} +window.matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', handleSystemThemeChange); diff --git a/src/util/windowEnvironment.ts b/src/util/windowEnvironment.ts index 56dd29b63..39e7ab282 100644 --- a/src/util/windowEnvironment.ts +++ b/src/util/windowEnvironment.ts @@ -80,8 +80,6 @@ export const IS_WEBM_SUPPORTED = Boolean(TEST_VIDEO.canPlayType('video/webm; cod export const ARE_WEBCODECS_SUPPORTED = 'VideoDecoder' in window; -export const DPR = window.devicePixelRatio || 1; - export const MASK_IMAGE_DISABLED = true; export const IS_OPFS_SUPPORTED = Boolean(navigator.storage?.getDirectory); if (IS_OPFS_SUPPORTED) {