Move browser env into main env (#9718)

This commit is contained in:
Eric Bailey
2026-01-16 17:20:03 -06:00
committed by GitHub
parent 0589bd7d9e
commit 62c0c06bbf
18 changed files with 70 additions and 55 deletions
+2 -2
View File
@@ -1,9 +1,9 @@
import {StyleSheet} from 'react-native' import {StyleSheet} from 'react-native'
import type React from 'react' import type React from 'react'
import {isHighDPI} from '#/lib/browser'
import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf' import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf'
import {Fill} from '#/components/Fill' import {Fill} from '#/components/Fill'
import {IS_HIGH_DPI} from '#/env'
/** /**
* Applies and thin border within a bounding box. Used to contrast media from * 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), // while we generally use hairlineWidth (aka 1px),
// we make an exception here for high DPI screens // we make an exception here for high DPI screens
// as the 1px border is very noticeable -sfn // as the 1px border is very noticeable -sfn
web: isHighDPI ? 0.5 : StyleSheet.hairlineWidth, web: IS_HIGH_DPI ? 0.5 : StyleSheet.hairlineWidth,
}), }),
}, },
opaque opaque
@@ -3,10 +3,10 @@ import {View} from 'react-native'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isFirefox, isTouchDevice} from '#/lib/browser'
import {clamp} from '#/lib/numbers' import {clamp} from '#/lib/numbers'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
import {useInteractionState} from '#/components/hooks/useInteractionState' import {useInteractionState} from '#/components/hooks/useInteractionState'
import {IS_WEB_FIREFOX, IS_WEB_TOUCH_DEVICE} from '#/env'
import {formatTime} from './utils' import {formatTime} from './utils'
export function Scrubber({ export function Scrubber({
@@ -100,7 +100,7 @@ export function Scrubber({
// a pointerUp event is fired outside the element that captured the // a pointerUp event is fired outside the element that captured the
// pointer. Firefox clicks on the element the mouse is over, so we have // pointer. Firefox clicks on the element the mouse is over, so we have
// to make everything unclickable while seeking -sfn // to make everything unclickable while seeking -sfn
if (isFirefox && scrubberActive) { if (IS_WEB_FIREFOX && scrubberActive) {
document.body.classList.add('force-no-clicks') document.body.classList.add('force-no-clicks')
return () => { return () => {
@@ -151,7 +151,7 @@ export function Scrubber({
<View <View
testID="scrubber" testID="scrubber"
style={[ style={[
{height: isTouchDevice ? 32 : 18, width: '100%'}, {height: IS_WEB_TOUCH_DEVICE ? 32 : 18, width: '100%'},
a.flex_shrink_0, a.flex_shrink_0,
a.px_xs, a.px_xs,
]} ]}
@@ -4,7 +4,6 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import type Hls from 'hls.js' import type Hls from 'hls.js'
import {isTouchDevice} from '#/lib/browser'
import {clamp} from '#/lib/numbers' import {clamp} from '#/lib/numbers'
import { import {
useAutoplayDisabled, useAutoplayDisabled,
@@ -27,7 +26,7 @@ import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Paus
import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_WEB_MOBILE_IOS} from '#/env' import {IS_WEB_MOBILE_IOS, IS_WEB_TOUCH_DEVICE} from '#/env'
import {TimeIndicator} from '../TimeIndicator' import {TimeIndicator} from '../TimeIndicator'
import {ControlButton} from './ControlButton' import {ControlButton} from './ControlButton'
import {Scrubber} from './Scrubber' import {Scrubber} from './Scrubber'
@@ -342,7 +341,7 @@ export function Controls({
{opacity: showControls ? 1 : 0}, {opacity: showControls ? 1 : 0},
{transition: 'opacity 0.2s ease-in-out'}, {transition: 'opacity 0.2s ease-in-out'},
]}> ]}>
{(!volumeHovered || isTouchDevice) && ( {(!volumeHovered || IS_WEB_TOUCH_DEVICE) && (
<Scrubber <Scrubber
duration={duration} duration={duration}
currentTime={currentTime} currentTime={currentTime}
@@ -4,11 +4,11 @@ import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isSafari, isTouchDevice} from '#/lib/browser'
import {atoms as a} from '#/alf' import {atoms as a} from '#/alf'
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker'
import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
import {ControlButton} from './ControlButton' import {ControlButton} from './ControlButton'
export function VolumeControl({ export function VolumeControl({
@@ -57,7 +57,7 @@ export function VolumeControl({
onPointerEnter={onHover} onPointerEnter={onHover}
onPointerLeave={onEndHover} onPointerLeave={onEndHover}
style={[a.relative]}> style={[a.relative]}>
{hovered && !isTouchDevice && ( {hovered && !IS_WEB_TOUCH_DEVICE && (
<Animated.View <Animated.View
entering={FadeIn.duration(100)} entering={FadeIn.duration(100)}
exiting={FadeOut.duration(100)} exiting={FadeOut.duration(100)}
@@ -80,7 +80,9 @@ export function VolumeControl({
aria-label={_(msg`Volume`)} aria-label={_(msg`Volume`)}
style={ style={
// Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h // Ridiculous safari hack for old version of safari. Fixed in sonoma beta -h
isSafari ? {height: 92, minHeight: '100%'} : {height: '100%'} IS_WEB_SAFARI
? {height: 92, minHeight: '100%'}
: {height: '100%'}
} }
onChange={onVolumeChange} onChange={onVolumeChange}
// @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn // @ts-expect-error for old versions of firefox, and then re-using it for targeting the CSS -sfn
@@ -1,8 +1,8 @@
import {type RefObject, useCallback, useEffect, useRef, useState} from 'react' import {type RefObject, useCallback, useEffect, useRef, useState} from 'react'
import {isSafari} from '#/lib/browser'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {IS_WEB_SAFARI} from '#/env'
export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) { export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) {
const [playing, setPlaying] = useState(false) const [playing, setPlaying] = useState(false)
@@ -41,7 +41,7 @@ export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) {
setCurrentTime(round(ref.current.currentTime) || 0) setCurrentTime(round(ref.current.currentTime) || 0)
// HACK: Safari randomly fires `stalled` events when changing between segments // HACK: Safari randomly fires `stalled` events when changing between segments
// let's just clear the buffering state if the video is still progressing -sfn // let's just clear the buffering state if the video is still progressing -sfn
if (isSafari) { if (IS_WEB_SAFARI) {
if (bufferingTimeout) clearTimeout(bufferingTimeout) if (bufferingTimeout) clearTimeout(bufferingTimeout)
setBuffering(false) setBuffering(false)
} }
@@ -11,7 +11,6 @@ import {type AppBskyEmbedVideo} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {isFirefox} from '#/lib/browser'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {useIsWithinMessage} from '#/components/dms/MessageContext' import {useIsWithinMessage} from '#/components/dms/MessageContext'
@@ -23,6 +22,7 @@ import {
VideoEmbedInnerWeb, VideoEmbedInnerWeb,
VideoNotFoundError, VideoNotFoundError,
} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb'
import {IS_WEB_FIREFOX} from '#/env'
import {useActiveVideoWeb} from './ActiveVideoWebContext' import {useActiveVideoWeb} from './ActiveVideoWebContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback' import * as VideoFallback from './VideoEmbedInner/VideoFallback'
@@ -37,7 +37,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
useEffect(() => { useEffect(() => {
if (!ref.current) return if (!ref.current) return
if (isFullscreen && !isFirefox) return if (isFullscreen && !IS_WEB_FIREFOX) return
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
entries => { entries => {
const entry = entries[0] const entry = entries[0]
@@ -150,7 +150,7 @@ function ViewportObserver({
// observing a div of 100vh height // observing a div of 100vh height
useEffect(() => { useEffect(() => {
if (!ref.current) return if (!ref.current) return
if (isFullscreen && !isFirefox) return if (isFullscreen && !IS_WEB_FIREFOX) return
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
entries => { entries => {
const entry = entries[0] const entry = entries[0]
@@ -11,7 +11,6 @@ import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {useActorStatus} from '#/lib/actor-status' import {useActorStatus} from '#/lib/actor-status'
import {isTouchDevice} from '#/lib/browser'
import {getModerationCauseKey} from '#/lib/moderation' import {getModerationCauseKey} from '#/lib/moderation'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
@@ -43,6 +42,7 @@ import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification' import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck' import {VerificationCheck} from '#/components/verification/VerificationCheck'
import {IS_WEB_TOUCH_DEVICE} from '#/env'
import {type ProfileHoverCardProps} from './types' import {type ProfileHoverCardProps} from './types'
const floatingMiddlewares = [ 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 return props.children
} else { } else {
return ( return (
+2 -3
View File
@@ -1,8 +1,7 @@
import {View} from 'react-native' import {View} from 'react-native'
import {isTouchDevice} from '#/lib/browser'
import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 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({ export function SubtleHover({
style, style,
@@ -40,7 +39,7 @@ export function SubtleHover({
) )
if (IS_WEB && web) { if (IS_WEB && web) {
return isTouchDevice ? null : el return IS_WEB_TOUCH_DEVICE ? null : el
} else if (IS_NATIVE && native) { } else if (IS_NATIVE && native) {
return el return el
} }
+2 -3
View File
@@ -6,8 +6,7 @@ import {
useSyncExternalStore, useSyncExternalStore,
} from 'react' } from 'react'
import {isFirefox, isSafari} from '#/lib/browser' import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env'
import {IS_WEB} from '#/env'
function fullscreenSubscribe(onChange: () => void) { function fullscreenSubscribe(onChange: () => void) {
document.addEventListener('fullscreenchange', onChange) document.addEventListener('fullscreenchange', onChange)
@@ -39,7 +38,7 @@ export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) {
// Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen // 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 // 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(() => { setTimeout(() => {
if (scrollYRef.current !== null) { if (scrollYRef.current !== null) {
window.scrollTo(0, scrollYRef.current) window.scrollTo(0, scrollYRef.current)
+13
View File
@@ -26,5 +26,18 @@ export const IS_IOS: boolean = Platform.OS === 'ios'
export const IS_ANDROID: boolean = Platform.OS === 'android' export const IS_ANDROID: boolean = Platform.OS === 'android'
export const IS_NATIVE: boolean = true export const IS_NATIVE: boolean = true
export const IS_WEB: boolean = false 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: boolean = false
export const IS_WEB_MOBILE_IOS: 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
+23 -2
View File
@@ -21,8 +21,29 @@ export const IS_IOS: boolean = false
export const IS_ANDROID: boolean = false export const IS_ANDROID: boolean = false
export const IS_NATIVE: boolean = false export const IS_NATIVE: boolean = false
export const IS_WEB: boolean = true 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)', 'only screen and (max-width: 1300px)',
)?.matches )?.matches
export const IS_WEB_MOBILE_IOS: boolean = /iPhone/.test(navigator.userAgent) 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
-5
View File
@@ -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
-9
View File
@@ -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
+2 -3
View File
@@ -12,10 +12,9 @@ import Animated, {
withTiming, withTiming,
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {isTouchDevice} from '#/lib/browser' import {IS_NATIVE, IS_WEB_TOUCH_DEVICE} from '#/env'
import {IS_NATIVE} 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) const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
+2 -3
View File
@@ -1,7 +1,6 @@
import {Dimensions} from 'react-native' import {Dimensions} from 'react-native'
import {isSafari} from '#/lib/browser' import {IS_WEB, IS_WEB_SAFARI} from '#/env'
import {IS_WEB} from '#/env'
const {height: SCREEN_HEIGHT} = Dimensions.get('window') const {height: SCREEN_HEIGHT} = Dimensions.get('window')
@@ -560,7 +559,7 @@ export function parseTenorGif(urlp: URL):
} }
if (IS_WEB) { if (IS_WEB) {
if (isSafari) { if (IS_WEB_SAFARI) {
id = id.replace('AAAAC', 'AAAP1') id = id.replace('AAAAC', 'AAAP1')
filename = filename.replace('.gif', '.mp4') filename = filename.replace('.gif', '.mp4')
} else { } else {
@@ -6,7 +6,6 @@ import {flushSync} from 'react-dom'
import TextareaAutosize from 'react-textarea-autosize' import TextareaAutosize from 'react-textarea-autosize'
import {countGraphemes} from 'unicode-segmenter/grapheme' import {countGraphemes} from 'unicode-segmenter/grapheme'
import {isSafari, isTouchDevice} from '#/lib/browser'
import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import { import {
@@ -24,6 +23,7 @@ import {Button} from '#/components/Button'
import {useSharedInputStyles} from '#/components/forms/TextField' import {useSharedInputStyles} from '#/components/forms/TextField'
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 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' import {useExtractEmbedFromFacets} from './MessageInputEmbed'
export function MessageInput({ 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 // 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 // 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. // 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 return
} }
@@ -228,7 +228,7 @@ export function MessageInput({
onChange={onChange} onChange={onChange}
// On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
// in these cases. // in these cases.
onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown} onKeyDown={IS_WEB_TOUCH_DEVICE && isMobile ? undefined : onKeyDown}
/> />
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
@@ -11,7 +11,6 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {IS_ANDROIDWeb} from '#/lib/browser'
import {JOINED_THIS_WEEK} from '#/lib/constants' import {JOINED_THIS_WEEK} from '#/lib/constants'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
@@ -37,7 +36,7 @@ import {Default as ProfileCard} from '#/components/ProfileCard'
import * as Prompt from '#/components/Prompt' import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env' import {IS_WEB, IS_WEB_MOBILE_ANDROID} from '#/env'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
const AnimatedPressable = Animated.createAnimatedComponent(Pressable) const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
@@ -142,7 +141,7 @@ function LandingScreenLoaded({
postAppClipMessage({ postAppClipMessage({
action: 'present', action: 'present',
}) })
} else if (IS_ANDROIDWeb) { } else if (IS_WEB_MOBILE_ANDROID) {
androidDialogControl.open() androidDialogControl.open()
} else { } else {
onContinue() onContinue()
+2 -3
View File
@@ -16,7 +16,6 @@ import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {useActorStatus} from '#/lib/actor-status' import {useActorStatus} from '#/lib/actor-status'
import {isTouchDevice} from '#/lib/browser'
import {useHaptics} from '#/lib/haptics' import {useHaptics} from '#/lib/haptics'
import { import {
useCameraPermission, useCameraPermission,
@@ -53,7 +52,7 @@ import {LiveStatusDialog} from '#/components/live/LiveStatusDialog'
import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import * as Menu from '#/components/Menu' import * as Menu from '#/components/Menu'
import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env' import {IS_ANDROID, IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
import type * as bsky from '#/types/bsky' import type * as bsky from '#/types/bsky'
export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler'
@@ -571,7 +570,7 @@ let PreviewableUserAvatar = ({
<ProfileHoverCard did={profile.did} disable={disableHoverCard}> <ProfileHoverCard did={profile.did} disable={disableHoverCard}>
{disableNavigation ? ( {disableNavigation ? (
avatarEl avatarEl
) : status.isActive && (IS_NATIVE || isTouchDevice) ? ( ) : status.isActive && (IS_NATIVE || IS_WEB_TOUCH_DEVICE) ? (
<> <>
<Button <Button
label={_( label={_(