diff --git a/src/components/MediaInsetBorder.tsx b/src/components/MediaInsetBorder.tsx index 7b938ae9a8..2f27f6f887 100644 --- a/src/components/MediaInsetBorder.tsx +++ b/src/components/MediaInsetBorder.tsx @@ -1,9 +1,9 @@ import {StyleSheet} from 'react-native' import type React from 'react' -import {isHighDPI} from '#/lib/browser' import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf' import {Fill} from '#/components/Fill' +import {IS_HIGH_DPI} from '#/env' /** * Applies and thin border within a bounding box. Used to contrast media from @@ -33,7 +33,7 @@ export function MediaInsetBorder({ // while we generally use hairlineWidth (aka 1px), // we make an exception here for high DPI screens // as the 1px border is very noticeable -sfn - web: isHighDPI ? 0.5 : StyleSheet.hairlineWidth, + web: IS_HIGH_DPI ? 0.5 : StyleSheet.hairlineWidth, }), }, opaque diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx index 39661adef3..f138da48f2 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx @@ -3,10 +3,10 @@ import {View} from 'react-native' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {isFirefox, isTouchDevice} from '#/lib/browser' import {clamp} from '#/lib/numbers' import {atoms as a, useTheme, web} from '#/alf' import {useInteractionState} from '#/components/hooks/useInteractionState' +import {IS_WEB_FIREFOX, IS_WEB_TOUCH_DEVICE} from '#/env' import {formatTime} from './utils' export function Scrubber({ @@ -100,7 +100,7 @@ export function Scrubber({ // a pointerUp event is fired outside the element that captured the // pointer. Firefox clicks on the element the mouse is over, so we have // to make everything unclickable while seeking -sfn - if (isFirefox && scrubberActive) { + if (IS_WEB_FIREFOX && scrubberActive) { document.body.classList.add('force-no-clicks') return () => { @@ -151,7 +151,7 @@ export function Scrubber({ - {(!volumeHovered || isTouchDevice) && ( + {(!volumeHovered || IS_WEB_TOUCH_DEVICE) && ( - {hovered && !isTouchDevice && ( + {hovered && !IS_WEB_TOUCH_DEVICE && ( ) { const [playing, setPlaying] = useState(false) @@ -41,7 +41,7 @@ export function useVideoElement(ref: RefObject) { setCurrentTime(round(ref.current.currentTime) || 0) // HACK: Safari randomly fires `stalled` events when changing between segments // let's just clear the buffering state if the video is still progressing -sfn - if (isSafari) { + if (IS_WEB_SAFARI) { if (bufferingTimeout) clearTimeout(bufferingTimeout) setBuffering(false) } diff --git a/src/components/Post/Embed/VideoEmbed/index.web.tsx b/src/components/Post/Embed/VideoEmbed/index.web.tsx index b436d7a934..19c6c83b2c 100644 --- a/src/components/Post/Embed/VideoEmbed/index.web.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.web.tsx @@ -11,7 +11,6 @@ import {type AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {isFirefox} from '#/lib/browser' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {atoms as a, useTheme} from '#/alf' import {useIsWithinMessage} from '#/components/dms/MessageContext' @@ -23,6 +22,7 @@ import { VideoEmbedInnerWeb, VideoNotFoundError, } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' +import {IS_WEB_FIREFOX} from '#/env' import {useActiveVideoWeb} from './ActiveVideoWebContext' import * as VideoFallback from './VideoEmbedInner/VideoFallback' @@ -37,7 +37,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { useEffect(() => { if (!ref.current) return - if (isFullscreen && !isFirefox) return + if (isFullscreen && !IS_WEB_FIREFOX) return const observer = new IntersectionObserver( entries => { const entry = entries[0] @@ -150,7 +150,7 @@ function ViewportObserver({ // observing a div of 100vh height useEffect(() => { if (!ref.current) return - if (isFullscreen && !isFirefox) return + if (isFullscreen && !IS_WEB_FIREFOX) return const observer = new IntersectionObserver( entries => { const entry = entries[0] diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 2f8e3105cc..12246387fd 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -11,7 +11,6 @@ import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {useActorStatus} from '#/lib/actor-status' -import {isTouchDevice} from '#/lib/browser' import {getModerationCauseKey} from '#/lib/moderation' import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' @@ -43,6 +42,7 @@ import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' import {VerificationCheck} from '#/components/verification/VerificationCheck' +import {IS_WEB_TOUCH_DEVICE} from '#/env' import {type ProfileHoverCardProps} from './types' const floatingMiddlewares = [ @@ -70,7 +70,7 @@ export function ProfileHoverCard(props: ProfileHoverCardProps) { } } - if (props.disable || isTouchDevice) { + if (props.disable || IS_WEB_TOUCH_DEVICE) { return props.children } else { return ( diff --git a/src/components/SubtleHover.tsx b/src/components/SubtleHover.tsx index c228da2cb7..22a26f0989 100644 --- a/src/components/SubtleHover.tsx +++ b/src/components/SubtleHover.tsx @@ -1,8 +1,7 @@ import {View} from 'react-native' -import {isTouchDevice} from '#/lib/browser' import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' -import {IS_NATIVE, IS_WEB} from '#/env' +import {IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env' export function SubtleHover({ style, @@ -40,7 +39,7 @@ export function SubtleHover({ ) if (IS_WEB && web) { - return isTouchDevice ? null : el + return IS_WEB_TOUCH_DEVICE ? null : el } else if (IS_NATIVE && native) { return el } diff --git a/src/components/hooks/useFullscreen.ts b/src/components/hooks/useFullscreen.ts index bbda454b8e..107095ef84 100644 --- a/src/components/hooks/useFullscreen.ts +++ b/src/components/hooks/useFullscreen.ts @@ -6,8 +6,7 @@ import { useSyncExternalStore, } from 'react' -import {isFirefox, isSafari} from '#/lib/browser' -import {IS_WEB} from '#/env' +import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env' function fullscreenSubscribe(onChange: () => void) { document.addEventListener('fullscreenchange', onChange) @@ -39,7 +38,7 @@ export function useFullscreen(ref?: React.RefObject) { // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium - if (prevIsFullscreen && !isFirefox && !isSafari) { + if (prevIsFullscreen && !IS_WEB_FIREFOX && !IS_WEB_SAFARI) { setTimeout(() => { if (scrollYRef.current !== null) { window.scrollTo(0, scrollYRef.current) diff --git a/src/env/index.ts b/src/env/index.ts index f3b1e8efa1..93068d57ee 100644 --- a/src/env/index.ts +++ b/src/env/index.ts @@ -26,5 +26,18 @@ export const IS_IOS: boolean = Platform.OS === 'ios' export const IS_ANDROID: boolean = Platform.OS === 'android' export const IS_NATIVE: boolean = true export const IS_WEB: boolean = false + +/** + * Web-specific platform detection + */ +export const IS_WEB_TOUCH_DEVICE: boolean = true export const IS_WEB_MOBILE: boolean = false export const IS_WEB_MOBILE_IOS: boolean = false +export const IS_WEB_MOBILE_ANDROID: boolean = false +export const IS_WEB_SAFARI: boolean = false +export const IS_WEB_FIREFOX: boolean = false + +/** + * Misc + */ +export const IS_HIGH_DPI: boolean = true diff --git a/src/env/index.web.ts b/src/env/index.web.ts index a66709d078..0435569ed6 100644 --- a/src/env/index.web.ts +++ b/src/env/index.web.ts @@ -21,8 +21,29 @@ export const IS_IOS: boolean = false export const IS_ANDROID: boolean = false export const IS_NATIVE: boolean = false export const IS_WEB: boolean = true -// @ts-ignore we know window exists -prf -export const IS_WEB_MOBILE: boolean = global.window.matchMedia( + +/** + * Web-specific platform detection + */ +export const IS_WEB_TOUCH_DEVICE = + window.matchMedia('(pointer: coarse)').matches +export const IS_WEB_MOBILE: boolean = window.matchMedia( 'only screen and (max-width: 1300px)', )?.matches export const IS_WEB_MOBILE_IOS: boolean = /iPhone/.test(navigator.userAgent) +export const IS_WEB_MOBILE_ANDROID: boolean = + /android/i.test(navigator.userAgent) && IS_WEB_TOUCH_DEVICE +export const IS_WEB_SAFARI: boolean = /^((?!chrome|android).)*safari/i.test( + // https://stackoverflow.com/questions/7944460/detect-safari-browser + navigator.userAgent, +) +export const IS_WEB_FIREFOX: boolean = /firefox|fxios/i.test( + navigator.userAgent, +) + +/** + * Misc + */ +export const IS_HIGH_DPI: boolean = window.matchMedia( + '(min-resolution: 2dppx)', +).matches diff --git a/src/lib/browser.native.ts b/src/lib/browser.native.ts deleted file mode 100644 index 60a43a7b0d..0000000000 --- a/src/lib/browser.native.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const isSafari = false -export const isFirefox = false -export const isTouchDevice = true -export const IS_ANDROIDWeb = false -export const isHighDPI = true diff --git a/src/lib/browser.ts b/src/lib/browser.ts deleted file mode 100644 index 516ac5a624..0000000000 --- a/src/lib/browser.ts +++ /dev/null @@ -1,9 +0,0 @@ -// https://stackoverflow.com/questions/7944460/detect-safari-browser -export const isSafari = /^((?!chrome|android).)*safari/i.test( - navigator.userAgent, -) -export const isFirefox = /firefox|fxios/i.test(navigator.userAgent) -export const isTouchDevice = window.matchMedia('(pointer: coarse)').matches -export const IS_ANDROIDWeb = - /android/i.test(navigator.userAgent) && isTouchDevice -export const isHighDPI = window.matchMedia('(min-resolution: 2dppx)').matches diff --git a/src/lib/custom-animations/PressableScale.tsx b/src/lib/custom-animations/PressableScale.tsx index 01f39dc0f1..acf87a0dc8 100644 --- a/src/lib/custom-animations/PressableScale.tsx +++ b/src/lib/custom-animations/PressableScale.tsx @@ -12,10 +12,9 @@ import Animated, { withTiming, } from 'react-native-reanimated' -import {isTouchDevice} from '#/lib/browser' -import {IS_NATIVE} from '#/env' +import {IS_NATIVE, IS_WEB_TOUCH_DEVICE} from '#/env' -const DEFAULT_TARGET_SCALE = IS_NATIVE || isTouchDevice ? 0.98 : 1 +const DEFAULT_TARGET_SCALE = IS_NATIVE || IS_WEB_TOUCH_DEVICE ? 0.98 : 1 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts index ebaf417ff8..c2d2f3b065 100644 --- a/src/lib/strings/embed-player.ts +++ b/src/lib/strings/embed-player.ts @@ -1,7 +1,6 @@ import {Dimensions} from 'react-native' -import {isSafari} from '#/lib/browser' -import {IS_WEB} from '#/env' +import {IS_WEB, IS_WEB_SAFARI} from '#/env' const {height: SCREEN_HEIGHT} = Dimensions.get('window') @@ -560,7 +559,7 @@ export function parseTenorGif(urlp: URL): } if (IS_WEB) { - if (isSafari) { + if (IS_WEB_SAFARI) { id = id.replace('AAAAC', 'AAAP1') filename = filename.replace('.gif', '.mp4') } else { diff --git a/src/screens/Messages/components/MessageInput.web.tsx b/src/screens/Messages/components/MessageInput.web.tsx index deb77b51d7..9506f16650 100644 --- a/src/screens/Messages/components/MessageInput.web.tsx +++ b/src/screens/Messages/components/MessageInput.web.tsx @@ -6,7 +6,6 @@ import {flushSync} from 'react-dom' import TextareaAutosize from 'react-textarea-autosize' import {countGraphemes} from 'unicode-segmenter/grapheme' -import {isSafari, isTouchDevice} from '#/lib/browser' import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import { @@ -24,6 +23,7 @@ import {Button} from '#/components/Button' import {useSharedInputStyles} from '#/components/forms/TextField' import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' +import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env' import {useExtractEmbedFromFacets} from './MessageInputEmbed' export function MessageInput({ @@ -85,7 +85,7 @@ export function MessageInput({ // far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time // frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger // time gap between the two events firing. - if (isSafari && e.key === 'Enter' && e.keyCode === 229) { + if (IS_WEB_SAFARI && e.key === 'Enter' && e.keyCode === 229) { return } @@ -228,7 +228,7 @@ export function MessageInput({ onChange={onChange} // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message // in these cases. - onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown} + onKeyDown={IS_WEB_TOUCH_DEVICE && isMobile ? undefined : onKeyDown} /> {disableNavigation ? ( avatarEl - ) : status.isActive && (IS_NATIVE || isTouchDevice) ? ( + ) : status.isActive && (IS_NATIVE || IS_WEB_TOUCH_DEVICE) ? ( <>