Bluesky app fork with some witchin' additions 💫

Move `/platform/detection` vars into `/env` (#9707)

* Add platform vars to env

* Replace /platform/detection with /env

authored by

Eric Bailey and committed by
GitHub
0589bd7d bd510d84

+789 -789
+2 -2
CLAUDE.md
··· 439 439 440 440 Platform detection: 441 441 ```tsx 442 - import {isWeb, isNative, isIOS, isAndroid} from '#/platform/detection' 442 + import {IS_WEB, IS_NATIVE, IS_IOS, IS_ANDROID} from '#/env' 443 443 444 - if (isNative) { 444 + if (IS_NATIVE) { 445 445 // Native-specific logic 446 446 } 447 447 ```
+5 -5
modules/bottom-sheet/src/BottomSheetNativeComponent.tsx
··· 11 11 import {useSafeAreaInsets} from 'react-native-safe-area-context' 12 12 import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core' 13 13 14 - import {isIOS} from '#/platform/detection' 14 + import {IS_IOS} from '#/env' 15 15 import { 16 16 type BottomSheetState, 17 17 type BottomSheetViewProps, ··· 30 30 31 31 const NativeModule = requireNativeModule('BottomSheet') 32 32 33 - const isIOS15 = 33 + const IS_IOS15 = 34 34 Platform.OS === 'ios' && 35 35 // semvar - can be 3 segments, so can't use Number(Platform.Version) 36 36 Number(Platform.Version.split('.').at(0)) < 16 ··· 91 91 } 92 92 93 93 let extraStyles 94 - if (isIOS15 && this.state.viewHeight) { 94 + if (IS_IOS15 && this.state.viewHeight) { 95 95 const {viewHeight} = this.state 96 96 const cornerRadius = this.props.cornerRadius ?? 0 97 97 if (viewHeight < screenHeight / 2) { ··· 112 112 onStateChange={this.onStateChange} 113 113 extraStyles={extraStyles} 114 114 onLayout={e => { 115 - if (isIOS15) { 115 + if (IS_IOS15) { 116 116 const {height} = e.nativeEvent.layout 117 117 this.setState({viewHeight: height}) 118 118 } ··· 153 153 const insets = useSafeAreaInsets() 154 154 const cornerRadius = rest.cornerRadius ?? 0 155 155 156 - const sheetHeight = isIOS ? screenHeight - insets.top : screenHeight 156 + const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight 157 157 158 158 return ( 159 159 <NativeView
+3 -3
src/App.native.tsx
··· 23 23 import {ThemeProvider} from '#/lib/ThemeContext' 24 24 import I18nProvider from '#/locale/i18nProvider' 25 25 import {logger} from '#/logger' 26 - import {isAndroid, isIOS} from '#/platform/detection' 27 26 import {Provider as A11yProvider} from '#/state/a11y' 28 27 import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' 29 28 import {Provider as DialogStateProvider} from '#/state/dialogs' ··· 69 68 import {ToastOutlet} from '#/components/Toast' 70 69 import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance' 71 70 import {prefetchAgeAssuranceConfig} from '#/ageAssurance' 71 + import {IS_ANDROID, IS_IOS} from '#/env' 72 72 import { 73 73 prefetchLiveEvents, 74 74 Provider as LiveEventsProvider, ··· 79 79 import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 80 80 81 81 SplashScreen.preventAutoHideAsync() 82 - if (isIOS) { 82 + if (IS_IOS) { 83 83 SystemUI.setBackgroundColorAsync('black') 84 84 } 85 - if (isAndroid) { 85 + if (IS_ANDROID) { 86 86 // iOS is handled by the config plugin -sfn 87 87 ScreenOrientation.lockAsync( 88 88 ScreenOrientation.OrientationLock.PORTRAIT_UP,
+6 -6
src/Navigation.tsx
··· 44 44 import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig' 45 45 import {bskyTitle} from '#/lib/strings/headings' 46 46 import {logger} from '#/logger' 47 - import {isNative, isWeb} from '#/platform/detection' 48 47 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 49 48 import {useSession} from '#/state/session' 50 49 import {useLoggedOutViewControls} from '#/state/shell/logged-out' ··· 138 137 EmailDialogScreenID, 139 138 useEmailDialogControl, 140 139 } from '#/components/dialogs/EmailDialog' 140 + import {IS_NATIVE, IS_WEB} from '#/env' 141 141 import {router} from '#/routes' 142 142 import {Referrer} from '../modules/expo-bluesky-swiss-army' 143 143 ··· 842 842 // native, since the home tab and the home screen are defined as initial routes, we don't need to return a state 843 843 // since it will be created by react-navigation. 844 844 if (path.includes('intent/')) { 845 - if (isNative) return 845 + if (IS_NATIVE) return 846 846 return buildStateObject('Flat', 'Home', params) 847 847 } 848 848 849 - if (isNative) { 849 + if (IS_NATIVE) { 850 850 if (name === 'Search') { 851 851 return buildStateObject('SearchTab', 'Search', params) 852 852 } ··· 921 921 ) 922 922 923 923 async function handlePushNotificationEntry() { 924 - if (!isNative) return 924 + if (!IS_NATIVE) return 925 925 926 926 // deep links take precedence - on android, 927 927 // getLastNotificationResponseAsync returns a "notification" ··· 1069 1069 navigationRef.dispatch( 1070 1070 CommonActions.reset({ 1071 1071 index: 0, 1072 - routes: [{name: isNative ? 'HomeTab' : 'Home'}], 1072 + routes: [{name: IS_NATIVE ? 'HomeTab' : 'Home'}], 1073 1073 }), 1074 1074 ) 1075 1075 return Promise.race([ ··· 1103 1103 initMs, 1104 1104 }) 1105 1105 1106 - if (isWeb) { 1106 + if (IS_WEB) { 1107 1107 const referrerInfo = Referrer.getReferrerInfo() 1108 1108 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { 1109 1109 logEvent('deepLink:referrerReceived', {
+5 -5
src/ageAssurance/components/NoAccessScreen.tsx
··· 10 10 } from '#/lib/hooks/useCreateSupportLink' 11 11 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 12 12 import {logger} from '#/logger' 13 - import {isWeb} from '#/platform/detection' 14 - import {isNative} from '#/platform/detection' 15 13 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 16 14 import {useSessionApi} from '#/state/session' 17 15 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' ··· 38 36 isLegacyBirthdateBug, 39 37 useAgeAssuranceRegionConfig, 40 38 } from '#/ageAssurance/util' 39 + import {IS_WEB} from '#/env' 40 + import {IS_NATIVE} from '#/env' 41 41 import {useDeviceGeolocationApi} from '#/geolocation' 42 42 43 43 const textStyles = [a.text_md, a.leading_snug] ··· 74 74 }, []) 75 75 76 76 const onPressLogout = useCallback(() => { 77 - if (isWeb) { 77 + if (IS_WEB) { 78 78 // We're switching accounts, which remounts the entire app. 79 79 // On mobile, this gets us Home, but on the web we also need reset the URL. 80 80 // We can't change the URL via a navigate() call because the navigator ··· 139 139 contentContainerStyle={[ 140 140 a.px_2xl, 141 141 { 142 - paddingTop: isWeb 142 + paddingTop: IS_WEB 143 143 ? a.p_5xl.padding 144 144 : insets.top + a.p_2xl.padding, 145 145 paddingBottom: 100, ··· 359 359 )} 360 360 361 361 <View style={[a.gap_xs]}> 362 - {isNative && ( 362 + {IS_NATIVE && ( 363 363 <> 364 364 <Admonition> 365 365 <Trans>
+4 -4
src/ageAssurance/components/RedirectOverlay.tsx
··· 15 15 import {retry} from '#/lib/async/retry' 16 16 import {wait} from '#/lib/async/wait' 17 17 import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 18 - import {isWeb} from '#/platform/detection' 19 - import {isIOS} from '#/platform/detection' 20 18 import {useAgent, useSession} from '#/state/session' 21 19 import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' 22 20 import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' ··· 28 26 import {Text} from '#/components/Typography' 29 27 import {refetchAgeAssuranceServerState} from '#/ageAssurance' 30 28 import {logger} from '#/ageAssurance' 29 + import {IS_WEB} from '#/env' 30 + import {IS_IOS} from '#/env' 31 31 32 32 export type RedirectOverlayState = { 33 33 result: 'success' | 'unknown' ··· 92 92 actorDid: params.get('actorDid') ?? undefined, 93 93 }) 94 94 95 - if (isWeb) { 95 + if (IS_WEB) { 96 96 // Clear the URL parameters so they don't re-trigger 97 97 history.pushState(null, '', '/') 98 98 } ··· 145 145 // setting a zIndex when using FullWindowOverlay on iOS 146 146 // means the taps pass straight through to the underlying content (???) 147 147 // so don't set it on iOS. FullWindowOverlay already does the job. 148 - !isIOS && {zIndex: 9999}, 148 + !IS_IOS && {zIndex: 9999}, 149 149 t.atoms.bg, 150 150 gtMobile ? a.p_2xl : a.p_xl, 151 151 a.align_center,
+4 -4
src/alf/fonts.ts
··· 1 1 import {type TextStyle} from 'react-native' 2 2 3 - import {isAndroid, isWeb} from '#/platform/detection' 3 + import {IS_ANDROID, IS_WEB} from '#/env' 4 4 import {type Device, device} from '#/storage' 5 5 6 6 const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"` ··· 39 39 */ 40 40 export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') { 41 41 if (fontFamily === 'theme') { 42 - if (isAndroid) { 42 + if (IS_ANDROID) { 43 43 style.fontFamily = 44 44 { 45 45 400: 'Inter-Regular', ··· 71 71 } 72 72 } 73 73 74 - if (isWeb) { 74 + if (IS_WEB) { 75 75 // fallback families only supported on web 76 76 style.fontFamily += `, ${WEB_FONT_FAMILIES}` 77 77 } ··· 83 83 style.fontVariant = (style.fontVariant || []).concat('no-contextual') 84 84 } else { 85 85 // fallback families only supported on web 86 - if (isWeb) { 86 + if (IS_WEB) { 87 87 style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES 88 88 } 89 89
+4 -4
src/alf/typography.tsx
··· 4 4 import {UITextView} from 'react-native-uitextview' 5 5 import createEmojiRegex from 'emoji-regex' 6 6 7 - import {isNative} from '#/platform/detection' 8 - import {isIOS} from '#/platform/detection' 9 7 import {type Alf, applyFonts, atoms, flatten} from '#/alf' 8 + import {IS_NATIVE} from '#/env' 9 + import {IS_IOS} from '#/env' 10 10 11 11 /** 12 12 * Ensures that `lineHeight` defaults to a relative value of `1`, or applies ··· 34 34 if (s.lineHeight !== 0 && s.lineHeight <= 2) { 35 35 s.lineHeight = Math.round(s.fontSize * s.lineHeight) 36 36 } 37 - } else if (!isNative) { 37 + } else if (!IS_NATIVE) { 38 38 s.lineHeight = s.fontSize 39 39 } 40 40 ··· 81 81 props: Omit<TextProps, 'children'> = {}, 82 82 emoji: boolean, 83 83 ) { 84 - if (!isIOS || !emoji) { 84 + if (!IS_IOS || !emoji) { 85 85 return children 86 86 } 87 87 return Children.map(children, child => {
+2 -2
src/alf/util/systemUI.ts
··· 2 2 import {type Theme} from '@bsky.app/alf' 3 3 4 4 import {logger} from '#/logger' 5 - import {isAndroid} from '#/platform/detection' 5 + import {IS_ANDROID} from '#/env' 6 6 7 7 export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 8 - if (isAndroid) { 8 + if (IS_ANDROID) { 9 9 try { 10 10 if (themeType === 'theme') { 11 11 SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
+2 -2
src/alf/util/useColorModeTheme.ts
··· 2 2 import {type ColorSchemeName, useColorScheme} from 'react-native' 3 3 import {type ThemeName} from '@bsky.app/alf' 4 4 5 - import {isWeb} from '#/platform/detection' 6 5 import {useThemePrefs} from '#/state/shell' 7 6 import {dark, dim, light} from '#/alf/themes' 7 + import {IS_WEB} from '#/env' 8 8 9 9 export function useColorModeTheme(): ThemeName { 10 10 const theme = useThemeName() ··· 40 40 41 41 function updateDocument(theme: ThemeName) { 42 42 // @ts-ignore web only 43 - if (isWeb && typeof window !== 'undefined') { 43 + if (IS_WEB && typeof window !== 'undefined') { 44 44 // @ts-ignore web only 45 45 const html = window.document.documentElement 46 46 // @ts-ignore web only
+5 -5
src/components/ContextMenu/index.tsx
··· 49 49 import {useHaptics} from '#/lib/haptics' 50 50 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 51 51 import {logger} from '#/logger' 52 - import {isAndroid, isIOS} from '#/platform/detection' 53 52 import {atoms as a, platform, tokens, useTheme} from '#/alf' 54 53 import { 55 54 Context, ··· 71 70 import {useInteractionState} from '#/components/hooks/useInteractionState' 72 71 import {createPortalGroup} from '#/components/Portal' 73 72 import {Text} from '#/components/Typography' 73 + import {IS_ANDROID, IS_IOS} from '#/env' 74 74 import {Backdrop} from './Backdrop' 75 75 76 76 export { ··· 81 81 const {Provider: PortalProvider, Outlet, Portal} = createPortalGroup() 82 82 83 83 const SPRING_IN: WithSpringConfig = { 84 - mass: isIOS ? 1.25 : 0.75, 84 + mass: IS_IOS ? 1.25 : 0.75, 85 85 damping: 50, 86 86 stiffness: 1100, 87 87 restDisplacementThreshold: 0.01, 88 88 } 89 89 90 90 const SPRING_OUT: WithSpringConfig = { 91 - mass: isIOS ? 1.25 : 0.75, 91 + mass: IS_IOS ? 1.25 : 0.75, 92 92 damping: 150, 93 93 stiffness: 1000, 94 94 restDisplacementThreshold: 0.01, ··· 209 209 ) 210 210 211 211 useEffect(() => { 212 - if (isAndroid && context.isOpen) { 212 + if (IS_ANDROID && context.isOpen) { 213 213 const listener = BackHandler.addEventListener('hardwareBackPress', () => { 214 214 context.close() 215 215 return true ··· 331 331 <GestureDetector gesture={composedGestures}> 332 332 <View ref={ref} style={[{opacity: context.isOpen ? 0 : 1}, style]}> 333 333 {children({ 334 - isNative: true, 334 + IS_NATIVE: true, 335 335 control: {isOpen: context.isOpen, open}, 336 336 state: { 337 337 pressed: false,
+2 -2
src/components/ContextMenu/types.ts
··· 85 85 } 86 86 export type TriggerChildProps = 87 87 | { 88 - isNative: true 88 + IS_NATIVE: true 89 89 control: { 90 90 isOpen: boolean 91 91 open: (mode: 'full' | 'auxiliary-only') => void ··· 115 115 } 116 116 } 117 117 | { 118 - isNative: false 118 + IS_NATIVE: false 119 119 control: Dialog.DialogOuterProps['control'] 120 120 state: { 121 121 hovered: false
+1 -1
src/components/Dialog/context.ts
··· 18 18 19 19 export const Context = createContext<DialogContextProps>({ 20 20 close: () => {}, 21 - isNativeDialog: false, 21 + IS_NATIVEDialog: false, 22 22 nativeSnapPoint: BottomSheetSnapPoint.Hidden, 23 23 disableDrag: false, 24 24 setDisableDrag: () => {},
+11 -11
src/components/Dialog/index.tsx
··· 26 26 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 27 27 import {ScrollProvider} from '#/lib/ScrollContext' 28 28 import {logger} from '#/logger' 29 - import {isAndroid, isIOS} from '#/platform/detection' 30 29 import {useA11y} from '#/state/a11y' 31 30 import {useDialogStateControlContext} from '#/state/dialogs' 32 31 import {List, type ListMethods, type ListProps} from '#/view/com/util/List' ··· 39 38 type DialogOuterProps, 40 39 } from '#/components/Dialog/types' 41 40 import {createInput} from '#/components/forms/TextField' 41 + import {IS_ANDROID, IS_IOS} from '#/env' 42 42 import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet' 43 43 import { 44 44 type BottomSheetSnapPointChangeEvent, ··· 154 154 const context = React.useMemo( 155 155 () => ({ 156 156 close, 157 - isNativeDialog: true, 157 + IS_NATIVEDialog: true, 158 158 nativeSnapPoint: snapPoint, 159 159 disableDrag, 160 160 setDisableDrag, ··· 209 209 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 210 210 const insets = useSafeAreaInsets() 211 211 212 - useEnableKeyboardController(isIOS) 212 + useEnableKeyboardController(IS_IOS) 213 213 214 214 const [keyboardHeight, setKeyboardHeight] = React.useState(0) 215 215 ··· 224 224 ) 225 225 226 226 let paddingBottom = 0 227 - if (isIOS) { 227 + if (IS_IOS) { 228 228 paddingBottom += keyboardHeight / 4 229 229 if (nativeSnapPoint === BottomSheetSnapPoint.Full) { 230 230 paddingBottom += insets.bottom + tokens.space.md ··· 240 240 } 241 241 242 242 const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => { 243 - if (!isAndroid) { 243 + if (!IS_ANDROID) { 244 244 return 245 245 } 246 246 const {contentOffset} = e.nativeEvent ··· 260 260 contentContainerStyle, 261 261 ]} 262 262 ref={ref} 263 - showsVerticalScrollIndicator={isAndroid ? false : undefined} 263 + showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} 264 264 {...props} 265 265 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} 266 266 bottomOffset={30} 267 267 scrollEventThrottle={50} 268 - onScroll={isAndroid ? onScroll : undefined} 268 + onScroll={IS_ANDROID ? onScroll : undefined} 269 269 keyboardShouldPersistTaps="handled" 270 270 // TODO: figure out why this positions the header absolutely (rather than stickily) 271 271 // on Android. fine to disable for now, because we don't have any ··· 289 289 const insets = useSafeAreaInsets() 290 290 const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() 291 291 292 - useEnableKeyboardController(isIOS) 292 + useEnableKeyboardController(IS_IOS) 293 293 294 294 const onScroll = (e: ScrollEvent) => { 295 295 'worklet' 296 - if (!isAndroid) { 296 + if (!IS_ANDROID) { 297 297 return 298 298 } 299 299 const {contentOffset} = e ··· 311 311 bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} 312 312 ListFooterComponent={<View style={{height: insets.bottom + 100}} />} 313 313 ref={ref} 314 - showsVerticalScrollIndicator={isAndroid ? false : undefined} 314 + showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} 315 315 {...props} 316 316 style={[a.h_full, style]} 317 317 /> ··· 326 326 const {height} = useReanimatedKeyboardAnimation() 327 327 328 328 const animatedStyle = useAnimatedStyle(() => { 329 - if (!isIOS) return {} 329 + if (!IS_IOS) return {} 330 330 return { 331 331 transform: [{translateY: Math.min(0, height.get() + bottom - 10)}], 332 332 }
+1 -1
src/components/Dialog/index.web.tsx
··· 98 98 const context = React.useMemo( 99 99 () => ({ 100 100 close, 101 - isNativeDialog: false, 101 + IS_NATIVEDialog: false, 102 102 nativeSnapPoint: 0, 103 103 disableDrag: false, 104 104 setDisableDrag: () => {},
+2 -2
src/components/Dialog/sheet-wrapper.ts
··· 1 1 import {useCallback} from 'react' 2 2 import {SystemBars} from 'react-native-edge-to-edge' 3 3 4 - import {isIOS} from '#/platform/detection' 4 + import {IS_IOS} from '#/env' 5 5 6 6 /** 7 7 * If we're calling a system API like the image picker that opens a sheet ··· 9 9 */ 10 10 export function useSheetWrapper() { 11 11 return useCallback(async <T>(promise: Promise<T>): Promise<T> => { 12 - if (isIOS) { 12 + if (IS_IOS) { 13 13 const entry = SystemBars.pushStackEntry({ 14 14 style: { 15 15 statusBar: 'light',
+1 -1
src/components/Dialog/types.ts
··· 39 39 40 40 export type DialogContextProps = { 41 41 close: DialogControlProps['close'] 42 - isNativeDialog: boolean 42 + IS_NATIVEDialog: boolean 43 43 nativeSnapPoint: BottomSheetSnapPoint 44 44 disableDrag: boolean 45 45 setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
+3 -3
src/components/FeedInterstitials.tsx
··· 10 10 import {logEvent, useGate} from '#/lib/statsig/statsig' 11 11 import {logger} from '#/logger' 12 12 import {type MetricEvents} from '#/logger/metrics' 13 - import {isIOS} from '#/platform/detection' 14 13 import {useModerationOpts} from '#/state/preferences/moderation-opts' 15 14 import {useGetPopularFeedsQuery} from '#/state/queries/feed' 16 15 import {type FeedDescriptor} from '#/state/queries/post-feed' ··· 39 38 import {InlineLinkText} from '#/components/Link' 40 39 import * as ProfileCard from '#/components/ProfileCard' 41 40 import {Text} from '#/components/Typography' 41 + import {IS_IOS} from '#/env' 42 42 import type * as bsky from '#/types/bsky' 43 43 import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' 44 44 import {ProgressGuideList} from './ProgressGuide/List' ··· 692 692 t.atoms.border_contrast_low, 693 693 t.atoms.bg_contrast_25, 694 694 ]} 695 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 695 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 696 696 <View 697 697 style={[ 698 698 a.px_lg, ··· 701 701 a.align_center, 702 702 a.justify_between, 703 703 ]} 704 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 704 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 705 705 <Text style={[a.text_sm, a.font_semi_bold, t.atoms.text]}> 706 706 {isFeedContext ? ( 707 707 <Trans>Suggested for you</Trans>
+3 -3
src/components/InterestTabs.tsx
··· 9 9 import {useLingui} from '@lingui/react' 10 10 11 11 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 12 - import {isWeb} from '#/platform/detection' 13 12 import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView' 14 13 import {atoms as a, tokens, useTheme, web} from '#/alf' 15 14 import {transparentifyColor} from '#/alf/util/colorGeneration' ··· 19 18 ArrowRight_Stroke2_Corner0_Rounded as ArrowRight, 20 19 } from '#/components/icons/Arrow' 21 20 import {Text} from '#/components/Typography' 21 + import {IS_WEB} from '#/env' 22 22 23 23 /** 24 24 * Tab component that automatically scrolls the selected tab into view - used for interests ··· 236 236 ) 237 237 })} 238 238 </DraggableScrollView> 239 - {isWeb && canScrollLeft && ( 239 + {IS_WEB && canScrollLeft && ( 240 240 <View 241 241 style={[ 242 242 a.absolute, ··· 270 270 </Button> 271 271 </View> 272 272 )} 273 - {isWeb && canScrollRight && ( 273 + {IS_WEB && canScrollRight && ( 274 274 <View 275 275 style={[ 276 276 a.absolute,
+3 -3
src/components/InternationalPhoneCodeSelect.tsx
··· 10 10 INTERNATIONAL_TELEPHONE_CODES, 11 11 } from '#/lib/international-telephone-codes' 12 12 import {regionName} from '#/locale/helpers' 13 - import {isWeb} from '#/platform/detection' 14 13 import {atoms as a, web} from '#/alf' 15 14 import * as Select from '#/components/Select' 15 + import {IS_WEB} from '#/env' 16 16 import {useGeolocation} from '#/geolocation' 17 17 18 18 /** ··· 84 84 <Select.Item value={item.value} label={item.label}> 85 85 <Select.ItemIndicator /> 86 86 <Select.ItemText style={[a.flex_1]} emoji> 87 - {isWeb ? <Flag {...item} /> : item.unicodeFlag + ' '} 87 + {IS_WEB ? <Flag {...item} /> : item.unicodeFlag + ' '} 88 88 {item.name} 89 89 </Select.ItemText> 90 90 <Select.ItemText style={[a.text_right]}> ··· 101 101 } 102 102 103 103 function Flag({unicodeFlag, svgFlag}: {unicodeFlag: string; svgFlag: any}) { 104 - if (isWeb) { 104 + if (IS_WEB) { 105 105 return ( 106 106 <Image 107 107 source={svgFlag}
+4 -4
src/components/Layout/Header/index.tsx
··· 6 6 7 7 import {HITSLOP_30} from '#/lib/constants' 8 8 import {type NavigationProp} from '#/lib/routes/types' 9 - import {isIOS} from '#/platform/detection' 10 9 import {useSetDrawerOpen} from '#/state/shell' 11 10 import { 12 11 atoms as a, ··· 29 28 } from '#/components/Layout/const' 30 29 import {ScrollbarOffsetContext} from '#/components/Layout/context' 31 30 import {Text} from '#/components/Typography' 31 + import {IS_IOS} from '#/env' 32 32 33 33 export function Outer({ 34 34 children, ··· 91 91 style={[ 92 92 a.flex_1, 93 93 a.justify_center, 94 - isIOS && align === 'platform' && a.align_center, 94 + IS_IOS && align === 'platform' && a.align_center, 95 95 {minHeight: HEADER_SLOT_SIZE}, 96 96 ]}> 97 97 <AlignmentContext.Provider value={align}> ··· 186 186 a.text_lg, 187 187 a.font_semi_bold, 188 188 a.leading_tight, 189 - isIOS && align === 'platform' && a.text_center, 189 + IS_IOS && align === 'platform' && a.text_center, 190 190 gtMobile && a.text_xl, 191 191 style, 192 192 ]} ··· 205 205 style={[ 206 206 a.text_sm, 207 207 a.leading_snug, 208 - isIOS && align === 'platform' && a.text_center, 208 + IS_IOS && align === 'platform' && a.text_center, 209 209 t.atoms.text_contrast_medium, 210 210 ]} 211 211 numberOfLines={2}>
+4 -4
src/components/Layout/index.tsx
··· 11 11 } from 'react-native-reanimated' 12 12 import {useSafeAreaInsets} from 'react-native-safe-area-context' 13 13 14 - import {isWeb} from '#/platform/detection' 15 14 import {useShellLayout} from '#/state/shell/shell-layout' 16 15 import { 17 16 atoms as a, ··· 23 22 import {useDialogContext} from '#/components/Dialog' 24 23 import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const' 25 24 import {ScrollbarOffsetContext} from '#/components/Layout/context' 25 + import {IS_WEB} from '#/env' 26 26 27 27 export * from '#/components/Layout/const' 28 28 export * as Header from '#/components/Layout/Header' ··· 43 43 const {top} = useSafeAreaInsets() 44 44 return ( 45 45 <> 46 - {isWeb && <WebCenterBorders />} 46 + {IS_WEB && <WebCenterBorders />} 47 47 <View 48 48 style={[a.util_screen_outer, {paddingTop: noInsetTop ? 0 : top}, style]} 49 49 {...props} ··· 98 98 contentContainerStyle, 99 99 ]} 100 100 {...props}> 101 - {isWeb ? ( 101 + {IS_WEB ? ( 102 102 <Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}> 103 103 {/* @ts-expect-error web only -esb */} 104 104 {children} ··· 145 145 ]} 146 146 keyboardShouldPersistTaps="handled" 147 147 {...props}> 148 - {isWeb ? <Center>{children}</Center> : children} 148 + {IS_WEB ? <Center>{children}</Center> : children} 149 149 </KeyboardAwareScrollView> 150 150 ) 151 151 })
+11 -11
src/components/Link.tsx
··· 18 18 isExternalUrl, 19 19 linkRequiresWarning, 20 20 } from '#/lib/strings/url-helpers' 21 - import {isNative, isWeb} from '#/platform/detection' 22 21 import {useModalControls} from '#/state/modals' 23 22 import {atoms as a, flatten, type TextStyleProp, useTheme, web} from '#/alf' 24 23 import {Button, type ButtonProps} from '#/components/Button' 25 24 import {useInteractionState} from '#/components/hooks/useInteractionState' 26 25 import {Text, type TextProps} from '#/components/Typography' 26 + import {IS_NATIVE, IS_WEB} from '#/env' 27 27 import {router} from '#/routes' 28 28 import {useGlobalDialogsControlContext} from './dialogs/Context' 29 29 ··· 130 130 linkRequiresWarning(href, displayText), 131 131 ) 132 132 133 - if (isWeb) { 133 + if (IS_WEB) { 134 134 e.preventDefault() 135 135 } 136 136 ··· 162 162 ] 163 163 164 164 // does not apply to web's flat navigator 165 - if (isNative && screen !== 'NotFound') { 165 + if (IS_NATIVE && screen !== 'NotFound') { 166 166 const state = navigation.getState() 167 167 // if screen is not in the current navigator, it means it's 168 168 // most likely a tab screen. note: state can be undefined ··· 246 246 (e: GestureResponderEvent) => { 247 247 const exitEarlyIfFalse = outerOnLongPress?.(e) 248 248 if (exitEarlyIfFalse === false) return 249 - return isNative && shareOnLongPress ? handleLongPress() : undefined 249 + return IS_NATIVE && shareOnLongPress ? handleLongPress() : undefined 250 250 }, 251 251 [outerOnLongPress, handleLongPress, shareOnLongPress], 252 252 ) ··· 501 501 onPress, 502 502 ...props 503 503 }: Omit<InlineLinkProps, 'onLongPress'>) { 504 - return isWeb ? ( 504 + return IS_WEB ? ( 505 505 <InlineLinkText {...props} to={to} onPress={onPress}> 506 506 {children} 507 507 </InlineLinkText> ··· 547 547 ): {onPress: Exclude<BaseLinkProps['onPress'], undefined>} { 548 548 return { 549 549 onPress(e: GestureResponderEvent) { 550 - if (!isWeb || !isModifiedClickEvent(e)) { 550 + if (!IS_WEB || !isModifiedClickEvent(e)) { 551 551 e.preventDefault() 552 552 onPressHandler(e) 553 553 return false ··· 561 561 * intends to deviate from default behavior. 562 562 */ 563 563 export function isClickEventWithMetaKey(e: GestureResponderEvent) { 564 - if (!isWeb) return false 564 + if (!IS_WEB) return false 565 565 const event = e as unknown as MouseEvent 566 566 return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey 567 567 } ··· 570 570 * Determines if the web click target is anything other than `_self` 571 571 */ 572 572 export function isClickTargetExternal(e: GestureResponderEvent) { 573 - if (!isWeb) return false 573 + if (!IS_WEB) return false 574 574 const event = e as unknown as MouseEvent 575 575 const el = event.currentTarget as HTMLAnchorElement 576 576 return el && el.target && el.target !== '_self' ··· 582 582 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} 583 583 */ 584 584 export function isModifiedClickEvent(e: GestureResponderEvent): boolean { 585 - if (!isWeb) return false 585 + if (!IS_WEB) return false 586 586 const event = e as unknown as MouseEvent 587 587 const isPrimaryButton = event.button === 0 588 588 return ( ··· 596 596 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button} 597 597 */ 598 598 export function shouldClickOpenNewTab(e: GestureResponderEvent) { 599 - if (!isWeb) return false 599 + if (!IS_WEB) return false 600 600 const event = e as unknown as MouseEvent 601 - const isMiddleClick = isWeb && event.button === 1 601 + const isMiddleClick = IS_WEB && event.button === 1 602 602 return isClickEventWithMetaKey(e) || isClickTargetExternal(e) || isMiddleClick 603 603 }
+5 -5
src/components/Menu/index.tsx
··· 10 10 import {useLingui} from '@lingui/react' 11 11 import flattenReactChildren from 'react-keyed-flatten-children' 12 12 13 - import {isAndroid, isIOS, isNative} from '#/platform/detection' 14 13 import {atoms as a, useTheme} from '#/alf' 15 14 import {Button, ButtonText} from '#/components/Button' 16 15 import * as Dialog from '#/components/Dialog' ··· 30 29 type TriggerProps, 31 30 } from '#/components/Menu/types' 32 31 import {Text} from '#/components/Typography' 32 + import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env' 33 33 34 34 export { 35 35 type DialogControlProps as MenuControlProps, ··· 70 70 } = useInteractionState() 71 71 72 72 return children({ 73 - isNative: true, 73 + IS_NATIVE: true, 74 74 control: context.control, 75 75 state: { 76 76 hovered: false, ··· 111 111 <Dialog.ScrollableInner label={_(msg`Menu`)}> 112 112 <View style={[a.gap_lg]}> 113 113 {children} 114 - {isNative && showCancel && <Cancel />} 114 + {IS_NATIVE && showCancel && <Cancel />} 115 115 </View> 116 116 </Dialog.ScrollableInner> 117 117 </Context.Provider> ··· 137 137 onFocus={onFocus} 138 138 onBlur={onBlur} 139 139 onPress={async e => { 140 - if (isAndroid) { 140 + if (IS_ANDROID) { 141 141 /** 142 142 * Below fix for iOS doesn't work for Android, this does. 143 143 */ 144 144 onPress?.(e) 145 145 context.control.close() 146 - } else if (isIOS) { 146 + } else if (IS_IOS) { 147 147 /** 148 148 * Fixes a subtle bug on iOS 149 149 * {@link https://github.com/bluesky-social/social-app/pull/5849/files#diff-de516ef5e7bd9840cd639213301df38cf03acfcad5bda85a1d63efd249ba79deL124-L127}
+1 -1
src/components/Menu/index.web.tsx
··· 138 138 <RadixTriggerPassThrough> 139 139 {props => 140 140 children({ 141 - isNative: false, 141 + IS_NATIVE: false, 142 142 control, 143 143 state: { 144 144 hovered,
+2 -2
src/components/Menu/types.ts
··· 43 43 } 44 44 export type TriggerChildProps = 45 45 | { 46 - isNative: true 46 + IS_NATIVE: true 47 47 control: Dialog.DialogOuterProps['control'] 48 48 state: { 49 49 /** ··· 73 73 } 74 74 } 75 75 | { 76 - isNative: false 76 + IS_NATIVE: false 77 77 control: Dialog.DialogOuterProps['control'] 78 78 state: { 79 79 hovered: boolean
+2 -2
src/components/NewskieDialog.tsx
··· 8 8 import {HITSLOP_10} from '#/lib/constants' 9 9 import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 10 10 import {sanitizeDisplayName} from '#/lib/strings/display-names' 11 - import {isNative} from '#/platform/detection' 12 11 import {useModerationOpts} from '#/state/preferences/moderation-opts' 13 12 import {useSession} from '#/state/session' 14 13 import {atoms as a, useTheme, web} from '#/alf' ··· 18 17 import {Newskie} from '#/components/icons/Newskie' 19 18 import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' 20 19 import {Text} from '#/components/Typography' 20 + import {IS_NATIVE} from '#/env' 21 21 22 22 export function NewskieDialog({ 23 23 profile, ··· 162 162 </StarterPackCard.Link> 163 163 ) : null} 164 164 165 - {isNative && ( 165 + {IS_NATIVE && ( 166 166 <Button 167 167 label={_(msg`Close`)} 168 168 color="secondary"
+3 -3
src/components/PolicyUpdateOverlay/Overlay.tsx
··· 7 7 import {LinearGradient} from 'expo-linear-gradient' 8 8 import {utils} from '@bsky.app/alf' 9 9 10 - import {isAndroid, isNative} from '#/platform/detection' 11 10 import {useA11y} from '#/state/a11y' 12 11 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 13 12 import {FocusScope} from '#/components/FocusScope' 14 13 import {LockScroll} from '#/components/LockScroll' 14 + import {IS_ANDROID, IS_NATIVE} from '#/env' 15 15 16 16 const GUTTER = 24 17 17 ··· 80 80 a.z_20, 81 81 a.align_center, 82 82 !gtPhone && [a.justify_end, {minHeight: frame.height}], 83 - isNative && [ 83 + IS_NATIVE && [ 84 84 { 85 85 paddingBottom: Math.max(insets.bottom, a.p_2xl.padding), 86 86 }, ··· 109 109 110 110 <FocusScope> 111 111 <View 112 - accessible={isAndroid} 112 + accessible={IS_ANDROID} 113 113 role="dialog" 114 114 aria-role="dialog" 115 115 aria-label={label}
+2 -2
src/components/PolicyUpdateOverlay/index.tsx
··· 1 1 import {useEffect} from 'react' 2 2 import {View} from 'react-native' 3 3 4 - import {isIOS} from '#/platform/detection' 5 4 import {atoms as a} from '#/alf' 6 5 import {FullWindowOverlay} from '#/components/FullWindowOverlay' 7 6 import {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' 8 7 import {Portal} from '#/components/PolicyUpdateOverlay/Portal' 9 8 import {Content} from '#/components/PolicyUpdateOverlay/updates/202508' 9 + import {IS_IOS} from '#/env' 10 10 11 11 export {Provider} from '#/components/PolicyUpdateOverlay/context' 12 12 export {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context' ··· 39 39 // setting a zIndex when using FullWindowOverlay on iOS 40 40 // means the taps pass straight through to the underlying content (???) 41 41 // so don't set it on iOS. FullWindowOverlay already does the job. 42 - !isIOS && {zIndex: 9999}, 42 + !IS_IOS && {zIndex: 9999}, 43 43 ]}> 44 44 <Content state={state} /> 45 45 </View>
+2 -2
src/components/PolicyUpdateOverlay/updates/202508/index.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {isAndroid} from '#/platform/detection' 7 6 import {useA11y} from '#/state/a11y' 8 7 import {atoms as a, useTheme} from '#/alf' 9 8 import {Button, ButtonText} from '#/components/Button' ··· 12 11 import {Overlay} from '#/components/PolicyUpdateOverlay/Overlay' 13 12 import {type PolicyUpdateState} from '#/components/PolicyUpdateOverlay/usePolicyUpdateState' 14 13 import {Text} from '#/components/Typography' 14 + import {IS_ANDROID} from '#/env' 15 15 16 16 export function Content({state}: {state: PolicyUpdateState}) { 17 17 const t = useTheme() ··· 56 56 size: 'small', 57 57 } as const 58 58 59 - const label = isAndroid 59 + const label = IS_ANDROID 60 60 ? _( 61 61 msg`We’re updating our Terms of Service, Privacy Policy, and Copyright Policy, effective September 15th, 2025. We're also updating our Community Guidelines, and we want your input! These new guidelines will take effect on October 15th, 2025. Learn more about these changes and how to share your thoughts with us by reading our blog post.`, 62 62 )
+5 -5
src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx
··· 10 10 import {useLingui} from '@lingui/react' 11 11 12 12 import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 13 - import {isIOS, isNative, isWeb} from '#/platform/detection' 14 13 import {useExternalEmbedsPrefs} from '#/state/preferences' 15 14 import {atoms as a, useTheme} from '#/alf' 16 15 import {useDialogControl} from '#/components/Dialog' 17 16 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' 18 17 import {Fill} from '#/components/Fill' 19 18 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 19 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 20 20 21 21 export function ExternalGif({ 22 22 link, ··· 66 66 // Control animation on native 67 67 setIsAnimating(prev => { 68 68 if (prev) { 69 - if (isNative) { 69 + if (IS_NATIVE) { 70 70 imageRef.current?.stopAnimating() 71 71 } 72 72 return false 73 73 } else { 74 - if (isNative) { 74 + if (IS_NATIVE) { 75 75 imageRef.current?.startAnimating() 76 76 } 77 77 return true ··· 112 112 <Image 113 113 source={{ 114 114 uri: 115 - !isPrefetched || (isWeb && !isAnimating) 115 + !isPrefetched || (IS_WEB && !isAnimating) 116 116 ? link.thumb 117 117 : params.playerUri, 118 118 }} // Web uses the thumb to control playback ··· 123 123 accessibilityIgnoresInvertColors 124 124 accessibilityLabel={link.title} 125 125 accessibilityHint={link.title} 126 - cachePolicy={isIOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios 126 + cachePolicy={IS_IOS ? 'disk' : 'memory-disk'} // cant control playback with memory-disk on ios 127 127 /> 128 128 129 129 {(!isPrefetched || !isAnimating) && (
+2 -2
src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx
··· 26 26 type EmbedPlayerParams, 27 27 getPlayerAspect, 28 28 } from '#/lib/strings/embed-player' 29 - import {isNative} from '#/platform/detection' 30 29 import {useExternalEmbedsPrefs} from '#/state/preferences' 31 30 import {EventStopper} from '#/view/com/util/EventStopper' 32 31 import {atoms as a, useTheme} from '#/alf' ··· 34 33 import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' 35 34 import {Fill} from '#/components/Fill' 36 35 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 36 + import {IS_NATIVE} from '#/env' 37 37 38 38 interface ShouldStartLoadRequest { 39 39 url: string ··· 148 148 const {height: winHeight, width: winWidth} = windowDims 149 149 150 150 // Get the proper screen height depending on what is going on 151 - const realWinHeight = isNative // If it is native, we always want the larger number 151 + const realWinHeight = IS_NATIVE // If it is native, we always want the larger number 152 152 ? winHeight > winWidth 153 153 ? winHeight 154 154 : winWidth
+6 -6
src/components/Post/Embed/ExternalEmbed/Gif.tsx
··· 13 13 import {HITSLOP_20} from '#/lib/constants' 14 14 import {clamp} from '#/lib/numbers' 15 15 import {type EmbedPlayerParams} from '#/lib/strings/embed-player' 16 - import {isWeb} from '#/platform/detection' 17 16 import {useAutoplayDisabled} from '#/state/preferences' 18 17 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' 19 18 import {atoms as a, useTheme} from '#/alf' ··· 22 21 import * as Prompt from '#/components/Prompt' 23 22 import {Text} from '#/components/Typography' 24 23 import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' 24 + import {IS_WEB} from '#/env' 25 25 import {GifView} from '../../../../../modules/expo-bluesky-gif-view' 26 26 import {type GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types' 27 27 ··· 218 218 altContainer: { 219 219 backgroundColor: 'rgba(0, 0, 0, 0.75)', 220 220 borderRadius: 6, 221 - paddingHorizontal: isWeb ? 8 : 6, 222 - paddingVertical: isWeb ? 6 : 3, 221 + paddingHorizontal: IS_WEB ? 8 : 6, 222 + paddingVertical: IS_WEB ? 6 : 3, 223 223 position: 'absolute', 224 224 // Related to margin/gap hack. This keeps the alt label in the same position 225 225 // on all platforms 226 - right: isWeb ? 8 : 5, 227 - bottom: isWeb ? 8 : 5, 226 + right: IS_WEB ? 8 : 5, 227 + bottom: IS_WEB ? 8 : 5, 228 228 zIndex: 2, 229 229 }, 230 230 alt: { 231 231 color: 'white', 232 - fontSize: isWeb ? 10 : 7, 232 + fontSize: IS_WEB ? 10 : 7, 233 233 fontWeight: '600', 234 234 }, 235 235 })
+2 -2
src/components/Post/Embed/ExternalEmbed/index.tsx
··· 10 10 import {shareUrl} from '#/lib/sharing' 11 11 import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' 12 12 import {toNiceDomain} from '#/lib/strings/url-helpers' 13 - import {isNative} from '#/platform/detection' 14 13 import {useExternalEmbedsPrefs} from '#/state/preferences' 15 14 import {atoms as a, useTheme} from '#/alf' 16 15 import {Divider} from '#/components/Divider' 17 16 import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' 18 17 import {Link} from '#/components/Link' 19 18 import {Text} from '#/components/Typography' 19 + import {IS_NATIVE} from '#/env' 20 20 import {ExternalGif} from './ExternalGif' 21 21 import {ExternalPlayer} from './ExternalPlayer' 22 22 import {GifEmbed} from './Gif' ··· 53 53 }, [playHaptic, onOpen]) 54 54 55 55 const onShareExternal = useCallback(() => { 56 - if (link.uri && isNative) { 56 + if (link.uri && IS_NATIVE) { 57 57 playHaptic('Heavy') 58 58 shareUrl(link.uri) 59 59 }
+3 -3
src/components/Post/Embed/VideoEmbed/ActiveVideoWebContext.tsx
··· 8 8 } from 'react' 9 9 import {useWindowDimensions} from 'react-native' 10 10 11 - import {isNative, isWeb} from '#/platform/detection' 11 + import {IS_NATIVE, IS_WEB} from '#/env' 12 12 13 13 const Context = React.createContext<{ 14 14 activeViewId: string | null ··· 18 18 Context.displayName = 'ActiveVideoWebContext' 19 19 20 20 export function Provider({children}: {children: React.ReactNode}) { 21 - if (!isWeb) { 21 + if (!IS_WEB) { 22 22 throw new Error('ActiveVideoWebContext may only be used on web.') 23 23 } 24 24 ··· 47 47 48 48 const sendViewPosition = useCallback( 49 49 (viewId: string, y: number) => { 50 - if (isNative) return 50 + if (IS_NATIVE) return 51 51 52 52 if (viewId === activeViewIdRef.current) { 53 53 activeViewLocationRef.current = y
+2 -2
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
··· 6 6 7 7 import {isTouchDevice} from '#/lib/browser' 8 8 import {clamp} from '#/lib/numbers' 9 - import {isIPhoneWeb} from '#/platform/detection' 10 9 import { 11 10 useAutoplayDisabled, 12 11 useSetSubtitlesEnabled, ··· 28 27 import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' 29 28 import {Loader} from '#/components/Loader' 30 29 import {Text} from '#/components/Typography' 30 + import {IS_WEB_MOBILE_IOS} from '#/env' 31 31 import {TimeIndicator} from '../TimeIndicator' 32 32 import {ControlButton} from './ControlButton' 33 33 import {Scrubber} from './Scrubber' ··· 400 400 onEndHover={onVolumeEndHover} 401 401 drawFocus={drawFocus} 402 402 /> 403 - {!isIPhoneWeb && ( 403 + {!IS_WEB_MOBILE_IOS && ( 404 404 <ControlButton 405 405 active={isFullscreen} 406 406 activeLabel={_(msg`Exit fullscreen`)}
+2 -2
src/components/PostControls/ShareMenu/ShareMenuItems.tsx
··· 10 10 import {shareText, shareUrl} from '#/lib/sharing' 11 11 import {toShareUrl} from '#/lib/strings/url-helpers' 12 12 import {logger} from '#/logger' 13 - import {isIOS} from '#/platform/detection' 14 13 import {useProfileShadow} from '#/state/cache/profile-shadow' 15 14 import {useSession} from '#/state/session' 16 15 import * as Toast from '#/view/com/util/Toast' ··· 24 23 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane' 25 24 import * as Menu from '#/components/Menu' 26 25 import {useAgeAssurance} from '#/ageAssurance' 26 + import {IS_IOS} from '#/env' 27 27 import {useDevMode} from '#/storage/hooks/dev-mode' 28 28 import {RecentChats} from './RecentChats' 29 29 import {type ShareMenuItemsProps} from './ShareMenuItems.types' ··· 63 63 const onCopyLink = async () => { 64 64 logger.metric('share:press:copyLink', {}, {statsig: true}) 65 65 const url = toShareUrl(href) 66 - if (isIOS) { 66 + if (IS_IOS) { 67 67 // iOS only 68 68 await ExpoClipboard.setUrlAsync(url) 69 69 } else {
+2 -2
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 9 9 import {shareText, shareUrl} from '#/lib/sharing' 10 10 import {toShareUrl} from '#/lib/strings/url-helpers' 11 11 import {logger} from '#/logger' 12 - import {isWeb} from '#/platform/detection' 13 12 import {useProfileShadow} from '#/state/cache/profile-shadow' 14 13 import {useSession} from '#/state/session' 15 14 import {useBreakpoints} from '#/alf' ··· 22 21 import {PaperPlane_Stroke2_Corner0_Rounded as Send} from '#/components/icons/PaperPlane' 23 22 import * as Menu from '#/components/Menu' 24 23 import {useAgeAssurance} from '#/ageAssurance' 24 + import {IS_WEB} from '#/env' 25 25 import {useDevMode} from '#/storage/hooks/dev-mode' 26 26 import {type ShareMenuItemsProps} from './ShareMenuItems.types' 27 27 ··· 70 70 }) 71 71 } 72 72 73 - const canEmbed = isWeb && gtMobile && !hideInPWI 73 + const canEmbed = IS_WEB && gtMobile && !hideInPWI 74 74 75 75 const onShareATURI = () => { 76 76 shareText(postUri)
+5 -5
src/components/ProgressGuide/FollowDialog.tsx
··· 12 12 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 13 13 import {logEvent} from '#/lib/statsig/statsig' 14 14 import {logger} from '#/logger' 15 - import {isWeb} from '#/platform/detection' 16 15 import {useModerationOpts} from '#/state/preferences/moderation-opts' 17 16 import {useActorSearch} from '#/state/queries/actor-search' 18 17 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 37 36 import {boostInterests, InterestTabs} from '#/components/InterestTabs' 38 37 import * as ProfileCard from '#/components/ProfileCard' 39 38 import {Text} from '#/components/Typography' 39 + import {IS_WEB} from '#/env' 40 40 import type * as bsky from '#/types/bsky' 41 41 import {ProgressGuideTask} from './Task' 42 42 ··· 431 431 <Trans>Find people to follow</Trans> 432 432 </Text> 433 433 {guide && ( 434 - <View style={isWeb && {paddingRight: 36}}> 434 + <View style={IS_WEB && {paddingRight: 36}}> 435 435 <ProgressGuideTask 436 436 current={guide.numFollows + 1} 437 437 total={10 + 1} ··· 440 440 /> 441 441 </View> 442 442 )} 443 - {isWeb ? ( 443 + {IS_WEB ? ( 444 444 <Button 445 445 label={_(msg`Close`)} 446 446 size="small" 447 447 shape="round" 448 - variant={isWeb ? 'ghost' : 'solid'} 448 + variant={IS_WEB ? 'ghost' : 'solid'} 449 449 color="secondary" 450 450 style={[ 451 451 a.absolute, ··· 579 579 <ProfileCard.Outer> 580 580 <ProfileCard.Header> 581 581 <ProfileCard.Avatar 582 - disabledPreview={!isWeb} 582 + disabledPreview={!IS_WEB} 583 583 profile={profile} 584 584 moderationOpts={moderationOpts} 585 585 />
+3 -3
src/components/ProgressGuide/Toast.tsx
··· 11 11 import {msg} from '@lingui/macro' 12 12 import {useLingui} from '@lingui/react' 13 13 14 - import {isWeb} from '#/platform/detection' 15 14 import {atoms as a, useTheme} from '#/alf' 16 15 import {Portal} from '#/components/Portal' 16 + import {IS_WEB} from '#/env' 17 17 import {AnimatedCheck, type AnimatedCheckRef} from '../anim/AnimatedCheck' 18 18 import {Text} from '../Typography' 19 19 ··· 108 108 const containerStyle = React.useMemo(() => { 109 109 let left = 10 110 110 let right = 10 111 - if (isWeb && winDim.width > 400) { 111 + if (IS_WEB && winDim.width > 400) { 112 112 left = right = (winDim.width - 380) / 2 113 113 } 114 114 return { 115 - position: isWeb ? 'fixed' : 'absolute', 115 + position: IS_WEB ? 'fixed' : 'absolute', 116 116 top: 0, 117 117 left, 118 118 right,
+5 -5
src/components/RichTextTag.tsx
··· 6 6 7 7 import {type NavigationProp} from '#/lib/routes/types' 8 8 import {isInvalidHandle} from '#/lib/strings/handles' 9 - import {isNative, isWeb} from '#/platform/detection' 10 9 import { 11 10 usePreferencesQuery, 12 11 useRemoveMutedWordsMutation, ··· 22 21 } from '#/components/Link' 23 22 import {Loader} from '#/components/Loader' 24 23 import * as Menu from '#/components/Menu' 24 + import {IS_NATIVE, IS_WEB} from '#/env' 25 25 26 26 export function RichTextTag({ 27 27 tag, ··· 50 50 const navigation = useNavigation<NavigationProp>() 51 51 const isCashtag = tag.startsWith('$') 52 52 const label = isCashtag ? _(msg`Cashtag ${tag}`) : _(msg`Hashtag ${tag}`) 53 - const hint = isNative 53 + const hint = IS_NATIVE 54 54 ? _(msg`Long press to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 55 55 : _(msg`Click to open tag menu for ${isCashtag ? tag : `#${tag}`}`) 56 56 ··· 86 86 }} 87 87 {...menuProps} 88 88 onPress={e => { 89 - if (isWeb) { 89 + if (IS_WEB) { 90 90 return createStaticClickIfUnmodified(() => { 91 - if (!isNative) { 91 + if (!IS_NATIVE) { 92 92 menuProps.onPress() 93 93 } 94 94 }).onPress(e) ··· 99 99 label={label} 100 100 style={textStyle} 101 101 emoji> 102 - {isNative ? ( 102 + {IS_NATIVE ? ( 103 103 display 104 104 ) : ( 105 105 <RNText ref={menuProps.ref}>{display}</RNText>
+3 -3
src/components/ScreenTransition.tsx
··· 8 8 } from 'react-native-reanimated' 9 9 import type React from 'react' 10 10 11 - import {isWeb} from '#/platform/detection' 11 + import {IS_WEB} from '#/env' 12 12 13 13 export function ScreenTransition({ 14 14 direction, ··· 31 31 32 32 return ( 33 33 <Animated.View 34 - entering={isWeb ? webEntering : entering} 35 - exiting={isWeb ? webExiting : exiting} 34 + entering={IS_WEB ? webEntering : entering} 35 + exiting={IS_WEB ? webExiting : exiting} 36 36 style={style}> 37 37 {children} 38 38 </Animated.View>
+1 -1
src/components/Select/index.tsx
··· 82 82 83 83 if (typeof children === 'function') { 84 84 return children({ 85 - isNative: true, 85 + IS_NATIVE: true, 86 86 control, 87 87 state: { 88 88 hovered: false,
+1 -1
src/components/Select/index.web.tsx
··· 68 68 <RadixTriggerPassThrough> 69 69 {props => 70 70 children({ 71 - isNative: false, 71 + IS_NATIVE: false, 72 72 state: { 73 73 hovered, 74 74 focused,
+2 -2
src/components/Select/types.ts
··· 65 65 66 66 export type TriggerChildProps = 67 67 | { 68 - isNative: true 68 + IS_NATIVE: true 69 69 control: DialogControlProps 70 70 state: { 71 71 /** ··· 92 92 } 93 93 } 94 94 | { 95 - isNative: false 95 + IS_NATIVE: false 96 96 state: { 97 97 hovered: boolean 98 98 focused: boolean
+3 -3
src/components/StarterPack/Main/FeedsList.tsx
··· 3 3 import {type AppBskyFeedDefs} from '@atproto/api' 4 4 5 5 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 6 - import {isNative, isWeb} from '#/platform/detection' 7 6 import {List, type ListRef} from '#/view/com/util/List' 8 7 import {type SectionRef} from '#/screens/Profile/Sections/types' 9 8 import {atoms as a, useTheme} from '#/alf' 10 9 import * as FeedCard from '#/components/FeedCard' 10 + import {IS_NATIVE, IS_WEB} from '#/env' 11 11 12 12 function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { 13 13 return item.uri ··· 27 27 28 28 const onScrollToTop = useCallback(() => { 29 29 scrollElRef.current?.scrollToOffset({ 30 - animated: isNative, 30 + animated: IS_NATIVE, 31 31 offset: -headerHeight, 32 32 }) 33 33 }, [scrollElRef, headerHeight]) ··· 44 44 <View 45 45 style={[ 46 46 a.p_lg, 47 - (isWeb || index !== 0) && a.border_t, 47 + (IS_WEB || index !== 0) && a.border_t, 48 48 t.atoms.border_contrast_low, 49 49 ]}> 50 50 <FeedCard.Default view={item} />
+2 -2
src/components/StarterPack/Main/PostsList.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {isNative} from '#/platform/detection' 7 6 import {type FeedDescriptor} from '#/state/queries/post-feed' 8 7 import {PostFeed} from '#/view/com/posts/PostFeed' 9 8 import {EmptyState} from '#/view/com/util/EmptyState' 10 9 import {type ListRef} from '#/view/com/util/List' 11 10 import {type SectionRef} from '#/screens/Profile/Sections/types' 12 11 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 12 + import {IS_NATIVE} from '#/env' 13 13 14 14 interface ProfilesListProps { 15 15 listUri: string ··· 24 24 25 25 const onScrollToTop = useCallback(() => { 26 26 scrollElRef.current?.scrollToOffset({ 27 - animated: isNative, 27 + animated: IS_NATIVE, 28 28 offset: -headerHeight, 29 29 }) 30 30 }, [scrollElRef, headerHeight])
+3 -3
src/components/StarterPack/Main/ProfilesList.tsx
··· 14 14 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 15 15 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 16 16 import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' 17 - import {isNative, isWeb} from '#/platform/detection' 18 17 import {useAllListMembersQuery} from '#/state/queries/list-members' 19 18 import {useSession} from '#/state/session' 20 19 import {List, type ListRef} from '#/view/com/util/List' ··· 22 21 import {atoms as a, useTheme} from '#/alf' 23 22 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 24 23 import {Default as ProfileCard} from '#/components/ProfileCard' 24 + import {IS_NATIVE, IS_WEB} from '#/env' 25 25 26 26 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) { 27 27 return `${item.did}-${index}` ··· 75 75 } 76 76 const onScrollToTop = useCallback(() => { 77 77 scrollElRef.current?.scrollToOffset({ 78 - animated: isNative, 78 + animated: IS_NATIVE, 79 79 offset: -headerHeight, 80 80 }) 81 81 }, [scrollElRef, headerHeight]) ··· 93 93 style={[ 94 94 a.p_lg, 95 95 t.atoms.border_contrast_low, 96 - (isWeb || index !== 0) && a.border_t, 96 + (IS_WEB || index !== 0) && a.border_t, 97 97 ]}> 98 98 <ProfileCard 99 99 profile={item}
+2 -2
src/components/StarterPack/ProfileStarterPacks.tsx
··· 19 19 import {type NavigationProp} from '#/lib/routes/types' 20 20 import {parseStarterPackUri} from '#/lib/strings/starter-pack' 21 21 import {logger} from '#/logger' 22 - import {isIOS} from '#/platform/detection' 23 22 import {useActorStarterPacksQuery} from '#/state/queries/actor-starter-packs' 24 23 import { 25 24 EmptyState, ··· 36 35 import * as Prompt from '#/components/Prompt' 37 36 import {Default as StarterPackCard} from '#/components/StarterPack/StarterPackCard' 38 37 import {Text} from '#/components/Typography' 38 + import {IS_IOS} from '#/env' 39 39 40 40 interface SectionRef { 41 41 scrollToTop: () => void ··· 136 136 }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 137 137 138 138 useEffect(() => { 139 - if (isIOS && enabled && scrollElRef.current) { 139 + if (IS_IOS && enabled && scrollElRef.current) { 140 140 const nativeTag = findNodeHandle(scrollElRef.current) 141 141 setScrollViewTag(nativeTag) 142 142 }
+6 -6
src/components/StarterPack/QrCode.tsx
··· 6 6 import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 7 7 import {Trans} from '@lingui/macro' 8 8 9 - import {isWeb} from '#/platform/detection' 10 9 import {Logo} from '#/view/icons/Logo' 11 10 import {Logotype} from '#/view/icons/Logotype' 12 11 import {useTheme} from '#/alf' 13 12 import {atoms as a} from '#/alf' 14 13 import {LinearGradientBackground} from '#/components/LinearGradientBackground' 15 14 import {Text} from '#/components/Typography' 15 + import {IS_WEB} from '#/env' 16 16 import * as bsky from '#/types/bsky' 17 17 18 18 const LazyViewShot = lazy( ··· 121 121 return ( 122 122 <View style={{position: 'relative'}}> 123 123 {/* An SVG version of the logo is placed on top of normal `QRCode` `logo` prop, since the PNG fails to load before the export completes on web. */} 124 - {isWeb && logoArea && ( 124 + {IS_WEB && logoArea && ( 125 125 <View 126 126 style={{ 127 127 position: 'absolute', ··· 139 139 a.rounded_sm, 140 140 {height: 225, width: 225, backgroundColor: '#f3f3f3'}, 141 141 ]} 142 - pieceSize={isWeb ? 8 : 6} 142 + pieceSize={IS_WEB ? 8 : 6} 143 143 padding={20} 144 - pieceBorderRadius={isWeb ? 4.5 : 3.5} 144 + pieceBorderRadius={IS_WEB ? 4.5 : 3.5} 145 145 outerEyesOptions={{ 146 146 topLeft: { 147 147 borderRadius: [12, 12, 0, 12], ··· 159 159 innerEyesOptions={{borderRadius: 3}} 160 160 logo={{ 161 161 href: require('../../../assets/logo.png'), 162 - ...(isWeb && { 162 + ...(IS_WEB && { 163 163 onChange: onLogoAreaChange, 164 164 padding: 28, 165 165 }), 166 - ...(!isWeb && { 166 + ...(!IS_WEB && { 167 167 padding: 2, 168 168 scale: 0.95, 169 169 }),
+6 -6
src/components/StarterPack/QrCodeDialog.tsx
··· 9 9 import {useLingui} from '@lingui/react' 10 10 11 11 import {logger} from '#/logger' 12 - import {isNative, isWeb} from '#/platform/detection' 13 12 import {atoms as a, useBreakpoints} from '#/alf' 14 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import * as Dialog from '#/components/Dialog' ··· 20 19 import {Loader} from '#/components/Loader' 21 20 import {QrCode} from '#/components/StarterPack/QrCode' 22 21 import * as Toast from '#/components/Toast' 22 + import {IS_NATIVE, IS_WEB} from '#/env' 23 23 import * as bsky from '#/types/bsky' 24 24 25 25 export function QrCodeDialog({ ··· 56 56 57 57 const onSavePress = async () => { 58 58 ref.current?.capture?.().then(async (uri: string) => { 59 - if (isNative) { 59 + if (IS_NATIVE) { 60 60 const res = await requestMediaLibraryPermissionsAsync() 61 61 62 62 if (!res.granted) { ··· 111 111 }) 112 112 setIsSaveProcessing(false) 113 113 Toast.show( 114 - isWeb 114 + IS_WEB 115 115 ? _(msg`QR code has been downloaded!`) 116 116 : _(msg`QR code saved to your camera roll!`), 117 117 ) ··· 178 178 label={_(msg`Copy QR code`)} 179 179 color="primary_subtle" 180 180 size="large" 181 - onPress={isWeb ? onCopyPress : onSharePress}> 181 + onPress={IS_WEB ? onCopyPress : onSharePress}> 182 182 <ButtonIcon 183 183 icon={ 184 184 isCopyProcessing 185 185 ? Loader 186 - : isWeb 186 + : IS_WEB 187 187 ? ChainLinkIcon 188 188 : ShareIcon 189 189 } 190 190 /> 191 191 <ButtonText> 192 - {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 192 + {IS_WEB ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 193 193 </ButtonText> 194 194 </Button> 195 195 <Button
+8 -4
src/components/StarterPack/ShareDialog.tsx
··· 8 8 import {shareUrl} from '#/lib/sharing' 9 9 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 10 10 import {logger} from '#/logger' 11 - import {isNative, isWeb} from '#/platform/detection' 12 11 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 12 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 13 import {type DialogControlProps} from '#/components/Dialog' ··· 18 17 import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' 19 18 import {Loader} from '#/components/Loader' 20 19 import {Text} from '#/components/Typography' 20 + import {IS_NATIVE, IS_WEB} from '#/env' 21 21 22 22 interface Props { 23 23 starterPack: AppBskyGraphDefs.StarterPackView ··· 110 110 ], 111 111 ]}> 112 112 <Button 113 - label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)} 113 + label={IS_WEB ? _(msg`Copy link`) : _(msg`Share link`)} 114 114 color="primary_subtle" 115 115 size="large" 116 116 onPress={onShareLink}> 117 117 <ButtonIcon icon={ChainLinkIcon} /> 118 118 <ButtonText> 119 - {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>} 119 + {IS_WEB ? ( 120 + <Trans>Copy Link</Trans> 121 + ) : ( 122 + <Trans>Share link</Trans> 123 + )} 120 124 </ButtonText> 121 125 </Button> 122 126 <Button ··· 133 137 <Trans>Share QR code</Trans> 134 138 </ButtonText> 135 139 </Button> 136 - {isNative && ( 140 + {IS_NATIVE && ( 137 141 <Button 138 142 label={_(msg`Save image`)} 139 143 color="secondary"
+3 -3
src/components/StarterPack/Wizard/WizardEditListDialog.tsx
··· 10 10 import {useLingui} from '@lingui/react' 11 11 12 12 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 13 - import {isWeb} from '#/platform/detection' 14 13 import {type ListMethods} from '#/view/com/util/List' 15 14 import { 16 15 type WizardAction, ··· 24 23 WizardProfileCard, 25 24 } from '#/components/StarterPack/Wizard/WizardListCard' 26 25 import {Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 27 28 28 function keyExtractor( 29 29 item: AppBskyActorDefs.ProfileViewBasic | AppBskyFeedDefs.GeneratorView, ··· 95 95 a.mb_sm, 96 96 t.atoms.bg, 97 97 t.atoms.border_contrast_medium, 98 - isWeb 98 + IS_WEB 99 99 ? [ 100 100 a.align_center, 101 101 { ··· 113 113 )} 114 114 </Text> 115 115 <View style={{width: 60}}> 116 - {isWeb && ( 116 + {IS_WEB && ( 117 117 <Button 118 118 label={_(msg`Close`)} 119 119 variant="ghost"
+3 -3
src/components/SubtleHover.tsx
··· 1 1 import {View} from 'react-native' 2 2 3 3 import {isTouchDevice} from '#/lib/browser' 4 - import {isNative, isWeb} from '#/platform/detection' 5 4 import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 5 + import {IS_NATIVE, IS_WEB} from '#/env' 6 6 7 7 export function SubtleHover({ 8 8 style, ··· 39 39 /> 40 40 ) 41 41 42 - if (isWeb && web) { 42 + if (IS_WEB && web) { 43 43 return isTouchDevice ? null : el 44 - } else if (isNative && native) { 44 + } else if (IS_NATIVE && native) { 45 45 return el 46 46 } 47 47
+3 -3
src/components/WhoCanReply.tsx
··· 18 18 import {HITSLOP_10} from '#/lib/constants' 19 19 import {makeListLink, makeProfileLink} from '#/lib/routes/links' 20 20 import {logger} from '#/logger' 21 - import {isNative} from '#/platform/detection' 22 21 import { 23 22 type ThreadgateAllowUISetting, 24 23 threadgateViewToAllowUISetting, ··· 37 36 import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' 38 37 import {InlineLinkText} from '#/components/Link' 39 38 import {Text} from '#/components/Typography' 39 + import {IS_NATIVE} from '#/env' 40 40 import * as bsky from '#/types/bsky' 41 41 42 42 interface WhoCanReplyProps { ··· 86 86 : _(msg`Some people can reply`) 87 87 88 88 const onPressOpen = () => { 89 - if (isNative && Keyboard.isVisible()) { 89 + if (IS_NATIVE && Keyboard.isVisible()) { 90 90 Keyboard.dismiss() 91 91 } 92 92 if (isThreadAuthor) { ··· 229 229 embeddingDisabled={embeddingDisabled} 230 230 /> 231 231 </View> 232 - {isNative && ( 232 + {IS_NATIVE && ( 233 233 <Button 234 234 label={_(msg`Close`)} 235 235 onPress={() => control.close()}
+2 -2
src/components/activity-notifications/SubscribeProfileDialog.tsx
··· 18 18 import {cleanError} from '#/lib/strings/errors' 19 19 import {sanitizeHandle} from '#/lib/strings/handles' 20 20 import {logger} from '#/logger' 21 - import {isWeb} from '#/platform/detection' 22 21 import {updateProfileShadow} from '#/state/cache/profile-shadow' 23 22 import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions' 24 23 import {useAgent} from '#/state/session' ··· 37 36 import {Loader} from '#/components/Loader' 38 37 import * as ProfileCard from '#/components/ProfileCard' 39 38 import {Text} from '#/components/Typography' 39 + import {IS_WEB} from '#/env' 40 40 import type * as bsky from '#/types/bsky' 41 41 42 42 export function SubscribeProfileDialog({ ··· 195 195 } 196 196 } else { 197 197 // on web, a disabled save button feels more natural than a massive close button 198 - if (isWeb) { 198 + if (IS_WEB) { 199 199 return { 200 200 label: _(msg`Save changes`), 201 201 color: 'secondary',
+2 -2
src/components/ageAssurance/AgeAssuranceAccountCard.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 5 5 import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 6 - import {isNative} from '#/platform/detection' 7 6 import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' 8 7 import {Admonition} from '#/components/Admonition' 9 8 import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' ··· 23 22 import {Text} from '#/components/Typography' 24 23 import {logger, useAgeAssurance} from '#/ageAssurance' 25 24 import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' 25 + import {IS_NATIVE} from '#/env' 26 26 import {useDeviceGeolocationApi} from '#/geolocation' 27 27 28 28 export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { ··· 86 86 <View style={[a.pb_md, a.gap_xs]}> 87 87 <Text style={[a.text_sm, a.leading_snug]}>{copy.notice}</Text> 88 88 89 - {isNative && ( 89 + {IS_NATIVE && ( 90 90 <> 91 91 <Text style={[a.text_sm, a.leading_snug]}> 92 92 <Trans>
+3 -3
src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx
··· 5 5 6 6 import {retry} from '#/lib/async/retry' 7 7 import {wait} from '#/lib/async/wait' 8 - import {isNative} from '#/platform/detection' 9 8 import {useAgent} from '#/state/session' 10 9 import {atoms as a, useTheme, web} from '#/alf' 11 10 import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' ··· 18 17 import {Text} from '#/components/Typography' 19 18 import {refetchAgeAssuranceServerState} from '#/ageAssurance' 20 19 import {logger} from '#/ageAssurance' 20 + import {IS_NATIVE} from '#/env' 21 21 22 22 export type AgeAssuranceRedirectDialogState = { 23 23 result: 'success' | 'unknown' ··· 166 166 </Trans> 167 167 </Text> 168 168 169 - {isNative && ( 169 + {IS_NATIVE && ( 170 170 <View style={[a.w_full, a.pt_lg]}> 171 171 <Button 172 172 label={_(msg`Close`)} ··· 225 225 )} 226 226 </Text> 227 227 228 - {error && isNative && ( 228 + {error && IS_NATIVE && ( 229 229 <View style={[a.w_full, a.pt_lg]}> 230 230 <Button 231 231 label={_(msg`Close`)}
+2 -2
src/components/contacts/FindContactsBannerNUX.tsx
··· 8 8 import {HITSLOP_10} from '#/lib/constants' 9 9 import {useGate} from '#/lib/statsig/statsig' 10 10 import {logger} from '#/logger' 11 - import {isWeb} from '#/platform/detection' 12 11 import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' 13 12 import {atoms as a, useTheme} from '#/alf' 14 13 import {Button} from '#/components/Button' 15 14 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 16 15 import {Text} from '#/components/Typography' 16 + import {IS_WEB} from '#/env' 17 17 import {Link} from '../Link' 18 18 import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist' 19 19 ··· 92 92 const gate = useGate() 93 93 94 94 const visible = useMemo(() => { 95 - if (isWeb) return false 95 + if (IS_WEB) return false 96 96 if (hidden) return false 97 97 if (nux && nux.completed) return false 98 98 if (!isFeatureEnabled) return false
+3 -3
src/components/contacts/components/OTPInput.tsx
··· 9 9 import {useLingui} from '@lingui/react' 10 10 11 11 import {mergeRefs} from '#/lib/merge-refs' 12 - import {isAndroid, isIOS} from '#/platform/detection' 13 12 import {atoms as a, ios, platform, useTheme} from '#/alf' 14 13 import {useInteractionState} from '#/components/hooks/useInteractionState' 15 14 import {Text} from '#/components/Typography' 15 + import {IS_ANDROID, IS_IOS} from '#/env' 16 16 17 17 export function OTPInput({ 18 18 label, ··· 95 95 <TextInput 96 96 // SMS autofill is borked on iOS if you open the keyboard immediately -sfn 97 97 onLayout={ios(() => setTimeout(() => innerRef.current?.focus(), 100))} 98 - autoFocus={isAndroid} 98 + autoFocus={IS_ANDROID} 99 99 accessible 100 100 accessibilityLabel={label} 101 101 accessibilityHint="" ··· 135 135 android: {opacity: 0}, 136 136 }), 137 137 ]} 138 - caretHidden={isIOS} 138 + caretHidden={IS_IOS} 139 139 clearTextOnFocus 140 140 /> 141 141 </Pressable>
+3 -3
src/components/dialogs/BirthDateSettings.tsx
··· 7 7 import {isAppPassword} from '#/lib/jwt' 8 8 import {getAge, getDateAgo} from '#/lib/strings/time' 9 9 import {logger} from '#/logger' 10 - import {isIOS, isWeb} from '#/platform/detection' 11 10 import { 12 11 useBirthdateMutation, 13 12 useIsBirthdateUpdateAllowed, ··· 26 25 import {SimpleInlineLinkText} from '#/components/Link' 27 26 import {Loader} from '#/components/Loader' 28 27 import {Span, Text} from '#/components/Typography' 28 + import {IS_IOS, IS_WEB} from '#/env' 29 29 30 30 export function BirthDateSettingsDialog({ 31 31 control, ··· 154 154 155 155 return ( 156 156 <View style={a.gap_lg} testID="birthDateSettingsDialog"> 157 - <View style={isIOS && [a.w_full, a.align_center]}> 157 + <View style={IS_IOS && [a.w_full, a.align_center]}> 158 158 <DateField 159 159 testID="birthdayInput" 160 160 value={date} ··· 191 191 <ErrorMessage message={errorMessage} style={[a.rounded_sm]} /> 192 192 ) : undefined} 193 193 194 - <View style={isWeb && [a.flex_row, a.justify_end]}> 194 + <View style={IS_WEB && [a.flex_row, a.justify_end]}> 195 195 <Button 196 196 label={hasChanged ? _(msg`Save birthdate`) : _(msg`Done`)} 197 197 size="large"
+4 -4
src/components/dialogs/DeviceLocationRequestDialog.tsx
··· 6 6 import {wait} from '#/lib/async/wait' 7 7 import {isNetworkError, useCleanError} from '#/lib/hooks/useCleanError' 8 8 import {logger} from '#/logger' 9 - import {isWeb} from '#/platform/detection' 10 9 import {atoms as a, useTheme, web} from '#/alf' 11 10 import {Admonition} from '#/components/Admonition' 12 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 14 13 import {PinLocation_Stroke2_Corner0_Rounded as LocationIcon} from '#/components/icons/PinLocation' 15 14 import {Loader} from '#/components/Loader' 16 15 import {Text} from '#/components/Typography' 16 + import {IS_WEB} from '#/env' 17 17 import {type Geolocation, useRequestDeviceGeolocation} from '#/geolocation' 18 18 19 19 export type Props = { ··· 138 138 disabled={isRequesting} 139 139 label={_(msg`Allow location access`)} 140 140 onPress={onPressConfirm} 141 - size={isWeb ? 'small' : 'large'} 141 + size={IS_WEB ? 'small' : 'large'} 142 142 color="primary"> 143 143 <ButtonIcon icon={isRequesting ? Loader : LocationIcon} /> 144 144 <ButtonText> ··· 147 147 </Button> 148 148 )} 149 149 150 - {!isWeb && ( 150 + {!IS_WEB && ( 151 151 <Button 152 152 label={_(msg`Cancel`)} 153 153 onPress={() => close()} 154 - size={isWeb ? 'small' : 'large'} 154 + size={IS_WEB ? 'small' : 'large'} 155 155 color="secondary"> 156 156 <ButtonText> 157 157 <Trans>Cancel</Trans>
+3 -3
src/components/dialogs/GifSelect.tsx
··· 13 13 14 14 import {logEvent} from '#/lib/statsig/statsig' 15 15 import {cleanError} from '#/lib/strings/errors' 16 - import {isWeb} from '#/platform/detection' 17 16 import { 18 17 type Gif, 19 18 tenorUrlToBskyGifUrl, ··· 31 30 import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' 32 31 import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass' 33 32 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 33 + import {IS_WEB} from '#/env' 34 34 35 35 export function GifSelectDialog({ 36 36 controlRef, ··· 149 149 a.pb_sm, 150 150 t.atoms.bg, 151 151 ]}> 152 - {!gtMobile && isWeb && ( 152 + {!gtMobile && IS_WEB && ( 153 153 <Button 154 154 size="small" 155 155 variant="ghost" ··· 161 161 </Button> 162 162 )} 163 163 164 - <TextField.Root style={[!gtMobile && isWeb && a.flex_1]}> 164 + <TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}> 165 165 <TextField.Icon icon={Search} /> 166 166 <TextField.Input 167 167 label={_(msg`Search GIFs`)}
+2 -2
src/components/dialogs/InAppBrowserConsent.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {useOpenLink} from '#/lib/hooks/useOpenLink' 7 - import {isWeb} from '#/platform/detection' 8 7 import {useSetInAppBrowser} from '#/state/preferences/in-app-browser' 9 8 import {atoms as a, useTheme} from '#/alf' 10 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 10 import * as Dialog from '#/components/Dialog' 12 11 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as External} from '#/components/icons/SquareArrowTopRight' 13 12 import {Text} from '#/components/Typography' 13 + import {IS_WEB} from '#/env' 14 14 import {useGlobalDialogsControlContext} from './Context' 15 15 16 16 export function InAppBrowserConsentDialog() { 17 17 const {inAppBrowserConsentControl} = useGlobalDialogsControlContext() 18 18 19 - if (isWeb) return null 19 + if (IS_WEB) return null 20 20 21 21 return ( 22 22 <Dialog.Outer
+2 -2
src/components/dialogs/MutedWords.tsx
··· 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {logger} from '#/logger' 8 - import {isNative} from '#/platform/detection' 9 8 import { 10 9 usePreferencesQuery, 11 10 useRemoveMutedWordMutation, ··· 32 31 import {Loader} from '#/components/Loader' 33 32 import * as Prompt from '#/components/Prompt' 34 33 import {Text} from '#/components/Typography' 34 + import {IS_NATIVE} from '#/env' 35 35 36 36 const ONE_DAY = 24 * 60 * 60 * 1000 37 37 ··· 406 406 )} 407 407 </View> 408 408 409 - {isNative && <View style={{height: 20}} />} 409 + {IS_NATIVE && <View style={{height: 20}} />} 410 410 </View> 411 411 412 412 <Dialog.Close />
+2 -2
src/components/dialogs/PostInteractionSettingsDialog.tsx
··· 12 12 import {useHaptics} from '#/lib/haptics' 13 13 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 14 14 import {logger} from '#/logger' 15 - import {isIOS} from '#/platform/detection' 16 15 import {STALE} from '#/state/queries' 17 16 import {useMyListsQuery} from '#/state/queries/my-lists' 18 17 import {useGetPost} from '#/state/queries/post' ··· 52 51 import {CloseQuote_Stroke2_Corner1_Rounded as QuoteIcon} from '#/components/icons/Quote' 53 52 import {Loader} from '#/components/Loader' 54 53 import {Text} from '#/components/Typography' 54 + import {IS_IOS} from '#/env' 55 55 56 56 export type PostInteractionSettingsFormProps = { 57 57 canSave?: boolean ··· 531 531 hitSlop={0} 532 532 onPress={() => { 533 533 playHaptic('Light') 534 - if (isIOS && !showLists) { 534 + if (IS_IOS && !showLists) { 535 535 LayoutAnimation.configureNext({ 536 536 ...LayoutAnimation.Presets.linear, 537 537 duration: 175,
+4 -4
src/components/dialogs/SearchablePeopleList.tsx
··· 13 13 14 14 import {sanitizeDisplayName} from '#/lib/strings/display-names' 15 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 - import {isWeb} from '#/platform/detection' 17 16 import {useModerationOpts} from '#/state/preferences/moderation-opts' 18 17 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 19 18 import {useListConvosQuery} from '#/state/queries/messages/list-conversations' ··· 29 28 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 30 29 import * as ProfileCard from '#/components/ProfileCard' 31 30 import {Text} from '#/components/Typography' 31 + import {IS_WEB} from '#/env' 32 32 import type * as bsky from '#/types/bsky' 33 33 34 34 export type ProfileItem = { ··· 254 254 ) 255 255 256 256 useLayoutEffect(() => { 257 - if (isWeb) { 257 + if (IS_WEB) { 258 258 setImmediate(() => { 259 259 inputRef?.current?.focus() 260 260 }) ··· 290 290 ]}> 291 291 {title} 292 292 </Text> 293 - {isWeb ? ( 293 + {IS_WEB ? ( 294 294 <Button 295 295 label={_(msg`Close`)} 296 296 size="small" 297 297 shape="round" 298 - variant={isWeb ? 'ghost' : 'solid'} 298 + variant={IS_WEB ? 'ghost' : 'solid'} 299 299 color="secondary" 300 300 style={[ 301 301 a.absolute,
+3 -3
src/components/dialogs/Signin.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {isNative} from '#/platform/detection' 7 6 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 8 7 import {useCloseAllActiveElements} from '#/state/util' 9 8 import {Logo} from '#/view/icons/Logo' ··· 13 12 import * as Dialog from '#/components/Dialog' 14 13 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 15 14 import {Text} from '#/components/Typography' 15 + import {IS_NATIVE} from '#/env' 16 16 17 17 export function SigninDialog() { 18 18 const {signinDialogControl: control} = useGlobalDialogsControlContext() ··· 45 45 <Dialog.ScrollableInner 46 46 label={_(msg`Sign in to Bluesky or create a new account`)} 47 47 style={[gtMobile ? {width: 'auto', maxWidth: 420} : a.w_full]}> 48 - <View style={[!isNative && a.p_2xl]}> 48 + <View style={[!IS_NATIVE && a.p_2xl]}> 49 49 <View 50 50 style={[ 51 51 a.flex_row, ··· 101 101 </Button> 102 102 </View> 103 103 104 - {isNative && <View style={{height: 10}} />} 104 + {IS_NATIVE && <View style={{height: 10}} />} 105 105 </View> 106 106 107 107 <Dialog.Close />
+4 -4
src/components/dialogs/StarterPackDialog.tsx
··· 12 12 import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' 13 13 import {type NavigationProp} from '#/lib/routes/types' 14 14 import {logger} from '#/logger' 15 - import {isWeb} from '#/platform/detection' 16 15 import { 17 16 invalidateActorStarterPacksWithMembershipQuery, 18 17 useActorStarterPacksWithMembershipsQuery, ··· 32 31 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 33 32 import {Loader} from '#/components/Loader' 34 33 import {Text} from '#/components/Typography' 34 + import {IS_WEB} from '#/env' 35 35 import * as bsky from '#/types/bsky' 36 36 37 37 type StarterPackWithMembership = ··· 91 91 const t = useTheme() 92 92 93 93 return ( 94 - <View style={[a.gap_2xl, {paddingTop: isWeb ? 100 : 64}]}> 94 + <View style={[a.gap_2xl, {paddingTop: IS_WEB ? 100 : 64}]}> 95 95 <View style={[a.gap_xs, a.align_center]}> 96 96 <StarterPack 97 97 width={48} ··· 169 169 <View 170 170 style={[ 171 171 {justifyContent: 'space-between', flexDirection: 'row'}, 172 - isWeb ? a.mb_2xl : a.my_lg, 172 + IS_WEB ? a.mb_2xl : a.my_lg, 173 173 a.align_center, 174 174 ]}> 175 175 <Text style={[a.text_lg, a.font_semi_bold]}> ··· 232 232 onEndReachedThreshold={0.1} 233 233 ListHeaderComponent={listHeader} 234 234 ListEmptyComponent={<Empty onStartWizard={onStartWizard} />} 235 - style={isWeb ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]} 235 + style={IS_WEB ? [a.px_md, {minHeight: 500}] : [a.px_2xl, a.pt_lg]} 236 236 /> 237 237 ) 238 238 }
+2 -2
src/components/dialogs/lists/CreateOrEditListDialog.tsx
··· 9 9 import {richTextToString} from '#/lib/strings/rich-text-helpers' 10 10 import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' 11 11 import {logger} from '#/logger' 12 - import {isWeb} from '#/platform/detection' 13 12 import {type ImageMeta} from '#/state/gallery' 14 13 import { 15 14 useListCreateMutation, ··· 26 25 import {Loader} from '#/components/Loader' 27 26 import * as Prompt from '#/components/Prompt' 28 27 import {Text} from '#/components/Typography' 28 + import {IS_WEB} from '#/env' 29 29 30 30 const DISPLAY_NAME_MAX_GRAPHEMES = 64 31 31 const DESCRIPTION_MAX_GRAPHEMES = 300 ··· 48 48 49 49 // 'You might lose unsaved changes' warning 50 50 useEffect(() => { 51 - if (isWeb && dirty) { 51 + if (IS_WEB && dirty) { 52 52 const abortController = new AbortController() 53 53 const {signal} = abortController 54 54 window.addEventListener('beforeunload', evt => evt.preventDefault(), {
+6 -6
src/components/dialogs/nuxs/ActivitySubscriptions.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {isWeb} from '#/platform/detection' 8 7 import {atoms as a, useTheme, web} from '#/alf' 9 8 import {Button, ButtonText} from '#/components/Button' 10 9 import * as Dialog from '#/components/Dialog' 11 10 import {useNuxDialogContext} from '#/components/dialogs/nuxs' 12 11 import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 13 12 import {Text} from '#/components/Typography' 13 + import {IS_WEB} from '#/env' 14 14 15 15 export function ActivitySubscriptionsNUX() { 16 16 const t = useTheme() ··· 44 44 a.overflow_hidden, 45 45 t.atoms.bg_contrast_25, 46 46 { 47 - gap: isWeb ? 16 : 24, 48 - paddingTop: isWeb ? 24 : 48, 47 + gap: IS_WEB ? 16 : 24, 48 + paddingTop: IS_WEB ? 24 : 48, 49 49 borderTopLeftRadius: a.rounded_md.borderRadius, 50 50 borderTopRightRadius: a.rounded_md.borderRadius, 51 51 }, ··· 120 120 style={[ 121 121 a.align_center, 122 122 a.px_xl, 123 - isWeb ? [a.pt_xl, a.gap_xl, a.pb_sm] : [a.pt_3xl, a.gap_3xl], 123 + IS_WEB ? [a.pt_xl, a.gap_xl, a.pb_sm] : [a.pt_3xl, a.gap_3xl], 124 124 ]}> 125 125 <View style={[a.gap_md, a.align_center]}> 126 126 <Text ··· 130 130 a.font_bold, 131 131 a.text_center, 132 132 { 133 - fontSize: isWeb ? 28 : 32, 133 + fontSize: IS_WEB ? 28 : 32, 134 134 maxWidth: 300, 135 135 }, 136 136 ]}> ··· 153 153 </Text> 154 154 </View> 155 155 156 - {!isWeb && ( 156 + {!IS_WEB && ( 157 157 <Button 158 158 label={_(msg`Close`)} 159 159 size="large"
+5 -5
src/components/dialogs/nuxs/BookmarksAnnouncement.tsx
··· 5 5 import {msg, Trans} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 8 - import {isWeb} from '#/platform/detection' 9 8 import {atoms as a, useTheme, web} from '#/alf' 10 9 import {transparentifyColor} from '#/alf/util/colorGeneration' 11 10 import {Button, ButtonText} from '#/components/Button' ··· 13 12 import {useNuxDialogContext} from '#/components/dialogs/nuxs' 14 13 import {Sparkle_Stroke2_Corner0_Rounded as SparkleIcon} from '#/components/icons/Sparkle' 15 14 import {Text} from '#/components/Typography' 15 + import {IS_WEB} from '#/env' 16 16 17 17 export function BookmarksAnnouncement() { 18 18 const t = useTheme() ··· 49 49 a.overflow_hidden, 50 50 { 51 51 gap: 16, 52 - paddingTop: isWeb ? 24 : 40, 52 + paddingTop: IS_WEB ? 24 : 40, 53 53 borderTopLeftRadius: a.rounded_md.borderRadius, 54 54 borderTopRightRadius: a.rounded_md.borderRadius, 55 55 }, ··· 90 90 borderRadius: 24, 91 91 aspectRatio: 333 / 104, 92 92 }, 93 - isWeb 93 + IS_WEB 94 94 ? [ 95 95 { 96 96 boxShadow: `0px 10px 15px -3px ${transparentifyColor(t.palette.black, 0.2)}`, ··· 136 136 a.font_bold, 137 137 a.text_center, 138 138 { 139 - fontSize: isWeb ? 28 : 32, 139 + fontSize: IS_WEB ? 28 : 32, 140 140 maxWidth: 300, 141 141 }, 142 142 ]}> ··· 158 158 </Text> 159 159 </View> 160 160 161 - {!isWeb && ( 161 + {!IS_WEB && ( 162 162 <Button 163 163 label={_(msg`Close`)} 164 164 size="large"
+3 -3
src/components/dialogs/nuxs/FindContactsAnnouncement.tsx
··· 6 6 import {useLingui} from '@lingui/react' 7 7 8 8 import {logger} from '#/logger' 9 - import {isNative, isWeb} from '#/platform/detection' 10 9 import {atoms as a, useTheme, web} from '#/alf' 11 10 import {Button, ButtonText} from '#/components/Button' 12 11 import {isFindContactsFeatureEnabled} from '#/components/contacts/country-allowlist' ··· 17 16 isExistingUserAsOf, 18 17 } from '#/components/dialogs/nuxs/utils' 19 18 import {Text} from '#/components/Typography' 19 + import {IS_NATIVE, IS_WEB} from '#/env' 20 20 import {IS_E2E} from '#/env' 21 21 import {navigate} from '#/Navigation' 22 22 23 23 export const enabled = createIsEnabledCheck(props => { 24 24 return ( 25 25 !IS_E2E && 26 - isNative && 26 + IS_NATIVE && 27 27 isExistingUserAsOf( 28 28 '2025-12-16T00:00:00.000Z', 29 29 props.currentProfile.createdAt, ··· 89 89 a.font_bold, 90 90 a.text_center, 91 91 { 92 - fontSize: isWeb ? 28 : 32, 92 + fontSize: IS_WEB ? 28 : 32, 93 93 maxWidth: 300, 94 94 }, 95 95 ]}>
+2 -2
src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx
··· 6 6 7 7 import {urls} from '#/lib/constants' 8 8 import {logger} from '#/logger' 9 - import {isNative} from '#/platform/detection' 10 9 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 11 10 import {Button, ButtonText} from '#/components/Button' 12 11 import * as Dialog from '#/components/Dialog' ··· 15 14 import {VerifierCheck} from '#/components/icons/VerifierCheck' 16 15 import {Link} from '#/components/Link' 17 16 import {Span, Text} from '#/components/Typography' 17 + import {IS_NATIVE} from '#/env' 18 18 19 19 export function InitialVerificationAnnouncement() { 20 20 const t = useTheme() ··· 173 173 <Trans>Read blog post</Trans> 174 174 </ButtonText> 175 175 </Link> 176 - {isNative && ( 176 + {IS_NATIVE && ( 177 177 <Button 178 178 label={_(msg`Close`)} 179 179 size="small"
+5 -5
src/components/dialogs/nuxs/LiveNowBetaDialog.tsx
··· 5 5 import {msg, Trans} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 7 8 - import {isWeb} from '#/platform/detection' 9 8 import {atoms as a, select, useTheme, utils, web} from '#/alf' 10 9 import {Button, ButtonText} from '#/components/Button' 11 10 import * as Dialog from '#/components/Dialog' ··· 16 15 } from '#/components/dialogs/nuxs/utils' 17 16 import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker' 18 17 import {Text} from '#/components/Typography' 18 + import {IS_WEB} from '#/env' 19 19 import {IS_E2E} from '#/env' 20 20 21 21 export const enabled = createIsEnabledCheck(props => { ··· 72 72 a.overflow_hidden, 73 73 { 74 74 gap: 16, 75 - paddingTop: isWeb ? 24 : 40, 75 + paddingTop: IS_WEB ? 24 : 40, 76 76 borderTopLeftRadius: a.rounded_md.borderRadius, 77 77 borderTopRightRadius: a.rounded_md.borderRadius, 78 78 }, ··· 116 116 borderRadius: 24, 117 117 aspectRatio: 652 / 211, 118 118 }, 119 - isWeb 119 + IS_WEB 120 120 ? [ 121 121 { 122 122 boxShadow: `0px 10px 15px -3px ${shadowColor}`, ··· 163 163 a.font_bold, 164 164 a.text_center, 165 165 { 166 - fontSize: isWeb ? 28 : 32, 166 + fontSize: IS_WEB ? 28 : 32, 167 167 maxWidth: 360, 168 168 }, 169 169 ]}> ··· 186 186 </Text> 187 187 </View> 188 188 189 - {!isWeb && ( 189 + {!IS_WEB && ( 190 190 <Button 191 191 label={_(msg`Close`)} 192 192 size="large"
+1 -1
src/components/dms/ActionsWrapper.tsx
··· 21 21 <MessageContextMenu message={message}> 22 22 {trigger => 23 23 // will always be true, since this file is platform split 24 - trigger.isNative && ( 24 + trigger.IS_NATIVE && ( 25 25 <View style={[a.flex_1, a.relative]}> 26 26 <View 27 27 style={[
+4 -4
src/components/dms/ActionsWrapper.web.tsx
··· 89 89 : [a.ml_xs, {marginRight: 'auto'}], 90 90 ]}> 91 91 <EmojiReactionPicker message={message} onEmojiSelect={onEmojiSelect}> 92 - {({props, state, isNative, control}) => { 92 + {({props, state, IS_NATIVE, control}) => { 93 93 // always false, file is platform split 94 - if (isNative) return null 94 + if (IS_NATIVE) return null 95 95 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 96 96 return ( 97 97 <Pressable ··· 111 111 }} 112 112 </EmojiReactionPicker> 113 113 <MessageContextMenu message={message}> 114 - {({props, state, isNative, control}) => { 114 + {({props, state, IS_NATIVE, control}) => { 115 115 // always false, file is platform split 116 - if (isNative) return null 116 + if (IS_NATIVE) return null 117 117 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 118 118 return ( 119 119 <Pressable
+2 -2
src/components/dms/AfterReportDialog.tsx
··· 7 7 import type React from 'react' 8 8 9 9 import {type NavigationProp} from '#/lib/routes/types' 10 - import {isNative} from '#/platform/detection' 11 10 import {useProfileShadow} from '#/state/cache/profile-shadow' 12 11 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 13 12 import { ··· 21 20 import * as Toggle from '#/components/forms/Toggle' 22 21 import {Loader} from '#/components/Loader' 23 22 import {Text} from '#/components/Typography' 23 + import {IS_NATIVE} from '#/env' 24 24 25 25 type ReportDialogParams = { 26 26 convoId: string ··· 130 130 onMutate: () => { 131 131 if (currentScreen === 'conversation') { 132 132 navigation.dispatch( 133 - StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}), 133 + StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}), 134 134 ) 135 135 } 136 136 },
+3 -3
src/components/dms/ChatEmptyPill.tsx
··· 12 12 import {ScaleAndFadeIn} from '#/lib/custom-animations/ScaleAndFade' 13 13 import {ShrinkAndPop} from '#/lib/custom-animations/ShrinkAndPop' 14 14 import {useHaptics} from '#/lib/haptics' 15 - import {isWeb} from '#/platform/detection' 16 15 import {atoms as a, useTheme} from '#/alf' 17 16 import {Text} from '#/components/Typography' 17 + import {IS_WEB} from '#/env' 18 18 19 19 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 20 20 ··· 41 41 }, [_]) 42 42 43 43 const onPressIn = React.useCallback(() => { 44 - if (isWeb) return 44 + if (IS_WEB) return 45 45 scale.set(() => withTiming(1.075, {duration: 100})) 46 46 }, [scale]) 47 47 48 48 const onPressOut = React.useCallback(() => { 49 - if (isWeb) return 49 + if (IS_WEB) return 50 50 scale.set(() => withTiming(1, {duration: 100})) 51 51 }, [scale]) 52 52
+2 -2
src/components/dms/LeaveConvoPrompt.tsx
··· 3 3 import {StackActions, useNavigation} from '@react-navigation/native' 4 4 5 5 import {type NavigationProp} from '#/lib/routes/types' 6 - import {isNative} from '#/platform/detection' 7 6 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 8 7 import * as Toast from '#/view/com/util/Toast' 9 8 import {type DialogOuterProps} from '#/components/Dialog' 10 9 import * as Prompt from '#/components/Prompt' 10 + import {IS_NATIVE} from '#/env' 11 11 12 12 export function LeaveConvoPrompt({ 13 13 control, ··· 27 27 onMutate: () => { 28 28 if (currentScreen === 'conversation') { 29 29 navigation.dispatch( 30 - StackActions.replace('Messages', isNative ? {animation: 'pop'} : {}), 30 + StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}), 31 31 ) 32 32 } 33 33 },
+2 -2
src/components/dms/MessageContextMenu.tsx
··· 8 8 import {useTranslate} from '#/lib/hooks/useTranslate' 9 9 import {richTextToString} from '#/lib/strings/rich-text-helpers' 10 10 import {logger} from '#/logger' 11 - import {isNative} from '#/platform/detection' 12 11 import {useConvoActive} from '#/state/messages/convo' 13 12 import {useLanguagePrefs} from '#/state/preferences' 14 13 import {useSession} from '#/state/session' ··· 23 22 import {ReportDialog} from '#/components/moderation/ReportDialog' 24 23 import * as Prompt from '#/components/Prompt' 25 24 import {usePromptControl} from '#/components/Prompt' 25 + import {IS_NATIVE} from '#/env' 26 26 import {EmojiReactionPicker} from './EmojiReactionPicker' 27 27 import {hasReachedReactionLimit} from './util' 28 28 ··· 112 112 return ( 113 113 <> 114 114 <ContextMenu.Root> 115 - {isNative && ( 115 + {IS_NATIVE && ( 116 116 <ContextMenu.AuxiliaryView align={isFromSelf ? 'right' : 'left'}> 117 117 <EmojiReactionPicker 118 118 message={message}
+3 -3
src/components/dms/MessageItem.tsx
··· 21 21 import {useLingui} from '@lingui/react' 22 22 23 23 import {sanitizeDisplayName} from '#/lib/strings/display-names' 24 - import {isNative} from '#/platform/detection' 25 24 import {useConvoActive} from '#/state/messages/convo' 26 25 import {type ConvoItem} from '#/state/messages/convo/types' 27 26 import {useSession} from '#/state/session' ··· 32 31 import {InlineLinkText} from '#/components/Link' 33 32 import {RichText} from '#/components/RichText' 34 33 import {Text} from '#/components/Typography' 34 + import {IS_NATIVE} from '#/env' 35 35 import {DateDivider} from './DateDivider' 36 36 import {MessageItemEmbed} from './MessageItemEmbed' 37 37 import {localDateString} from './util' ··· 218 218 </View> 219 219 )} 220 220 221 - {isNative && appliedReactions} 221 + {IS_NATIVE && appliedReactions} 222 222 </ActionsWrapper> 223 223 224 - {!isNative && appliedReactions} 224 + {!IS_NATIVE && appliedReactions} 225 225 226 226 {isLastInGroup && ( 227 227 <MessageItemMetadata
+2 -2
src/components/dms/MessagesListHeader.tsx
··· 10 10 11 11 import {makeProfileLink} from '#/lib/routes/links' 12 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 13 - import {isWeb} from '#/platform/detection' 14 13 import {type Shadow} from '#/state/cache/profile-shadow' 15 14 import {isConvoActive, useConvo} from '#/state/messages/convo' 16 15 import {type ConvoItem} from '#/state/messages/convo/types' ··· 24 23 import {Text} from '#/components/Typography' 25 24 import {useSimpleVerificationState} from '#/components/verification' 26 25 import {VerificationCheck} from '#/components/verification/VerificationCheck' 26 + import {IS_WEB} from '#/env' 27 27 28 - const PFP_SIZE = isWeb ? 40 : Layout.HEADER_SLOT_SIZE 28 + const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE 29 29 30 30 export function MessagesListHeader({ 31 31 profile,
+5 -5
src/components/dms/NewMessagesPill.tsx
··· 14 14 ScaleAndFadeOut, 15 15 } from '#/lib/custom-animations/ScaleAndFade' 16 16 import {useHaptics} from '#/lib/haptics' 17 - import {isAndroid, isIOS, isWeb} from '#/platform/detection' 18 17 import {atoms as a, useTheme} from '#/alf' 19 18 import {Text} from '#/components/Typography' 19 + import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env' 20 20 21 21 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 22 22 ··· 28 28 const t = useTheme() 29 29 const playHaptic = useHaptics() 30 30 const {bottom: bottomInset} = useSafeAreaInsets() 31 - const bottomBarHeight = isIOS ? 42 : isAndroid ? 60 : 0 32 - const bottomOffset = isWeb ? 0 : bottomInset + bottomBarHeight 31 + const bottomBarHeight = IS_IOS ? 42 : IS_ANDROID ? 60 : 0 32 + const bottomOffset = IS_WEB ? 0 : bottomInset + bottomBarHeight 33 33 34 34 const scale = useSharedValue(1) 35 35 36 36 const onPressIn = React.useCallback(() => { 37 - if (isWeb) return 37 + if (IS_WEB) return 38 38 scale.set(() => withTiming(1.075, {duration: 100})) 39 39 }, [scale]) 40 40 41 41 const onPressOut = React.useCallback(() => { 42 - if (isWeb) return 42 + if (IS_WEB) return 43 43 scale.set(() => withTiming(1, {duration: 100})) 44 44 }, [scale]) 45 45
+2 -2
src/components/forms/SearchInput.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {HITSLOP_10} from '#/lib/constants' 7 - import {isNative} from '#/platform/detection' 8 7 import {atoms as a, useTheme} from '#/alf' 9 8 import {Button, ButtonIcon} from '#/components/Button' 10 9 import * as TextField from '#/components/forms/TextField' 11 10 import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass' 12 11 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 12 + import {IS_NATIVE} from '#/env' 13 13 14 14 type SearchInputProps = Omit<TextField.InputProps, 'label'> & { 15 15 label?: TextField.InputProps['label'] ··· 36 36 placeholder={_(msg`Search`)} 37 37 returnKeyType="search" 38 38 keyboardAppearance={t.scheme} 39 - selectTextOnFocus={isNative} 39 + selectTextOnFocus={IS_NATIVE} 40 40 autoFocus={false} 41 41 accessibilityRole="search" 42 42 autoCorrect={false}
+2 -2
src/components/forms/Toggle/index.tsx
··· 10 10 11 11 import {HITSLOP_10} from '#/lib/constants' 12 12 import {useHaptics} from '#/lib/haptics' 13 - import {isNative} from '#/platform/detection' 14 13 import { 15 14 atoms as a, 16 15 native, ··· 22 21 import {useInteractionState} from '#/components/hooks/useInteractionState' 23 22 import {CheckThick_Stroke2_Corner0_Rounded as Checkmark} from '#/components/icons/Check' 24 23 import {Text} from '#/components/Typography' 24 + import {IS_NATIVE} from '#/env' 25 25 26 26 export * from './Panel' 27 27 ··· 562 562 ) 563 563 } 564 564 565 - export const Platform = isNative ? Switch : Checkbox 565 + export const Platform = IS_NATIVE ? Switch : Checkbox
+2 -2
src/components/hooks/useFullscreen.ts
··· 7 7 } from 'react' 8 8 9 9 import {isFirefox, isSafari} from '#/lib/browser' 10 - import {isWeb} from '#/platform/detection' 10 + import {IS_WEB} from '#/env' 11 11 12 12 function fullscreenSubscribe(onChange: () => void) { 13 13 document.addEventListener('fullscreenchange', onChange) ··· 15 15 } 16 16 17 17 export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) { 18 - if (!isWeb) throw new Error("'useFullscreen' is a web-only hook") 18 + if (!IS_WEB) throw new Error("'useFullscreen' is a web-only hook") 19 19 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => 20 20 Boolean(document.fullscreenElement), 21 21 )
+2 -2
src/components/hooks/useStarterPackEntry.native.ts
··· 4 4 createStarterPackLinkFromAndroidReferrer, 5 5 httpStarterPackUriToAtUri, 6 6 } from '#/lib/strings/starter-pack' 7 - import {isAndroid} from '#/platform/detection' 8 7 import {useHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 9 8 import {useSetActiveStarterPack} from '#/state/shell/starter-pack' 9 + import {IS_ANDROID} from '#/env' 10 10 import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army' 11 11 12 12 export function useStarterPackEntry() { ··· 32 32 ;(async () => { 33 33 let uri: string | null | undefined 34 34 35 - if (isAndroid) { 35 + if (IS_ANDROID) { 36 36 const res = await Referrer.getGooglePlayReferrerInfoAsync() 37 37 38 38 if (res && res.installReferrer) {
+2 -2
src/components/hooks/useWelcomeModal.ts
··· 1 1 import {useEffect, useState} from 'react' 2 2 3 - import {isWeb} from '#/platform/detection' 4 3 import {useSession} from '#/state/session' 4 + import {IS_WEB} from '#/env' 5 5 6 6 export function useWelcomeModal() { 7 7 const {hasSession} = useSession() ··· 22 22 // 2. We're on the web (this is a web-only feature) 23 23 // 3. We're on the homepage (path is '/' or '/home') 24 24 // 4. User hasn't actively closed the modal in this session 25 - if (isWeb && !hasSession && typeof window !== 'undefined') { 25 + if (IS_WEB && !hasSession && typeof window !== 'undefined') { 26 26 const currentPath = window.location.pathname 27 27 const isHomePage = currentPath === '/' 28 28 const hasUserClosedModal =
+2 -2
src/components/images/AutoSizedImage.tsx
··· 11 11 import {useLingui} from '@lingui/react' 12 12 13 13 import {type Dimensions} from '#/lib/media/types' 14 - import {isNative} from '#/platform/detection' 15 14 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge' 16 15 import {atoms as a, useTheme} from '#/alf' 17 16 import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal' 18 17 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 19 18 import {Text} from '#/components/Typography' 19 + import {IS_NATIVE} from '#/env' 20 20 21 21 export function ConstrainedImage({ 22 22 aspectRatio, ··· 35 35 * the height of the image. 36 36 */ 37 37 const outerAspectRatio = useMemo<DimensionValue>(() => { 38 - const ratio = isNative 38 + const ratio = IS_NATIVE 39 39 ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box 40 40 : Math.min(1 / aspectRatio, 1) // 1:1 bounding box 41 41 return `${ratio * 100}%`
+3 -3
src/components/intents/VerifyEmailIntentDialog.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {isNative} from '#/platform/detection' 7 6 import {useAgent, useSession} from '#/state/session' 8 7 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 8 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 15 14 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 16 15 import {Loader} from '#/components/Loader' 17 16 import {Text} from '#/components/Typography' 17 + import {IS_NATIVE} from '#/env' 18 18 19 19 export function VerifyEmailIntentDialog() { 20 20 const {verifyEmailDialogControl: control} = useIntentDialogs() ··· 68 68 <Loader size="xl" fill={t.atoms.text_contrast_low.color} /> 69 69 </View> 70 70 ) : status === 'success' ? ( 71 - <View style={[a.gap_sm, isNative && a.pb_xl]}> 71 + <View style={[a.gap_sm, IS_NATIVE && a.pb_xl]}> 72 72 <Text style={[a.font_bold, a.text_2xl]}> 73 73 <Trans>Email Verified</Trans> 74 74 </Text> ··· 93 93 </Text> 94 94 </View> 95 95 ) : ( 96 - <View style={[a.gap_sm, isNative && a.pb_xl]}> 96 + <View style={[a.gap_sm, IS_NATIVE && a.pb_xl]}> 97 97 <Text style={[a.font_bold, a.text_2xl]}> 98 98 <Trans>Email Resent</Trans> 99 99 </Text>
+2 -2
src/components/moderation/LabelsOnMeDialog.tsx
··· 12 12 import {makeProfileLink} from '#/lib/routes/links' 13 13 import {sanitizeHandle} from '#/lib/strings/handles' 14 14 import {logger} from '#/logger' 15 - import {isAndroid} from '#/platform/detection' 16 15 import {useAgent, useSession} from '#/state/session' 17 16 import * as Toast from '#/view/com/util/Toast' 18 17 import {atoms as a, useBreakpoints, useTheme} from '#/alf' ··· 20 19 import * as Dialog from '#/components/Dialog' 21 20 import {InlineLinkText} from '#/components/Link' 22 21 import {Text} from '#/components/Typography' 22 + import {IS_ANDROID} from '#/env' 23 23 import {Admonition} from '../Admonition' 24 24 import {Divider} from '../Divider' 25 25 import {Loader} from '../Loader' ··· 344 344 {isPending && <ButtonIcon icon={Loader} />} 345 345 </Button> 346 346 </View> 347 - {isAndroid && <View style={{height: 300}} />} 347 + {IS_ANDROID && <View style={{height: 300}} />} 348 348 </> 349 349 ) 350 350 }
+3 -3
src/components/moderation/ModerationDetailsDialog.tsx
··· 7 7 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 8 8 import {makeProfileLink} from '#/lib/routes/links' 9 9 import {listUriToHref} from '#/lib/strings/url-helpers' 10 - import {isNative} from '#/platform/detection' 11 10 import {useSession} from '#/state/session' 12 11 import {atoms as a, useGutters, useTheme} from '#/alf' 13 12 import * as Dialog from '#/components/Dialog' 14 13 import {InlineLinkText} from '#/components/Link' 15 14 import {type AppModerationCause} from '#/components/Pills' 16 15 import {Text} from '#/components/Typography' 16 + import {IS_NATIVE} from '#/env' 17 17 18 18 export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog' 19 19 ··· 158 158 xGutters, 159 159 a.py_md, 160 160 a.border_t, 161 - !isNative && t.atoms.bg_contrast_25, 161 + !IS_NATIVE && t.atoms.bg_contrast_25, 162 162 t.atoms.border_contrast_low, 163 163 { 164 164 borderBottomLeftRadius: a.rounded_md.borderRadius, ··· 219 219 </View> 220 220 )} 221 221 222 - {isNative && <View style={{height: 40}} />} 222 + {IS_NATIVE && <View style={{height: 40}} />} 223 223 224 224 <Dialog.Close /> 225 225 </Dialog.ScrollableInner>
+2 -2
src/components/moderation/ReportDialog/index.tsx
··· 8 8 import {getLabelingServiceTitle} from '#/lib/moderation' 9 9 import {sanitizeHandle} from '#/lib/strings/handles' 10 10 import {Logger} from '#/logger' 11 - import {isNative} from '#/platform/detection' 12 11 import {useMyLabelersQuery} from '#/state/queries/preferences' 13 12 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' 14 13 import {UserAvatar} from '#/view/com/util/UserAvatar' ··· 29 28 import {createStaticClick, InlineLinkText, Link} from '#/components/Link' 30 29 import {Loader} from '#/components/Loader' 31 30 import {Text} from '#/components/Typography' 31 + import {IS_NATIVE} from '#/env' 32 32 import {useSubmitReportMutation} from './action' 33 33 import { 34 34 BSKY_LABELER_ONLY_REPORT_REASONS, ··· 253 253 label={_(msg`Report dialog`)} 254 254 ref={ref} 255 255 style={[a.w_full, {maxWidth: 500}]}> 256 - <View style={[a.gap_2xl, isNative && a.pt_md]}> 256 + <View style={[a.gap_2xl, IS_NATIVE && a.pt_md]}> 257 257 <StepOuter> 258 258 <StepTitle 259 259 index={1}
+11
src/env/index.ts
··· 1 + import {Platform} from 'react-native' 1 2 import {nativeBuildVersion} from 'expo-application' 2 3 3 4 import {BUNDLE_IDENTIFIER, IS_TESTFLIGHT, RELEASE_VERSION} from '#/env/common' ··· 17 18 export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${ 18 19 __DEV__ ? 'dev' : IS_TESTFLIGHT ? 'tf' : 'prod' 19 20 })` 21 + 22 + /** 23 + * Platform detection 24 + */ 25 + export const IS_IOS: boolean = Platform.OS === 'ios' 26 + export const IS_ANDROID: boolean = Platform.OS === 'android' 27 + export const IS_NATIVE: boolean = true 28 + export const IS_WEB: boolean = false 29 + export const IS_WEB_MOBILE: boolean = false 30 + export const IS_WEB_MOBILE_IOS: boolean = false
+13
src/env/index.web.ts
··· 13 13 * The short commit hash and environment of the current bundle. 14 14 */ 15 15 export const APP_METADATA = `${BUNDLE_IDENTIFIER.slice(0, 7)} (${__DEV__ ? 'dev' : 'prod'})` 16 + 17 + /** 18 + * Platform detection 19 + */ 20 + export const IS_IOS: boolean = false 21 + export const IS_ANDROID: boolean = false 22 + export const IS_NATIVE: boolean = false 23 + export const IS_WEB: boolean = true 24 + // @ts-ignore we know window exists -prf 25 + export const IS_WEB_MOBILE: boolean = global.window.matchMedia( 26 + 'only screen and (max-width: 1300px)', 27 + )?.matches 28 + export const IS_WEB_MOBILE_IOS: boolean = /iPhone/.test(navigator.userAgent)
+2 -2
src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 5 5 import {useCleanError} from '#/lib/hooks/useCleanError' 6 - import {isNative} from '#/platform/detection' 7 6 import {atoms as a, web} from '#/alf' 8 7 import {Admonition} from '#/components/Admonition' 9 8 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 11 10 import {Loader} from '#/components/Loader' 12 11 import * as Toast from '#/components/Toast' 13 12 import {Span, Text} from '#/components/Typography' 13 + import {IS_NATIVE} from '#/env' 14 14 import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences' 15 15 import { 16 16 type LiveEventFeed, ··· 146 146 </ButtonText> 147 147 {isHidingAllFeeds && <ButtonIcon icon={Loader} />} 148 148 </Button> 149 - {isNative && ( 149 + {IS_NATIVE && ( 150 150 <Button 151 151 label={_(msg`Cancel`)} 152 152 size="large"
+2 -2
src/features/liveEvents/preferences.ts
··· 3 3 import {useMutation, useQueryClient} from '@tanstack/react-query' 4 4 5 5 import {logger} from '#/logger' 6 - import {isWeb} from '#/platform/detection' 7 6 import { 8 7 preferencesQueryKey, 9 8 usePreferencesQuery, 10 9 } from '#/state/queries/preferences' 11 10 import {useAgent} from '#/state/session' 11 + import {IS_WEB} from '#/env' 12 12 import * as env from '#/env' 13 13 import { 14 14 type LiveEventFeed, ··· 41 41 const agent = useAgent() 42 42 43 43 useEffect(() => { 44 - if (env.IS_DEV && isWeb && typeof window !== 'undefined') { 44 + if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') { 45 45 // @ts-ignore 46 46 window.__updateLiveEventPreferences = async ( 47 47 action: LiveEventPreferencesAction,
+2 -2
src/geolocation/device.ts
··· 3 3 import * as Location from 'expo-location' 4 4 import {createPermissionHook} from 'expo-modules-core' 5 5 6 - import {isNative} from '#/platform/detection' 6 + import {IS_NATIVE} from '#/env' 7 7 import * as debug from '#/geolocation/debug' 8 8 import {logger} from '#/geolocation/logger' 9 9 import {type Geolocation} from '#/geolocation/types' ··· 118 118 const synced = useRef(false) 119 119 const [status] = useForegroundPermissions() 120 120 useEffect(() => { 121 - if (!isNative) return 121 + if (!IS_NATIVE) return 122 122 123 123 async function get() { 124 124 // no need to set this more than once per session
+2 -2
src/geolocation/util.ts
··· 1 1 import {type LocationGeocodedAddress} from 'expo-location' 2 2 3 - import {isAndroid} from '#/platform/detection' 3 + import {IS_ANDROID} from '#/env' 4 4 import {logger} from '#/geolocation/logger' 5 5 import {type Geolocation} from '#/geolocation/types' 6 6 ··· 81 81 /* 82 82 * Android doesn't give us ISO 3166-2 short codes. We need these for US 83 83 */ 84 - if (isAndroid) { 84 + if (IS_ANDROID) { 85 85 if (region && isoCountryCode === 'US') { 86 86 /* 87 87 * We need short codes for US states. If we can't remap it, just drop it
+2 -2
src/lib/api/feed/utils.ts
··· 1 1 import {AtUri} from '@atproto/api' 2 2 3 3 import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' 4 - import {isWeb} from '#/platform/detection' 5 4 import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 5 + import {IS_WEB} from '#/env' 6 6 7 7 let debugTopics = '' 8 - if (isWeb && typeof window !== 'undefined') { 8 + if (IS_WEB && typeof window !== 'undefined') { 9 9 const params = new URLSearchParams(window.location.search) 10 10 debugTopics = params.get('debug_topics') ?? '' 11 11 }
+1 -1
src/lib/browser.native.ts
··· 1 1 export const isSafari = false 2 2 export const isFirefox = false 3 3 export const isTouchDevice = true 4 - export const isAndroidWeb = false 4 + export const IS_ANDROIDWeb = false 5 5 export const isHighDPI = true
+1 -1
src/lib/browser.ts
··· 4 4 ) 5 5 export const isFirefox = /firefox|fxios/i.test(navigator.userAgent) 6 6 export const isTouchDevice = window.matchMedia('(pointer: coarse)').matches 7 - export const isAndroidWeb = 7 + export const IS_ANDROIDWeb = 8 8 /android/i.test(navigator.userAgent) && isTouchDevice 9 9 export const isHighDPI = window.matchMedia('(min-resolution: 2dppx)').matches
+3 -3
src/lib/custom-animations/AccordionAnimation.tsx
··· 13 13 withTiming, 14 14 } from 'react-native-reanimated' 15 15 16 - import {isIOS, isWeb} from '#/platform/detection' 16 + import {IS_IOS, IS_WEB} from '#/env' 17 17 18 18 type AccordionAnimationProps = React.PropsWithChildren<{ 19 19 isExpanded: boolean ··· 66 66 style={style} 67 67 entering={FadeInUp.duration(duration)} 68 68 exiting={FadeOutUp.duration(duration / 2)} 69 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 69 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 70 70 {children} 71 71 </Animated.View> 72 72 ) 73 73 } 74 74 75 75 export function AccordionAnimation(props: AccordionAnimationProps) { 76 - return isWeb ? <WebAccordion {...props} /> : <MobileAccordion {...props} /> 76 + return IS_WEB ? <WebAccordion {...props} /> : <MobileAccordion {...props} /> 77 77 }
+2 -2
src/lib/custom-animations/PressableScale.tsx
··· 13 13 } from 'react-native-reanimated' 14 14 15 15 import {isTouchDevice} from '#/lib/browser' 16 - import {isNative} from '#/platform/detection' 16 + import {IS_NATIVE} from '#/env' 17 17 18 - const DEFAULT_TARGET_SCALE = isNative || isTouchDevice ? 0.98 : 1 18 + const DEFAULT_TARGET_SCALE = IS_NATIVE || isTouchDevice ? 0.98 : 1 19 19 20 20 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) 21 21
+3 -3
src/lib/haptics.ts
··· 2 2 import * as Device from 'expo-device' 3 3 import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics' 4 4 5 - import {isIOS, isWeb} from '#/platform/detection' 6 5 import {useHapticsDisabled} from '#/state/preferences/disable-haptics' 6 + import {IS_IOS, IS_WEB} from '#/env' 7 7 8 8 export function useHaptics() { 9 9 const isHapticsDisabled = useHapticsDisabled() 10 10 11 11 return React.useCallback( 12 12 (strength: 'Light' | 'Medium' | 'Heavy' = 'Medium') => { 13 - if (isHapticsDisabled || isWeb) { 13 + if (isHapticsDisabled || IS_WEB) { 14 14 return 15 15 } 16 16 17 17 // Users said the medium impact was too strong on Android; see APP-537s 18 - const style = isIOS 18 + const style = IS_IOS 19 19 ? ImpactFeedbackStyle[strength] 20 20 : ImpactFeedbackStyle.Light 21 21 impactAsync(style)
+2 -2
src/lib/hooks/useAccountSwitcher.ts
··· 3 3 import {useLingui} from '@lingui/react' 4 4 5 5 import {logger} from '#/logger' 6 - import {isWeb} from '#/platform/detection' 7 6 import {type SessionAccount, useSessionApi} from '#/state/session' 8 7 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 9 8 import * as Toast from '#/view/com/util/Toast' 9 + import {IS_WEB} from '#/env' 10 10 import {logEvent} from '../statsig/statsig' 11 11 import {type LogEvents} from '../statsig/statsig' 12 12 ··· 28 28 try { 29 29 setPendingDid(account.did) 30 30 if (account.accessJwt) { 31 - if (isWeb) { 31 + if (IS_WEB) { 32 32 // We're switching accounts, which remounts the entire app. 33 33 // On mobile, this gets us Home, but on the web we also need reset the URL. 34 34 // We can't change the URL via a navigate() call because the navigator
+2 -2
src/lib/hooks/useBottomBarOffset.ts
··· 2 2 3 3 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 4 import {clamp} from '#/lib/numbers' 5 - import {isWeb} from '#/platform/detection' 5 + import {IS_WEB} from '#/env' 6 6 7 7 export function useBottomBarOffset(modifier: number = 0) { 8 8 const {isTabletOrDesktop} = useWebMediaQueries() 9 9 const {bottom: bottomInset} = useSafeAreaInsets() 10 10 return ( 11 - (isWeb && isTabletOrDesktop ? 0 : clamp(60 + bottomInset, 60, 75)) + 11 + (IS_WEB && isTabletOrDesktop ? 0 : clamp(60 + bottomInset, 60, 75)) + 12 12 modifier 13 13 ) 14 14 }
+3 -3
src/lib/hooks/useIntentHandler.ts
··· 6 6 import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 7 7 import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 8 8 import {logger} from '#/logger' 9 - import {isIOS, isNative} from '#/platform/detection' 10 9 import {useSession} from '#/state/session' 11 10 import {useCloseAllActiveElements} from '#/state/util' 12 11 import {useIntentDialogs} from '#/components/intents/IntentDialogs' 12 + import {IS_IOS, IS_NATIVE} from '#/env' 13 13 import {Referrer} from '../../../modules/expo-bluesky-swiss-army' 14 14 import {useApplyPullRequestOTAUpdate} from './useOTAUpdates' 15 15 ··· 29 29 30 30 React.useEffect(() => { 31 31 const handleIncomingURL = async (url: string) => { 32 - if (isIOS) { 32 + if (IS_IOS) { 33 33 // Close in-app browser if it's open (iOS only) 34 34 await WebBrowser.dismissBrowser().catch(() => {}) 35 35 } ··· 150 150 setTimeout(() => { 151 151 openComposer({ 152 152 text: text ?? undefined, 153 - imageUris: isNative ? imageUris : undefined, 153 + imageUris: IS_NATIVE ? imageUris : undefined, 154 154 }) 155 155 }, 500) 156 156 },
+3 -3
src/lib/hooks/useIsKeyboardVisible.ts
··· 1 1 import {useEffect, useState} from 'react' 2 2 import {Keyboard} from 'react-native' 3 3 4 - import {isIOS} from '#/platform/detection' 4 + import {IS_IOS} from '#/env' 5 5 6 6 export function useIsKeyboardVisible({ 7 7 iosUseWillEvents, ··· 14 14 // only iOS supports the "will" events 15 15 // -prf 16 16 const showEvent = 17 - isIOS && iosUseWillEvents ? 'keyboardWillShow' : 'keyboardDidShow' 17 + IS_IOS && iosUseWillEvents ? 'keyboardWillShow' : 'keyboardDidShow' 18 18 const hideEvent = 19 - isIOS && iosUseWillEvents ? 'keyboardWillHide' : 'keyboardDidHide' 19 + IS_IOS && iosUseWillEvents ? 'keyboardWillHide' : 'keyboardDidHide' 20 20 21 21 useEffect(() => { 22 22 const keyboardShowListener = Keyboard.addListener(showEvent, () =>
+3 -3
src/lib/hooks/useNotificationHandler.ts
··· 9 9 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 10 10 import {logger as notyLogger} from '#/lib/notifications/util' 11 11 import {type NavigationProp} from '#/lib/routes/types' 12 - import {isAndroid, isIOS} from '#/platform/detection' 13 12 import {useCurrentConvoId} from '#/state/messages/current-convo-id' 14 13 import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed' 15 14 import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread' ··· 17 16 import {useSession} from '#/state/session' 18 17 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 19 18 import {useCloseAllActiveElements} from '#/state/util' 19 + import {IS_ANDROID, IS_IOS} from '#/env' 20 20 import {resetToTab} from '#/Navigation' 21 21 import {router} from '#/routes' 22 22 ··· 90 90 // channels allow for the mute/unmute functionality we want for the background 91 91 // handler. 92 92 useEffect(() => { 93 - if (!isAndroid) return 93 + if (!IS_ANDROID) return 94 94 // assign both chat notifications to a group 95 95 // NOTE: I don't think that it will retroactively move them into the group 96 96 // if the channels already exist. no big deal imo -sfn ··· 379 379 } 380 380 381 381 const payload = ( 382 - isIOS ? e.request.trigger.payload : e.request.content.data 382 + IS_IOS ? e.request.trigger.payload : e.request.content.data 383 383 ) as NotificationPayload 384 384 385 385 if (payload && payload.reason) {
+4 -4
src/lib/hooks/useOTAUpdates.ts
··· 12 12 13 13 import {isNetworkError} from '#/lib/strings/errors' 14 14 import {logger} from '#/logger' 15 - import {isAndroid, isIOS} from '#/platform/detection' 15 + import {IS_ANDROID, IS_IOS} from '#/env' 16 16 import {IS_TESTFLIGHT} from '#/env' 17 17 18 18 const MINIMUM_MINIMIZE_TIME = 15 * 60e3 19 19 20 20 async function setExtraParams() { 21 21 await setExtraParamAsync( 22 - isIOS ? 'ios-build-number' : 'android-build-number', 22 + IS_IOS ? 'ios-build-number' : 'android-build-number', 23 23 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 24 24 // This just ensures it gets passed as a string 25 25 `${nativeBuildVersion}`, ··· 32 32 33 33 async function setExtraParamsPullRequest(channel: string) { 34 34 await setExtraParamAsync( 35 - isIOS ? 'ios-build-number' : 'android-build-number', 35 + IS_IOS ? 'ios-build-number' : 'android-build-number', 36 36 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is. 37 37 // This just ensures it gets passed as a string 38 38 `${nativeBuildVersion}`, ··· 198 198 // `maintainVisibleContentPosition`. See repro repo for more details: 199 199 // https://github.com/mozzius/ota-crash-repro 200 200 // Old Arch only - re-enable once we're on the New Archictecture! -sfn 201 - if (isAndroid) return 201 + if (IS_ANDROID) return 202 202 203 203 const subscription = AppState.addEventListener( 204 204 'change',
+2 -2
src/lib/hooks/useOpenLink.ts
··· 12 12 toNiceDomain, 13 13 } from '#/lib/strings/url-helpers' 14 14 import {logger} from '#/logger' 15 - import {isNative} from '#/platform/detection' 16 15 import {useInAppBrowser} from '#/state/preferences/in-app-browser' 17 16 import {useTheme} from '#/alf' 18 17 import {useDialogContext} from '#/components/Dialog' 19 18 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 19 + import {IS_NATIVE} from '#/env' 20 20 21 21 export function useOpenLink() { 22 22 const enabled = useInAppBrowser() ··· 41 41 } 42 42 } 43 43 44 - if (isNative && !url.startsWith('mailto:')) { 44 + if (IS_NATIVE && !url.startsWith('mailto:')) { 45 45 if (override === undefined && enabled === undefined) { 46 46 // consent dialog is a global dialog, and while it's possible to nest dialogs, 47 47 // the actual components need to be nested. sibling dialogs on iOS are not supported.
+3 -3
src/lib/hooks/usePermissions.ts
··· 2 2 import {useCameraPermissions as useExpoCameraPermissions} from 'expo-camera' 3 3 import * as MediaLibrary from 'expo-media-library' 4 4 5 - import {isWeb} from '#/platform/detection' 6 5 import {Alert} from '#/view/com/util/Alert' 6 + import {IS_WEB} from '#/env' 7 7 8 8 const openPermissionAlert = (perm: string) => { 9 9 Alert.alert( ··· 26 26 const requestPhotoAccessIfNeeded = async () => { 27 27 // On the, we use <input type="file"> to produce a filepicker 28 28 // This does not need any permission granting. 29 - if (isWeb) { 29 + if (IS_WEB) { 30 30 return true 31 31 } 32 32 ··· 55 55 const requestVideoAccessIfNeeded = async () => { 56 56 // On the, we use <input type="file"> to produce a filepicker 57 57 // This does not need any permission granting. 58 - if (isWeb) { 58 + if (IS_WEB) { 59 59 return true 60 60 } 61 61
+2 -2
src/lib/hooks/useTranslate.ts
··· 2 2 import * as IntentLauncher from 'expo-intent-launcher' 3 3 4 4 import {getTranslatorLink} from '#/locale/helpers' 5 - import {isAndroid} from '#/platform/detection' 5 + import {IS_ANDROID} from '#/env' 6 6 import {useOpenLink} from './useOpenLink' 7 7 8 8 export function useTranslate() { ··· 11 11 return useCallback( 12 12 async (text: string, language: string) => { 13 13 const translateUrl = getTranslatorLink(text, language) 14 - if (isAndroid) { 14 + if (IS_ANDROID) { 15 15 try { 16 16 // use getApplicationIconAsync to determine if the translate app is installed 17 17 if (
+2 -2
src/lib/hooks/useWebMediaQueries.tsx
··· 1 1 import {useMediaQuery} from 'react-responsive' 2 2 3 - import {isNative} from '#/platform/detection' 3 + import {IS_NATIVE} from '#/env' 4 4 5 5 /** 6 6 * @deprecated use `useBreakpoints` from `#/alf` instead ··· 11 11 const isMobile = useMediaQuery({maxWidth: 800 - 1}) 12 12 const isTabletOrMobile = isMobile || isTablet 13 13 const isTabletOrDesktop = isDesktop || isTablet 14 - if (isNative) { 14 + if (IS_NATIVE) { 15 15 return { 16 16 isMobile: true, 17 17 isTablet: false,
+4 -4
src/lib/media/manip.ts
··· 18 18 19 19 import {POST_IMG_MAX} from '#/lib/constants' 20 20 import {logger} from '#/logger' 21 - import {isAndroid, isIOS} from '#/platform/detection' 21 + import {IS_ANDROID, IS_IOS} from '#/env' 22 22 import {type PickerImage} from './picker.shared' 23 23 import {type Dimensions} from './types' 24 24 ··· 108 108 109 109 // save 110 110 try { 111 - if (isAndroid) { 111 + if (IS_ANDROID) { 112 112 // android triggers an annoying permission prompt if you try and move an image 113 113 // between albums. therefore, we need to either create the album with the image 114 114 // as the starting image, or put it directly into the album ··· 305 305 } 306 306 307 307 function normalizePath(str: string, allPlatforms = false): string { 308 - if (isAndroid || allPlatforms) { 308 + if (IS_ANDROID || allPlatforms) { 309 309 if (!str.startsWith('file://')) { 310 310 return `file://${str}` 311 311 } ··· 328 328 type: string, 329 329 ) { 330 330 try { 331 - if (isIOS) { 331 + if (IS_IOS) { 332 332 await withTempFile(filename, encoded, async tmpFileUrl => { 333 333 await Sharing.shareAsync(tmpFileUrl, {UTI: type}) 334 334 })
+3 -3
src/lib/media/picker.shared.ts
··· 5 5 } from 'expo-image-picker' 6 6 import {t} from '@lingui/macro' 7 7 8 - import {isIOS, isWeb} from '#/platform/detection' 9 8 import {type ImageMeta} from '#/state/gallery' 10 9 import * as Toast from '#/view/com/util/Toast' 10 + import {IS_IOS, IS_WEB} from '#/env' 11 11 import {VIDEO_MAX_DURATION_MS} from '../constants' 12 12 import {getDataUriSize} from './util' 13 13 ··· 53 53 quality: 1, 54 54 allowsMultipleSelection: true, 55 55 legacy: true, 56 - base64: isWeb, 57 - selectionLimit: isIOS ? selectionCountRemaining : undefined, 56 + base64: IS_WEB, 57 + selectionLimit: IS_IOS ? selectionCountRemaining : undefined, 58 58 preferredAssetRepresentationMode: 59 59 UIImagePickerPreferredAssetRepresentationMode.Automatic, 60 60 videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000,
+2 -2
src/lib/media/save-image.ios.ts
··· 2 2 import {msg} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4 5 - import {isNative} from '#/platform/detection' 6 5 import * as Toast from '#/components/Toast' 6 + import {IS_NATIVE} from '#/env' 7 7 import {saveImageToMediaLibrary} from './manip' 8 8 9 9 /** ··· 16 16 const {_} = useLingui() 17 17 return useCallback( 18 18 async (uri: string) => { 19 - if (!isNative) { 19 + if (!IS_NATIVE) { 20 20 throw new Error('useSaveImageToMediaLibrary is native only') 21 21 } 22 22
+2 -2
src/lib/media/save-image.ts
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {isNative} from '#/platform/detection' 7 6 import * as Toast from '#/components/Toast' 7 + import {IS_NATIVE} from '#/env' 8 8 import {saveImageToMediaLibrary} from './manip' 9 9 10 10 /** ··· 18 18 }) 19 19 return useCallback( 20 20 async (uri: string) => { 21 - if (!isNative) { 21 + if (!IS_NATIVE) { 22 22 throw new Error('useSaveImageToMediaLibrary is native only') 23 23 } 24 24
+5 -5
src/lib/notifications/notifications.ts
··· 13 13 } from '#/lib/constants' 14 14 import {logger as notyLogger} from '#/lib/notifications/util' 15 15 import {isNetworkError} from '#/lib/strings/errors' 16 - import {isNative} from '#/platform/detection' 17 16 import {type SessionAccount, useAgent, useSession} from '#/state/session' 18 17 import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' 19 18 import {useAgeAssurance} from '#/ageAssurance' 19 + import {IS_NATIVE} from '#/env' 20 20 import {IS_DEV} from '#/env' 21 21 22 22 /** ··· 140 140 }: { 141 141 isAgeRestricted?: boolean 142 142 } = {}) => { 143 - if (!isNative || IS_DEV) return 143 + if (!IS_NATIVE || IS_DEV) return 144 144 145 145 /** 146 146 * This will also fire the listener added via `addPushTokenListener`. That ··· 236 236 const permissions = await Notifications.getPermissionsAsync() 237 237 238 238 if ( 239 - !isNative || 239 + !IS_NATIVE || 240 240 permissions?.status === 'granted' || 241 241 (permissions?.status === 'denied' && !permissions.canAskAgain) 242 242 ) { ··· 277 277 } 278 278 279 279 export async function decrementBadgeCount(by: number) { 280 - if (!isNative) return 280 + if (!IS_NATIVE) return 281 281 282 282 let count = await getBadgeCountAsync() 283 283 count -= by ··· 295 295 } 296 296 297 297 export async function unregisterPushToken(agents: AtpAgent[]) { 298 - if (!isNative) return 298 + if (!IS_NATIVE) return 299 299 300 300 try { 301 301 const token = await getPushToken()
+3 -3
src/lib/react-query.tsx
··· 9 9 } from '@tanstack/react-query-persist-client' 10 10 import type React from 'react' 11 11 12 - import {isNative, isWeb} from '#/platform/detection' 13 12 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events' 13 + import {IS_NATIVE, IS_WEB} from '#/env' 14 14 15 15 declare global { 16 16 interface Window { ··· 87 87 }, 2000) 88 88 89 89 focusManager.setEventListener(onFocus => { 90 - if (isNative) { 90 + if (IS_NATIVE) { 91 91 const subscription = AppState.addEventListener( 92 92 'change', 93 93 (status: AppStateStatus) => { ··· 187 187 } 188 188 }) 189 189 useEffect(() => { 190 - if (isWeb) { 190 + if (IS_WEB) { 191 191 window.__TANSTACK_QUERY_CLIENT__ = queryClient 192 192 } 193 193 }, [queryClient])
+4 -4
src/lib/sharing.ts
··· 4 4 // TODO: replace global i18n instance with one returned from useLingui -sfn 5 5 import {t} from '@lingui/macro' 6 6 7 - import {isAndroid, isIOS} from '#/platform/detection' 8 7 import * as Toast from '#/view/com/util/Toast' 8 + import {IS_ANDROID, IS_IOS} from '#/env' 9 9 10 10 /** 11 11 * This function shares a URL using the native Share API if available, or copies it to the clipboard ··· 14 14 * clipboard. 15 15 */ 16 16 export async function shareUrl(url: string) { 17 - if (isAndroid) { 17 + if (IS_ANDROID) { 18 18 await Share.share({message: url}) 19 - } else if (isIOS) { 19 + } else if (IS_IOS) { 20 20 await Share.share({url}) 21 21 } else { 22 22 // React Native Share is not supported by web. Web Share API ··· 34 34 * clipboard. 35 35 */ 36 36 export async function shareText(text: string) { 37 - if (isAndroid || isIOS) { 37 + if (IS_ANDROID || IS_IOS) { 38 38 await Share.share({message: text}) 39 39 } else { 40 40 await setStringAsync(text)
+2 -2
src/lib/statsig/statsig.tsx
··· 5 5 6 6 import {logger} from '#/logger' 7 7 import {type MetricEvents} from '#/logger/metrics' 8 - import {isWeb} from '#/platform/detection' 9 8 import * as persisted from '#/state/persisted' 9 + import {IS_WEB} from '#/env' 10 10 import * as env from '#/env' 11 11 import {useSession} from '../../state/session' 12 12 import {timeout} from '../async/timeout' ··· 37 37 38 38 let refSrc = '' 39 39 let refUrl = '' 40 - if (isWeb && typeof window !== 'undefined') { 40 + if (IS_WEB && typeof window !== 'undefined') { 41 41 const params = new URLSearchParams(window.location.search) 42 42 refSrc = params.get('ref_src') ?? '' 43 43 refUrl = decodeURIComponent(params.get('ref_url') ?? '')
+4 -4
src/lib/strings/embed-player.ts
··· 1 1 import {Dimensions} from 'react-native' 2 2 3 3 import {isSafari} from '#/lib/browser' 4 - import {isWeb} from '#/platform/detection' 4 + import {IS_WEB} from '#/env' 5 5 6 6 const {height: SCREEN_HEIGHT} = Dimensions.get('window') 7 7 8 - const IFRAME_HOST = isWeb 8 + const IFRAME_HOST = IS_WEB 9 9 ? // @ts-ignore only for web 10 10 window.location.host === 'localhost:8100' 11 11 ? 'http://localhost:8100' ··· 132 132 urlp.hostname === 'www.twitch.tv' || 133 133 urlp.hostname === 'm.twitch.tv' 134 134 ) { 135 - const parent = isWeb 135 + const parent = IS_WEB 136 136 ? // @ts-ignore only for web 137 137 window.location.hostname 138 138 : 'localhost' ··· 559 559 width: Number(w), 560 560 } 561 561 562 - if (isWeb) { 562 + if (IS_WEB) { 563 563 if (isSafari) { 564 564 id = id.replace('AAAAC', 'AAAP1') 565 565 filename = filename.replace('.gif', '.mp4')
+2 -2
src/lib/styles.ts
··· 5 5 type TextStyle, 6 6 } from 'react-native' 7 7 8 - import {isWeb} from '#/platform/detection' 8 + import {IS_WEB} from '#/env' 9 9 import {type Theme, type TypographyVariant} from './ThemeContext' 10 10 11 11 // 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest ··· 186 186 // dimensions 187 187 w100pct: {width: '100%'}, 188 188 h100pct: {height: '100%'}, 189 - hContentRegion: isWeb ? {minHeight: '100%'} : {height: '100%'}, 189 + hContentRegion: IS_WEB ? {minHeight: '100%'} : {height: '100%'}, 190 190 window: { 191 191 width: Dimensions.get('window').width, 192 192 height: Dimensions.get('window').height,
+2 -2
src/logger/index.ts
··· 13 13 type Transport, 14 14 } from '#/logger/types' 15 15 import {enabledLogLevels} from '#/logger/util' 16 - import {isNative} from '#/platform/detection' 16 + import {IS_NATIVE} from '#/env' 17 17 import {ENV} from '#/env' 18 18 19 19 export {type MetricEvents as Metrics} from '#/logger/metrics' ··· 21 21 const TRANSPORTS: Transport[] = (function configureTransports() { 22 22 switch (ENV) { 23 23 case 'production': { 24 - return [sentryTransport, isNative && bitdriftTransport].filter( 24 + return [sentryTransport, IS_NATIVE && bitdriftTransport].filter( 25 25 Boolean, 26 26 ) as Transport[] 27 27 }
+2 -2
src/logger/transports/console.ts
··· 2 2 3 3 import {LogLevel, type Transport} from '#/logger/types' 4 4 import {prepareMetadata} from '#/logger/util' 5 - import {isWeb} from '#/platform/detection' 5 + import {IS_WEB} from '#/env' 6 6 7 7 /** 8 8 * Used in dev mode to nicely log to the console ··· 33 33 msg += ` ${message.toString()}` 34 34 } 35 35 36 - if (isWeb) { 36 + if (IS_WEB) { 37 37 if (hasMetadata) { 38 38 console.groupCollapsed(msg) 39 39 console.log(metadata)
-12
src/platform/detection.ts
··· 1 - import {Platform} from 'react-native' 2 - 3 - export const isIOS = Platform.OS === 'ios' 4 - export const isAndroid = Platform.OS === 'android' 5 - export const isNative = isIOS || isAndroid 6 - export const isWeb = !isNative 7 - export const isMobileWebMediaQuery = 'only screen and (max-width: 1300px)' 8 - export const isMobileWeb = 9 - isWeb && 10 - // @ts-ignore we know window exists -prf 11 - global.window.matchMedia(isMobileWebMediaQuery)?.matches 12 - export const isIPhoneWeb = isWeb && /iPhone/.test(navigator.userAgent)
-26
src/platform/urls.tsx
··· 1 - import {Linking} from 'react-native' 2 - 3 - import {isNative, isWeb} from './detection' 4 - 5 - export async function getInitialURL(): Promise<string | undefined> { 6 - if (isNative) { 7 - const url = await Linking.getInitialURL() 8 - if (url) { 9 - return url 10 - } 11 - return undefined 12 - } else { 13 - // @ts-ignore window exists -prf 14 - if (window.location.pathname !== '/') { 15 - return window.location.pathname 16 - } 17 - return undefined 18 - } 19 - } 20 - 21 - export function clearHash() { 22 - if (isWeb) { 23 - // @ts-ignore window exists -prf 24 - window.location.hash = '' 25 - } 26 - }
+2 -2
src/screens/Bookmarks/index.tsx
··· 21 21 type NativeStackScreenProps, 22 22 } from '#/lib/routes/types' 23 23 import {logger} from '#/logger' 24 - import {isIOS} from '#/platform/detection' 25 24 import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation' 26 25 import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery' 27 26 import {useSetMinimalShellMode} from '#/state/shell' ··· 38 37 import * as Skele from '#/components/Skeleton' 39 38 import * as toast from '#/components/Toast' 40 39 import {Text} from '#/components/Typography' 40 + import {IS_IOS} from '#/env' 41 41 42 42 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'> 43 43 ··· 193 193 } 194 194 initialNumToRender={initialNumToRender} 195 195 windowSize={9} 196 - maxToRenderPerBatch={isIOS ? 5 : 1} 196 + maxToRenderPerBatch={IS_IOS ? 5 : 1} 197 197 updateCellsBatchingPeriod={40} 198 198 sideBorders={false} 199 199 />
+4 -4
src/screens/Deactivated.tsx
··· 7 7 8 8 import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' 9 9 import {logger} from '#/logger' 10 - import {isWeb} from '#/platform/detection' 11 10 import { 12 11 type SessionAccount, 13 12 useAgent, ··· 24 23 import * as Layout from '#/components/Layout' 25 24 import {Loader} from '#/components/Loader' 26 25 import {Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 27 28 28 const COL_WIDTH = 400 29 29 ··· 55 55 }, [setShowLoggedOut]) 56 56 57 57 const onPressLogout = React.useCallback(() => { 58 - if (isWeb) { 58 + if (IS_WEB) { 59 59 // We're switching accounts, which remounts the entire app. 60 60 // On mobile, this gets us Home, but on the web we also need reset the URL. 61 61 // We can't change the URL via a navigate() call because the navigator ··· 101 101 contentContainerStyle={[ 102 102 a.px_2xl, 103 103 { 104 - paddingTop: isWeb ? 64 : insets.top + 16, 105 - paddingBottom: isWeb ? 64 : insets.bottom, 104 + paddingTop: IS_WEB ? 64 : insets.top + 16, 105 + paddingBottom: IS_WEB ? 64 : insets.bottom, 106 106 }, 107 107 ]}> 108 108 <View
+2 -2
src/screens/FindContactsFlowScreen.tsx
··· 9 9 type AllNavigatorParams, 10 10 type NativeStackScreenProps, 11 11 } from '#/lib/routes/types' 12 - import {isNative} from '#/platform/detection' 13 12 import {useSetMinimalShellMode} from '#/state/shell' 14 13 import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' 15 14 import {FindContactsFlow} from '#/components/contacts/FindContactsFlow' 16 15 import {useFindContactsFlowState} from '#/components/contacts/state' 17 16 import * as Layout from '#/components/Layout' 18 17 import {ScreenTransition} from '#/components/ScreenTransition' 18 + import {IS_NATIVE} from '#/env' 19 19 20 20 type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsFlow'> 21 21 export function FindContactsFlowScreen({navigation}: Props) { ··· 48 48 49 49 return ( 50 50 <Layout.Screen> 51 - {isNative ? ( 51 + {IS_NATIVE ? ( 52 52 <LayoutAnimationConfig skipEntering skipExiting> 53 53 <ScreenTransition key={state.step} direction={transitionDirection}> 54 54 <FindContactsFlow
+2 -2
src/screens/Login/LoginForm.tsx
··· 18 18 import {cleanError} from '#/lib/strings/errors' 19 19 import {createFullHandle} from '#/lib/strings/handles' 20 20 import {logger} from '#/logger' 21 - import {isIOS} from '#/platform/detection' 22 21 import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 23 22 import {useSessionApi} from '#/state/session' 24 23 import {useLoggedOutViewControls} from '#/state/shell/logged-out' ··· 32 31 import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket' 33 32 import {Loader} from '#/components/Loader' 34 33 import {Text} from '#/components/Typography' 34 + import {IS_IOS} from '#/env' 35 35 import {FormContainer} from './FormContainer' 36 36 37 37 type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema ··· 201 201 inputRef={identifierRef} 202 202 label={_(msg`Username or email address`)} 203 203 autoCapitalize="none" 204 - autoFocus={!isIOS} 204 + autoFocus={!IS_IOS} 205 205 autoCorrect={false} 206 206 autoComplete="username" 207 207 returnKeyType="next"
+3 -3
src/screens/Messages/ChatList.tsx
··· 13 13 import {type MessagesTabNavigatorParams} from '#/lib/routes/types' 14 14 import {cleanError} from '#/lib/strings/errors' 15 15 import {logger} from '#/logger' 16 - import {isNative} from '#/platform/detection' 17 16 import {listenSoftReset} from '#/state/events' 18 17 import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' 19 18 import {useMessagesEventBus} from '#/state/messages/events' ··· 38 37 import {Link} from '#/components/Link' 39 38 import {ListFooter} from '#/components/Lists' 40 39 import {Text} from '#/components/Typography' 40 + import {IS_NATIVE} from '#/env' 41 41 import {ChatListItem} from './components/ChatListItem' 42 42 import {InboxPreview} from './components/InboxPreview' 43 43 ··· 222 222 223 223 const onSoftReset = useCallback(async () => { 224 224 scrollElRef.current?.scrollToOffset({ 225 - animated: isNative, 225 + animated: IS_NATIVE, 226 226 offset: 0, 227 227 }) 228 228 try { ··· 348 348 hasNextPage={hasNextPage} 349 349 /> 350 350 } 351 - onEndReachedThreshold={isNative ? 1.5 : 0} 351 + onEndReachedThreshold={IS_NATIVE ? 1.5 : 0} 352 352 initialNumToRender={initialNumToRender} 353 353 windowSize={11} 354 354 desktopFixedHeight
+2 -2
src/screens/Messages/Conversation.tsx
··· 21 21 type CommonNavigatorParams, 22 22 type NavigationProp, 23 23 } from '#/lib/routes/types' 24 - import {isWeb} from '#/platform/detection' 25 24 import {type Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow' 26 25 import {useEmail} from '#/state/email-verification' 27 26 import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' ··· 43 42 import {Error} from '#/components/Error' 44 43 import * as Layout from '#/components/Layout' 45 44 import {Loader} from '#/components/Loader' 45 + import {IS_WEB} from '#/env' 46 46 47 47 type Props = NativeStackScreenProps< 48 48 CommonNavigatorParams, ··· 74 74 useCallback(() => { 75 75 setCurrentConvoId(convoId) 76 76 77 - if (isWeb && !gtMobile) { 77 + if (IS_WEB && !gtMobile) { 78 78 setMinimalShellMode(true) 79 79 } else { 80 80 setMinimalShellMode(false)
+2 -2
src/screens/Messages/Inbox.tsx
··· 21 21 } from '#/lib/routes/types' 22 22 import {cleanError} from '#/lib/strings/errors' 23 23 import {logger} from '#/logger' 24 - import {isNative} from '#/platform/detection' 25 24 import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' 26 25 import {useMessagesEventBus} from '#/state/messages/events' 27 26 import {useLeftConvos} from '#/state/queries/messages/leave-conversation' ··· 44 43 import * as Layout from '#/components/Layout' 45 44 import {ListFooter} from '#/components/Lists' 46 45 import {Text} from '#/components/Typography' 46 + import {IS_NATIVE} from '#/env' 47 47 import {RequestListItem} from './components/RequestListItem' 48 48 49 49 type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'> ··· 288 288 hasNextPage={hasNextPage} 289 289 /> 290 290 } 291 - onEndReachedThreshold={isNative ? 1.5 : 0} 291 + onEndReachedThreshold={IS_NATIVE ? 1.5 : 0} 292 292 initialNumToRender={initialNumToRender} 293 293 windowSize={11} 294 294 desktopFixedHeight
+2 -2
src/screens/Messages/Settings.tsx
··· 5 5 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 6 6 7 7 import {type CommonNavigatorParams} from '#/lib/routes/types' 8 - import {isNative} from '#/platform/detection' 9 8 import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' 10 9 import {useProfileQuery} from '#/state/queries/profile' 11 10 import {useSession} from '#/state/session' ··· 16 15 import * as Toggle from '#/components/forms/Toggle' 17 16 import * as Layout from '#/components/Layout' 18 17 import {Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 19 import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' 20 20 21 21 type AllowIncoming = 'all' | 'none' | 'following' ··· 118 118 you choose. 119 119 </Trans> 120 120 </Admonition> 121 - {isNative && ( 121 + {IS_NATIVE && ( 122 122 <> 123 123 <Divider style={a.my_md} /> 124 124 <Text style={[a.text_lg, a.font_semi_bold]}>
+4 -4
src/screens/Messages/components/ChatListItem.tsx
··· 20 20 toBskyAppUrl, 21 21 toShortUrl, 22 22 } from '#/lib/strings/url-helpers' 23 - import {isNative} from '#/platform/detection' 24 23 import {useProfileShadow} from '#/state/cache/profile-shadow' 25 24 import {useModerationOpts} from '#/state/preferences/moderation-opts' 26 25 import { ··· 46 45 import {Text} from '#/components/Typography' 47 46 import {useSimpleVerificationState} from '#/components/verification' 48 47 import {VerificationCheck} from '#/components/verification/VerificationCheck' 48 + import {IS_NATIVE} from '#/env' 49 49 import type * as bsky from '#/types/bsky' 50 50 51 51 export const ChatListItemPortal = createPortalGroup() ··· 366 366 ) 367 367 } 368 368 accessibilityActions={ 369 - isNative 369 + IS_NATIVE 370 370 ? [ 371 371 { 372 372 name: 'magicTap', ··· 380 380 : undefined 381 381 } 382 382 onPress={onPress} 383 - onLongPress={isNative ? onLongPress : undefined} 383 + onLongPress={IS_NATIVE ? onLongPress : undefined} 384 384 onAccessibilityAction={onLongPress}> 385 385 {({hovered, pressed, focused}) => ( 386 386 <View ··· 519 519 control={menuControl} 520 520 currentScreen="list" 521 521 showMarkAsRead={convo.unreadCount > 0} 522 - hideTrigger={isNative} 522 + hideTrigger={IS_NATIVE} 523 523 blockInfo={blockInfo} 524 524 style={[ 525 525 a.absolute,
+5 -5
src/screens/Messages/components/MessageInput.tsx
··· 18 18 19 19 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' 20 20 import {useHaptics} from '#/lib/haptics' 21 - import {isIOS, isWeb} from '#/platform/detection' 22 21 import {useEmail} from '#/state/email-verification' 23 22 import { 24 23 useMessageDraft, ··· 29 28 import {android, atoms as a, useTheme} from '#/alf' 30 29 import {useSharedInputStyles} from '#/components/forms/TextField' 31 30 import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' 31 + import {IS_IOS, IS_WEB} from '#/env' 32 32 import {useExtractEmbedFromFacets} from './MessageInputEmbed' 33 33 34 34 const AnimatedTextInput = Animated.createAnimatedComponent(TextInput) ··· 84 84 playHaptic() 85 85 setEmbed(undefined) 86 86 setMessage('') 87 - if (isIOS) { 87 + if (IS_IOS) { 88 88 setShouldEnforceClear(true) 89 89 } 90 - if (isWeb) { 90 + if (IS_WEB) { 91 91 // Pressing the send button causes the text input to lose focus, so we need to 92 92 // re-focus it after sending 93 93 setTimeout(() => { ··· 160 160 // next change and double make sure the input is cleared. It should *always* send an onChange event after 161 161 // clearing via setMessage('') that happens in onSubmit() 162 162 // -sfn 163 - if (isIOS && shouldEnforceClear) { 163 + if (IS_IOS && shouldEnforceClear) { 164 164 setShouldEnforceClear(false) 165 165 setMessage('') 166 166 return ··· 175 175 a.px_sm, 176 176 t.atoms.text, 177 177 android({paddingTop: 0}), 178 - {paddingBottom: isIOS ? 5 : 0}, 178 + {paddingBottom: IS_IOS ? 5 : 0}, 179 179 animatedStyle, 180 180 ]} 181 181 keyboardAppearance={t.scheme}
+7 -7
src/screens/Messages/components/MessagesList.tsx
··· 24 24 isBskyPostUrl, 25 25 } from '#/lib/strings/url-helpers' 26 26 import {logger} from '#/logger' 27 - import {isNative} from '#/platform/detection' 28 - import {isWeb} from '#/platform/detection' 29 27 import { 30 28 type ActiveConvoStates, 31 29 isConvoActive, ··· 52 50 import {NewMessagesPill} from '#/components/dms/NewMessagesPill' 53 51 import {Loader} from '#/components/Loader' 54 52 import {Text} from '#/components/Typography' 53 + import {IS_NATIVE} from '#/env' 54 + import {IS_WEB} from '#/env' 55 55 import {ChatStatusInfo} from './ChatStatusInfo' 56 56 import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed' 57 57 ··· 159 159 (_: number, height: number) => { 160 160 // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the 161 161 // previous off whenever we add new content to the previous offset whenever we add new content to the list. 162 - if (isWeb && isAtTop.get() && hasScrolled) { 162 + if (IS_WEB && isAtTop.get() && hasScrolled) { 163 163 flatListRef.current?.scrollToOffset({ 164 164 offset: height - prevContentHeight.current, 165 165 animated: false, ··· 390 390 (e: LayoutChangeEvent) => { 391 391 layoutHeight.set(e.nativeEvent.layout.height) 392 392 393 - if (isWeb || !keyboardIsOpening.get()) { 393 + if (IS_WEB || !keyboardIsOpening.get()) { 394 394 flatListRef.current?.scrollToEnd({ 395 395 animated: !layoutScrollWithoutAnimation.get(), 396 396 }) ··· 429 429 disableVirtualization={true} 430 430 style={animatedListStyle} 431 431 // The extra two items account for the header and the footer components 432 - initialNumToRender={isNative ? 32 : 62} 433 - maxToRenderPerBatch={isWeb ? 32 : 62} 432 + initialNumToRender={IS_NATIVE ? 32 : 62} 433 + maxToRenderPerBatch={IS_WEB ? 32 : 62} 434 434 keyboardDismissMode="on-drag" 435 435 keyboardShouldPersistTaps="handled" 436 436 maintainVisibleContentPosition={{ ··· 468 468 )} 469 469 </Animated.View> 470 470 471 - {isWeb && ( 471 + {IS_WEB && ( 472 472 <EmojiPicker 473 473 pinToTop 474 474 state={emojiPickerState}
+2 -2
src/screens/Moderation/index.tsx
··· 11 11 type NativeStackScreenProps, 12 12 } from '#/lib/routes/types' 13 13 import {logger} from '#/logger' 14 - import {isIOS} from '#/platform/detection' 15 14 import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 16 15 import { 17 16 useMyLabelersQuery, ··· 45 44 import {GlobalLabelPreference} from '#/components/moderation/LabelPreference' 46 45 import {Text} from '#/components/Typography' 47 46 import {useAgeAssurance} from '#/ageAssurance' 47 + import {IS_IOS} from '#/env' 48 48 49 49 function ErrorState({error}: {error: string}) { 50 50 const t = useTheme() ··· 182 182 (optimisticAdultContent && optimisticAdultContent.enabled) || 183 183 (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) 184 184 ) 185 - const adultContentUIDisabledOnIOS = isIOS && !adultContentEnabled 185 + const adultContentUIDisabledOnIOS = IS_IOS && !adultContentEnabled 186 186 let adultContentUIDisabled = adultContentUIDisabledOnIOS 187 187 188 188 if (aa.flags.adultContentDisabled) {
+5 -5
src/screens/Onboarding/Layout.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {isAndroid, isWeb} from '#/platform/detection' 8 7 import {useOnboardingDispatch} from '#/state/shell' 9 8 import {useOnboardingInternalState} from '#/screens/Onboarding/state' 10 9 import { ··· 21 20 import {HEADER_SLOT_SIZE} from '#/components/Layout' 22 21 import {createPortalGroup} from '#/components/Portal' 23 22 import {P, Text} from '#/components/Typography' 23 + import {IS_ANDROID, IS_WEB} from '#/env' 24 24 import {IS_INTERNAL} from '#/env' 25 25 26 26 const ONBOARDING_COL_WIDTH = 420 ··· 58 58 aria-label={dialogLabel} 59 59 accessibilityLabel={dialogLabel} 60 60 accessibilityHint={_(msg`Customizes your Bluesky experience`)} 61 - style={[isWeb ? a.fixed : a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}> 61 + style={[IS_WEB ? a.fixed : a.absolute, a.inset_0, a.flex_1, t.atoms.bg]}> 62 62 {!gtMobile ? ( 63 63 <View 64 64 style={[ ··· 151 151 paddingTop: gtMobile ? 40 : headerHeight, 152 152 paddingBottom: footerHeight, 153 153 }} 154 - showsVerticalScrollIndicator={!isAndroid} 154 + showsVerticalScrollIndicator={!IS_ANDROID} 155 155 scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}} 156 156 // @ts-expect-error web only --prf 157 157 dataSet={{'stable-gutters': 1}} ··· 167 167 <View 168 168 onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)} 169 169 style={[ 170 - isWeb ? a.fixed : a.absolute, 170 + IS_WEB ? a.fixed : a.absolute, 171 171 {bottom: 0, left: 0, right: 0}, 172 172 t.atoms.bg, 173 173 t.atoms.border_contrast_low, 174 174 a.border_t, 175 175 a.align_center, 176 176 gtMobile ? a.px_5xl : a.px_xl, 177 - isWeb 177 + IS_WEB 178 178 ? a.py_2xl 179 179 : { 180 180 paddingTop: tokens.space.md,
+2 -2
src/screens/Onboarding/StepFinished/index.tsx
··· 22 22 import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' 23 23 import {logEvent, useGate} from '#/lib/statsig/statsig' 24 24 import {logger} from '#/logger' 25 - import {isWeb} from '#/platform/detection' 26 25 import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' 27 26 import {getAllListMembers} from '#/state/queries/list-members' 28 27 import {preferencesQueryKey} from '#/state/queries/preferences' ··· 47 46 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 48 47 import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow' 49 48 import {Loader} from '#/components/Loader' 49 + import {IS_WEB} from '#/env' 50 50 import * as bsky from '#/types/bsky' 51 51 import {ValuePropositionPager} from './ValuePropositionPager' 52 52 ··· 305 305 306 306 <OnboardingControls.Portal> 307 307 <View style={gtMobile && [a.gap_md, a.flex_row]}> 308 - {gtMobile && (isWeb ? subStep !== 2 : true) && ( 308 + {gtMobile && (IS_WEB ? subStep !== 2 : true) && ( 309 309 <Button 310 310 disabled={saving} 311 311 color="secondary"
+3 -3
src/screens/Onboarding/StepProfile/index.tsx
··· 17 17 import {logEvent, useGate} from '#/lib/statsig/statsig' 18 18 import {isCancelledError} from '#/lib/strings/errors' 19 19 import {logger} from '#/logger' 20 - import {isNative, isWeb} from '#/platform/detection' 21 20 import { 22 21 OnboardingControls, 23 22 OnboardingDescriptionText, ··· 38 37 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' 39 38 import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo' 40 39 import {Text} from '#/components/Typography' 40 + import {IS_NATIVE, IS_WEB} from '#/env' 41 41 import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types' 42 42 43 43 export interface Avatar { ··· 183 183 let image = items[0] 184 184 if (!image) return 185 185 186 - if (!isWeb) { 186 + if (!IS_WEB) { 187 187 try { 188 188 image = await openCropper({ 189 189 imageUri: image.path, ··· 200 200 201 201 // If we are on mobile, prefetching the image will load the image into memory before we try and display it, 202 202 // stopping any brief flickers. 203 - if (isNative) { 203 + if (IS_NATIVE) { 204 204 await ExpoImage.prefetch(image.path) 205 205 } 206 206
+5 -5
src/screens/Onboarding/StepSuggestedAccounts/index.tsx
··· 10 10 import {popularInterests, useInterestsDisplayNames} from '#/lib/interests' 11 11 import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' 12 12 import {logger} from '#/logger' 13 - import {isWeb} from '#/platform/detection' 14 13 import {updateProfileShadow} from '#/state/cache/profile-shadow' 15 14 import {useLanguagePrefs} from '#/state/preferences' 16 15 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 31 30 import {Loader} from '#/components/Loader' 32 31 import * as ProfileCard from '#/components/ProfileCard' 33 32 import * as toast from '#/components/Toast' 33 + import {IS_WEB} from '#/env' 34 34 import type * as bsky from '#/types/bsky' 35 35 import {bulkWriteFollows} from '../util' 36 36 ··· 161 161 style={[ 162 162 a.overflow_hidden, 163 163 a.mt_sm, 164 - isWeb 164 + IS_WEB 165 165 ? [a.max_w_full, web({minHeight: '100vh'})] 166 166 : {marginHorizontal: tokens.space.xl * -1}, 167 167 a.flex_1, ··· 213 213 a.mt_md, 214 214 a.border_y, 215 215 t.atoms.border_contrast_low, 216 - isWeb && [a.border_x, a.rounded_sm, a.overflow_hidden], 216 + IS_WEB && [a.border_x, a.rounded_sm, a.overflow_hidden], 217 217 ]}> 218 218 {suggestedUsers?.actors.map((user, index) => ( 219 219 <SuggestedProfileCard ··· 324 324 ...interestsDisplayNames, 325 325 } 326 326 } 327 - gutterWidth={isWeb ? 0 : tokens.space.xl} 327 + gutterWidth={IS_WEB ? 0 : tokens.space.xl} 328 328 /> 329 329 ) 330 330 } ··· 350 350 const node = cardRef.current 351 351 if (!node || hasTrackedRef.current) return 352 352 353 - if (isWeb && typeof IntersectionObserver !== 'undefined') { 353 + if (IS_WEB && typeof IntersectionObserver !== 'undefined') { 354 354 const observer = new IntersectionObserver( 355 355 entries => { 356 356 if (entries[0]?.isIntersecting && !hasTrackedRef.current) {
+2 -2
src/screens/Onboarding/index.tsx
··· 4 4 5 5 import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' 6 6 import {useGate} from '#/lib/statsig/statsig' 7 - import {isNative} from '#/platform/detection' 8 7 import {useLanguagePrefs} from '#/state/preferences' 9 8 import { 10 9 Layout, ··· 24 23 import {useFindContactsFlowState} from '#/components/contacts/state' 25 24 import {Portal} from '#/components/Portal' 26 25 import {ScreenTransition} from '#/components/ScreenTransition' 26 + import {IS_NATIVE} from '#/env' 27 27 import {ENV} from '#/env' 28 28 import {StepFindContacts} from './StepFindContacts' 29 29 import {StepFindContactsIntro} from './StepFindContactsIntro' ··· 50 50 useIsFindContactsFeatureEnabledBasedOnGeolocation() 51 51 const showFindContacts = 52 52 ENV !== 'e2e' && 53 - isNative && 53 + IS_NATIVE && 54 54 findContactsEnabled && 55 55 !gate('disable_onboarding_find_contacts') 56 56
+2 -2
src/screens/PostThread/components/GrowthHack.tsx
··· 3 3 import {PrivacySensitive} from 'expo-privacy-sensitive' 4 4 5 5 import {useAppState} from '#/lib/hooks/useAppState' 6 - import {isIOS} from '#/platform/detection' 7 6 import {atoms as a, useTheme} from '#/alf' 8 7 import {sizes as iconSizes} from '#/components/icons/common' 9 8 import {Mark as Logo} from '#/components/icons/Logo' 9 + import {IS_IOS} from '#/env' 10 10 11 11 const ICON_SIZE = 'xl' as const 12 12 ··· 25 25 26 26 const appState = useAppState() 27 27 28 - if (!isIOS || appState !== 'active') return children 28 + if (!IS_IOS || appState !== 'active') return children 29 29 30 30 return ( 31 31 <View
+2 -2
src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx
··· 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 7 import {logger} from '#/logger' 8 - import {isIOS} from '#/platform/detection' 9 8 import {useProfileShadow} from '#/state/cache/profile-shadow' 10 9 import { 11 10 useProfileFollowMutationQueue, ··· 17 16 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 18 17 import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 19 18 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 19 + import {IS_IOS} from '#/env' 20 20 import {GrowthHack} from './GrowthHack' 21 21 22 22 export function ThreadItemAnchorFollowButton({did}: {did: string}) { 23 - if (isIOS) { 23 + if (IS_IOS) { 24 24 return ( 25 25 <GrowthHack> 26 26 <ThreadItemAnchorFollowButtonInner did={did} />
+2 -2
src/screens/Profile/Header/GrowableAvatar.tsx
··· 7 7 } from 'react-native-reanimated' 8 8 import type React from 'react' 9 9 10 - import {isIOS} from '#/platform/detection' 11 10 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 11 + import {IS_IOS} from '#/env' 12 12 13 13 export function GrowableAvatar({ 14 14 children, ··· 20 20 const pagerContext = usePagerHeaderContext() 21 21 22 22 // pagerContext should only be present on iOS, but better safe than sorry 23 - if (!pagerContext || !isIOS) { 23 + if (!pagerContext || !IS_IOS) { 24 24 return <View style={style}>{children}</View> 25 25 } 26 26
+3 -3
src/screens/Profile/Header/GrowableBanner.tsx
··· 15 15 import {useIsFetching} from '@tanstack/react-query' 16 16 import type React from 'react' 17 17 18 - import {isIOS} from '#/platform/detection' 19 18 import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs' 20 19 import {RQKEY_ROOT as FEED_RQKEY_ROOT} from '#/state/queries/post-feed' 21 20 import {RQKEY_ROOT as FEEDGEN_RQKEY_ROOT} from '#/state/queries/profile-feedgens' 22 21 import {RQKEY_ROOT as LIST_RQKEY_ROOT} from '#/state/queries/profile-lists' 23 22 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 24 23 import {atoms as a} from '#/alf' 24 + import {IS_IOS} from '#/env' 25 25 26 26 const AnimatedBlurView = Animated.createAnimatedComponent(BlurView) 27 27 ··· 39 39 const pagerContext = usePagerHeaderContext() 40 40 41 41 // plain non-growable mode for Android/Web 42 - if (!pagerContext || !isIOS) { 42 + if (!pagerContext || !IS_IOS) { 43 43 return ( 44 44 <Pressable 45 45 onPress={onPress} ··· 164 164 style={[ 165 165 a.absolute, 166 166 a.inset_0, 167 - {top: topInset - (isIOS ? 15 : 0)}, 167 + {top: topInset - (IS_IOS ? 15 : 0)}, 168 168 a.justify_center, 169 169 a.align_center, 170 170 ]}>
+3 -3
src/screens/Profile/Header/Handle.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles' 7 - import {isIOS, isNative} from '#/platform/detection' 8 7 import {type Shadow} from '#/state/cache/types' 9 8 import {atoms as a, useTheme, web} from '#/alf' 10 9 import {NewskieDialog} from '#/components/NewskieDialog' 11 10 import {Text} from '#/components/Typography' 11 + import {IS_IOS, IS_NATIVE} from '#/env' 12 12 13 13 export function ProfileHeaderHandle({ 14 14 profile, ··· 24 24 return ( 25 25 <View 26 26 style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]} 27 - pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}> 27 + pointerEvents={disableTaps ? 'none' : IS_IOS ? 'auto' : 'box-none'}> 28 28 <NewskieDialog profile={profile} disabled={disableTaps} /> 29 29 {profile.viewer?.followedBy && !blockHide ? ( 30 30 <View style={[t.atoms.bg_contrast_50, a.rounded_xs, a.px_sm, a.py_xs]}> ··· 59 59 profile.handle, 60 60 '@', 61 61 // forceLTR handled by CSS above on web 62 - isNative, 62 + IS_NATIVE, 63 63 )} 64 64 </Text> 65 65 </View>
+3 -3
src/screens/Profile/Header/ProfileHeaderLabeler.tsx
··· 15 15 import {useHaptics} from '#/lib/haptics' 16 16 import {isAppLabeler} from '#/lib/moderation' 17 17 import {logger} from '#/logger' 18 - import {isIOS} from '#/platform/detection' 19 18 import {useProfileShadow} from '#/state/cache/profile-shadow' 20 19 import {type Shadow} from '#/state/cache/types' 21 20 import {useLabelerSubscriptionMutation} from '#/state/queries/labeler' ··· 35 34 import {RichText} from '#/components/RichText' 36 35 import * as Toast from '#/components/Toast' 37 36 import {Text} from '#/components/Typography' 37 + import {IS_IOS} from '#/env' 38 38 import {ProfileHeaderDisplayName} from './DisplayName' 39 39 import {EditProfileDialog} from './EditProfileDialog' 40 40 import {ProfileHeaderHandle} from './Handle' ··· 111 111 isPlaceholderProfile={isPlaceholderProfile}> 112 112 <View 113 113 style={[a.px_lg, a.pt_md, a.pb_sm]} 114 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 114 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 115 115 <View 116 116 style={[a.flex_row, a.justify_end, a.align_center, a.gap_xs, a.pb_lg]} 117 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 117 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 118 118 <HeaderLabelerButtons profile={profile} /> 119 119 </View> 120 120 <View style={[a.flex_col, a.gap_2xs, a.pt_2xs, a.pb_md]}>
+3 -3
src/screens/Profile/Header/ProfileHeaderStandard.tsx
··· 15 15 import {sanitizeDisplayName} from '#/lib/strings/display-names' 16 16 import {sanitizeHandle} from '#/lib/strings/handles' 17 17 import {logger} from '#/logger' 18 - import {isIOS} from '#/platform/detection' 19 18 import {type Shadow, useProfileShadow} from '#/state/cache/profile-shadow' 20 19 import { 21 20 useProfileBlockMutationQueue, ··· 39 38 import * as Toast from '#/components/Toast' 40 39 import {Text} from '#/components/Typography' 41 40 import {VerificationCheckButton} from '#/components/verification/VerificationCheckButton' 41 + import {IS_IOS} from '#/env' 42 42 import {EditProfileDialog} from './EditProfileDialog' 43 43 import {ProfileHeaderHandle} from './Handle' 44 44 import {ProfileHeaderMetrics} from './Metrics' ··· 103 103 isPlaceholderProfile={isPlaceholderProfile}> 104 104 <View 105 105 style={[a.px_lg, a.pt_md, a.pb_sm, a.overflow_hidden]} 106 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 106 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 107 107 <View 108 108 style={[ 109 109 {paddingLeft: 90}, ··· 114 114 a.pb_sm, 115 115 a.flex_wrap, 116 116 ]} 117 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 117 + pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 118 118 <HeaderStandardButtons 119 119 profile={profile} 120 120 moderation={moderation}
+5 -5
src/screens/Profile/Header/Shell.tsx
··· 19 19 import {useHaptics} from '#/lib/haptics' 20 20 import {type NavigationProp} from '#/lib/routes/types' 21 21 import {logger} from '#/logger' 22 - import {isIOS} from '#/platform/detection' 23 22 import {type Shadow} from '#/state/cache/types' 24 23 import {useLightboxControls} from '#/state/lightbox' 25 24 import {useSession} from '#/state/session' ··· 35 34 import {LiveStatusDialog} from '#/components/live/LiveStatusDialog' 36 35 import {LabelsOnMe} from '#/components/moderation/LabelsOnMe' 37 36 import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts' 37 + import {IS_IOS} from '#/env' 38 38 import {GrowableAvatar} from './GrowableAvatar' 39 39 import {GrowableBanner} from './GrowableBanner' 40 40 import {StatusBarShadow} from './StatusBarShadow' ··· 167 167 }, [profile.banner, moderation, _openLightbox, bannerRef]) 168 168 169 169 return ( 170 - <View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}> 170 + <View style={t.atoms.bg} pointerEvents={IS_IOS ? 'auto' : 'box-none'}> 171 171 <View 172 - pointerEvents={isIOS ? 'auto' : 'box-none'} 172 + pointerEvents={IS_IOS ? 'auto' : 'box-none'} 173 173 style={[a.relative, {height: 150}]}> 174 174 <StatusBarShadow /> 175 175 <GrowableBanner ··· 244 244 a.px_lg, 245 245 a.pt_xs, 246 246 a.pb_sm, 247 - isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 247 + IS_IOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 248 248 ]} 249 249 /> 250 250 ) : ( ··· 254 254 a.px_lg, 255 255 a.pt_xs, 256 256 a.pb_sm, 257 - isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 257 + IS_IOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 258 258 ]} 259 259 /> 260 260 ))}
+2 -2
src/screens/Profile/Header/StatusBarShadow.tsx
··· 5 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 6 6 import {LinearGradient} from 'expo-linear-gradient' 7 7 8 - import {isIOS} from '#/platform/detection' 9 8 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext' 10 9 import {atoms as a} from '#/alf' 10 + import {IS_IOS} from '#/env' 11 11 12 12 const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient) 13 13 ··· 15 15 const {top: topInset} = useSafeAreaInsets() 16 16 const pagerContext = usePagerHeaderContext() 17 17 18 - if (isIOS && pagerContext) { 18 + if (IS_IOS && pagerContext) { 19 19 const {scrollY} = pagerContext 20 20 return <StatusBarShadowInnner scrollY={scrollY} /> 21 21 }
+2 -2
src/screens/Profile/Header/SuggestedFollows.tsx
··· 2 2 import {type AppBskyActorDefs} from '@atproto/api' 3 3 4 4 import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation' 5 - import {isAndroid} from '#/platform/detection' 6 5 import {useModerationOpts} from '#/state/preferences/moderation-opts' 7 6 import { 8 7 useSuggestedFollowsByActorQuery, ··· 10 9 } from '#/state/queries/suggested-follows' 11 10 import {useBreakpoints} from '#/alf' 12 11 import {ProfileGrid} from '#/components/FeedInterstitials' 12 + import {IS_ANDROID} from '#/env' 13 13 14 14 const DISMISS_ANIMATION_DURATION = 200 15 15 ··· 210 210 * This issue stems from Android not allowing dragging on clickable elements in the profile header. 211 211 * Blocking the ability to scroll on Android is too much of a trade-off for now. 212 212 **/ 213 - if (isAndroid) return null 213 + if (IS_ANDROID) return null 214 214 215 215 return ( 216 216 <AccordionAnimation isExpanded={isExpanded}>
+2 -2
src/screens/Profile/Header/index.tsx
··· 17 17 import {useIsFocused} from '@react-navigation/native' 18 18 19 19 import {sanitizeHandle} from '#/lib/strings/handles' 20 - import {isNative} from '#/platform/detection' 21 20 import {useProfileShadow} from '#/state/cache/profile-shadow' 22 21 import {useModerationOpts} from '#/state/preferences/moderation-opts' 23 22 import {useSetLightStatusBar} from '#/state/shell/light-status-bar' ··· 26 25 import {atoms as a, useTheme} from '#/alf' 27 26 import {Header} from '#/components/Layout' 28 27 import * as ProfileCard from '#/components/ProfileCard' 28 + import {IS_NATIVE} from '#/env' 29 29 import { 30 30 HeaderLabelerButtons, 31 31 ProfileHeaderLabeler, ··· 83 83 84 84 return ( 85 85 <> 86 - {isNative && ( 86 + {IS_NATIVE && ( 87 87 <MinimalHeader 88 88 onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)} 89 89 profile={props.profile}
+3 -3
src/screens/Profile/ProfileFeed/index.tsx
··· 17 17 import {type NavigationProp} from '#/lib/routes/types' 18 18 import {makeRecordUri} from '#/lib/strings/url-helpers' 19 19 import {s} from '#/lib/styles' 20 - import {isNative} from '#/platform/detection' 21 20 import {listenSoftReset} from '#/state/events' 22 21 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 23 22 import { ··· 47 46 } from '#/screens/Profile/components/ProfileFeedHeader' 48 47 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 49 48 import * as Layout from '#/components/Layout' 49 + import {IS_NATIVE} from '#/env' 50 50 51 51 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'> 52 52 export function ProfileFeedScreen(props: Props) { ··· 175 175 176 176 const onScrollToTop = useCallback(() => { 177 177 scrollElRef.current?.scrollToOffset({ 178 - animated: isNative, 178 + animated: IS_NATIVE, 179 179 offset: 0, // -headerHeight, 180 180 }) 181 181 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) ··· 204 204 const feedIsVideoMode = 205 205 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 206 206 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 207 - return isNative && _isVideoFeed 207 + return IS_NATIVE && _isVideoFeed 208 208 }, [feedInfo]) 209 209 210 210 return (
+4 -4
src/screens/Profile/Sections/Feed.tsx
··· 5 5 import {useQueryClient} from '@tanstack/react-query' 6 6 7 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 - import {isIOS, isNative} from '#/platform/detection' 9 8 import { 10 9 type FeedDescriptor, 11 10 RQKEY as FEED_RQKEY, ··· 21 20 import {atoms as a, ios, useTheme} from '#/alf' 22 21 import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' 23 22 import {Text} from '#/components/Typography' 23 + import {IS_IOS, IS_NATIVE} from '#/env' 24 24 import {type SectionRef} from './types' 25 25 26 26 interface FeedSectionProps { ··· 53 53 const [hasNew, setHasNew] = useState(false) 54 54 const [isScrolledDown, setIsScrolledDown] = useState(false) 55 55 const shouldUseAdjustedNumToRender = feed.endsWith('posts_and_author_threads') 56 - const isVideoFeed = isNative && feed.endsWith('posts_with_video') 56 + const isVideoFeed = IS_NATIVE && feed.endsWith('posts_with_video') 57 57 const adjustedInitialNumToRender = useInitialNumToRender({ 58 58 screenHeightOffset: headerHeight, 59 59 }) 60 60 const onScrollToTop = useCallback(() => { 61 61 scrollElRef.current?.scrollToOffset({ 62 - animated: isNative, 62 + animated: IS_NATIVE, 63 63 offset: -headerHeight, 64 64 }) 65 65 truncateAndInvalidate(queryClient, FEED_RQKEY(feed)) ··· 85 85 }, [_, emptyStateButton, emptyStateIcon, emptyStateMessage]) 86 86 87 87 useEffect(() => { 88 - if (isIOS && isFocused && scrollElRef.current) { 88 + if (IS_IOS && isFocused && scrollElRef.current) { 89 89 const nativeTag = findNodeHandle(scrollElRef.current) 90 90 setScrollViewTag(nativeTag) 91 91 }
+3 -3
src/screens/Profile/Sections/Labels.tsx
··· 10 10 import {useLingui} from '@lingui/react' 11 11 12 12 import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation' 13 - import {isIOS, isNative} from '#/platform/detection' 14 13 import {List, type ListRef} from '#/view/com/util/List' 15 14 import {atoms as a, ios, tokens, useTheme} from '#/alf' 16 15 import {Divider} from '#/components/Divider' ··· 19 18 import {Loader} from '#/components/Loader' 20 19 import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' 21 20 import {Text} from '#/components/Typography' 21 + import {IS_IOS, IS_NATIVE} from '#/env' 22 22 import {ErrorState} from '../ErrorState' 23 23 import {type SectionRef} from './types' 24 24 ··· 49 49 50 50 const onScrollToTop = useCallback(() => { 51 51 scrollElRef.current?.scrollToOffset({ 52 - animated: isNative, 52 + animated: IS_NATIVE, 53 53 offset: -headerHeight, 54 54 }) 55 55 }, [scrollElRef, headerHeight]) ··· 59 59 })) 60 60 61 61 useEffect(() => { 62 - if (isIOS && isFocused && scrollElRef.current) { 62 + if (IS_IOS && isFocused && scrollElRef.current) { 63 63 const nativeTag = findNodeHandle(scrollElRef.current) 64 64 setScrollViewTag(nativeTag) 65 65 }
+3 -3
src/screens/Profile/components/ProfileFeedHeader.tsx
··· 11 11 import {sanitizeHandle} from '#/lib/strings/handles' 12 12 import {toShareUrl} from '#/lib/strings/url-helpers' 13 13 import {logger} from '#/logger' 14 - import {isWeb} from '#/platform/detection' 15 14 import {type FeedSourceFeedInfo} from '#/state/queries/feed' 16 15 import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' 17 16 import { ··· 52 51 } from '#/components/moderation/ReportDialog' 53 52 import {RichText} from '#/components/RichText' 54 53 import {Text} from '#/components/Typography' 54 + import {IS_WEB} from '#/env' 55 55 56 56 export function ProfileFeedHeaderSkeleton() { 57 57 const t = useTheme() ··· 192 192 style={[ 193 193 a.justify_start, 194 194 { 195 - paddingVertical: isWeb ? 2 : 4, 195 + paddingVertical: IS_WEB ? 2 : 4, 196 196 paddingRight: 8, 197 197 }, 198 198 ]} ··· 211 211 t.atoms.bg_contrast_25, 212 212 { 213 213 opacity: 0, 214 - left: isWeb ? -2 : -4, 214 + left: IS_WEB ? -2 : -4, 215 215 right: 0, 216 216 }, 217 217 pressed && {
+2 -2
src/screens/ProfileList/AboutSection.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {isNative} from '#/platform/detection' 8 7 import {useSession} from '#/state/session' 9 8 import {ListMembers} from '#/view/com/lists/ListMembers' 10 9 import {EmptyState} from '#/view/com/util/EmptyState' ··· 14 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 16 15 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 16 + import {IS_NATIVE} from '#/env' 17 17 18 18 interface SectionRef { 19 19 scrollToTop: () => void ··· 42 42 43 43 const onScrollToTop = useCallback(() => { 44 44 scrollElRef.current?.scrollToOffset({ 45 - animated: isNative, 45 + animated: IS_NATIVE, 46 46 offset: -headerHeight, 47 47 }) 48 48 }, [scrollElRef, headerHeight])
+2 -2
src/screens/ProfileList/FeedSection.tsx
··· 5 5 import {useIsFocused} from '@react-navigation/native' 6 6 import {useQueryClient} from '@tanstack/react-query' 7 7 8 - import {isNative} from '#/platform/detection' 9 8 import {listenSoftReset} from '#/state/events' 10 9 import { 11 10 type FeedDescriptor, ··· 19 18 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 20 19 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 21 20 import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 21 + import {IS_NATIVE} from '#/env' 22 22 23 23 interface SectionRef { 24 24 scrollToTop: () => void ··· 51 51 52 52 const onScrollToTop = useCallback(() => { 53 53 scrollElRef.current?.scrollToOffset({ 54 - animated: isNative, 54 + animated: IS_NATIVE, 55 55 offset: -headerHeight, 56 56 }) 57 57 queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
+4 -4
src/screens/ProfileList/components/MoreOptionsMenu.tsx
··· 7 7 import {shareUrl} from '#/lib/sharing' 8 8 import {toShareUrl} from '#/lib/strings/url-helpers' 9 9 import {logger} from '#/logger' 10 - import {isWeb} from '#/platform/detection' 11 10 import { 12 11 useListBlockMutation, 13 12 useListDeleteMutation, ··· 34 33 } from '#/components/moderation/ReportDialog' 35 34 import * as Prompt from '#/components/Prompt' 36 35 import * as Toast from '#/components/Toast' 36 + import {IS_WEB} from '#/env' 37 37 38 38 export function MoreOptionsMenu({ 39 39 list, ··· 162 162 <Menu.Outer> 163 163 <Menu.Group> 164 164 <Menu.Item 165 - label={isWeb ? _(msg`Copy link to list`) : _(msg`Share via...`)} 165 + label={IS_WEB ? _(msg`Copy link to list`) : _(msg`Share via...`)} 166 166 onPress={onPressShare}> 167 167 <Menu.ItemText> 168 - {isWeb ? ( 168 + {IS_WEB ? ( 169 169 <Trans>Copy link to list</Trans> 170 170 ) : ( 171 171 <Trans>Share via...</Trans> ··· 173 173 </Menu.ItemText> 174 174 <Menu.ItemIcon 175 175 position="right" 176 - icon={isWeb ? ChainLink : ShareIcon} 176 + icon={IS_WEB ? ChainLink : ShareIcon} 177 177 /> 178 178 </Menu.Item> 179 179 {savedFeedConfig && (
+11 -11
src/screens/Search/Shell.tsx
··· 22 22 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 23 23 import {MagnifyingGlassIcon} from '#/lib/icons' 24 24 import {type NavigationProp} from '#/lib/routes/types' 25 - import {isWeb} from '#/platform/detection' 26 25 import {listenSoftReset} from '#/state/events' 27 26 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 28 27 import { ··· 41 40 import {SearchInput} from '#/components/forms/SearchInput' 42 41 import * as Layout from '#/components/Layout' 43 42 import {Text} from '#/components/Typography' 43 + import {IS_WEB} from '#/env' 44 44 import {account, useStorage} from '#/storage' 45 45 import type * as bsky from '#/types/bsky' 46 46 import {AutocompleteResults} from './components/AutocompleteResults' ··· 141 141 const [headerHeight, setHeaderHeight] = useState(0) 142 142 const headerRef = useRef(null) 143 143 useLayoutEffect(() => { 144 - if (isWeb) { 144 + if (IS_WEB) { 145 145 if (!headerRef.current) return 146 146 const measurement = (headerRef.current as Element).getBoundingClientRect() 147 147 setHeaderHeight(measurement.height) ··· 150 150 151 151 useFocusEffect( 152 152 useNonReactiveCallback(() => { 153 - if (isWeb) { 153 + if (IS_WEB) { 154 154 setSearchText(queryParam) 155 155 } 156 156 }), ··· 173 173 setShowAutocomplete(false) 174 174 updateSearchHistory(item) 175 175 176 - if (isWeb) { 176 + if (IS_WEB) { 177 177 // @ts-expect-error route is not typesafe 178 178 navigation.push(route.name, {...route.params, q: item}) 179 179 } else { ··· 188 188 scrollToTopWeb() 189 189 textInput.current?.blur() 190 190 setShowAutocomplete(false) 191 - if (isWeb) { 191 + if (IS_WEB) { 192 192 // Empty params resets the URL to be /search rather than /search?q= 193 193 // Also clear the tab parameter 194 194 const { ··· 211 211 }, [navigateToItem, searchText]) 212 212 213 213 const onAutocompleteResultPress = useCallback(() => { 214 - if (isWeb) { 214 + if (IS_WEB) { 215 215 setShowAutocomplete(false) 216 216 } else { 217 217 textInput.current?.blur() ··· 238 238 ) 239 239 240 240 const onSoftReset = useCallback(() => { 241 - if (isWeb) { 241 + if (IS_WEB) { 242 242 // Empty params resets the URL to be /search rather than /search?q= 243 243 // Also clear the tab parameter when soft resetting 244 244 const { ··· 265 265 ) 266 266 267 267 const onSearchInputFocus = useCallback(() => { 268 - if (isWeb) { 268 + if (IS_WEB) { 269 269 // Prevent a jump on iPad by ensuring that 270 270 // the initial focused render has no result list. 271 271 requestAnimationFrame(() => { ··· 282 282 283 283 // If a tab is specified, set the tab parameter 284 284 if (tab) { 285 - if (isWeb) { 285 + if (IS_WEB) { 286 286 navigation.setParams({...route.params, tab}) 287 287 } else { 288 288 navigation.setParams({tab}) ··· 299 299 <View 300 300 ref={headerRef} 301 301 onLayout={evt => { 302 - if (isWeb) setHeaderHeight(evt.nativeEvent.layout.height) 302 + if (IS_WEB) setHeaderHeight(evt.nativeEvent.layout.height) 303 303 }} 304 304 style={[ 305 305 a.relative, ··· 581 581 } 582 582 583 583 function scrollToTopWeb() { 584 - if (isWeb) { 584 + if (IS_WEB) { 585 585 window.scrollTo(0, 0) 586 586 } 587 587 }
+2 -2
src/screens/Search/components/AutocompleteResults.tsx
··· 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {isNative} from '#/platform/detection' 8 7 import {useModerationOpts} from '#/state/preferences/moderation-opts' 9 8 import {SearchLinkCard} from '#/view/shell/desktop/Search' 10 9 import {SearchProfileCard} from '#/screens/Search/components/SearchProfileCard' 11 10 import {atoms as a, native} from '#/alf' 12 11 import * as Layout from '#/components/Layout' 12 + import {IS_NATIVE} from '#/env' 13 13 14 14 let AutocompleteResults = ({ 15 15 isAutocompleteFetching, ··· 45 45 label={_(msg`Search for "${searchText}"`)} 46 46 onPress={native(onSubmit)} 47 47 to={ 48 - isNative 48 + IS_NATIVE 49 49 ? undefined 50 50 : `/search?q=${encodeURIComponent(searchText)}` 51 51 }
+2 -2
src/screens/Search/modules/ExploreRecommendations.tsx
··· 3 3 import {Trans} from '@lingui/macro' 4 4 5 5 import {logger} from '#/logger' 6 - import {isWeb} from '#/platform/detection' 7 6 import { 8 7 DEFAULT_LIMIT as RECOMMENDATIONS_COUNT, 9 8 useTrendingTopics, ··· 17 16 TrendingTopicSkeleton, 18 17 } from '#/components/TrendingTopics' 19 18 import {Text} from '#/components/Typography' 19 + import {IS_WEB} from '#/env' 20 20 21 21 // Note: This module is not currently used and may be removed in the future. 22 22 ··· 37 37 <View 38 38 style={[ 39 39 a.flex_row, 40 - isWeb 40 + IS_WEB 41 41 ? [a.px_lg, a.py_lg, a.pt_2xl, a.gap_md] 42 42 : [a.p_lg, a.pt_2xl, a.gap_md], 43 43 a.border_b,
+4 -4
src/screens/Settings/AboutSettings.tsx
··· 11 11 12 12 import {STATUS_PAGE_URL} from '#/lib/constants' 13 13 import {type CommonNavigatorParams} from '#/lib/routes/types' 14 - import {isAndroid, isIOS, isNative} from '#/platform/detection' 15 14 import * as Toast from '#/view/com/util/Toast' 16 15 import * as SettingsList from '#/screens/Settings/components/SettingsList' 17 16 import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom' ··· 22 21 import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench' 23 22 import * as Layout from '#/components/Layout' 24 23 import {Loader} from '#/components/Loader' 24 + import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env' 25 25 import * as env from '#/env' 26 26 import {useDemoMode} from '#/storage/hooks/demo-mode' 27 27 import {useDevMode} from '#/storage/hooks/dev-mode' ··· 44 44 return spaceDiff * -1 45 45 }, 46 46 onSuccess: sizeDiffBytes => { 47 - if (isAndroid) { 47 + if (IS_ANDROID) { 48 48 Toast.show( 49 49 _( 50 50 msg({ ··· 110 110 <Trans>System log</Trans> 111 111 </SettingsList.ItemText> 112 112 </SettingsList.LinkItem> 113 - {isNative && ( 113 + {IS_NATIVE && ( 114 114 <SettingsList.PressableItem 115 115 onPress={() => onClearImageCache()} 116 116 label={_(msg`Clear image cache`)} ··· 159 159 {devModeEnabled && ( 160 160 <> 161 161 <OTAInfo /> 162 - {isIOS && ( 162 + {IS_IOS && ( 163 163 <SettingsList.PressableItem 164 164 onPress={() => { 165 165 const newDemoModeEnabled = !demoModeEnabled
+2 -2
src/screens/Settings/AccessibilitySettings.tsx
··· 3 3 import {type NativeStackScreenProps} from '@react-navigation/native-stack' 4 4 5 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 - import {isNative} from '#/platform/detection' 7 6 import { 8 7 useHapticsDisabled, 9 8 useRequireAltTextEnabled, ··· 20 19 import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility' 21 20 import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic' 22 21 import * as Layout from '#/components/Layout' 22 + import {IS_NATIVE} from '#/env' 23 23 24 24 type Props = NativeStackScreenProps< 25 25 CommonNavigatorParams, ··· 76 76 <Toggle.Platform /> 77 77 </Toggle.Item> 78 78 </SettingsList.Group> 79 - {isNative && ( 79 + {IS_NATIVE && ( 80 80 <> 81 81 <SettingsList.Divider /> 82 82 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
+3 -3
src/screens/Settings/AppIconSettings/index.tsx
··· 8 8 import {PressableScale} from '#/lib/custom-animations/PressableScale' 9 9 import {type CommonNavigatorParams} from '#/lib/routes/types' 10 10 import {useGate} from '#/lib/statsig/statsig' 11 - import {isAndroid} from '#/platform/detection' 12 11 import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' 13 12 import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 14 13 import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' ··· 16 15 import * as Toggle from '#/components/forms/Toggle' 17 16 import * as Layout from '#/components/Layout' 18 17 import {Text} from '#/components/Typography' 18 + import {IS_ANDROID} from '#/env' 19 19 import {IS_INTERNAL} from '#/env' 20 20 21 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'> ··· 29 29 ) 30 30 31 31 const onSetAppIcon = (icon: DynamicAppIcon.IconName) => { 32 - if (isAndroid) { 32 + if (IS_ANDROID) { 33 33 const next = 34 34 sets.defaults.find(i => i.id === icon) ?? 35 35 sets.core.find(i => i.id === icon) ··· 221 221 accessibilityHint={_(msg`Changes app icon`)} 222 222 targetScale={0.95} 223 223 onPress={() => { 224 - if (isAndroid) { 224 + if (IS_ANDROID) { 225 225 Alert.alert( 226 226 _(msg`Change app icon to "${icon.name}"`), 227 227 _(msg`The app will be restarted`),
+2 -2
src/screens/Settings/AppearanceSettings.tsx
··· 12 12 type CommonNavigatorParams, 13 13 type NativeStackScreenProps, 14 14 } from '#/lib/routes/types' 15 - import {isNative} from '#/platform/detection' 16 15 import {useSetThemePrefs, useThemePrefs} from '#/state/shell' 17 16 import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem' 18 17 import {type Alf, atoms as a, native, useAlf, useTheme} from '#/alf' ··· 24 23 import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase' 25 24 import * as Layout from '#/components/Layout' 26 25 import {Text} from '#/components/Typography' 26 + import {IS_NATIVE} from '#/env' 27 27 import {IS_INTERNAL} from '#/env' 28 28 import * as SettingsList from './components/SettingsList' 29 29 ··· 165 165 onChange={onChangeFontScale} 166 166 /> 167 167 168 - {isNative && IS_INTERNAL && ( 168 + {IS_NATIVE && IS_INTERNAL && ( 169 169 <> 170 170 <SettingsList.Divider /> 171 171 <AppIconSettingsListItem />
+2 -2
src/screens/Settings/ContentAndMediaSettings.tsx
··· 4 4 5 5 import {type CommonNavigatorParams} from '#/lib/routes/types' 6 6 import {logEvent} from '#/lib/statsig/statsig' 7 - import {isNative} from '#/platform/detection' 8 7 import {useAutoplayDisabled, useSetAutoplayDisabled} from '#/state/preferences' 9 8 import { 10 9 useInAppBrowser, ··· 26 25 import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending' 27 26 import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window' 28 27 import * as Layout from '#/components/Layout' 28 + import {IS_NATIVE} from '#/env' 29 29 import {LiveEventFeedsSettingsToggle} from '#/features/liveEvents/components/LiveEventFeedsSettingsToggle' 30 30 31 31 type Props = NativeStackScreenProps< ··· 97 97 </SettingsList.ItemText> 98 98 </SettingsList.LinkItem> 99 99 <SettingsList.Divider /> 100 - {isNative && ( 100 + {IS_NATIVE && ( 101 101 <Toggle.Item 102 102 name="use_in_app_browser" 103 103 label={_(msg`Use in-app browser to open links`)}
+2 -2
src/screens/Settings/FindContactsSettings.tsx
··· 20 20 } from '#/lib/routes/types' 21 21 import {cleanError, isNetworkError} from '#/lib/strings/errors' 22 22 import {logger} from '#/logger' 23 - import {isNative} from '#/platform/detection' 24 23 import { 25 24 updateProfileShadow, 26 25 useProfileShadow, ··· 48 47 import * as ProfileCard from '#/components/ProfileCard' 49 48 import * as Toast from '#/components/Toast' 50 49 import {Text} from '#/components/Typography' 50 + import {IS_NATIVE} from '#/env' 51 51 import type * as bsky from '#/types/bsky' 52 52 import {bulkWriteFollows} from '../Onboarding/util' 53 53 ··· 78 78 </Layout.Header.Content> 79 79 <Layout.Header.Slot /> 80 80 </Layout.Header.Outer> 81 - {isNative ? ( 81 + {IS_NATIVE ? ( 82 82 data ? ( 83 83 !data.syncStatus ? ( 84 84 <Intro />
+5 -5
src/screens/Settings/NotificationSettings/index.tsx
··· 11 11 type AllNavigatorParams, 12 12 type NativeStackScreenProps, 13 13 } from '#/lib/routes/types' 14 - import {isAndroid, isIOS, isWeb} from '#/platform/detection' 15 14 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' 16 15 import {atoms as a} from '#/alf' 17 16 import {Admonition} from '#/components/Admonition' ··· 31 30 } from '#/components/icons/Repost' 32 31 import {Shapes_Stroke2_Corner0_Rounded as ShapesIcon} from '#/components/icons/Shapes' 33 32 import * as Layout from '#/components/Layout' 33 + import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env' 34 34 import * as SettingsList from '../components/SettingsList' 35 35 import {ItemTextWithSubtitle} from './components/ItemTextWithSubtitle' 36 36 ··· 45 45 const {data: permissions, refetch} = useQuery({ 46 46 queryKey: RQKEY, 47 47 queryFn: async () => { 48 - if (isWeb) return null 48 + if (IS_WEB) return null 49 49 return await Notification.getPermissionsAsync() 50 50 }, 51 51 }) ··· 58 58 }, [appState, refetch]) 59 59 60 60 const onRequestPermissions = async () => { 61 - if (isWeb) return 61 + if (IS_WEB) return 62 62 if (permissions?.canAskAgain) { 63 63 const response = await Notification.requestPermissionsAsync() 64 64 queryClient.setQueryData(RQKEY, response) 65 65 } else { 66 - if (isAndroid) { 66 + if (IS_ANDROID) { 67 67 try { 68 68 await Linking.sendIntent( 69 69 'android.settings.APP_NOTIFICATION_SETTINGS', ··· 77 77 } catch { 78 78 Linking.openSettings() 79 79 } 80 - } else if (isIOS) { 80 + } else if (IS_IOS) { 81 81 Linking.openSettings() 82 82 } 83 83 }
+4 -4
src/screens/Settings/Settings.tsx
··· 19 19 import {useGate} from '#/lib/statsig/statsig' 20 20 import {sanitizeDisplayName} from '#/lib/strings/display-names' 21 21 import {sanitizeHandle} from '#/lib/strings/handles' 22 - import {isIOS, isNative} from '#/platform/detection' 23 22 import {useProfileShadow} from '#/state/cache/profile-shadow' 24 23 import * as persisted from '#/state/persisted' 25 24 import {clearStorage} from '#/state/persisted' ··· 71 70 shouldShowVerificationCheckButton, 72 71 VerificationCheckButton, 73 72 } from '#/components/verification/VerificationCheckButton' 73 + import {IS_IOS, IS_NATIVE} from '#/env' 74 74 import {IS_INTERNAL} from '#/env' 75 75 import {device, useStorage} from '#/storage' 76 76 import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' ··· 213 213 <Trans>Content and media</Trans> 214 214 </SettingsList.ItemText> 215 215 </SettingsList.LinkItem> 216 - {isNative && 216 + {IS_NATIVE && 217 217 findContactsEnabled && 218 218 !gate('disable_settings_find_contacts') && ( 219 219 <SettingsList.LinkItem ··· 510 510 <Trans>Clear all storage data (restart after this)</Trans> 511 511 </SettingsList.ItemText> 512 512 </SettingsList.PressableItem> 513 - {isIOS ? ( 513 + {IS_IOS ? ( 514 514 <SettingsList.PressableItem 515 515 onPress={onPressApplyOta} 516 516 label={_(msg`Apply Pull Request`)}> ··· 519 519 </SettingsList.ItemText> 520 520 </SettingsList.PressableItem> 521 521 ) : null} 522 - {isNative && isCurrentlyRunningPullRequestDeployment ? ( 522 + {IS_NATIVE && isCurrentlyRunningPullRequestDeployment ? ( 523 523 <SettingsList.PressableItem 524 524 onPress={revertToEmbedded} 525 525 label={_(msg`Unapply Pull Request`)}>
+2 -2
src/screens/Settings/components/AddAppPasswordDialog.tsx
··· 13 13 import {useLingui} from '@lingui/react' 14 14 import {useMutation} from '@tanstack/react-query' 15 15 16 - import {isWeb} from '#/platform/detection' 17 16 import {useAppPasswordCreateMutation} from '#/state/queries/app-passwords' 18 17 import {atoms as a, native, useTheme} from '#/alf' 19 18 import {Admonition} from '#/components/Admonition' ··· 24 23 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron' 25 24 import {SquareBehindSquare4_Stroke2_Corner0_Rounded as CopyIcon} from '#/components/icons/SquareBehindSquare4' 26 25 import {Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 27 import {CopyButton} from './CopyButton' 28 28 29 29 export function AddAppPasswordDialog({ ··· 181 181 ) : ( 182 182 <Animated.View 183 183 style={[a.gap_lg]} 184 - entering={isWeb ? FadeIn.delay(200) : SlideInRight} 184 + entering={IS_WEB ? FadeIn.delay(200) : SlideInRight} 185 185 key={1}> 186 186 <Text style={[a.text_2xl, a.font_semi_bold]}> 187 187 <Trans>Here is your app password!</Trans>
+2 -2
src/screens/Settings/components/ChangePasswordDialog.tsx
··· 7 7 import {cleanError, isNetworkError} from '#/lib/strings/errors' 8 8 import {checkAndFormatResetCode} from '#/lib/strings/password' 9 9 import {logger} from '#/logger' 10 - import {isNative} from '#/platform/detection' 11 10 import {useAgent, useSession} from '#/state/session' 12 11 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 13 12 import {android, atoms as a, web} from '#/alf' ··· 16 15 import * as TextField from '#/components/forms/TextField' 17 16 import {Loader} from '#/components/Loader' 18 17 import {Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 19 20 20 enum Stages { 21 21 RequestCode = 'RequestCode', ··· 242 242 <Trans>Already have a code?</Trans> 243 243 </ButtonText> 244 244 </Button> 245 - {isNative && ( 245 + {IS_NATIVE && ( 246 246 <Button 247 247 label={_(msg`Cancel`)} 248 248 color="secondary"
+2 -2
src/screens/Settings/components/DisableEmail2FADialog.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {cleanError} from '#/lib/strings/errors' 7 - import {isNative} from '#/platform/detection' 8 7 import {useAgent, useSession} from '#/state/session' 9 8 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 10 9 import * as Toast from '#/view/com/util/Toast' ··· 15 14 import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock' 16 15 import {Loader} from '#/components/Loader' 17 16 import {P, Text} from '#/components/Typography' 17 + import {IS_NATIVE} from '#/env' 18 18 19 19 enum Stages { 20 20 Email, ··· 193 193 </View> 194 194 ) : undefined} 195 195 196 - {!gtMobile && isNative && <View style={{height: 40}} />} 196 + {!gtMobile && IS_NATIVE && <View style={{height: 40}} />} 197 197 </View> 198 198 </Dialog.ScrollableInner> 199 199 </Dialog.Outer>
+8 -6
src/screens/Signup/StepCaptcha/index.tsx
··· 7 7 8 8 import {createFullHandle} from '#/lib/strings/handles' 9 9 import {logger} from '#/logger' 10 - import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' 11 10 import {useSignupContext} from '#/screens/Signup/state' 12 11 import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' 13 12 import {atoms as a, useTheme} from '#/alf' 14 13 import {FormError} from '#/components/forms/FormError' 14 + import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 15 15 import {GCP_PROJECT_ID} from '#/env' 16 16 import {BackNextButtons} from '../BackNextButtons' 17 17 18 18 const CAPTCHA_PATH = 19 - isWeb || GCP_PROJECT_ID === 0 ? '/gate/signup' : '/gate/signup/attempt-attest' 19 + IS_WEB || GCP_PROJECT_ID === 0 20 + ? '/gate/signup' 21 + : '/gate/signup/attempt-attest' 20 22 21 23 export function StepCaptcha() { 22 - if (isWeb) { 24 + if (IS_WEB) { 23 25 return <StepCaptchaInner /> 24 26 } else { 25 27 return <StepCaptchaNative /> ··· 35 37 ;(async () => { 36 38 logger.debug('trying to generate attestation token...') 37 39 try { 38 - if (isIOS) { 40 + if (IS_IOS) { 39 41 logger.debug('starting to generate devicecheck token...') 40 42 const token = await ReactNativeDeviceAttest.getDeviceCheckToken() 41 43 setToken(token) ··· 85 87 newUrl.searchParams.set('state', stateParam) 86 88 newUrl.searchParams.set('colorScheme', theme.name) 87 89 88 - if (isNative && token) { 90 + if (IS_NATIVE && token) { 89 91 newUrl.searchParams.set('platform', Platform.OS) 90 92 newUrl.searchParams.set('token', token) 91 - if (isAndroid && payload) { 93 + if (IS_ANDROID && payload) { 92 94 newUrl.searchParams.set('payload', payload) 93 95 } 94 96 }
+3 -3
src/screens/Signup/StepInfo/index.tsx
··· 7 7 8 8 import {isEmailMaybeInvalid} from '#/lib/strings/email' 9 9 import {logger} from '#/logger' 10 - import {isNative} from '#/platform/detection' 11 10 import {useSignupContext} from '#/screens/Signup/state' 12 11 import {Policies} from '#/screens/Signup/StepInfo/Policies' 13 12 import {atoms as a, native} from '#/alf' ··· 31 30 MIN_ACCESS_AGE, 32 31 useAgeAssuranceRegionConfigWithFallback, 33 32 } from '#/ageAssurance/util' 33 + import {IS_NATIVE} from '#/env' 34 34 import { 35 35 useDeviceGeolocationApi, 36 36 useIsDeviceGeolocationGranted, ··· 325 325 </Trans> 326 326 )} 327 327 </Admonition.Text> 328 - {isNative && 328 + {IS_NATIVE && 329 329 !isDeviceGeolocationGranted && 330 330 isOverAppMinAccessAge && ( 331 331 <Admonition.Text> ··· 357 357 ) : undefined} 358 358 </View> 359 359 360 - {isNative && ( 360 + {IS_NATIVE && ( 361 361 <DeviceLocationRequestDialog 362 362 control={locationControl} 363 363 onLocationAcquired={props => {
+2 -2
src/screens/Signup/index.tsx
··· 8 8 9 9 import {FEEDBACK_FORM_URL} from '#/lib/constants' 10 10 import {logger} from '#/logger' 11 - import {isAndroid} from '#/platform/detection' 12 11 import {useServiceQuery} from '#/state/queries/service' 13 12 import {useStarterPackQuery} from '#/state/queries/starter-packs' 14 13 import {useActiveStarterPack} from '#/state/shell/starter-pack' ··· 30 29 import {InlineLinkText} from '#/components/Link' 31 30 import {ScreenTransition} from '#/components/ScreenTransition' 32 31 import {Text} from '#/components/Typography' 32 + import {IS_ANDROID} from '#/env' 33 33 import {GCP_PROJECT_ID} from '#/env' 34 34 import * as bsky from '#/types/bsky' 35 35 ··· 108 108 109 109 // On Android, warmup the Play Integrity API on the signup screen so it is ready by the time we get to the gate screen. 110 110 useEffect(() => { 111 - if (!isAndroid) { 111 + if (!IS_ANDROID) { 112 112 return 113 113 } 114 114 ReactNativeDeviceAttest.warmupIntegrity(GCP_PROJECT_ID).catch(err =>
+3 -3
src/screens/SignupQueued.tsx
··· 6 6 import {useLingui} from '@lingui/react' 7 7 8 8 import {logger} from '#/logger' 9 - import {isIOS, isWeb} from '#/platform/detection' 10 9 import {isSignupQueued, useAgent, useSessionApi} from '#/state/session' 11 10 import {useOnboardingDispatch} from '#/state/shell' 12 11 import {Logo} from '#/view/icons/Logo' ··· 14 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import {Loader} from '#/components/Loader' 16 15 import {P, Text} from '#/components/Typography' 16 + import {IS_IOS, IS_WEB} from '#/env' 17 17 18 18 const COL_WIDTH = 400 19 19 ··· 98 98 </Button> 99 99 ) 100 100 101 - const webLayout = isWeb && gtMobile 101 + const webLayout = IS_WEB && gtMobile 102 102 103 103 return ( 104 104 <Modal ··· 106 106 animationType={native('slide')} 107 107 presentationStyle="formSheet" 108 108 style={[web(a.util_screen_outer)]}> 109 - {isIOS && <SystemBars style={{statusBar: 'light'}} />} 109 + {IS_IOS && <SystemBars style={{statusBar: 'light'}} />} 110 110 <ScrollView 111 111 style={[a.flex_1, t.atoms.bg]} 112 112 contentContainerStyle={{borderWidth: 0}}
+4 -4
src/screens/StarterPack/StarterPackLandingScreen.tsx
··· 11 11 import {msg, Trans} from '@lingui/macro' 12 12 import {useLingui} from '@lingui/react' 13 13 14 - import {isAndroidWeb} from '#/lib/browser' 14 + import {IS_ANDROIDWeb} from '#/lib/browser' 15 15 import {JOINED_THIS_WEEK} from '#/lib/constants' 16 16 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 17 17 import {logEvent} from '#/lib/statsig/statsig' 18 18 import {createStarterPackGooglePlayUri} from '#/lib/strings/starter-pack' 19 - import {isWeb} from '#/platform/detection' 20 19 import {useModerationOpts} from '#/state/preferences/moderation-opts' 21 20 import {useStarterPackQuery} from '#/state/queries/starter-packs' 22 21 import { ··· 38 37 import * as Prompt from '#/components/Prompt' 39 38 import {RichText} from '#/components/RichText' 40 39 import {Text} from '#/components/Typography' 40 + import {IS_WEB} from '#/env' 41 41 import * as bsky from '#/types/bsky' 42 42 43 43 const AnimatedPressable = Animated.createAnimatedComponent(Pressable) ··· 142 142 postAppClipMessage({ 143 143 action: 'present', 144 144 }) 145 - } else if (isAndroidWeb) { 145 + } else if (IS_ANDROIDWeb) { 146 146 androidDialogControl.open() 147 147 } else { 148 148 onContinue() ··· 359 359 /> 360 360 </Prompt.Actions> 361 361 </Prompt.Outer> 362 - {isWeb && ( 362 + {IS_WEB && ( 363 363 <meta 364 364 name="apple-itunes-app" 365 365 content="app-id=xyz.blueskyweb.app, app-clip-bundle-id=xyz.blueskyweb.app.AppClip, app-clip-display=card"
+4 -4
src/screens/StarterPack/StarterPackScreen.tsx
··· 27 27 import {cleanError} from '#/lib/strings/errors' 28 28 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 29 29 import {logger} from '#/logger' 30 - import {isWeb} from '#/platform/detection' 31 30 import {updateProfileShadow} from '#/state/cache/profile-shadow' 32 31 import {useModerationOpts} from '#/state/preferences/moderation-opts' 33 32 import {getAllListMembers} from '#/state/queries/list-members' ··· 72 71 import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog' 73 72 import {ShareDialog} from '#/components/StarterPack/ShareDialog' 74 73 import {Text} from '#/components/Typography' 74 + import {IS_WEB} from '#/env' 75 75 import * as bsky from '#/types/bsky' 76 76 77 77 type StarterPackScreeProps = NativeStackScreenProps< ··· 608 608 <Menu.Group> 609 609 <Menu.Item 610 610 label={ 611 - isWeb 611 + IS_WEB 612 612 ? _(msg`Copy link to starter pack`) 613 613 : _(msg`Share via...`) 614 614 } 615 615 testID="shareStarterPackLinkBtn" 616 616 onPress={onOpenShareDialog}> 617 617 <Menu.ItemText> 618 - {isWeb ? ( 618 + {IS_WEB ? ( 619 619 <Trans>Copy link</Trans> 620 620 ) : ( 621 621 <Trans>Share via...</Trans> 622 622 )} 623 623 </Menu.ItemText> 624 624 <Menu.ItemIcon 625 - icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} 625 + icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} 626 626 position="right" 627 627 /> 628 628 </Menu.Item>
+2 -2
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 4 4 import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 - import {isNative} from '#/platform/detection' 8 7 import {useA11y} from '#/state/a11y' 9 8 import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete' 10 9 import {useActorSearch} from '#/state/queries/actor-search' ··· 16 15 import {ScreenTransition} from '#/components/ScreenTransition' 17 16 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' 18 17 import {Text} from '#/components/Typography' 18 + import {IS_NATIVE} from '#/env' 19 19 import type * as bsky from '#/types/bsky' 20 20 21 21 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { ··· 89 89 onEndReached={ 90 90 !query && !screenReaderEnabled ? () => fetchNextPage() : undefined 91 91 } 92 - onEndReachedThreshold={isNative ? 2 : 0.25} 92 + onEndReachedThreshold={IS_NATIVE ? 2 : 0.25} 93 93 keyboardDismissMode="on-drag" 94 94 ListEmptyComponent={ 95 95 <View style={[a.flex_1, a.align_center, a.mt_lg, a.px_lg]}>
+3 -3
src/screens/StarterPack/Wizard/index.tsx
··· 31 31 parseStarterPackUri, 32 32 } from '#/lib/strings/starter-pack' 33 33 import {logger} from '#/logger' 34 - import {isNative} from '#/platform/detection' 35 34 import {useModerationOpts} from '#/state/preferences/moderation-opts' 36 35 import {useAllListMembersQuery} from '#/state/queries/list-members' 37 36 import {useProfileQuery} from '#/state/queries/profile' ··· 59 58 import {Loader} from '#/components/Loader' 60 59 import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' 61 60 import {Text} from '#/components/Typography' 61 + import {IS_NATIVE} from '#/env' 62 62 import type * as bsky from '#/types/bsky' 63 63 import {Provider} from './State' 64 64 ··· 435 435 { 436 436 paddingBottom: a.pb_lg.paddingBottom + bottomInset, 437 437 }, 438 - isNative && [ 438 + IS_NATIVE && [ 439 439 a.border_l, 440 440 a.border_r, 441 441 t.atoms.shadow_md, ··· 601 601 a.w_full, 602 602 a.align_center, 603 603 a.gap_2xl, 604 - isNative ? a.mt_sm : a.mt_md, 604 + IS_NATIVE ? a.mt_sm : a.mt_md, 605 605 ]}> 606 606 {state.currentStep === 'Profiles' && items.length < 8 && ( 607 607 <Text
+2 -2
src/screens/Takendown.tsx
··· 14 14 } from '#/lib/constants' 15 15 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 16 16 import {cleanError} from '#/lib/strings/errors' 17 - import {isWeb} from '#/platform/detection' 18 17 import {useAgent, useSession, useSessionApi} from '#/state/session' 19 18 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' 20 19 import {Logo} from '#/view/icons/Logo' ··· 24 23 import {SimpleInlineLinkText} from '#/components/Link' 25 24 import {Loader} from '#/components/Loader' 26 25 import {P, Text} from '#/components/Typography' 26 + import {IS_WEB} from '#/env' 27 27 28 28 const COL_WIDTH = 400 29 29 ··· 119 119 </Button> 120 120 ) 121 121 122 - const webLayout = isWeb && gtMobile 122 + const webLayout = IS_WEB && gtMobile 123 123 124 124 useEnableKeyboardController(true) 125 125
+4 -4
src/screens/VideoFeed/index.tsx
··· 57 57 import {cleanError} from '#/lib/strings/errors' 58 58 import {sanitizeHandle} from '#/lib/strings/handles' 59 59 import {logger} from '#/logger' 60 - import {isAndroid} from '#/platform/detection' 61 60 import {useA11y} from '#/state/a11y' 62 61 import { 63 62 POST_TOMBSTONE, ··· 101 100 import {PostControls} from '#/components/PostControls' 102 101 import {RichText} from '#/components/RichText' 103 102 import {Text} from '#/components/Typography' 103 + import {IS_ANDROID} from '#/env' 104 104 import * as bsky from '#/types/bsky' 105 105 import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' 106 106 ··· 590 590 embed: AppBskyEmbedVideo.View 591 591 }) { 592 592 const {bottom} = useSafeAreaInsets() 593 - const [isReady, setIsReady] = useState(!isAndroid) 593 + const [isReady, setIsReady] = useState(!IS_ANDROID) 594 594 595 595 useEventListener(player, 'timeUpdate', evt => { 596 - if (isAndroid && !isReady && evt.currentTime >= 0.05) { 596 + if (IS_ANDROID && !isReady && evt.currentTime >= 0.05) { 597 597 setIsReady(true) 598 598 } 599 599 }) ··· 920 920 </LinearGradient> 921 921 </View> 922 922 {/* 923 - {isAndroid && status === 'loading' && ( 923 + {IS_ANDROID && status === 'loading' && ( 924 924 <View 925 925 style={[ 926 926 a.absolute,
+2 -2
src/state/a11y.tsx
··· 1 1 import React from 'react' 2 2 import {AccessibilityInfo} from 'react-native' 3 3 4 - import {isWeb} from '#/platform/detection' 4 + import {IS_WEB} from '#/env' 5 5 import {PlatformInfo} from '../../modules/expo-bluesky-swiss-army' 6 6 7 7 const Context = React.createContext({ ··· 58 58 * 59 59 * @see https://github.com/necolas/react-native-web/discussions/2072 60 60 */ 61 - screenReaderEnabled: isWeb ? false : screenReaderEnabled, 61 + screenReaderEnabled: IS_WEB ? false : screenReaderEnabled, 62 62 } 63 63 }, [reduceMotionEnabled, screenReaderEnabled]) 64 64
+2 -2
src/state/dialogs/index.tsx
··· 1 1 import React from 'react' 2 2 3 - import {isWeb} from '#/platform/detection' 4 3 import {type DialogControlRefProps} from '#/components/Dialog' 5 4 import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context' 5 + import {IS_WEB} from '#/env' 6 6 import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet' 7 7 8 8 interface IDialogContext { ··· 62 62 const openDialogs = React.useRef<Set<string>>(new Set()) 63 63 64 64 const closeAllDialogs = React.useCallback(() => { 65 - if (isWeb) { 65 + if (IS_WEB) { 66 66 openDialogs.current.forEach(id => { 67 67 const dialog = activeDialogs.current.get(id) 68 68 if (dialog) dialog.current.close()
+5 -5
src/state/gallery.ts
··· 18 18 import {type PickerImage} from '#/lib/media/picker.shared' 19 19 import {getDataUriSize} from '#/lib/media/util' 20 20 import {isCancelledError} from '#/lib/strings/errors' 21 - import {isNative} from '#/platform/detection' 21 + import {IS_NATIVE} from '#/env' 22 22 23 23 export type ImageTransformation = { 24 24 crop?: ActionCrop['crop'] ··· 55 55 let _imageCacheDirectory: string 56 56 57 57 function getImageCacheDirectory(): string | null { 58 - if (isNative) { 58 + if (IS_NATIVE) { 59 59 return (_imageCacheDirectory ??= joinPath(cacheDirectory!, 'bsky-composer')) 60 60 } 61 61 ··· 120 120 } 121 121 122 122 export async function cropImage(img: ComposerImage): Promise<ComposerImage> { 123 - if (!isNative) { 123 + if (!IS_NATIVE) { 124 124 return img 125 125 } 126 126 ··· 244 244 } 245 245 246 246 async function moveIfNecessary(from: string) { 247 - const cacheDir = isNative && getImageCacheDirectory() 247 + const cacheDir = IS_NATIVE && getImageCacheDirectory() 248 248 249 249 if (cacheDir && from.startsWith(cacheDir)) { 250 250 const to = joinPath(cacheDir, nanoid(36)) ··· 260 260 261 261 /** Purge files that were created to accomodate image manipulation */ 262 262 export async function purgeTemporaryImageFiles() { 263 - const cacheDir = isNative && getImageCacheDirectory() 263 + const cacheDir = IS_NATIVE && getImageCacheDirectory() 264 264 265 265 if (cacheDir) { 266 266 await deleteAsync(cacheDir, {idempotent: true})
+2 -2
src/state/messages/convo/agent.ts
··· 16 16 isNetworkError, 17 17 } from '#/lib/strings/errors' 18 18 import {Logger} from '#/logger' 19 - import {isNative} from '#/platform/detection' 20 19 import { 21 20 ACTIVE_POLL_INTERVAL, 22 21 BACKGROUND_POLL_INTERVAL, ··· 37 36 } from '#/state/messages/convo/types' 38 37 import {type MessagesEventBus} from '#/state/messages/events/agent' 39 38 import {type MessagesEventBusError} from '#/state/messages/events/types' 39 + import {IS_NATIVE} from '#/env' 40 40 41 41 const logger = Logger.create(Logger.Context.ConversationAgent) 42 42 ··· 639 639 { 640 640 cursor: nextCursor, 641 641 convoId: this.convoId, 642 - limit: isNative ? 30 : 60, 642 + limit: IS_NATIVE ? 30 : 60, 643 643 }, 644 644 {headers: DM_SERVICE_HEADERS}, 645 645 )
+2 -2
src/state/preferences/kawaii.tsx
··· 1 1 import React from 'react' 2 2 3 - import {isWeb} from '#/platform/detection' 4 3 import * as persisted from '#/state/persisted' 4 + import {IS_WEB} from '#/env' 5 5 6 6 type StateContext = persisted.Schema['kawaii'] 7 7 ··· 30 30 React.useEffect(() => { 31 31 // dumb and stupid but it's web only so just refresh the page if you want to change it 32 32 33 - if (isWeb) { 33 + if (IS_WEB) { 34 34 const kawaii = new URLSearchParams(window.location.search).get('kawaii') 35 35 switch (kawaii) { 36 36 case 'true':
+2 -2
src/state/queries/usePostThread/index.ts
··· 1 1 import {useCallback, useMemo, useState} from 'react' 2 2 import {useQuery, useQueryClient} from '@tanstack/react-query' 3 3 4 - import {isWeb} from '#/platform/detection' 5 4 import {useModerationOpts} from '#/state/preferences/moderation-opts' 6 5 import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences' 7 6 import { ··· 31 30 import {useAgent, useSession} from '#/state/session' 32 31 import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies' 33 32 import {useBreakpoints} from '#/alf' 33 + import {IS_WEB} from '#/env' 34 34 35 35 export * from '#/state/queries/usePostThread/context' 36 36 export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache' ··· 53 53 const below = useMemo(() => { 54 54 return view === 'linear' 55 55 ? LINEAR_VIEW_BELOW 56 - : isWeb && gtPhone 56 + : IS_WEB && gtPhone 57 57 ? TREE_VIEW_BELOW_DESKTOP 58 58 : TREE_VIEW_BELOW 59 59 }, [view, gtPhone])
+2 -2
src/state/session/index.tsx
··· 1 1 import React from 'react' 2 2 import {type AtpSessionEvent, type BskyAgent} from '@atproto/api' 3 3 4 - import {isWeb} from '#/platform/detection' 5 4 import * as persisted from '#/state/persisted' 6 5 import {useCloseAllActiveElements} from '#/state/util' 7 6 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 7 + import {IS_WEB} from '#/env' 8 8 import {emitSessionDropped} from '../events' 9 9 import { 10 10 agentToSessionAccount, ··· 340 340 ) 341 341 342 342 // @ts-expect-error window type is not declared, debug only 343 - if (__DEV__ && isWeb) window.agent = state.currentAgentState.agent 343 + if (__DEV__ && IS_WEB) window.agent = state.currentAgentState.agent 344 344 345 345 const agent = state.currentAgentState.agent as BskyAppAgent 346 346 const currentAgentRef = React.useRef(agent)
+2 -2
src/state/shell/logged-out.tsx
··· 1 1 import React from 'react' 2 2 3 - import {isWeb} from '#/platform/detection' 4 3 import {useSession} from '#/state/session' 5 4 import {useActiveStarterPack} from '#/state/shell/starter-pack' 5 + import {IS_WEB} from '#/env' 6 6 7 7 type State = { 8 8 showLoggedOut: boolean ··· 55 55 const [state, setState] = React.useState<State>({ 56 56 showLoggedOut: shouldShowStarterPack, 57 57 requestedAccountSwitchTo: shouldShowStarterPack 58 - ? isWeb 58 + ? IS_WEB 59 59 ? 'starterpack' 60 60 : 'new' 61 61 : undefined,
+3 -3
src/state/shell/selected-feed.tsx
··· 1 1 import {createContext, useCallback, useContext, useState} from 'react' 2 2 3 - import {isWeb} from '#/platform/detection' 4 3 import {type FeedDescriptor} from '#/state/queries/post-feed' 5 4 import {useSession} from '#/state/session' 5 + import {IS_WEB} from '#/env' 6 6 import {account} from '#/storage' 7 7 8 8 type StateContext = FeedDescriptor | null ··· 14 14 setContext.displayName = 'SelectedFeedSetContext' 15 15 16 16 function getInitialFeed(did?: string): FeedDescriptor | null { 17 - if (isWeb) { 17 + if (IS_WEB) { 18 18 if (window.location.pathname === '/') { 19 19 const params = new URLSearchParams(window.location.search) 20 20 const feedFromUrl = params.get('feed') ··· 49 49 const saveState = useCallback( 50 50 (feed: FeedDescriptor) => { 51 51 setState(feed) 52 - if (isWeb) { 52 + if (IS_WEB) { 53 53 try { 54 54 sessionStorage.setItem('lastSelectedHomeFeed', feed) 55 55 } catch {}
+2 -2
src/view/com/auth/SplashScreen.web.tsx
··· 31 31 }) => { 32 32 const {_} = useLingui() 33 33 const t = useTheme() 34 - const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries() 34 + const {isTabletOrMobile: IS_WEB_MOBILE} = useWebMediaQueries() 35 35 const [showClipOverlay, setShowClipOverlay] = React.useState(false) 36 36 37 37 React.useEffect(() => { ··· 78 78 a.justify_center, 79 79 // @ts-expect-error web only 80 80 {paddingBottom: '20vh'}, 81 - isMobileWeb && a.pb_5xl, 81 + IS_WEB_MOBILE && a.pb_5xl, 82 82 t.atoms.border_contrast_medium, 83 83 a.align_center, 84 84 a.gap_5xl,
+19 -19
src/view/com/composer/Composer.tsx
··· 76 76 import {cleanError} from '#/lib/strings/errors' 77 77 import {colors} from '#/lib/styles' 78 78 import {logger} from '#/logger' 79 - import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' 80 79 import {useDialogStateControlContext} from '#/state/dialogs' 81 80 import {emitPostCreated} from '#/state/events' 82 81 import { ··· 130 129 import * as Prompt from '#/components/Prompt' 131 130 import * as Toast from '#/components/Toast' 132 131 import {Text as NewText} from '#/components/Typography' 132 + import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 133 133 import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' 134 134 import {PostLanguageSelect} from './select-language/PostLanguageSelect' 135 135 import { ··· 329 329 const insets = useSafeAreaInsets() 330 330 const viewStyles = useMemo( 331 331 () => ({ 332 - paddingTop: isAndroid ? insets.top : 0, 332 + paddingTop: IS_ANDROID ? insets.top : 0, 333 333 paddingBottom: 334 334 // iOS - when keyboard is closed, keep the bottom bar in the safe area 335 - (isIOS && !isKeyboardVisible) || 335 + (IS_IOS && !isKeyboardVisible) || 336 336 // Android - Android >=35 KeyboardAvoidingView adds double padding when 337 337 // keyboard is closed, so we subtract that in the offset and add it back 338 338 // here when the keyboard is open 339 - (isAndroid && isKeyboardVisible) 339 + (IS_ANDROID && isKeyboardVisible) 340 340 ? insets.bottom 341 341 : 0, 342 342 }), ··· 366 366 367 367 // On Android, pressing Back should ask confirmation. 368 368 useEffect(() => { 369 - if (!isAndroid) { 369 + if (!IS_ANDROID) { 370 370 return 371 371 } 372 372 const backHandler = BackHandler.addEventListener( ··· 671 671 composerState.mutableNeedsFocusActive = false 672 672 // On Android, this risks getting the cursor stuck behind the keyboard. 673 673 // Not worth it. 674 - if (!isAndroid) { 674 + if (!IS_ANDROID) { 675 675 textInput.current?.focus() 676 676 } 677 677 } ··· 727 727 </> 728 728 ) 729 729 730 - const isWebFooterSticky = !isNative && thread.posts.length > 1 730 + const IS_WEBFooterSticky = !IS_NATIVE && thread.posts.length > 1 731 731 return ( 732 732 <BottomSheetPortalProvider> 733 733 <KeyboardAvoidingView 734 734 testID="composePostView" 735 - behavior={isIOS ? 'padding' : 'height'} 735 + behavior={IS_IOS ? 'padding' : 'height'} 736 736 keyboardVerticalOffset={keyboardVerticalOffset} 737 737 style={a.flex_1}> 738 738 <View ··· 790 790 onPublish={onComposerPostPublish} 791 791 onError={setError} 792 792 /> 793 - {isWebFooterSticky && post.id === activePost.id && ( 793 + {IS_WEBFooterSticky && post.id === activePost.id && ( 794 794 <View style={styles.stickyFooterWeb}>{footer}</View> 795 795 )} 796 796 </React.Fragment> 797 797 ))} 798 798 </Animated.ScrollView> 799 - {!isWebFooterSticky && footer} 799 + {!IS_WEBFooterSticky && footer} 800 800 </View> 801 801 802 802 <Prompt.Basic ··· 849 849 const {data: currentProfile} = useProfileQuery({did: currentDid}) 850 850 const richtext = post.richtext 851 851 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media 852 - const forceMinHeight = isWeb && isTextOnly && isActive 852 + const forceMinHeight = IS_WEB && isTextOnly && isActive 853 853 const selectTextInputPlaceholder = isReply 854 854 ? isFirstPost 855 855 ? _(msg`Write your reply`) ··· 889 889 async (uri: string) => { 890 890 if ( 891 891 uri.startsWith('data:video/') || 892 - (isWeb && uri.startsWith('data:image/gif')) 892 + (IS_WEB && uri.startsWith('data:image/gif')) 893 893 ) { 894 - if (isNative) return // web only 894 + if (IS_NATIVE) return // web only 895 895 const [mimeType] = uri.slice('data:'.length).split(';') 896 896 if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { 897 897 Toast.show(_(msg`Unsupported video type: ${mimeType}`), { ··· 921 921 a.mb_sm, 922 922 !isActive && isLastPost && a.mb_lg, 923 923 !isActive && styles.inactivePost, 924 - isTextOnly && isNative && a.flex_grow, 924 + isTextOnly && IS_NATIVE && a.flex_grow, 925 925 ]}> 926 - <View style={[a.flex_row, isNative && a.flex_1]}> 926 + <View style={[a.flex_row, IS_NATIVE && a.flex_1]}> 927 927 <UserAvatar 928 928 avatar={currentProfile?.avatar} 929 929 size={42} ··· 1241 1241 </LayoutAnimationConfig> 1242 1242 {embed.quote?.uri ? ( 1243 1243 <View 1244 - style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], isWeb && [a.pb_md]]}> 1244 + style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], IS_WEB && [a.pb_md]]}> 1245 1245 <View style={[a.relative]}> 1246 1246 <View style={{pointerEvents: 'none'}}> 1247 1247 <LazyQuoteEmbed uri={embed.quote.uri} /> ··· 1652 1652 const {top, bottom} = useSafeAreaInsets() 1653 1653 1654 1654 // Android etc 1655 - if (!isIOS) { 1655 + if (!IS_IOS) { 1656 1656 // need to account for the edge-to-edge nav bar 1657 1657 return bottom * -1 1658 1658 } ··· 1696 1696 const appState = useAppState() 1697 1697 1698 1698 useEffect(() => { 1699 - if (isIOS) { 1699 + if (IS_IOS) { 1700 1700 if (appState === 'inactive') { 1701 1701 Keyboard.dismiss() 1702 1702 } ··· 1846 1846 style: StyleProp<ViewStyle> 1847 1847 children: React.ReactNode 1848 1848 }) { 1849 - if (isWeb) return children 1849 + if (IS_WEB) return children 1850 1850 return ( 1851 1851 <Animated.View 1852 1852 style={style}
+2 -2
src/view/com/composer/GifAltText.tsx
··· 9 9 type EmbedPlayerParams, 10 10 parseEmbedPlayerFromUrl, 11 11 } from '#/lib/strings/embed-player' 12 - import {isAndroid} from '#/platform/detection' 13 12 import {useResolveGifQuery} from '#/state/queries/resolve-link' 14 13 import {type Gif} from '#/state/queries/tenor' 15 14 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' ··· 23 22 import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 24 23 import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif' 25 24 import {Text} from '#/components/Typography' 25 + import {IS_ANDROID} from '#/env' 26 26 import {AltTextReminder} from './photos/Gallery' 27 27 28 28 export function GifAltTextDialog({ ··· 224 224 </View> 225 225 <Dialog.Close /> 226 226 {/* Maybe fix this later -h */} 227 - {isAndroid ? <View style={{height: 300}} /> : null} 227 + {IS_ANDROID ? <View style={{height: 300}} /> : null} 228 228 </Dialog.ScrollableInner> 229 229 ) 230 230 }
+2 -2
src/view/com/composer/KeyboardAccessory.tsx
··· 3 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 4 import type React from 'react' 5 5 6 - import {isWeb} from '#/platform/detection' 7 6 import {atoms as a, useTheme} from '#/alf' 7 + import {IS_WEB} from '#/env' 8 8 9 9 export function KeyboardAccessory({children}: {children: React.ReactNode}) { 10 10 const t = useTheme() ··· 22 22 ] 23 23 24 24 // todo: when iPad support is added, it should also not use the KeyboardStickyView 25 - if (isWeb) { 25 + if (IS_WEB) { 26 26 return <View style={style}>{children}</View> 27 27 } 28 28
+9 -9
src/view/com/composer/SelectMediaButton.tsx
··· 11 11 } from '#/lib/hooks/usePermissions' 12 12 import {openUnifiedPicker} from '#/lib/media/picker' 13 13 import {extractDataUriMime} from '#/lib/media/util' 14 - import {isNative, isWeb} from '#/platform/detection' 15 14 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 16 15 import {atoms as a, useTheme} from '#/alf' 17 16 import {Button} from '#/components/Button' 18 17 import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper' 19 18 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 20 19 import * as toast from '#/components/Toast' 20 + import {IS_NATIVE, IS_WEB} from '#/env' 21 21 22 22 export type SelectMediaButtonProps = { 23 23 disabled?: boolean ··· 91 91 'image/svg+xml', 92 92 'image/webp', 93 93 'image/avif', 94 - isNative && 'image/heic', 94 + IS_NATIVE && 'image/heic', 95 95 ] as const 96 96 ).filter(Boolean) 97 97 type SupportedImageMimeType = Exclude< ··· 261 261 * We don't care too much about mimeType at this point on native, 262 262 * since the `processVideo` step later on will convert to `.mp4`. 263 263 */ 264 - if (isWeb && !isSupportedVideoMimeType(mimeType)) { 264 + if (IS_WEB && !isSupportedVideoMimeType(mimeType)) { 265 265 errors.add(SelectedAssetError.Unsupported) 266 266 continue 267 267 } ··· 271 271 * to filter out large files on web. On native, we compress these anyway, 272 272 * so we only check on web. 273 273 */ 274 - if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 274 + if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 275 275 errors.add(SelectedAssetError.FileTooBig) 276 276 continue 277 277 } ··· 290 290 * to filter out large files on web. On native, we compress GIFs as 291 291 * videos anyway, so we only check on web. 292 292 */ 293 - if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 293 + if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) { 294 294 errors.add(SelectedAssetError.FileTooBig) 295 295 continue 296 296 } ··· 308 308 * base64 data-uri, so we construct it here for web only. 309 309 */ 310 310 uri: 311 - isWeb && asset.base64 311 + IS_WEB && asset.base64 312 312 ? `data:${mimeType};base64,${asset.base64}` 313 313 : asset.uri, 314 314 }) ··· 327 327 } 328 328 329 329 if (supportedAssets[0].duration) { 330 - if (isWeb) { 330 + if (IS_WEB) { 331 331 /* 332 332 * Web reports duration as seconds 333 333 */ ··· 432 432 ) 433 433 434 434 const onPressSelectMedia = useCallback(async () => { 435 - if (isNative) { 435 + if (IS_NATIVE) { 436 436 const [photoAccess, videoAccess] = await Promise.all([ 437 437 requestPhotoAccessIfNeeded(), 438 438 requestVideoAccessIfNeeded(), ··· 446 446 } 447 447 } 448 448 449 - if (isNative && Keyboard.isVisible()) { 449 + if (IS_NATIVE && Keyboard.isVisible()) { 450 450 Keyboard.dismiss() 451 451 } 452 452
+2 -2
src/view/com/composer/labels/LabelsBtn.tsx
··· 9 9 type OtherSelfLabel, 10 10 type SelfLabel, 11 11 } from '#/lib/moderation' 12 - import {isWeb} from '#/platform/detection' 13 12 import {atoms as a, useTheme, web} from '#/alf' 14 13 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import * as Dialog from '#/components/Dialog' ··· 18 17 import {TinyChevronBottom_Stroke2_Corner0_Rounded as TinyChevronIcon} from '#/components/icons/Chevron' 19 18 import {Shield_Stroke2_Corner0_Rounded} from '#/components/icons/Shield' 20 19 import {Text} from '#/components/Typography' 20 + import {IS_WEB} from '#/env' 21 21 22 22 export function LabelsBtn({ 23 23 labels, ··· 218 218 label={_(msg`Done`)} 219 219 onPress={() => control.close()} 220 220 color="primary" 221 - size={isWeb ? 'small' : 'large'} 221 + size={IS_WEB ? 'small' : 'large'} 222 222 variant="solid" 223 223 testID="confirmBtn"> 224 224 <ButtonText>
+2 -2
src/view/com/composer/photos/Gallery.tsx
··· 16 16 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 17 17 import {type Dimensions} from '#/lib/media/types' 18 18 import {colors, s} from '#/lib/styles' 19 - import {isNative} from '#/platform/detection' 20 19 import {type ComposerImage, cropImage} from '#/state/gallery' 21 20 import {Text} from '#/view/com/util/text/Text' 22 21 import {tokens, useTheme} from '#/alf' 23 22 import * as Dialog from '#/components/Dialog' 24 23 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 24 + import {IS_NATIVE} from '#/env' 25 25 import {type PostAction} from '../state/composer' 26 26 import {EditImageDialog} from './EditImageDialog' 27 27 import {ImageAltTextDialog} from './ImageAltTextDialog' ··· 145 145 const editControl = Dialog.useDialogControl() 146 146 147 147 const onImageEdit = () => { 148 - if (isNative) { 148 + if (IS_NATIVE) { 149 149 cropImage(image).then(next => { 150 150 onChange(next) 151 151 })
+3 -3
src/view/com/composer/photos/ImageAltTextDialog.tsx
··· 6 6 7 7 import {MAX_ALT_TEXT} from '#/lib/constants' 8 8 import {enforceLen} from '#/lib/strings/helpers' 9 - import {isAndroid, isWeb} from '#/platform/detection' 10 9 import {type ComposerImage} from '#/state/gallery' 11 10 import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' 12 11 import {atoms as a, useTheme} from '#/alf' ··· 16 15 import * as TextField from '#/components/forms/TextField' 17 16 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 18 17 import {Text} from '#/components/Typography' 18 + import {IS_ANDROID, IS_WEB} from '#/env' 19 19 20 20 type Props = { 21 21 control: Dialog.DialogOuterProps['control'] ··· 66 66 const windim = useWindowDimensions() 67 67 68 68 const imageStyle = React.useMemo<ImageStyle>(() => { 69 - const maxWidth = isWeb ? 450 : windim.width 69 + const maxWidth = IS_WEB ? 450 : windim.width 70 70 const source = image.transformed ?? image.source 71 71 72 72 if (source.height > source.width) { ··· 165 165 </AltTextCounterWrapper> 166 166 </View> 167 167 {/* Maybe fix this later -h */} 168 - {isAndroid ? <View style={{height: 300}} /> : null} 168 + {IS_ANDROID ? <View style={{height: 300}} /> : null} 169 169 </Dialog.ScrollableInner> 170 170 ) 171 171 }
+2 -2
src/view/com/composer/photos/OpenCameraBtn.tsx
··· 7 7 import {useCameraPermission} from '#/lib/hooks/usePermissions' 8 8 import {openCamera} from '#/lib/media/picker' 9 9 import {logger} from '#/logger' 10 - import {isMobileWeb, isNative} from '#/platform/detection' 11 10 import {type ComposerImage, createComposerImage} from '#/state/gallery' 12 11 import {atoms as a, useTheme} from '#/alf' 13 12 import {Button} from '#/components/Button' 14 13 import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera' 14 + import {IS_NATIVE, IS_WEB_MOBILE} from '#/env' 15 15 16 16 type Props = { 17 17 disabled?: boolean ··· 58 58 requestMediaPermission, 59 59 ]) 60 60 61 - const shouldShowCameraButton = isNative || isMobileWeb 61 + const shouldShowCameraButton = IS_NATIVE || IS_WEB_MOBILE 62 62 if (!shouldShowCameraButton) { 63 63 return null 64 64 }
+4 -4
src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
··· 6 6 7 7 import {languageName} from '#/locale/helpers' 8 8 import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' 9 - import {isNative, isWeb} from '#/platform/detection' 10 9 import { 11 10 toPostLanguages, 12 11 useLanguagePrefs, ··· 21 20 import * as Toggle from '#/components/forms/Toggle' 22 21 import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' 23 22 import {Text} from '#/components/Typography' 23 + import {IS_NATIVE, IS_WEB} from '#/env' 24 24 25 25 export function PostLanguageSelectDialog({ 26 26 control, ··· 168 168 169 169 const listHeader = ( 170 170 <View 171 - style={[a.pb_xs, t.atoms.bg, isNative && a.pt_2xl]} 171 + style={[a.pb_xs, t.atoms.bg, IS_NATIVE && a.pt_2xl]} 172 172 onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}> 173 173 <View style={[a.flex_row, a.w_full, a.justify_between]}> 174 174 <View> ··· 195 195 </Text> 196 196 </View> 197 197 198 - {isWeb && ( 198 + {IS_WEB && ( 199 199 <Button 200 200 variant="ghost" 201 201 size="small" ··· 252 252 ListHeaderComponent={listHeader} 253 253 stickyHeaderIndices={[0]} 254 254 contentContainerStyle={[a.gap_0]} 255 - style={[isNative && a.px_lg, web({paddingBottom: 120})]} 255 + style={[IS_NATIVE && a.px_lg, web({paddingBottom: 120})]} 256 256 scrollIndicatorInsets={{top: headerHeight}} 257 257 renderItem={({item, index}) => { 258 258 if (item.type === 'header') {
+3 -3
src/view/com/composer/text-input/TextInput.tsx
··· 25 25 import {cleanError} from '#/lib/strings/errors' 26 26 import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip' 27 27 import {useTheme} from '#/lib/ThemeContext' 28 - import {isAndroid, isNative} from '#/platform/detection' 29 28 import { 30 29 type LinkFacetMatch, 31 30 suggestLinkCardUri, 32 31 } from '#/view/com/composer/text-input/text-input-util' 33 32 import {atoms as a, useAlf} from '#/alf' 34 33 import {normalizeTextStyles} from '#/alf/typography' 34 + import {IS_ANDROID, IS_NATIVE} from '#/env' 35 35 import {Autocomplete} from './mobile/Autocomplete' 36 36 import {type TextInputProps} from './TextInput.types' 37 37 ··· 179 179 /** 180 180 * PasteInput doesn't like `lineHeight`, results in jumpiness 181 181 */ 182 - if (isNative) { 182 + if (IS_NATIVE) { 183 183 style.lineHeight = undefined 184 184 } 185 185 186 186 /* 187 187 * Android impl of `PasteInput` doesn't support the array syntax for `fontVariant` 188 188 */ 189 - if (isAndroid) { 189 + if (IS_ANDROID) { 190 190 // @ts-ignore 191 191 style.fontVariant = style.fontVariant 192 192 ? style.fontVariant.join(' ')
+2 -2
src/view/com/composer/threadgate/ThreadgateBtn.tsx
··· 8 8 9 9 import {isNetworkError} from '#/lib/strings/errors' 10 10 import {logger} from '#/logger' 11 - import {isNative} from '#/platform/detection' 12 11 import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings' 13 12 import {createPostgateRecord} from '#/state/queries/postgate/util' 14 13 import {usePreferencesQuery} from '#/state/queries/preferences' ··· 25 24 import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group' 26 25 import * as Tooltip from '#/components/Tooltip' 27 26 import {Text} from '#/components/Typography' 27 + import {IS_NATIVE} from '#/env' 28 28 import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged' 29 29 30 30 export function ThreadgateBtn({ ··· 70 70 nudged: tooltipWasShown, 71 71 }) 72 72 73 - if (isNative && Keyboard.isVisible()) { 73 + if (IS_NATIVE && Keyboard.isVisible()) { 74 74 Keyboard.dismiss() 75 75 } 76 76
+10 -6
src/view/com/composer/videos/SubtitleDialog.tsx
··· 6 6 import {MAX_ALT_TEXT} from '#/lib/constants' 7 7 import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' 8 8 import {LANGUAGES} from '#/locale/languages' 9 - import {isWeb} from '#/platform/detection' 10 9 import {useLanguagePrefs} from '#/state/preferences' 11 10 import {atoms as a, useTheme, web} from '#/alf' 12 11 import {Button, ButtonIcon, ButtonText} from '#/components/Button' ··· 17 16 import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' 18 17 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 19 18 import {Text} from '#/components/Typography' 19 + import {IS_WEB} from '#/env' 20 20 import {SubtitleFilePicker} from './SubtitleFilePicker' 21 21 22 22 const MAX_NUM_CAPTIONS = 1 ··· 37 37 return ( 38 38 <View style={[a.flex_row, a.my_xs]}> 39 39 <Button 40 - label={isWeb ? _(msg`Captions & alt text`) : _(msg`Alt text`)} 40 + label={IS_WEB ? _(msg`Captions & alt text`) : _(msg`Alt text`)} 41 41 accessibilityHint={ 42 - isWeb 42 + IS_WEB 43 43 ? _(msg`Opens captions and alt text dialog`) 44 44 : _(msg`Opens alt text dialog`) 45 45 } ··· 52 52 }}> 53 53 <ButtonIcon icon={CCIcon} /> 54 54 <ButtonText> 55 - {isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>} 55 + {IS_WEB ? ( 56 + <Trans>Captions & alt text</Trans> 57 + ) : ( 58 + <Trans>Alt text</Trans> 59 + )} 56 60 </ButtonText> 57 61 </Button> 58 62 <Dialog.Outer control={control}> ··· 134 138 </Text> 135 139 )} 136 140 137 - {isWeb && ( 141 + {IS_WEB && ( 138 142 <> 139 143 <View 140 144 style={[ ··· 182 186 <View style={web([a.flex_row, a.justify_end])}> 183 187 <Button 184 188 label={_(msg`Done`)} 185 - size={isWeb ? 'small' : 'large'} 189 + size={IS_WEB ? 'small' : 'large'} 186 190 color="primary" 187 191 variant="solid" 188 192 onPress={() => {
+2 -2
src/view/com/composer/videos/VideoTranscodeProgress.tsx
··· 4 4 import {type ImagePickerAsset} from 'expo-image-picker' 5 5 6 6 import {clamp} from '#/lib/numbers' 7 - import {isWeb} from '#/platform/detection' 8 7 import {atoms as a, useTheme} from '#/alf' 8 + import {IS_WEB} from '#/env' 9 9 import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn' 10 10 import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop' 11 11 ··· 20 20 }) { 21 21 const t = useTheme() 22 22 23 - if (isWeb) return null 23 + if (IS_WEB) return null 24 24 25 25 let aspectRatio = asset.width / asset.height 26 26
+5 -5
src/view/com/feeds/ComposerPrompt.tsx
··· 11 11 } from '#/lib/hooks/usePermissions' 12 12 import {openCamera, openUnifiedPicker} from '#/lib/media/picker' 13 13 import {logger} from '#/logger' 14 - import {isNative} from '#/platform/detection' 15 14 import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile' 16 15 import {MAX_IMAGES} from '#/view/com/composer/state/composer' 17 16 import {UserAvatar} from '#/view/com/util/UserAvatar' ··· 22 21 import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' 23 22 import {SubtleHover} from '#/components/SubtleHover' 24 23 import {Text} from '#/components/Typography' 24 + import {IS_NATIVE} from '#/env' 25 25 26 26 export function ComposerPrompt() { 27 27 const {_} = useLingui() ··· 43 43 logger.metric('composerPrompt:gallery:press', {}) 44 44 45 45 // On web, open the composer with the gallery picker auto-opening 46 - if (!isNative) { 46 + if (!IS_NATIVE) { 47 47 openComposer({openGallery: true}) 48 48 return 49 49 } ··· 105 105 return 106 106 } 107 107 108 - if (isNative && Keyboard.isVisible()) { 108 + if (IS_NATIVE && Keyboard.isVisible()) { 109 109 Keyboard.dismiss() 110 110 } 111 111 ··· 122 122 ] 123 123 124 124 openComposer({ 125 - imageUris: isNative ? imageUris : undefined, 125 + imageUris: IS_NATIVE ? imageUris : undefined, 126 126 }) 127 127 } catch (err: any) { 128 128 if (!String(err).toLowerCase().includes('cancel')) { ··· 189 189 <Trans>What's up?</Trans> 190 190 </Text> 191 191 <View style={[a.flex_row, a.gap_md]}> 192 - {isNative && ( 192 + {IS_NATIVE && ( 193 193 <Button 194 194 onPress={e => { 195 195 e.stopPropagation()
+4 -4
src/view/com/feeds/FeedPage.tsx
··· 20 20 import {type AllNavigatorParams} from '#/lib/routes/types' 21 21 import {logEvent} from '#/lib/statsig/statsig' 22 22 import {s} from '#/lib/styles' 23 - import {isNative} from '#/platform/detection' 24 23 import {listenSoftReset} from '#/state/events' 25 24 import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback' 26 25 import {useSetHomeBadge} from '#/state/home-badge' ··· 34 33 import {useSession} from '#/state/session' 35 34 import {useSetMinimalShellMode} from '#/state/shell' 36 35 import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' 36 + import {IS_NATIVE} from '#/env' 37 37 import {PostFeed} from '../posts/PostFeed' 38 38 import {FAB} from '../util/fab/FAB' 39 39 import {type ListMethods} from '../util/List' ··· 80 80 const feedIsVideoMode = 81 81 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO 82 82 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode 83 - return isNative && _isVideoFeed 83 + return IS_NATIVE && _isVideoFeed 84 84 }, [feedInfo]) 85 85 86 86 useEffect(() => { ··· 91 91 92 92 const scrollToTop = useCallback(() => { 93 93 scrollElRef.current?.scrollToOffset({ 94 - animated: isNative, 94 + animated: IS_NATIVE, 95 95 offset: -headerOffset, 96 96 }) 97 97 setMinimalShellMode(false) ··· 136 136 }) 137 137 }, [scrollToTop, feed, queryClient]) 138 138 139 - const shouldPrefetch = isNative && isPageAdjacent 139 + const shouldPrefetch = IS_NATIVE && isPageAdjacent 140 140 const isDiscoverFeed = feedInfo.uri === DISCOVER_FEED_URI 141 141 return ( 142 142 <View
+3 -3
src/view/com/feeds/MissingFeed.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {cleanError} from '#/lib/strings/errors' 7 - import {isNative, isWeb} from '#/platform/detection' 8 7 import {useModerationOpts} from '#/state/preferences/moderation-opts' 9 8 import {getFeedTypeFromUri} from '#/state/queries/feed' 10 9 import {useProfileQuery} from '#/state/queries/profile' ··· 15 14 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 16 15 import * as ProfileCard from '#/components/ProfileCard' 17 16 import {Text} from '#/components/Typography' 17 + import {IS_NATIVE, IS_WEB} from '#/env' 18 18 19 19 export function MissingFeed({ 20 20 style, ··· 83 83 a.italic, 84 84 ]} 85 85 numberOfLines={1}> 86 - {isWeb ? ( 86 + {IS_WEB ? ( 87 87 <Trans>Click for information</Trans> 88 88 ) : ( 89 89 <Trans>Tap for information</Trans> ··· 205 205 </> 206 206 )} 207 207 </View> 208 - {isNative && ( 208 + {IS_NATIVE && ( 209 209 <Button 210 210 label={_(msg`Close`)} 211 211 onPress={() => control.close()}
+4 -4
src/view/com/feeds/ProfileFeedgens.tsx
··· 20 20 21 21 import {cleanError} from '#/lib/strings/errors' 22 22 import {logger} from '#/logger' 23 - import {isIOS, isNative, isWeb} from '#/platform/detection' 24 23 import {usePreferencesQuery} from '#/state/queries/preferences' 25 24 import {RQKEY, useProfileFeedgensQuery} from '#/state/queries/profile-feedgens' 26 25 import {useSession} from '#/state/session' ··· 33 32 import * as FeedCard from '#/components/FeedCard' 34 33 import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' 35 34 import {ListFooter} from '#/components/Lists' 35 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 36 36 37 37 const LOADING = {_reactKey: '__loading__'} 38 38 const EMPTY = {_reactKey: '__empty__'} ··· 111 111 112 112 const onScrollToTop = useCallback(() => { 113 113 scrollElRef.current?.scrollToOffset({ 114 - animated: isNative, 114 + animated: IS_NATIVE, 115 115 offset: -headerOffset, 116 116 }) 117 117 queryClient.invalidateQueries({queryKey: RQKEY(did)}) ··· 194 194 return ( 195 195 <View 196 196 style={[ 197 - (index !== 0 || isWeb) && a.border_t, 197 + (index !== 0 || IS_WEB) && a.border_t, 198 198 t.atoms.border_contrast_low, 199 199 a.px_lg, 200 200 a.py_lg, ··· 218 218 ) 219 219 220 220 useEffect(() => { 221 - if (isIOS && enabled && scrollElRef.current) { 221 + if (IS_IOS && enabled && scrollElRef.current) { 222 222 const nativeTag = findNodeHandle(scrollElRef.current) 223 223 setScrollViewTag(nativeTag) 224 224 }
+5 -5
src/view/com/lightbox/ImageViewing/index.tsx
··· 41 41 42 42 import {type Dimensions} from '#/lib/media/types' 43 43 import {colors, s} from '#/lib/styles' 44 - import {isIOS} from '#/platform/detection' 45 44 import {type Lightbox} from '#/state/lightbox' 46 45 import {Button} from '#/view/com/util/forms/Button' 47 46 import {Text} from '#/view/com/util/text/Text' 48 47 import {ScrollView} from '#/view/com/util/Views' 49 48 import {useTheme} from '#/alf' 50 49 import {setSystemUITheme} from '#/alf/util/systemUI' 50 + import {IS_IOS} from '#/env' 51 51 import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army' 52 52 import {type ImageSource, type Transform} from './@types' 53 53 import ImageDefaultHeader from './components/ImageDefaultHeader' ··· 59 59 const PIXEL_RATIO = PixelRatio.get() 60 60 61 61 const SLOW_SPRING: WithSpringConfig = { 62 - mass: isIOS ? 1.25 : 0.75, 62 + mass: IS_IOS ? 1.25 : 0.75, 63 63 damping: 300, 64 64 stiffness: 800, 65 65 restDisplacementThreshold: 0.01, 66 66 } 67 67 const FAST_SPRING: WithSpringConfig = { 68 - mass: isIOS ? 1.25 : 0.75, 68 + mass: IS_IOS ? 1.25 : 0.75, 69 69 damping: 150, 70 70 stiffness: 900, 71 71 restDisplacementThreshold: 0.01, ··· 248 248 ) 249 249 opacity -= dragProgress 250 250 } 251 - const factor = isIOS ? 100 : 50 251 + const factor = IS_IOS ? 100 : 50 252 252 return { 253 253 opacity: Math.round(opacity * factor) / factor, 254 254 } ··· 686 686 maxHeight: '100%', 687 687 }, 688 688 footerText: { 689 - paddingBottom: isIOS ? 20 : 16, 689 + paddingBottom: IS_IOS ? 20 : 16, 690 690 }, 691 691 footerBtns: { 692 692 flexDirection: 'row',
+4 -4
src/view/com/lists/ProfileLists.tsx
··· 20 20 21 21 import {cleanError} from '#/lib/strings/errors' 22 22 import {logger} from '#/logger' 23 - import {isIOS, isNative, isWeb} from '#/platform/detection' 24 23 import {usePreferencesQuery} from '#/state/queries/preferences' 25 24 import {RQKEY, useProfileListsQuery} from '#/state/queries/profile-lists' 26 25 import {useSession} from '#/state/session' ··· 33 32 import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 34 33 import * as ListCard from '#/components/ListCard' 35 34 import {ListFooter} from '#/components/Lists' 35 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 36 36 37 37 const LOADING = {_reactKey: '__loading__'} 38 38 const EMPTY = {_reactKey: '__empty__'} ··· 111 111 112 112 const onScrollToTop = useCallback(() => { 113 113 scrollElRef.current?.scrollToOffset({ 114 - animated: isNative, 114 + animated: IS_NATIVE, 115 115 offset: -headerOffset, 116 116 }) 117 117 queryClient.invalidateQueries({queryKey: RQKEY(did)}) ··· 193 193 return ( 194 194 <View 195 195 style={[ 196 - (index !== 0 || isWeb) && a.border_t, 196 + (index !== 0 || IS_WEB) && a.border_t, 197 197 t.atoms.border_contrast_low, 198 198 a.px_lg, 199 199 a.py_lg, ··· 217 217 ) 218 218 219 219 useEffect(() => { 220 - if (isIOS && enabled && scrollElRef.current) { 220 + if (IS_IOS && enabled && scrollElRef.current) { 221 221 const nativeTag = findNodeHandle(scrollElRef.current) 222 222 setScrollViewTag(nativeTag) 223 223 }
+3 -3
src/view/com/modals/DeleteAccount.tsx
··· 16 16 import {cleanError} from '#/lib/strings/errors' 17 17 import {colors, gradients, s} from '#/lib/styles' 18 18 import {useTheme} from '#/lib/ThemeContext' 19 - import {isAndroid, isWeb} from '#/platform/detection' 20 19 import {useModalControls} from '#/state/modals' 21 20 import {useAgent, useSession, useSessionApi} from '#/state/session' 22 21 import {atoms as a, useTheme as useNewTheme} from '#/alf' 23 22 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 24 23 import {Text as NewText} from '#/components/Typography' 24 + import {IS_ANDROID, IS_WEB} from '#/env' 25 25 import {resetToTab} from '../../../Navigation' 26 26 import {ErrorMessage} from '../util/error/ErrorMessage' 27 27 import {Text} from '../util/text/Text' 28 28 import * as Toast from '../util/Toast' 29 29 import {ScrollView, TextInput} from './util' 30 30 31 - export const snapPoints = isAndroid ? ['90%'] : ['55%'] 31 + export const snapPoints = IS_ANDROID ? ['90%'] : ['55%'] 32 32 33 33 export function Component({}: {}) { 34 34 const pal = usePalette('default') ··· 173 173 </> 174 174 )} 175 175 176 - <View style={[!isWeb && a.px_xl]}> 176 + <View style={[!IS_WEB && a.px_xl]}> 177 177 <View 178 178 style={[ 179 179 a.w_full,
+5 -5
src/view/com/modals/UserAddRemoveLists.tsx
··· 14 14 import {cleanError} from '#/lib/strings/errors' 15 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 16 import {s} from '#/lib/styles' 17 - import {isAndroid, isMobileWeb, isWeb} from '#/platform/detection' 18 17 import {useModalControls} from '#/state/modals' 19 18 import { 20 19 getMembership, ··· 24 23 useListMembershipRemoveMutation, 25 24 } from '#/state/queries/list-memberships' 26 25 import {useSession} from '#/state/session' 26 + import {IS_ANDROID, IS_WEB, IS_WEB_MOBILE} from '#/env' 27 27 import {MyLists} from '../lists/MyLists' 28 28 import {Button} from '../util/forms/Button' 29 29 import {Text} from '../util/text/Text' ··· 56 56 }, [closeModal]) 57 57 58 58 const listStyle = React.useMemo(() => { 59 - if (isMobileWeb) { 59 + if (IS_WEB_MOBILE) { 60 60 return [pal.border, {height: screenHeight / 2}] 61 - } else if (isWeb) { 61 + } else if (IS_WEB) { 62 62 return [pal.border, {height: screenHeight / 1.5}] 63 63 } 64 64 ··· 243 243 244 244 const styles = StyleSheet.create({ 245 245 container: { 246 - paddingHorizontal: isWeb ? 0 : 16, 246 + paddingHorizontal: IS_WEB ? 0 : 16, 247 247 }, 248 248 btns: { 249 249 position: 'relative', ··· 252 252 justifyContent: 'center', 253 253 gap: 10, 254 254 paddingTop: 10, 255 - paddingBottom: isAndroid ? 10 : 0, 255 + paddingBottom: IS_ANDROID ? 10 : 0, 256 256 borderTopWidth: StyleSheet.hairlineWidth, 257 257 }, 258 258 footerBtn: {
+2 -2
src/view/com/pager/PagerHeaderContext.tsx
··· 1 1 import React, {useContext} from 'react' 2 2 import {type SharedValue} from 'react-native-reanimated' 3 3 4 - import {isNative} from '#/platform/detection' 4 + import {IS_NATIVE} from '#/env' 5 5 6 6 export const PagerHeaderContext = React.createContext<{ 7 7 scrollY: SharedValue<number> ··· 37 37 38 38 export function usePagerHeaderContext() { 39 39 const ctx = useContext(PagerHeaderContext) 40 - if (isNative) { 40 + if (IS_NATIVE) { 41 41 if (!ctx) { 42 42 throw new Error( 43 43 'usePagerHeaderContext must be used within a HeaderProvider',
+3 -3
src/view/com/pager/PagerWithHeader.tsx
··· 18 18 19 19 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' 20 20 import {ScrollProvider} from '#/lib/ScrollContext' 21 - import {isIOS} from '#/platform/detection' 22 21 import { 23 22 Pager, 24 23 type PagerRef, 25 24 type RenderTabBarFnProps, 26 25 } from '#/view/com/pager/Pager' 27 26 import {useTheme} from '#/alf' 27 + import {IS_IOS} from '#/env' 28 28 import {type ListMethods} from '../util/List' 29 29 import {PagerHeaderProvider} from './PagerHeaderContext' 30 30 import {TabBar} from './TabBar' ··· 273 273 const headerRef = useRef(null) 274 274 return ( 275 275 <Animated.View 276 - pointerEvents={isIOS ? 'auto' : 'box-none'} 276 + pointerEvents={IS_IOS ? 'auto' : 'box-none'} 277 277 style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}> 278 278 <View 279 279 ref={headerRef} 280 - pointerEvents={isIOS ? 'auto' : 'box-none'} 280 + pointerEvents={IS_IOS ? 'auto' : 'box-none'} 281 281 collapsable={false}> 282 282 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})} 283 283 {
+2 -2
src/view/com/posts/CustomFeedEmptyState.tsx
··· 13 13 import {type NavigationProp} from '#/lib/routes/types' 14 14 import {s} from '#/lib/styles' 15 15 import {logger} from '#/logger' 16 - import {isWeb} from '#/platform/detection' 17 16 import {useFeedFeedbackContext} from '#/state/feed-feedback' 18 17 import {useSession} from '#/state/session' 18 + import {IS_WEB} from '#/env' 19 19 import {Button} from '../util/forms/Button' 20 20 import {Text} from '../util/text/Text' 21 21 ··· 44 44 const navigation = useNavigation<NavigationProp>() 45 45 46 46 const onPressFindAccounts = React.useCallback(() => { 47 - if (isWeb) { 47 + if (IS_WEB) { 48 48 navigation.navigate('Search', {}) 49 49 } else { 50 50 navigation.navigate('SearchTab')
+2 -2
src/view/com/posts/FollowingEmptyState.tsx
··· 11 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 12 import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 - import {isWeb} from '#/platform/detection' 14 + import {IS_WEB} from '#/env' 15 15 import {Button} from '../util/forms/Button' 16 16 import {Text} from '../util/text/Text' 17 17 ··· 21 21 const navigation = useNavigation<NavigationProp>() 22 22 23 23 const onPressFindAccounts = React.useCallback(() => { 24 - if (isWeb) { 24 + if (IS_WEB) { 25 25 navigation.navigate('Search', {}) 26 26 } else { 27 27 navigation.navigate('SearchTab')
+2 -2
src/view/com/posts/FollowingEndOfFeed.tsx
··· 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 11 import {type NavigationProp} from '#/lib/routes/types' 12 12 import {s} from '#/lib/styles' 13 - import {isWeb} from '#/platform/detection' 13 + import {IS_WEB} from '#/env' 14 14 import {Button} from '../util/forms/Button' 15 15 import {Text} from '../util/text/Text' 16 16 ··· 20 20 const navigation = useNavigation<NavigationProp>() 21 21 22 22 const onPressFindAccounts = React.useCallback(() => { 23 - if (isWeb) { 23 + if (IS_WEB) { 24 24 navigation.navigate('Search', {}) 25 25 } else { 26 26 navigation.navigate('SearchTab')
+4 -4
src/view/com/posts/PostFeed.tsx
··· 34 34 import {logEvent, useGate} from '#/lib/statsig/statsig' 35 35 import {isNetworkError} from '#/lib/strings/errors' 36 36 import {logger} from '#/logger' 37 - import {isIOS, isNative, isWeb} from '#/platform/detection' 38 37 import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow' 39 38 import {listenPostCreated} from '#/state/events' 40 39 import {useFeedFeedbackContext} from '#/state/feed-feedback' ··· 70 69 } from '#/components/feeds/PostFeedVideoGridRow' 71 70 import {TrendingInterstitial} from '#/components/interstitials/Trending' 72 71 import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos' 72 + import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' 73 73 import {DiscoverFeedLiveEventFeedsAndTrendingBanner} from '#/features/liveEvents/components/DiscoverFeedLiveEventFeedsAndTrendingBanner' 74 74 import {ComposerPrompt} from '../feeds/ComposerPrompt' 75 75 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' ··· 243 243 const [feedType, feedUriOrActorDid, feedTab] = feed.split('|') 244 244 const {gtMobile} = useBreakpoints() 245 245 const {rightNavVisible} = useLayoutBreakpoints() 246 - const areVideoFeedsEnabled = isNative 246 + const areVideoFeedsEnabled = IS_NATIVE 247 247 248 248 const [hasPressedShowLessUris, setHasPressedShowLessUris] = useState( 249 249 () => new Set<string>(), ··· 873 873 * reach the end, so that content isn't cut off by the bottom of the 874 874 * screen. 875 875 */ 876 - const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2) 876 + const offset = Math.max(headerOffset, 32) * (IS_WEB ? 1 : 2) 877 877 878 878 return isFetchingNextPage ? ( 879 879 <View style={[styles.feedFooter]}> ··· 1024 1024 } 1025 1025 initialNumToRender={initialNumToRenderOverride ?? initialNumToRender} 1026 1026 windowSize={9} 1027 - maxToRenderPerBatch={isIOS ? 5 : 1} 1027 + maxToRenderPerBatch={IS_IOS ? 5 : 1} 1028 1028 updateCellsBatchingPeriod={40} 1029 1029 onItemSeen={onItemSeen} 1030 1030 />
+2 -2
src/view/com/profile/ProfileFollows.tsx
··· 8 8 import {type NavigationProp} from '#/lib/routes/types' 9 9 import {cleanError} from '#/lib/strings/errors' 10 10 import {logger} from '#/logger' 11 - import {isWeb} from '#/platform/detection' 12 11 import {useProfileFollowsQuery} from '#/state/queries/profile-follows' 13 12 import {useResolveDidQuery} from '#/state/queries/resolve-uri' 14 13 import {useSession} from '#/state/session' 15 14 import {FindContactsBannerNUX} from '#/components/contacts/FindContactsBannerNUX' 16 15 import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2' 17 16 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 17 + import {IS_WEB} from '#/env' 18 18 import {List} from '../util/List' 19 19 import {ProfileCardWithFollowBtn} from './ProfileCard' 20 20 ··· 49 49 const navigation = useNavigation<NavigationProp>() 50 50 51 51 const onPressFindAccounts = React.useCallback(() => { 52 - if (isWeb) { 52 + if (IS_WEB) { 53 53 navigation.navigate('Search', {}) 54 54 } else { 55 55 navigation.navigate('SearchTab')
+7 -5
src/view/com/profile/ProfileMenu.tsx
··· 12 12 import {shareText, shareUrl} from '#/lib/sharing' 13 13 import {toShareUrl} from '#/lib/strings/url-helpers' 14 14 import {logger} from '#/logger' 15 - import {isWeb} from '#/platform/detection' 16 15 import {type Shadow} from '#/state/cache/types' 17 16 import {useModalControls} from '#/state/modals' 18 17 import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs' ··· 61 60 import {useFullVerificationState} from '#/components/verification' 62 61 import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt' 63 62 import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' 63 + import {IS_WEB} from '#/env' 64 64 import {Dot} from '#/features/nuxs/components/Dot' 65 65 import {Gradient} from '#/features/nuxs/components/Gradient' 66 66 import {useDevMode} from '#/storage/hooks/dev-mode' ··· 266 266 <Menu.Item 267 267 testID="profileHeaderDropdownShareBtn" 268 268 label={ 269 - isWeb ? _(msg`Copy link to profile`) : _(msg`Share via...`) 269 + IS_WEB ? _(msg`Copy link to profile`) : _(msg`Share via...`) 270 270 } 271 271 onPress={() => { 272 272 if (showLoggedOutWarning) { ··· 276 276 } 277 277 }}> 278 278 <Menu.ItemText> 279 - {isWeb ? ( 279 + {IS_WEB ? ( 280 280 <Trans>Copy link to profile</Trans> 281 281 ) : ( 282 282 <Trans>Share via...</Trans> 283 283 )} 284 284 </Menu.ItemText> 285 - <Menu.ItemIcon icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} /> 285 + <Menu.ItemIcon 286 + icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon} 287 + /> 286 288 </Menu.Item> 287 289 <Menu.Item 288 290 testID="profileHeaderDropdownSearchBtn" ··· 382 384 a.flex_0, 383 385 { 384 386 color: t.palette.primary_500, 385 - right: isWeb ? -8 : -4, 387 + right: IS_WEB ? -8 : -4, 386 388 }, 387 389 ]}> 388 390 <Trans>New</Trans>
+4 -4
src/view/com/util/Link.tsx
··· 25 25 linkRequiresWarning, 26 26 } from '#/lib/strings/url-helpers' 27 27 import {type TypographyVariant} from '#/lib/ThemeContext' 28 - import {isAndroid, isWeb} from '#/platform/detection' 29 28 import {emitSoftReset} from '#/state/events' 30 29 import {useModalControls} from '#/state/modals' 31 30 import {WebAuxClickWrapper} from '#/view/com/util/WebAuxClickWrapper' 32 31 import {useTheme} from '#/alf' 33 32 import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' 33 + import {IS_ANDROID, IS_WEB} from '#/env' 34 34 import {router} from '../../../routes' 35 35 import {PressableWithHover} from './PressableWithHover' 36 36 import {Text} from './text/Text' ··· 130 130 android_ripple={{ 131 131 color: t.atoms.bg_contrast_25.backgroundColor, 132 132 }} 133 - unstable_pressDelay={isAndroid ? 90 : undefined}> 133 + unstable_pressDelay={IS_ANDROID ? 90 : undefined}> 134 134 {/* @ts-ignore web only -prf */} 135 135 <View style={style} href={anchorHref}> 136 136 {children ? children : <Text>{title || 'link'}</Text>} ··· 219 219 }) 220 220 } 221 221 if ( 222 - isWeb && 222 + IS_WEB && 223 223 href !== '#' && 224 224 e != null && 225 225 isModifiedEvent(e as React.MouseEvent) ··· 323 323 onBeforePress, 324 324 ...props 325 325 }: TextLinkOnWebOnlyProps) { 326 - if (isWeb) { 326 + if (IS_WEB) { 327 327 return ( 328 328 <TextLink 329 329 testID={testID}
+3 -3
src/view/com/util/List.tsx
··· 11 11 import {useDedupe} from '#/lib/hooks/useDedupe' 12 12 import {useScrollHandlers} from '#/lib/ScrollContext' 13 13 import {addStyle} from '#/lib/styles' 14 - import {isIOS} from '#/platform/detection' 15 14 import {useLightbox} from '#/state/lightbox' 16 15 import {useTheme} from '#/alf' 16 + import {IS_IOS} from '#/env' 17 17 import {FlatList_INTERNAL} from './Views' 18 18 19 19 export type ListMethods = FlatList_INTERNAL ··· 94 94 } 95 95 } 96 96 97 - if (isIOS) { 97 + if (IS_IOS) { 98 98 runOnJS(dedupe)(updateActiveVideoViewAsync) 99 99 } 100 100 }, ··· 184 184 185 185 // We only want to use this context value on iOS because the `scrollsToTop` prop is iOS-only 186 186 // removing it saves us a re-render on Android 187 - const useAllowScrollToTop = isIOS ? useAllowScrollToTopIOS : () => undefined 187 + const useAllowScrollToTop = IS_IOS ? useAllowScrollToTopIOS : () => undefined 188 188 function useAllowScrollToTopIOS() { 189 189 const {activeLightbox} = useLightbox() 190 190 return !activeLightbox
+8 -8
src/view/com/util/MainScrollProvider.tsx
··· 9 9 import EventEmitter from 'eventemitter3' 10 10 11 11 import {ScrollProvider} from '#/lib/ScrollContext' 12 - import {isNative, isWeb} from '#/platform/detection' 13 12 import {useMinimalShellMode} from '#/state/shell' 14 13 import {useShellLayout} from '#/state/shell/shell-layout' 14 + import {IS_NATIVE, IS_WEB} from '#/env' 15 15 16 16 const WEB_HIDE_SHELL_THRESHOLD = 200 17 17 ··· 35 35 ) 36 36 37 37 useEffect(() => { 38 - if (isWeb) { 38 + if (IS_WEB) { 39 39 return listenToForcedWindowScroll(() => { 40 40 startDragOffset.set(null) 41 41 startMode.set(null) ··· 48 48 (e: NativeScrollEvent) => { 49 49 'worklet' 50 50 const offsetY = Math.max(0, e.contentOffset.y) 51 - if (isNative) { 51 + if (IS_NATIVE) { 52 52 const startDragOffsetValue = startDragOffset.get() 53 53 if (startDragOffsetValue === null) { 54 54 return ··· 75 75 (e: NativeScrollEvent) => { 76 76 'worklet' 77 77 const offsetY = Math.max(0, e.contentOffset.y) 78 - if (isNative) { 78 + if (IS_NATIVE) { 79 79 startDragOffset.set(offsetY) 80 80 startMode.set(headerMode.get()) 81 81 } ··· 86 86 const onEndDrag = useCallback( 87 87 (e: NativeScrollEvent) => { 88 88 'worklet' 89 - if (isNative) { 89 + if (IS_NATIVE) { 90 90 if (e.velocity && e.velocity.y !== 0) { 91 91 // If we detect a velocity, wait for onMomentumEnd to snap. 92 92 return ··· 100 100 const onMomentumEnd = useCallback( 101 101 (e: NativeScrollEvent) => { 102 102 'worklet' 103 - if (isNative) { 103 + if (IS_NATIVE) { 104 104 snapToClosestState(e) 105 105 } 106 106 }, ··· 111 111 (e: NativeScrollEvent) => { 112 112 'worklet' 113 113 const offsetY = Math.max(0, e.contentOffset.y) 114 - if (isNative) { 114 + if (IS_NATIVE) { 115 115 const startDragOffsetValue = startDragOffset.get() 116 116 const startModeValue = startMode.get() 117 117 if (startDragOffsetValue === null || startModeValue === null) { ··· 177 177 178 178 const emitter = new EventEmitter() 179 179 180 - if (isWeb) { 180 + if (IS_WEB) { 181 181 const originalScroll = window.scroll 182 182 window.scroll = function () { 183 183 emitter.emit('forced-scroll')
+3 -3
src/view/com/util/PostMeta.tsx
··· 12 12 import {sanitizeDisplayName} from '#/lib/strings/display-names' 13 13 import {sanitizeHandle} from '#/lib/strings/handles' 14 14 import {niceDate} from '#/lib/strings/time' 15 - import {isAndroid} from '#/platform/detection' 16 15 import {useProfileShadow} from '#/state/cache/profile-shadow' 17 16 import {precacheProfile} from '#/state/queries/profile' 18 17 import {atoms as a, platform, useTheme, web} from '#/alf' ··· 21 20 import {Text} from '#/components/Typography' 22 21 import {useSimpleVerificationState} from '#/components/verification' 23 22 import {VerificationCheck} from '#/components/verification/VerificationCheck' 23 + import {IS_ANDROID} from '#/env' 24 24 import {TimeElapsed} from './TimeElapsed' 25 25 import {PreviewableUserAvatar} from './UserAvatar' 26 26 ··· 152 152 a.pl_xs, 153 153 a.text_md, 154 154 a.leading_tight, 155 - isAndroid && a.flex_grow, 155 + IS_ANDROID && a.flex_grow, 156 156 a.text_right, 157 157 t.atoms.text_contrast_medium, 158 158 web({ 159 159 whiteSpace: 'nowrap', 160 160 }), 161 161 ]}> 162 - {!isAndroid && ( 162 + {!IS_ANDROID && ( 163 163 <Text 164 164 style={[ 165 165 a.text_md,
+7 -7
src/view/com/util/UserAvatar.tsx
··· 30 30 import {isCancelledError} from '#/lib/strings/errors' 31 31 import {sanitizeHandle} from '#/lib/strings/handles' 32 32 import {logger} from '#/logger' 33 - import {isAndroid, isNative, isWeb} from '#/platform/detection' 34 33 import { 35 34 type ComposerImage, 36 35 compressImage, ··· 54 53 import {MediaInsetBorder} from '#/components/MediaInsetBorder' 55 54 import * as Menu from '#/components/Menu' 56 55 import {ProfileHoverCard} from '#/components/ProfileHoverCard' 56 + import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env' 57 57 import type * as bsky from '#/types/bsky' 58 58 59 59 export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler' ··· 88 88 onBeforePress?: () => void 89 89 } 90 90 91 - const BLUR_AMOUNT = isWeb ? 5 : 100 91 + const BLUR_AMOUNT = IS_WEB ? 5 : 100 92 92 93 93 let DefaultAvatar = ({ 94 94 type, ··· 286 286 }, [size, style]) 287 287 288 288 return avatar && 289 - !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( 289 + !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? ( 290 290 <View style={containerStyle}> 291 291 {usePlainRNImage ? ( 292 292 <RNImage ··· 394 394 } 395 395 396 396 try { 397 - if (isNative) { 397 + if (IS_NATIVE) { 398 398 onSelectNewAvatar( 399 399 await compressIfNeeded( 400 400 await openCropper({ ··· 464 464 </Menu.Trigger> 465 465 <Menu.Outer showCancel> 466 466 <Menu.Group> 467 - {isNative && ( 467 + {IS_NATIVE && ( 468 468 <Menu.Item 469 469 testID="changeAvatarCameraBtn" 470 470 label={_(msg`Upload from Camera`)} ··· 481 481 label={_(msg`Upload from Library`)} 482 482 onPress={onOpenLibrary}> 483 483 <Menu.ItemText> 484 - {isNative ? ( 484 + {IS_NATIVE ? ( 485 485 <Trans>Upload from Library</Trans> 486 486 ) : ( 487 487 <Trans>Upload from Files</Trans> ··· 571 571 <ProfileHoverCard did={profile.did} disable={disableHoverCard}> 572 572 {disableNavigation ? ( 573 573 avatarEl 574 - ) : status.isActive && (isNative || isTouchDevice) ? ( 574 + ) : status.isActive && (IS_NATIVE || isTouchDevice) ? ( 575 575 <> 576 576 <Button 577 577 label={_(
+5 -5
src/view/com/util/UserBanner.tsx
··· 14 14 import {type PickerImage} from '#/lib/media/picker.shared' 15 15 import {isCancelledError} from '#/lib/strings/errors' 16 16 import {logger} from '#/logger' 17 - import {isAndroid, isNative} from '#/platform/detection' 18 17 import { 19 18 type ComposerImage, 20 19 compressImage, ··· 32 31 import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive' 33 32 import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 34 33 import * as Menu from '#/components/Menu' 34 + import {IS_ANDROID, IS_NATIVE} from '#/env' 35 35 36 36 export function UserBanner({ 37 37 type, ··· 75 75 } 76 76 77 77 try { 78 - if (isNative) { 78 + if (IS_NATIVE) { 79 79 onSelectNewBanner?.( 80 80 await compressIfNeeded( 81 81 await openCropper({ ··· 153 153 </Menu.Trigger> 154 154 <Menu.Outer showCancel> 155 155 <Menu.Group> 156 - {isNative && ( 156 + {IS_NATIVE && ( 157 157 <Menu.Item 158 158 testID="changeBannerCameraBtn" 159 159 label={_(msg`Upload from Camera`)} ··· 170 170 label={_(msg`Upload from Library`)} 171 171 onPress={onOpenLibrary}> 172 172 <Menu.ItemText> 173 - {isNative ? ( 173 + {IS_NATIVE ? ( 174 174 <Trans>Upload from Library</Trans> 175 175 ) : ( 176 176 <Trans>Upload from Files</Trans> ··· 207 207 /> 208 208 </> 209 209 ) : banner && 210 - !((moderation?.blur && isAndroid) /* android crashes with blur */) ? ( 210 + !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? ( 211 211 <Image 212 212 testID="userBannerImage" 213 213 style={[styles.bannerImage, t.atoms.bg_contrast_25]}
+2 -2
src/view/com/util/ViewSelector.tsx
··· 13 13 import {usePalette} from '#/lib/hooks/usePalette' 14 14 import {clamp} from '#/lib/numbers' 15 15 import {colors, s} from '#/lib/styles' 16 - import {isAndroid} from '#/platform/detection' 16 + import {IS_ANDROID} from '#/env' 17 17 import {Text} from './text/Text' 18 18 import {FlatList_INTERNAL} from './Views' 19 19 ··· 120 120 renderItem={renderItemInternal} 121 121 ListFooterComponent={ListFooterComponent} 122 122 // NOTE sticky header disabled on android due to major performance issues -prf 123 - stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES} 123 + stickyHeaderIndices={IS_ANDROID ? undefined : STICKY_HEADER_INDICES} 124 124 onScroll={onScroll} 125 125 onEndReached={onEndReached} 126 126 refreshControl={
+2 -2
src/view/com/util/fab/FABInner.tsx
··· 12 12 import {useHaptics} from '#/lib/haptics' 13 13 import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform' 14 14 import {clamp} from '#/lib/numbers' 15 - import {isWeb} from '#/platform/detection' 16 15 import {ios, useBreakpoints, useTheme} from '#/alf' 17 16 import {atoms as a} from '#/alf' 17 + import {IS_WEB} from '#/env' 18 18 19 19 export interface FABProps extends ComponentProps<typeof Pressable> { 20 20 testID?: string ··· 84 84 }, 85 85 outer: { 86 86 // @ts-ignore web-only 87 - position: isWeb ? 'fixed' : 'absolute', 87 + position: IS_WEB ? 'fixed' : 'absolute', 88 88 zIndex: 1, 89 89 cursor: 'pointer', 90 90 },
+2 -2
src/view/com/util/layouts/LoggedOutLayout.tsx
··· 5 5 import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible' 6 6 import {usePalette} from '#/lib/hooks/usePalette' 7 7 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 8 - import {isWeb} from '#/platform/detection' 9 8 import {atoms as a} from '#/alf' 9 + import {IS_WEB} from '#/env' 10 10 import {Text} from '../text/Text' 11 11 12 12 export const LoggedOutLayout = ({ ··· 79 79 contentContainerStyle={styles.scrollViewContentContainer} 80 80 keyboardShouldPersistTaps="handled" 81 81 keyboardDismissMode="on-drag"> 82 - <View style={[styles.contentWrapper, isWeb && a.my_auto]}> 82 + <View style={[styles.contentWrapper, IS_WEB && a.my_auto]}> 83 83 {children} 84 84 </View> 85 85 </ScrollView>
+3 -3
src/view/com/util/text/Text.tsx
··· 5 5 import {lh, s} from '#/lib/styles' 6 6 import {type TypographyVariant, useTheme} from '#/lib/ThemeContext' 7 7 import {logger} from '#/logger' 8 - import {isIOS, isWeb} from '#/platform/detection' 9 8 import {applyFonts, useAlf} from '#/alf' 10 9 import { 11 10 childHasEmoji, 12 11 renderChildrenWithEmoji, 13 12 type StringChild, 14 13 } from '#/alf/typography' 14 + import {IS_IOS, IS_WEB} from '#/env' 15 15 16 16 export type CustomTextProps = Omit<TextProps, 'children'> & { 17 17 type?: TypographyVariant ··· 81 81 } 82 82 83 83 return { 84 - uiTextView: selectable && isIOS, 84 + uiTextView: selectable && IS_IOS, 85 85 selectable, 86 86 style: flattened, 87 - dataSet: isWeb 87 + dataSet: IS_WEB 88 88 ? Object.assign({tooltip: title}, dataSet || {}) 89 89 : undefined, 90 90 ...props,
+4 -4
src/view/screens/Feeds.tsx
··· 16 16 } from '#/lib/routes/types' 17 17 import {cleanError} from '#/lib/strings/errors' 18 18 import {s} from '#/lib/styles' 19 - import {isNative, isWeb} from '#/platform/detection' 20 19 import { 21 20 type SavedFeedItem, 22 21 useGetPopularFeedsQuery, ··· 46 45 import * as Layout from '#/components/Layout' 47 46 import {Link} from '#/components/Link' 48 47 import * as ListCard from '#/components/ListCard' 48 + import {IS_NATIVE, IS_WEB} from '#/env' 49 49 50 50 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'> 51 51 ··· 385 385 const onChangeSearchFocus = React.useCallback( 386 386 (focus: boolean) => { 387 387 if (focus && searchBarIndex > -1) { 388 - if (isNative) { 388 + if (IS_NATIVE) { 389 389 // scrollToIndex scrolls the exact right amount, so use if available 390 390 listRef.current?.scrollToIndex({ 391 391 index: searchBarIndex, ··· 681 681 return ( 682 682 <View 683 683 style={ 684 - isWeb 684 + IS_WEB 685 685 ? [ 686 686 a.flex_row, 687 687 a.px_md, ··· 717 717 return ( 718 718 <View 719 719 style={ 720 - isWeb 720 + IS_WEB 721 721 ? [a.flex_row, a.px_md, a.pt_lg, a.pb_lg, a.gap_md] 722 722 : [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md] 723 723 }>
+2 -2
src/view/screens/Home.tsx
··· 12 12 type NativeStackScreenProps, 13 13 } from '#/lib/routes/types' 14 14 import {logEvent} from '#/lib/statsig/statsig' 15 - import {isWeb} from '#/platform/detection' 16 15 import {emitSoftReset} from '#/state/events' 17 16 import { 18 17 type SavedFeedSourceInfo, ··· 37 36 import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed' 38 37 import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned' 39 38 import * as Layout from '#/components/Layout' 39 + import {IS_WEB} from '#/env' 40 40 import {useDemoMode} from '#/storage/hooks/demo-mode' 41 41 42 42 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'> ··· 48 48 usePinnedFeedsInfos() 49 49 50 50 React.useEffect(() => { 51 - if (isWeb && !currentAccount) { 51 + if (IS_WEB && !currentAccount) { 52 52 const getParams = new URLSearchParams(window.location.search) 53 53 const splash = getParams.get('splash') 54 54 if (splash === 'true') {
+3 -3
src/view/screens/Notifications.tsx
··· 14 14 } from '#/lib/routes/types' 15 15 import {s} from '#/lib/styles' 16 16 import {logger} from '#/logger' 17 - import {isNative} from '#/platform/detection' 18 17 import {emitSoftReset, listenSoftReset} from '#/state/events' 19 18 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed' 20 19 import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings' ··· 39 38 import * as Layout from '#/components/Layout' 40 39 import {InlineLinkText, Link} from '#/components/Link' 41 40 import {Loader} from '#/components/Loader' 41 + import {IS_NATIVE} from '#/env' 42 42 43 43 // We don't currently persist this across reloads since 44 44 // you gotta visit All to clear the badge anyway. ··· 197 197 // event handlers 198 198 // = 199 199 const scrollToTop = useCallback(() => { 200 - scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0}) 200 + scrollElRef.current?.scrollToOffset({animated: IS_NATIVE, offset: 0}) 201 201 setMinimalShellMode(false) 202 202 }, [scrollElRef, setMinimalShellMode]) 203 203 ··· 227 227 // on focus, check for latest, but only invalidate if the user 228 228 // isnt scrolled down to avoid moving content underneath them 229 229 let currentIsScrolledDown 230 - if (isNative) { 230 + if (IS_NATIVE) { 231 231 currentIsScrolledDown = isScrolledDown 232 232 } else { 233 233 // On the web, this isn't always updated in time so
+2 -2
src/view/shell/Drawer.tsx
··· 13 13 import {type NavigationProp} from '#/lib/routes/types' 14 14 import {sanitizeHandle} from '#/lib/strings/handles' 15 15 import {colors} from '#/lib/styles' 16 - import {isWeb} from '#/platform/detection' 17 16 import {emitSoftReset} from '#/state/events' 18 17 import {useKawaiiMode} from '#/state/preferences/kawaii' 19 18 import {useUnreadNotifications} from '#/state/queries/notifications/unread' ··· 55 54 import {Text} from '#/components/Typography' 56 55 import {useSimpleVerificationState} from '#/components/verification' 57 56 import {VerificationCheck} from '#/components/verification/VerificationCheck' 57 + import {IS_WEB} from '#/env' 58 58 59 59 const iconWidth = 26 60 60 ··· 165 165 (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => { 166 166 const state = navigation.getState() 167 167 setDrawerOpen(false) 168 - if (isWeb) { 168 + if (IS_WEB) { 169 169 // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh 170 170 if (tab === 'MyProfile') { 171 171 navigation.navigate('Profile', {name: currentAccount!.handle})
+3 -3
src/view/shell/createNativeStackNavigatorWithAuth.tsx
··· 27 27 28 28 import {PWI_ENABLED} from '#/lib/build-flags' 29 29 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 30 - import {isNative, isWeb} from '#/platform/detection' 31 30 import {useSession} from '#/state/session' 32 31 import {useOnboardingState} from '#/state/shell' 33 32 import { ··· 39 38 import {SignupQueued} from '#/screens/SignupQueued' 40 39 import {atoms as a, useLayoutBreakpoints} from '#/alf' 41 40 import {PolicyUpdateOverlay} from '#/components/PolicyUpdateOverlay' 41 + import {IS_NATIVE, IS_WEB} from '#/env' 42 42 import {BottomBarWeb} from './bottom-bar/BottomBarWeb' 43 43 import {DesktopLeftNav} from './desktop/LeftNav' 44 44 import {DesktopRightNav} from './desktop/RightNav' ··· 115 115 const {setShowLoggedOut} = useLoggedOutViewControls() 116 116 const {isMobile} = useWebMediaQueries() 117 117 const {leftNavMinimal} = useLayoutBreakpoints() 118 - if (!hasSession && (!PWI_ENABLED || activeRouteRequiresAuth || isNative)) { 118 + if (!hasSession && (!PWI_ENABLED || activeRouteRequiresAuth || IS_NATIVE)) { 119 119 return <LoggedOut /> 120 120 } 121 121 if (hasSession && currentAccount?.signupQueued) { ··· 158 158 describe={describe} 159 159 /> 160 160 </View> 161 - {isWeb && ( 161 + {IS_WEB && ( 162 162 <> 163 163 {showBottomBar ? <BottomBarWeb /> : <DesktopLeftNav />} 164 164 {!isMobile && <DesktopRightNav routeName={activeRoute.name} />}
+8 -6
src/view/shell/index.tsx
··· 11 11 import {useNotificationsHandler} from '#/lib/hooks/useNotificationHandler' 12 12 import {useNotificationsRegistration} from '#/lib/notifications/notifications' 13 13 import {isStateAtTabRoot} from '#/lib/routes/helpers' 14 - import {isAndroid, isIOS} from '#/platform/detection' 15 14 import {useDialogFullyExpandedCountContext} from '#/state/dialogs' 16 15 import {useSession} from '#/state/session' 17 16 import { ··· 43 42 import {useAgeAssurance} from '#/ageAssurance' 44 43 import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen' 45 44 import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' 45 + import {IS_ANDROID, IS_IOS} from '#/env' 46 46 import {RoutesContainer, TabsNavigator} from '#/Navigation' 47 47 import {BottomSheetOutlet} from '../../../modules/bottom-sheet' 48 48 import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' ··· 60 60 useNotificationsHandler() 61 61 62 62 useEffect(() => { 63 - if (isAndroid) { 63 + if (IS_ANDROID) { 64 64 const listener = BackHandler.addEventListener('hardwareBackPress', () => { 65 65 return closeAnyActiveElement() 66 66 }) ··· 80 80 const navigation = useNavigation() 81 81 const dedupe = useDedupe(1000) 82 82 useEffect(() => { 83 - if (!isAndroid) return 83 + if (!IS_ANDROID) return 84 84 const onFocusOrBlur = () => { 85 85 setTimeout(() => { 86 86 dedupe(updateActiveViewAsync) ··· 190 190 swipeEdgeWidth={winDim.width} 191 191 swipeMinVelocity={100} 192 192 swipeMinDistance={10} 193 - drawerType={isIOS ? 'slide' : 'front'} 193 + drawerType={IS_IOS ? 'slide' : 'front'} 194 194 overlayStyle={{ 195 195 backgroundColor: select(t.name, { 196 196 light: 'rgba(0, 57, 117, 0.1)', 197 - dark: isAndroid ? 'rgba(16, 133, 254, 0.1)' : 'rgba(1, 82, 168, 0.1)', 197 + dark: IS_ANDROID 198 + ? 'rgba(16, 133, 254, 0.1)' 199 + : 'rgba(1, 82, 168, 0.1)', 198 200 dim: 'rgba(10, 13, 16, 0.8)', 199 201 }), 200 202 }}> ··· 220 222 <SystemBars 221 223 style={{ 222 224 statusBar: 223 - t.name !== 'light' || (isIOS && fullyExpandedCount > 0) 225 + t.name !== 'light' || (IS_IOS && fullyExpandedCount > 0) 224 226 ? 'light' 225 227 : 'dark', 226 228 navigationBar: t.name !== 'light' ? 'light' : 'dark',