···1111import {useSafeAreaInsets} from 'react-native-safe-area-context'
1212import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
13131414-import {isIOS} from '#/platform/detection'
1414+import {IS_IOS} from '#/env'
1515import {
1616 type BottomSheetState,
1717 type BottomSheetViewProps,
···30303131const NativeModule = requireNativeModule('BottomSheet')
32323333-const isIOS15 =
3333+const IS_IOS15 =
3434 Platform.OS === 'ios' &&
3535 // semvar - can be 3 segments, so can't use Number(Platform.Version)
3636 Number(Platform.Version.split('.').at(0)) < 16
···9191 }
92929393 let extraStyles
9494- if (isIOS15 && this.state.viewHeight) {
9494+ if (IS_IOS15 && this.state.viewHeight) {
9595 const {viewHeight} = this.state
9696 const cornerRadius = this.props.cornerRadius ?? 0
9797 if (viewHeight < screenHeight / 2) {
···112112 onStateChange={this.onStateChange}
113113 extraStyles={extraStyles}
114114 onLayout={e => {
115115- if (isIOS15) {
115115+ if (IS_IOS15) {
116116 const {height} = e.nativeEvent.layout
117117 this.setState({viewHeight: height})
118118 }
···153153 const insets = useSafeAreaInsets()
154154 const cornerRadius = rest.cornerRadius ?? 0
155155156156- const sheetHeight = isIOS ? screenHeight - insets.top : screenHeight
156156+ const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight
157157158158 return (
159159 <NativeView
+3-3
src/App.native.tsx
···2323import {ThemeProvider} from '#/lib/ThemeContext'
2424import I18nProvider from '#/locale/i18nProvider'
2525import {logger} from '#/logger'
2626-import {isAndroid, isIOS} from '#/platform/detection'
2726import {Provider as A11yProvider} from '#/state/a11y'
2827import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
2928import {Provider as DialogStateProvider} from '#/state/dialogs'
···6968import {ToastOutlet} from '#/components/Toast'
7069import {Provider as AgeAssuranceV2Provider} from '#/ageAssurance'
7170import {prefetchAgeAssuranceConfig} from '#/ageAssurance'
7171+import {IS_ANDROID, IS_IOS} from '#/env'
7272import {
7373 prefetchLiveEvents,
7474 Provider as LiveEventsProvider,
···7979import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
80808181SplashScreen.preventAutoHideAsync()
8282-if (isIOS) {
8282+if (IS_IOS) {
8383 SystemUI.setBackgroundColorAsync('black')
8484}
8585-if (isAndroid) {
8585+if (IS_ANDROID) {
8686 // iOS is handled by the config plugin -sfn
8787 ScreenOrientation.lockAsync(
8888 ScreenOrientation.OrientationLock.PORTRAIT_UP,
+6-6
src/Navigation.tsx
···4444import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
4545import {bskyTitle} from '#/lib/strings/headings'
4646import {logger} from '#/logger'
4747-import {isNative, isWeb} from '#/platform/detection'
4847import {useUnreadNotifications} from '#/state/queries/notifications/unread'
4948import {useSession} from '#/state/session'
5049import {useLoggedOutViewControls} from '#/state/shell/logged-out'
···138137 EmailDialogScreenID,
139138 useEmailDialogControl,
140139} from '#/components/dialogs/EmailDialog'
140140+import {IS_NATIVE, IS_WEB} from '#/env'
141141import {router} from '#/routes'
142142import {Referrer} from '../modules/expo-bluesky-swiss-army'
143143···842842 // native, since the home tab and the home screen are defined as initial routes, we don't need to return a state
843843 // since it will be created by react-navigation.
844844 if (path.includes('intent/')) {
845845- if (isNative) return
845845+ if (IS_NATIVE) return
846846 return buildStateObject('Flat', 'Home', params)
847847 }
848848849849- if (isNative) {
849849+ if (IS_NATIVE) {
850850 if (name === 'Search') {
851851 return buildStateObject('SearchTab', 'Search', params)
852852 }
···921921 )
922922923923 async function handlePushNotificationEntry() {
924924- if (!isNative) return
924924+ if (!IS_NATIVE) return
925925926926 // deep links take precedence - on android,
927927 // getLastNotificationResponseAsync returns a "notification"
···10691069 navigationRef.dispatch(
10701070 CommonActions.reset({
10711071 index: 0,
10721072- routes: [{name: isNative ? 'HomeTab' : 'Home'}],
10721072+ routes: [{name: IS_NATIVE ? 'HomeTab' : 'Home'}],
10731073 }),
10741074 )
10751075 return Promise.race([
···11031103 initMs,
11041104 })
1105110511061106- if (isWeb) {
11061106+ if (IS_WEB) {
11071107 const referrerInfo = Referrer.getReferrerInfo()
11081108 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
11091109 logEvent('deepLink:referrerReceived', {
+5-5
src/ageAssurance/components/NoAccessScreen.tsx
···1010} from '#/lib/hooks/useCreateSupportLink'
1111import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
1212import {logger} from '#/logger'
1313-import {isWeb} from '#/platform/detection'
1414-import {isNative} from '#/platform/detection'
1513import {useIsBirthdateUpdateAllowed} from '#/state/birthdate'
1614import {useSessionApi} from '#/state/session'
1715import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
···3836 isLegacyBirthdateBug,
3937 useAgeAssuranceRegionConfig,
4038} from '#/ageAssurance/util'
3939+import {IS_WEB} from '#/env'
4040+import {IS_NATIVE} from '#/env'
4141import {useDeviceGeolocationApi} from '#/geolocation'
42424343const textStyles = [a.text_md, a.leading_snug]
···7474 }, [])
75757676 const onPressLogout = useCallback(() => {
7777- if (isWeb) {
7777+ if (IS_WEB) {
7878 // We're switching accounts, which remounts the entire app.
7979 // On mobile, this gets us Home, but on the web we also need reset the URL.
8080 // We can't change the URL via a navigate() call because the navigator
···139139 contentContainerStyle={[
140140 a.px_2xl,
141141 {
142142- paddingTop: isWeb
142142+ paddingTop: IS_WEB
143143 ? a.p_5xl.padding
144144 : insets.top + a.p_2xl.padding,
145145 paddingBottom: 100,
···359359 )}
360360361361 <View style={[a.gap_xs]}>
362362- {isNative && (
362362+ {IS_NATIVE && (
363363 <>
364364 <Admonition>
365365 <Trans>
+4-4
src/ageAssurance/components/RedirectOverlay.tsx
···1515import {retry} from '#/lib/async/retry'
1616import {wait} from '#/lib/async/wait'
1717import {parseLinkingUrl} from '#/lib/parseLinkingUrl'
1818-import {isWeb} from '#/platform/detection'
1919-import {isIOS} from '#/platform/detection'
2018import {useAgent, useSession} from '#/state/session'
2119import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf'
2220import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
···2826import {Text} from '#/components/Typography'
2927import {refetchAgeAssuranceServerState} from '#/ageAssurance'
3028import {logger} from '#/ageAssurance'
2929+import {IS_WEB} from '#/env'
3030+import {IS_IOS} from '#/env'
31313232export type RedirectOverlayState = {
3333 result: 'success' | 'unknown'
···9292 actorDid: params.get('actorDid') ?? undefined,
9393 })
94949595- if (isWeb) {
9595+ if (IS_WEB) {
9696 // Clear the URL parameters so they don't re-trigger
9797 history.pushState(null, '', '/')
9898 }
···145145 // setting a zIndex when using FullWindowOverlay on iOS
146146 // means the taps pass straight through to the underlying content (???)
147147 // so don't set it on iOS. FullWindowOverlay already does the job.
148148- !isIOS && {zIndex: 9999},
148148+ !IS_IOS && {zIndex: 9999},
149149 t.atoms.bg,
150150 gtMobile ? a.p_2xl : a.p_xl,
151151 a.align_center,
+4-4
src/alf/fonts.ts
···11import {type TextStyle} from 'react-native'
2233-import {isAndroid, isWeb} from '#/platform/detection'
33+import {IS_ANDROID, IS_WEB} from '#/env'
44import {type Device, device} from '#/storage'
5566const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"`
···3939 */
4040export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
4141 if (fontFamily === 'theme') {
4242- if (isAndroid) {
4242+ if (IS_ANDROID) {
4343 style.fontFamily =
4444 {
4545 400: 'Inter-Regular',
···7171 }
7272 }
73737474- if (isWeb) {
7474+ if (IS_WEB) {
7575 // fallback families only supported on web
7676 style.fontFamily += `, ${WEB_FONT_FAMILIES}`
7777 }
···8383 style.fontVariant = (style.fontVariant || []).concat('no-contextual')
8484 } else {
8585 // fallback families only supported on web
8686- if (isWeb) {
8686+ if (IS_WEB) {
8787 style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES
8888 }
8989
+4-4
src/alf/typography.tsx
···44import {UITextView} from 'react-native-uitextview'
55import createEmojiRegex from 'emoji-regex'
6677-import {isNative} from '#/platform/detection'
88-import {isIOS} from '#/platform/detection'
97import {type Alf, applyFonts, atoms, flatten} from '#/alf'
88+import {IS_NATIVE} from '#/env'
99+import {IS_IOS} from '#/env'
10101111/**
1212 * Ensures that `lineHeight` defaults to a relative value of `1`, or applies
···3434 if (s.lineHeight !== 0 && s.lineHeight <= 2) {
3535 s.lineHeight = Math.round(s.fontSize * s.lineHeight)
3636 }
3737- } else if (!isNative) {
3737+ } else if (!IS_NATIVE) {
3838 s.lineHeight = s.fontSize
3939 }
4040···8181 props: Omit<TextProps, 'children'> = {},
8282 emoji: boolean,
8383) {
8484- if (!isIOS || !emoji) {
8484+ if (!IS_IOS || !emoji) {
8585 return children
8686 }
8787 return Children.map(children, child => {
+2-2
src/alf/util/systemUI.ts
···22import {type Theme} from '@bsky.app/alf'
3344import {logger} from '#/logger'
55-import {isAndroid} from '#/platform/detection'
55+import {IS_ANDROID} from '#/env'
6677export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
88- if (isAndroid) {
88+ if (IS_ANDROID) {
99 try {
1010 if (themeType === 'theme') {
1111 SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
+2-2
src/alf/util/useColorModeTheme.ts
···22import {type ColorSchemeName, useColorScheme} from 'react-native'
33import {type ThemeName} from '@bsky.app/alf'
4455-import {isWeb} from '#/platform/detection'
65import {useThemePrefs} from '#/state/shell'
76import {dark, dim, light} from '#/alf/themes'
77+import {IS_WEB} from '#/env'
8899export function useColorModeTheme(): ThemeName {
1010 const theme = useThemeName()
···40404141function updateDocument(theme: ThemeName) {
4242 // @ts-ignore web only
4343- if (isWeb && typeof window !== 'undefined') {
4343+ if (IS_WEB && typeof window !== 'undefined') {
4444 // @ts-ignore web only
4545 const html = window.document.documentElement
4646 // @ts-ignore web only
···11import {useCallback} from 'react'
22import {SystemBars} from 'react-native-edge-to-edge'
3344-import {isIOS} from '#/platform/detection'
44+import {IS_IOS} from '#/env'
5566/**
77 * If we're calling a system API like the image picker that opens a sheet
···99 */
1010export function useSheetWrapper() {
1111 return useCallback(async <T>(promise: Promise<T>): Promise<T> => {
1212- if (isIOS) {
1212+ if (IS_IOS) {
1313 const entry = SystemBars.pushStackEntry({
1414 style: {
1515 statusBar: 'light',
···88import {HITSLOP_10} from '#/lib/constants'
99import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
1010import {sanitizeDisplayName} from '#/lib/strings/display-names'
1111-import {isNative} from '#/platform/detection'
1211import {useModerationOpts} from '#/state/preferences/moderation-opts'
1312import {useSession} from '#/state/session'
1413import {atoms as a, useTheme, web} from '#/alf'
···1817import {Newskie} from '#/components/icons/Newskie'
1918import * as StarterPackCard from '#/components/StarterPack/StarterPackCard'
2019import {Text} from '#/components/Typography'
2020+import {IS_NATIVE} from '#/env'
21212222export function NewskieDialog({
2323 profile,
···162162 </StarterPackCard.Link>
163163 ) : null}
164164165165- {isNative && (
165165+ {IS_NATIVE && (
166166 <Button
167167 label={_(msg`Close`)}
168168 color="secondary"
+3-3
src/components/PolicyUpdateOverlay/Overlay.tsx
···77import {LinearGradient} from 'expo-linear-gradient'
88import {utils} from '@bsky.app/alf'
991010-import {isAndroid, isNative} from '#/platform/detection'
1110import {useA11y} from '#/state/a11y'
1211import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
1312import {FocusScope} from '#/components/FocusScope'
1413import {LockScroll} from '#/components/LockScroll'
1414+import {IS_ANDROID, IS_NATIVE} from '#/env'
15151616const GUTTER = 24
1717···8080 a.z_20,
8181 a.align_center,
8282 !gtPhone && [a.justify_end, {minHeight: frame.height}],
8383- isNative && [
8383+ IS_NATIVE && [
8484 {
8585 paddingBottom: Math.max(insets.bottom, a.p_2xl.padding),
8686 },
···109109110110 <FocusScope>
111111 <View
112112- accessible={isAndroid}
112112+ accessible={IS_ANDROID}
113113 role="dialog"
114114 aria-role="dialog"
115115 aria-label={label}
+2-2
src/components/PolicyUpdateOverlay/index.tsx
···11import {useEffect} from 'react'
22import {View} from 'react-native'
3344-import {isIOS} from '#/platform/detection'
54import {atoms as a} from '#/alf'
65import {FullWindowOverlay} from '#/components/FullWindowOverlay'
76import {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context'
87import {Portal} from '#/components/PolicyUpdateOverlay/Portal'
98import {Content} from '#/components/PolicyUpdateOverlay/updates/202508'
99+import {IS_IOS} from '#/env'
10101111export {Provider} from '#/components/PolicyUpdateOverlay/context'
1212export {usePolicyUpdateContext} from '#/components/PolicyUpdateOverlay/context'
···3939 // setting a zIndex when using FullWindowOverlay on iOS
4040 // means the taps pass straight through to the underlying content (???)
4141 // so don't set it on iOS. FullWindowOverlay already does the job.
4242- !isIOS && {zIndex: 9999},
4242+ !IS_IOS && {zIndex: 9999},
4343 ]}>
4444 <Content state={state} />
4545 </View>
···33import {msg, Trans} from '@lingui/macro'
44import {useLingui} from '@lingui/react'
5566-import {isAndroid} from '#/platform/detection'
76import {useA11y} from '#/state/a11y'
87import {atoms as a, useTheme} from '#/alf'
98import {Button, ButtonText} from '#/components/Button'
···1211import {Overlay} from '#/components/PolicyUpdateOverlay/Overlay'
1312import {type PolicyUpdateState} from '#/components/PolicyUpdateOverlay/usePolicyUpdateState'
1413import {Text} from '#/components/Typography'
1414+import {IS_ANDROID} from '#/env'
15151616export function Content({state}: {state: PolicyUpdateState}) {
1717 const t = useTheme()
···5656 size: 'small',
5757 } as const
58585959- const label = isAndroid
5959+ const label = IS_ANDROID
6060 ? _(
6161 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.`,
6262 )
···2626 type EmbedPlayerParams,
2727 getPlayerAspect,
2828} from '#/lib/strings/embed-player'
2929-import {isNative} from '#/platform/detection'
3029import {useExternalEmbedsPrefs} from '#/state/preferences'
3130import {EventStopper} from '#/view/com/util/EventStopper'
3231import {atoms as a, useTheme} from '#/alf'
···3433import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
3534import {Fill} from '#/components/Fill'
3635import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
3636+import {IS_NATIVE} from '#/env'
37373838interface ShouldStartLoadRequest {
3939 url: string
···148148 const {height: winHeight, width: winWidth} = windowDims
149149150150 // Get the proper screen height depending on what is going on
151151- const realWinHeight = isNative // If it is native, we always want the larger number
151151+ const realWinHeight = IS_NATIVE // If it is native, we always want the larger number
152152 ? winHeight > winWidth
153153 ? winHeight
154154 : winWidth
+6-6
src/components/Post/Embed/ExternalEmbed/Gif.tsx
···1313import {HITSLOP_20} from '#/lib/constants'
1414import {clamp} from '#/lib/numbers'
1515import {type EmbedPlayerParams} from '#/lib/strings/embed-player'
1616-import {isWeb} from '#/platform/detection'
1716import {useAutoplayDisabled} from '#/state/preferences'
1817import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
1918import {atoms as a, useTheme} from '#/alf'
···2221import * as Prompt from '#/components/Prompt'
2322import {Text} from '#/components/Typography'
2423import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
2424+import {IS_WEB} from '#/env'
2525import {GifView} from '../../../../../modules/expo-bluesky-gif-view'
2626import {type GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
2727···218218 altContainer: {
219219 backgroundColor: 'rgba(0, 0, 0, 0.75)',
220220 borderRadius: 6,
221221- paddingHorizontal: isWeb ? 8 : 6,
222222- paddingVertical: isWeb ? 6 : 3,
221221+ paddingHorizontal: IS_WEB ? 8 : 6,
222222+ paddingVertical: IS_WEB ? 6 : 3,
223223 position: 'absolute',
224224 // Related to margin/gap hack. This keeps the alt label in the same position
225225 // on all platforms
226226- right: isWeb ? 8 : 5,
227227- bottom: isWeb ? 8 : 5,
226226+ right: IS_WEB ? 8 : 5,
227227+ bottom: IS_WEB ? 8 : 5,
228228 zIndex: 2,
229229 },
230230 alt: {
231231 color: 'white',
232232- fontSize: isWeb ? 10 : 7,
232232+ fontSize: IS_WEB ? 10 : 7,
233233 fontWeight: '600',
234234 },
235235})
+2-2
src/components/Post/Embed/ExternalEmbed/index.tsx
···1010import {shareUrl} from '#/lib/sharing'
1111import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
1212import {toNiceDomain} from '#/lib/strings/url-helpers'
1313-import {isNative} from '#/platform/detection'
1413import {useExternalEmbedsPrefs} from '#/state/preferences'
1514import {atoms as a, useTheme} from '#/alf'
1615import {Divider} from '#/components/Divider'
1716import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
1817import {Link} from '#/components/Link'
1918import {Text} from '#/components/Typography'
1919+import {IS_NATIVE} from '#/env'
2020import {ExternalGif} from './ExternalGif'
2121import {ExternalPlayer} from './ExternalPlayer'
2222import {GifEmbed} from './Gif'
···5353 }, [playHaptic, onOpen])
54545555 const onShareExternal = useCallback(() => {
5656- if (link.uri && isNative) {
5656+ if (link.uri && IS_NATIVE) {
5757 playHaptic('Heavy')
5858 shareUrl(link.uri)
5959 }
···88} from 'react'
99import {useWindowDimensions} from 'react-native'
10101111-import {isNative, isWeb} from '#/platform/detection'
1111+import {IS_NATIVE, IS_WEB} from '#/env'
12121313const Context = React.createContext<{
1414 activeViewId: string | null
···1818Context.displayName = 'ActiveVideoWebContext'
19192020export function Provider({children}: {children: React.ReactNode}) {
2121- if (!isWeb) {
2121+ if (!IS_WEB) {
2222 throw new Error('ActiveVideoWebContext may only be used on web.')
2323 }
2424···47474848 const sendViewPosition = useCallback(
4949 (viewId: string, y: number) => {
5050- if (isNative) return
5050+ if (IS_NATIVE) return
51515252 if (viewId === activeViewIdRef.current) {
5353 activeViewLocationRef.current = y
···1010import {shareText, shareUrl} from '#/lib/sharing'
1111import {toShareUrl} from '#/lib/strings/url-helpers'
1212import {logger} from '#/logger'
1313-import {isIOS} from '#/platform/detection'
1413import {useProfileShadow} from '#/state/cache/profile-shadow'
1514import {useSession} from '#/state/session'
1615import * as Toast from '#/view/com/util/Toast'
···2423import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
2524import * as Menu from '#/components/Menu'
2625import {useAgeAssurance} from '#/ageAssurance'
2626+import {IS_IOS} from '#/env'
2727import {useDevMode} from '#/storage/hooks/dev-mode'
2828import {RecentChats} from './RecentChats'
2929import {type ShareMenuItemsProps} from './ShareMenuItems.types'
···6363 const onCopyLink = async () => {
6464 logger.metric('share:press:copyLink', {}, {statsig: true})
6565 const url = toShareUrl(href)
6666- if (isIOS) {
6666+ if (IS_IOS) {
6767 // iOS only
6868 await ExpoClipboard.setUrlAsync(url)
6969 } else {
···1818import {cleanError} from '#/lib/strings/errors'
1919import {sanitizeHandle} from '#/lib/strings/handles'
2020import {logger} from '#/logger'
2121-import {isWeb} from '#/platform/detection'
2221import {updateProfileShadow} from '#/state/cache/profile-shadow'
2322import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions'
2423import {useAgent} from '#/state/session'
···3736import {Loader} from '#/components/Loader'
3837import * as ProfileCard from '#/components/ProfileCard'
3938import {Text} from '#/components/Typography'
3939+import {IS_WEB} from '#/env'
4040import type * as bsky from '#/types/bsky'
41414242export function SubscribeProfileDialog({
···195195 }
196196 } else {
197197 // on web, a disabled save button feels more natural than a massive close button
198198- if (isWeb) {
198198+ if (IS_WEB) {
199199 return {
200200 label: _(msg`Save changes`),
201201 color: 'secondary',
···5566import {retry} from '#/lib/async/retry'
77import {wait} from '#/lib/async/wait'
88-import {isNative} from '#/platform/detection'
98import {useAgent} from '#/state/session'
109import {atoms as a, useTheme, web} from '#/alf'
1110import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
···1817import {Text} from '#/components/Typography'
1918import {refetchAgeAssuranceServerState} from '#/ageAssurance'
2019import {logger} from '#/ageAssurance'
2020+import {IS_NATIVE} from '#/env'
21212222export type AgeAssuranceRedirectDialogState = {
2323 result: 'success' | 'unknown'
···166166 </Trans>
167167 </Text>
168168169169- {isNative && (
169169+ {IS_NATIVE && (
170170 <View style={[a.w_full, a.pt_lg]}>
171171 <Button
172172 label={_(msg`Close`)}
···225225 )}
226226 </Text>
227227228228- {error && isNative && (
228228+ {error && IS_NATIVE && (
229229 <View style={[a.w_full, a.pt_lg]}>
230230 <Button
231231 label={_(msg`Close`)}
+2-2
src/components/contacts/FindContactsBannerNUX.tsx
···88import {HITSLOP_10} from '#/lib/constants'
99import {useGate} from '#/lib/statsig/statsig'
1010import {logger} from '#/logger'
1111-import {isWeb} from '#/platform/detection'
1211import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
1312import {atoms as a, useTheme} from '#/alf'
1413import {Button} from '#/components/Button'
1514import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
1615import {Text} from '#/components/Typography'
1616+import {IS_WEB} from '#/env'
1717import {Link} from '../Link'
1818import {useIsFindContactsFeatureEnabledBasedOnGeolocation} from './country-allowlist'
1919···9292 const gate = useGate()
93939494 const visible = useMemo(() => {
9595- if (isWeb) return false
9595+ if (IS_WEB) return false
9696 if (hidden) return false
9797 if (nux && nux.completed) return false
9898 if (!isFeatureEnabled) return false
+3-3
src/components/contacts/components/OTPInput.tsx
···99import {useLingui} from '@lingui/react'
10101111import {mergeRefs} from '#/lib/merge-refs'
1212-import {isAndroid, isIOS} from '#/platform/detection'
1312import {atoms as a, ios, platform, useTheme} from '#/alf'
1413import {useInteractionState} from '#/components/hooks/useInteractionState'
1514import {Text} from '#/components/Typography'
1515+import {IS_ANDROID, IS_IOS} from '#/env'
16161717export function OTPInput({
1818 label,
···9595 <TextInput
9696 // SMS autofill is borked on iOS if you open the keyboard immediately -sfn
9797 onLayout={ios(() => setTimeout(() => innerRef.current?.focus(), 100))}
9898- autoFocus={isAndroid}
9898+ autoFocus={IS_ANDROID}
9999 accessible
100100 accessibilityLabel={label}
101101 accessibilityHint=""
···135135 android: {opacity: 0},
136136 }),
137137 ]}
138138- caretHidden={isIOS}
138138+ caretHidden={IS_IOS}
139139 clearTextOnFocus
140140 />
141141 </Pressable>
···66import {wait} from '#/lib/async/wait'
77import {isNetworkError, useCleanError} from '#/lib/hooks/useCleanError'
88import {logger} from '#/logger'
99-import {isWeb} from '#/platform/detection'
109import {atoms as a, useTheme, web} from '#/alf'
1110import {Admonition} from '#/components/Admonition'
1211import {Button, ButtonIcon, ButtonText} from '#/components/Button'
···1413import {PinLocation_Stroke2_Corner0_Rounded as LocationIcon} from '#/components/icons/PinLocation'
1514import {Loader} from '#/components/Loader'
1615import {Text} from '#/components/Typography'
1616+import {IS_WEB} from '#/env'
1717import {type Geolocation, useRequestDeviceGeolocation} from '#/geolocation'
18181919export type Props = {
···138138 disabled={isRequesting}
139139 label={_(msg`Allow location access`)}
140140 onPress={onPressConfirm}
141141- size={isWeb ? 'small' : 'large'}
141141+ size={IS_WEB ? 'small' : 'large'}
142142 color="primary">
143143 <ButtonIcon icon={isRequesting ? Loader : LocationIcon} />
144144 <ButtonText>
···147147 </Button>
148148 )}
149149150150- {!isWeb && (
150150+ {!IS_WEB && (
151151 <Button
152152 label={_(msg`Cancel`)}
153153 onPress={() => close()}
154154- size={isWeb ? 'small' : 'large'}
154154+ size={IS_WEB ? 'small' : 'large'}
155155 color="secondary">
156156 <ButtonText>
157157 <Trans>Cancel</Trans>
+3-3
src/components/dialogs/GifSelect.tsx
···13131414import {logEvent} from '#/lib/statsig/statsig'
1515import {cleanError} from '#/lib/strings/errors'
1616-import {isWeb} from '#/platform/detection'
1716import {
1817 type Gif,
1918 tenorUrlToBskyGifUrl,
···3130import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
3231import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
3332import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
3333+import {IS_WEB} from '#/env'
34343535export function GifSelectDialog({
3636 controlRef,
···149149 a.pb_sm,
150150 t.atoms.bg,
151151 ]}>
152152- {!gtMobile && isWeb && (
152152+ {!gtMobile && IS_WEB && (
153153 <Button
154154 size="small"
155155 variant="ghost"
···161161 </Button>
162162 )}
163163164164- <TextField.Root style={[!gtMobile && isWeb && a.flex_1]}>
164164+ <TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}>
165165 <TextField.Icon icon={Search} />
166166 <TextField.Input
167167 label={_(msg`Search GIFs`)}
+2-2
src/components/dialogs/InAppBrowserConsent.tsx
···44import {useLingui} from '@lingui/react'
5566import {useOpenLink} from '#/lib/hooks/useOpenLink'
77-import {isWeb} from '#/platform/detection'
87import {useSetInAppBrowser} from '#/state/preferences/in-app-browser'
98import {atoms as a, useTheme} from '#/alf'
109import {Button, ButtonIcon, ButtonText} from '#/components/Button'
1110import * as Dialog from '#/components/Dialog'
1211import {SquareArrowTopRight_Stroke2_Corner0_Rounded as External} from '#/components/icons/SquareArrowTopRight'
1312import {Text} from '#/components/Typography'
1313+import {IS_WEB} from '#/env'
1414import {useGlobalDialogsControlContext} from './Context'
15151616export function InAppBrowserConsentDialog() {
1717 const {inAppBrowserConsentControl} = useGlobalDialogsControlContext()
18181919- if (isWeb) return null
1919+ if (IS_WEB) return null
20202121 return (
2222 <Dialog.Outer
+2-2
src/components/dialogs/MutedWords.tsx
···55import {useLingui} from '@lingui/react'
6677import {logger} from '#/logger'
88-import {isNative} from '#/platform/detection'
98import {
109 usePreferencesQuery,
1110 useRemoveMutedWordMutation,
···3231import {Loader} from '#/components/Loader'
3332import * as Prompt from '#/components/Prompt'
3433import {Text} from '#/components/Typography'
3434+import {IS_NATIVE} from '#/env'
35353636const ONE_DAY = 24 * 60 * 60 * 1000
3737···406406 )}
407407 </View>
408408409409- {isNative && <View style={{height: 20}} />}
409409+ {IS_NATIVE && <View style={{height: 20}} />}
410410 </View>
411411412412 <Dialog.Close />
···6677import {urls} from '#/lib/constants'
88import {logger} from '#/logger'
99-import {isNative} from '#/platform/detection'
109import {atoms as a, useBreakpoints, useTheme} from '#/alf'
1110import {Button, ButtonText} from '#/components/Button'
1211import * as Dialog from '#/components/Dialog'
···1514import {VerifierCheck} from '#/components/icons/VerifierCheck'
1615import {Link} from '#/components/Link'
1716import {Span, Text} from '#/components/Typography'
1717+import {IS_NATIVE} from '#/env'
18181919export function InitialVerificationAnnouncement() {
2020 const t = useTheme()
···173173 <Trans>Read blog post</Trans>
174174 </ButtonText>
175175 </Link>
176176- {isNative && (
176176+ {IS_NATIVE && (
177177 <Button
178178 label={_(msg`Close`)}
179179 size="small"
···44import {useLingui} from '@lingui/react'
5566import {HITSLOP_10} from '#/lib/constants'
77-import {isNative} from '#/platform/detection'
87import {atoms as a, useTheme} from '#/alf'
98import {Button, ButtonIcon} from '#/components/Button'
109import * as TextField from '#/components/forms/TextField'
1110import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass'
1211import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
1212+import {IS_NATIVE} from '#/env'
13131414type SearchInputProps = Omit<TextField.InputProps, 'label'> & {
1515 label?: TextField.InputProps['label']
···3636 placeholder={_(msg`Search`)}
3737 returnKeyType="search"
3838 keyboardAppearance={t.scheme}
3939- selectTextOnFocus={isNative}
3939+ selectTextOnFocus={IS_NATIVE}
4040 autoFocus={false}
4141 accessibilityRole="search"
4242 autoCorrect={false}
+2-2
src/components/forms/Toggle/index.tsx
···10101111import {HITSLOP_10} from '#/lib/constants'
1212import {useHaptics} from '#/lib/haptics'
1313-import {isNative} from '#/platform/detection'
1413import {
1514 atoms as a,
1615 native,
···2221import {useInteractionState} from '#/components/hooks/useInteractionState'
2322import {CheckThick_Stroke2_Corner0_Rounded as Checkmark} from '#/components/icons/Check'
2423import {Text} from '#/components/Typography'
2424+import {IS_NATIVE} from '#/env'
25252626export * from './Panel'
2727···562562 )
563563}
564564565565-export const Platform = isNative ? Switch : Checkbox
565565+export const Platform = IS_NATIVE ? Switch : Checkbox
+2-2
src/components/hooks/useFullscreen.ts
···77} from 'react'
8899import {isFirefox, isSafari} from '#/lib/browser'
1010-import {isWeb} from '#/platform/detection'
1010+import {IS_WEB} from '#/env'
11111212function fullscreenSubscribe(onChange: () => void) {
1313 document.addEventListener('fullscreenchange', onChange)
···1515}
16161717export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) {
1818- if (!isWeb) throw new Error("'useFullscreen' is a web-only hook")
1818+ if (!IS_WEB) throw new Error("'useFullscreen' is a web-only hook")
1919 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
2020 Boolean(document.fullscreenElement),
2121 )
···44 createStarterPackLinkFromAndroidReferrer,
55 httpStarterPackUriToAtUri,
66} from '#/lib/strings/starter-pack'
77-import {isAndroid} from '#/platform/detection'
87import {useHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
98import {useSetActiveStarterPack} from '#/state/shell/starter-pack'
99+import {IS_ANDROID} from '#/env'
1010import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army'
11111212export function useStarterPackEntry() {
···3232 ;(async () => {
3333 let uri: string | null | undefined
34343535- if (isAndroid) {
3535+ if (IS_ANDROID) {
3636 const res = await Referrer.getGooglePlayReferrerInfoAsync()
37373838 if (res && res.installReferrer) {
+2-2
src/components/hooks/useWelcomeModal.ts
···11import {useEffect, useState} from 'react'
2233-import {isWeb} from '#/platform/detection'
43import {useSession} from '#/state/session'
44+import {IS_WEB} from '#/env'
5566export function useWelcomeModal() {
77 const {hasSession} = useSession()
···2222 // 2. We're on the web (this is a web-only feature)
2323 // 3. We're on the homepage (path is '/' or '/home')
2424 // 4. User hasn't actively closed the modal in this session
2525- if (isWeb && !hasSession && typeof window !== 'undefined') {
2525+ if (IS_WEB && !hasSession && typeof window !== 'undefined') {
2626 const currentPath = window.location.pathname
2727 const isHomePage = currentPath === '/'
2828 const hasUserClosedModal =
+2-2
src/components/images/AutoSizedImage.tsx
···1111import {useLingui} from '@lingui/react'
12121313import {type Dimensions} from '#/lib/media/types'
1414-import {isNative} from '#/platform/detection'
1514import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
1615import {atoms as a, useTheme} from '#/alf'
1716import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
1817import {MediaInsetBorder} from '#/components/MediaInsetBorder'
1918import {Text} from '#/components/Typography'
1919+import {IS_NATIVE} from '#/env'
20202121export function ConstrainedImage({
2222 aspectRatio,
···3535 * the height of the image.
3636 */
3737 const outerAspectRatio = useMemo<DimensionValue>(() => {
3838- const ratio = isNative
3838+ const ratio = IS_NATIVE
3939 ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box
4040 : Math.min(1 / aspectRatio, 1) // 1:1 bounding box
4141 return `${ratio * 100}%`
···33import {useLingui} from '@lingui/react'
4455import {useCleanError} from '#/lib/hooks/useCleanError'
66-import {isNative} from '#/platform/detection'
76import {atoms as a, web} from '#/alf'
87import {Admonition} from '#/components/Admonition'
98import {Button, ButtonIcon, ButtonText} from '#/components/Button'
···1110import {Loader} from '#/components/Loader'
1211import * as Toast from '#/components/Toast'
1312import {Span, Text} from '#/components/Typography'
1313+import {IS_NATIVE} from '#/env'
1414import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences'
1515import {
1616 type LiveEventFeed,
···146146 </ButtonText>
147147 {isHidingAllFeeds && <ButtonIcon icon={Loader} />}
148148 </Button>
149149- {isNative && (
149149+ {IS_NATIVE && (
150150 <Button
151151 label={_(msg`Cancel`)}
152152 size="large"
+2-2
src/features/liveEvents/preferences.ts
···33import {useMutation, useQueryClient} from '@tanstack/react-query'
4455import {logger} from '#/logger'
66-import {isWeb} from '#/platform/detection'
76import {
87 preferencesQueryKey,
98 usePreferencesQuery,
109} from '#/state/queries/preferences'
1110import {useAgent} from '#/state/session'
1111+import {IS_WEB} from '#/env'
1212import * as env from '#/env'
1313import {
1414 type LiveEventFeed,
···4141 const agent = useAgent()
42424343 useEffect(() => {
4444- if (env.IS_DEV && isWeb && typeof window !== 'undefined') {
4444+ if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') {
4545 // @ts-ignore
4646 window.__updateLiveEventPreferences = async (
4747 action: LiveEventPreferencesAction,
+2-2
src/geolocation/device.ts
···33import * as Location from 'expo-location'
44import {createPermissionHook} from 'expo-modules-core'
5566-import {isNative} from '#/platform/detection'
66+import {IS_NATIVE} from '#/env'
77import * as debug from '#/geolocation/debug'
88import {logger} from '#/geolocation/logger'
99import {type Geolocation} from '#/geolocation/types'
···118118 const synced = useRef(false)
119119 const [status] = useForegroundPermissions()
120120 useEffect(() => {
121121- if (!isNative) return
121121+ if (!IS_NATIVE) return
122122123123 async function get() {
124124 // no need to set this more than once per session
+2-2
src/geolocation/util.ts
···11import {type LocationGeocodedAddress} from 'expo-location'
2233-import {isAndroid} from '#/platform/detection'
33+import {IS_ANDROID} from '#/env'
44import {logger} from '#/geolocation/logger'
55import {type Geolocation} from '#/geolocation/types'
66···8181 /*
8282 * Android doesn't give us ISO 3166-2 short codes. We need these for US
8383 */
8484- if (isAndroid) {
8484+ if (IS_ANDROID) {
8585 if (region && isoCountryCode === 'US') {
8686 /*
8787 * We need short codes for US states. If we can't remap it, just drop it
+2-2
src/lib/api/feed/utils.ts
···11import {AtUri} from '@atproto/api'
2233import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants'
44-import {isWeb} from '#/platform/detection'
54import {type UsePreferencesQueryResponse} from '#/state/queries/preferences'
55+import {IS_WEB} from '#/env'
6677let debugTopics = ''
88-if (isWeb && typeof window !== 'undefined') {
88+if (IS_WEB && typeof window !== 'undefined') {
99 const params = new URLSearchParams(window.location.search)
1010 debugTopics = params.get('debug_topics') ?? ''
1111}
···1313} from 'react-native-reanimated'
14141515import {isTouchDevice} from '#/lib/browser'
1616-import {isNative} from '#/platform/detection'
1616+import {IS_NATIVE} from '#/env'
17171818-const DEFAULT_TARGET_SCALE = isNative || isTouchDevice ? 0.98 : 1
1818+const DEFAULT_TARGET_SCALE = IS_NATIVE || isTouchDevice ? 0.98 : 1
19192020const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
2121
+3-3
src/lib/haptics.ts
···22import * as Device from 'expo-device'
33import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics'
4455-import {isIOS, isWeb} from '#/platform/detection'
65import {useHapticsDisabled} from '#/state/preferences/disable-haptics'
66+import {IS_IOS, IS_WEB} from '#/env'
7788export function useHaptics() {
99 const isHapticsDisabled = useHapticsDisabled()
10101111 return React.useCallback(
1212 (strength: 'Light' | 'Medium' | 'Heavy' = 'Medium') => {
1313- if (isHapticsDisabled || isWeb) {
1313+ if (isHapticsDisabled || IS_WEB) {
1414 return
1515 }
16161717 // Users said the medium impact was too strong on Android; see APP-537s
1818- const style = isIOS
1818+ const style = IS_IOS
1919 ? ImpactFeedbackStyle[strength]
2020 : ImpactFeedbackStyle.Light
2121 impactAsync(style)
+2-2
src/lib/hooks/useAccountSwitcher.ts
···33import {useLingui} from '@lingui/react'
4455import {logger} from '#/logger'
66-import {isWeb} from '#/platform/detection'
76import {type SessionAccount, useSessionApi} from '#/state/session'
87import {useLoggedOutViewControls} from '#/state/shell/logged-out'
98import * as Toast from '#/view/com/util/Toast'
99+import {IS_WEB} from '#/env'
1010import {logEvent} from '../statsig/statsig'
1111import {type LogEvents} from '../statsig/statsig'
1212···2828 try {
2929 setPendingDid(account.did)
3030 if (account.accessJwt) {
3131- if (isWeb) {
3131+ if (IS_WEB) {
3232 // We're switching accounts, which remounts the entire app.
3333 // On mobile, this gets us Home, but on the web we also need reset the URL.
3434 // We can't change the URL via a navigate() call because the navigator
···99import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
1010import {logger as notyLogger} from '#/lib/notifications/util'
1111import {type NavigationProp} from '#/lib/routes/types'
1212-import {isAndroid, isIOS} from '#/platform/detection'
1312import {useCurrentConvoId} from '#/state/messages/current-convo-id'
1413import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
1514import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread'
···1716import {useSession} from '#/state/session'
1817import {useLoggedOutViewControls} from '#/state/shell/logged-out'
1918import {useCloseAllActiveElements} from '#/state/util'
1919+import {IS_ANDROID, IS_IOS} from '#/env'
2020import {resetToTab} from '#/Navigation'
2121import {router} from '#/routes'
2222···9090 // channels allow for the mute/unmute functionality we want for the background
9191 // handler.
9292 useEffect(() => {
9393- if (!isAndroid) return
9393+ if (!IS_ANDROID) return
9494 // assign both chat notifications to a group
9595 // NOTE: I don't think that it will retroactively move them into the group
9696 // if the channels already exist. no big deal imo -sfn
···379379 }
380380381381 const payload = (
382382- isIOS ? e.request.trigger.payload : e.request.content.data
382382+ IS_IOS ? e.request.trigger.payload : e.request.content.data
383383 ) as NotificationPayload
384384385385 if (payload && payload.reason) {
+4-4
src/lib/hooks/useOTAUpdates.ts
···12121313import {isNetworkError} from '#/lib/strings/errors'
1414import {logger} from '#/logger'
1515-import {isAndroid, isIOS} from '#/platform/detection'
1515+import {IS_ANDROID, IS_IOS} from '#/env'
1616import {IS_TESTFLIGHT} from '#/env'
17171818const MINIMUM_MINIMIZE_TIME = 15 * 60e3
19192020async function setExtraParams() {
2121 await setExtraParamAsync(
2222- isIOS ? 'ios-build-number' : 'android-build-number',
2222+ IS_IOS ? 'ios-build-number' : 'android-build-number',
2323 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is.
2424 // This just ensures it gets passed as a string
2525 `${nativeBuildVersion}`,
···32323333async function setExtraParamsPullRequest(channel: string) {
3434 await setExtraParamAsync(
3535- isIOS ? 'ios-build-number' : 'android-build-number',
3535+ IS_IOS ? 'ios-build-number' : 'android-build-number',
3636 // Hilariously, `buildVersion` is not actually a string on Android even though the TS type says it is.
3737 // This just ensures it gets passed as a string
3838 `${nativeBuildVersion}`,
···198198 // `maintainVisibleContentPosition`. See repro repo for more details:
199199 // https://github.com/mozzius/ota-crash-repro
200200 // Old Arch only - re-enable once we're on the New Archictecture! -sfn
201201- if (isAndroid) return
201201+ if (IS_ANDROID) return
202202203203 const subscription = AppState.addEventListener(
204204 'change',
+2-2
src/lib/hooks/useOpenLink.ts
···1212 toNiceDomain,
1313} from '#/lib/strings/url-helpers'
1414import {logger} from '#/logger'
1515-import {isNative} from '#/platform/detection'
1615import {useInAppBrowser} from '#/state/preferences/in-app-browser'
1716import {useTheme} from '#/alf'
1817import {useDialogContext} from '#/components/Dialog'
1918import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
1919+import {IS_NATIVE} from '#/env'
20202121export function useOpenLink() {
2222 const enabled = useInAppBrowser()
···4141 }
4242 }
43434444- if (isNative && !url.startsWith('mailto:')) {
4444+ if (IS_NATIVE && !url.startsWith('mailto:')) {
4545 if (override === undefined && enabled === undefined) {
4646 // consent dialog is a global dialog, and while it's possible to nest dialogs,
4747 // the actual components need to be nested. sibling dialogs on iOS are not supported.
+3-3
src/lib/hooks/usePermissions.ts
···22import {useCameraPermissions as useExpoCameraPermissions} from 'expo-camera'
33import * as MediaLibrary from 'expo-media-library'
4455-import {isWeb} from '#/platform/detection'
65import {Alert} from '#/view/com/util/Alert'
66+import {IS_WEB} from '#/env'
7788const openPermissionAlert = (perm: string) => {
99 Alert.alert(
···2626 const requestPhotoAccessIfNeeded = async () => {
2727 // On the, we use <input type="file"> to produce a filepicker
2828 // This does not need any permission granting.
2929- if (isWeb) {
2929+ if (IS_WEB) {
3030 return true
3131 }
3232···5555 const requestVideoAccessIfNeeded = async () => {
5656 // On the, we use <input type="file"> to produce a filepicker
5757 // This does not need any permission granting.
5858- if (isWeb) {
5858+ if (IS_WEB) {
5959 return true
6060 }
6161
+2-2
src/lib/hooks/useTranslate.ts
···22import * as IntentLauncher from 'expo-intent-launcher'
3344import {getTranslatorLink} from '#/locale/helpers'
55-import {isAndroid} from '#/platform/detection'
55+import {IS_ANDROID} from '#/env'
66import {useOpenLink} from './useOpenLink'
7788export function useTranslate() {
···1111 return useCallback(
1212 async (text: string, language: string) => {
1313 const translateUrl = getTranslatorLink(text, language)
1414- if (isAndroid) {
1414+ if (IS_ANDROID) {
1515 try {
1616 // use getApplicationIconAsync to determine if the translate app is installed
1717 if (
+2-2
src/lib/hooks/useWebMediaQueries.tsx
···11import {useMediaQuery} from 'react-responsive'
2233-import {isNative} from '#/platform/detection'
33+import {IS_NATIVE} from '#/env'
4455/**
66 * @deprecated use `useBreakpoints` from `#/alf` instead
···1111 const isMobile = useMediaQuery({maxWidth: 800 - 1})
1212 const isTabletOrMobile = isMobile || isTablet
1313 const isTabletOrDesktop = isDesktop || isTablet
1414- if (isNative) {
1414+ if (IS_NATIVE) {
1515 return {
1616 isMobile: true,
1717 isTablet: false,
+4-4
src/lib/media/manip.ts
···18181919import {POST_IMG_MAX} from '#/lib/constants'
2020import {logger} from '#/logger'
2121-import {isAndroid, isIOS} from '#/platform/detection'
2121+import {IS_ANDROID, IS_IOS} from '#/env'
2222import {type PickerImage} from './picker.shared'
2323import {type Dimensions} from './types'
2424···108108109109 // save
110110 try {
111111- if (isAndroid) {
111111+ if (IS_ANDROID) {
112112 // android triggers an annoying permission prompt if you try and move an image
113113 // between albums. therefore, we need to either create the album with the image
114114 // as the starting image, or put it directly into the album
···305305}
306306307307function normalizePath(str: string, allPlatforms = false): string {
308308- if (isAndroid || allPlatforms) {
308308+ if (IS_ANDROID || allPlatforms) {
309309 if (!str.startsWith('file://')) {
310310 return `file://${str}`
311311 }
···328328 type: string,
329329) {
330330 try {
331331- if (isIOS) {
331331+ if (IS_IOS) {
332332 await withTempFile(filename, encoded, async tmpFileUrl => {
333333 await Sharing.shareAsync(tmpFileUrl, {UTI: type})
334334 })
+3-3
src/lib/media/picker.shared.ts
···55} from 'expo-image-picker'
66import {t} from '@lingui/macro'
7788-import {isIOS, isWeb} from '#/platform/detection'
98import {type ImageMeta} from '#/state/gallery'
109import * as Toast from '#/view/com/util/Toast'
1010+import {IS_IOS, IS_WEB} from '#/env'
1111import {VIDEO_MAX_DURATION_MS} from '../constants'
1212import {getDataUriSize} from './util'
1313···5353 quality: 1,
5454 allowsMultipleSelection: true,
5555 legacy: true,
5656- base64: isWeb,
5757- selectionLimit: isIOS ? selectionCountRemaining : undefined,
5656+ base64: IS_WEB,
5757+ selectionLimit: IS_IOS ? selectionCountRemaining : undefined,
5858 preferredAssetRepresentationMode:
5959 UIImagePickerPreferredAssetRepresentationMode.Automatic,
6060 videoMaxDuration: VIDEO_MAX_DURATION_MS / 1000,
+2-2
src/lib/media/save-image.ios.ts
···22import {msg} from '@lingui/macro'
33import {useLingui} from '@lingui/react'
4455-import {isNative} from '#/platform/detection'
65import * as Toast from '#/components/Toast'
66+import {IS_NATIVE} from '#/env'
77import {saveImageToMediaLibrary} from './manip'
8899/**
···1616 const {_} = useLingui()
1717 return useCallback(
1818 async (uri: string) => {
1919- if (!isNative) {
1919+ if (!IS_NATIVE) {
2020 throw new Error('useSaveImageToMediaLibrary is native only')
2121 }
2222
+2-2
src/lib/media/save-image.ts
···33import {msg} from '@lingui/macro'
44import {useLingui} from '@lingui/react'
5566-import {isNative} from '#/platform/detection'
76import * as Toast from '#/components/Toast'
77+import {IS_NATIVE} from '#/env'
88import {saveImageToMediaLibrary} from './manip'
991010/**
···1818 })
1919 return useCallback(
2020 async (uri: string) => {
2121- if (!isNative) {
2121+ if (!IS_NATIVE) {
2222 throw new Error('useSaveImageToMediaLibrary is native only')
2323 }
2424
+5-5
src/lib/notifications/notifications.ts
···1313} from '#/lib/constants'
1414import {logger as notyLogger} from '#/lib/notifications/util'
1515import {isNetworkError} from '#/lib/strings/errors'
1616-import {isNative} from '#/platform/detection'
1716import {type SessionAccount, useAgent, useSession} from '#/state/session'
1817import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
1918import {useAgeAssurance} from '#/ageAssurance'
1919+import {IS_NATIVE} from '#/env'
2020import {IS_DEV} from '#/env'
21212222/**
···140140 }: {
141141 isAgeRestricted?: boolean
142142 } = {}) => {
143143- if (!isNative || IS_DEV) return
143143+ if (!IS_NATIVE || IS_DEV) return
144144145145 /**
146146 * This will also fire the listener added via `addPushTokenListener`. That
···236236 const permissions = await Notifications.getPermissionsAsync()
237237238238 if (
239239- !isNative ||
239239+ !IS_NATIVE ||
240240 permissions?.status === 'granted' ||
241241 (permissions?.status === 'denied' && !permissions.canAskAgain)
242242 ) {
···277277}
278278279279export async function decrementBadgeCount(by: number) {
280280- if (!isNative) return
280280+ if (!IS_NATIVE) return
281281282282 let count = await getBadgeCountAsync()
283283 count -= by
···295295}
296296297297export async function unregisterPushToken(agents: AtpAgent[]) {
298298- if (!isNative) return
298298+ if (!IS_NATIVE) return
299299300300 try {
301301 const token = await getPushToken()
+3-3
src/lib/react-query.tsx
···99} from '@tanstack/react-query-persist-client'
1010import type React from 'react'
11111212-import {isNative, isWeb} from '#/platform/detection'
1312import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events'
1313+import {IS_NATIVE, IS_WEB} from '#/env'
14141515declare global {
1616 interface Window {
···8787}, 2000)
88888989focusManager.setEventListener(onFocus => {
9090- if (isNative) {
9090+ if (IS_NATIVE) {
9191 const subscription = AppState.addEventListener(
9292 'change',
9393 (status: AppStateStatus) => {
···187187 }
188188 })
189189 useEffect(() => {
190190- if (isWeb) {
190190+ if (IS_WEB) {
191191 window.__TANSTACK_QUERY_CLIENT__ = queryClient
192192 }
193193 }, [queryClient])
+4-4
src/lib/sharing.ts
···44// TODO: replace global i18n instance with one returned from useLingui -sfn
55import {t} from '@lingui/macro'
6677-import {isAndroid, isIOS} from '#/platform/detection'
87import * as Toast from '#/view/com/util/Toast'
88+import {IS_ANDROID, IS_IOS} from '#/env'
991010/**
1111 * This function shares a URL using the native Share API if available, or copies it to the clipboard
···1414 * clipboard.
1515 */
1616export async function shareUrl(url: string) {
1717- if (isAndroid) {
1717+ if (IS_ANDROID) {
1818 await Share.share({message: url})
1919- } else if (isIOS) {
1919+ } else if (IS_IOS) {
2020 await Share.share({url})
2121 } else {
2222 // React Native Share is not supported by web. Web Share API
···3434 * clipboard.
3535 */
3636export async function shareText(text: string) {
3737- if (isAndroid || isIOS) {
3737+ if (IS_ANDROID || IS_IOS) {
3838 await Share.share({message: text})
3939 } else {
4040 await setStringAsync(text)
+2-2
src/lib/statsig/statsig.tsx
···5566import {logger} from '#/logger'
77import {type MetricEvents} from '#/logger/metrics'
88-import {isWeb} from '#/platform/detection'
98import * as persisted from '#/state/persisted'
99+import {IS_WEB} from '#/env'
1010import * as env from '#/env'
1111import {useSession} from '../../state/session'
1212import {timeout} from '../async/timeout'
···37373838let refSrc = ''
3939let refUrl = ''
4040-if (isWeb && typeof window !== 'undefined') {
4040+if (IS_WEB && typeof window !== 'undefined') {
4141 const params = new URLSearchParams(window.location.search)
4242 refSrc = params.get('ref_src') ?? ''
4343 refUrl = decodeURIComponent(params.get('ref_url') ?? '')
+4-4
src/lib/strings/embed-player.ts
···11import {Dimensions} from 'react-native'
2233import {isSafari} from '#/lib/browser'
44-import {isWeb} from '#/platform/detection'
44+import {IS_WEB} from '#/env'
5566const {height: SCREEN_HEIGHT} = Dimensions.get('window')
7788-const IFRAME_HOST = isWeb
88+const IFRAME_HOST = IS_WEB
99 ? // @ts-ignore only for web
1010 window.location.host === 'localhost:8100'
1111 ? 'http://localhost:8100'
···132132 urlp.hostname === 'www.twitch.tv' ||
133133 urlp.hostname === 'm.twitch.tv'
134134 ) {
135135- const parent = isWeb
135135+ const parent = IS_WEB
136136 ? // @ts-ignore only for web
137137 window.location.hostname
138138 : 'localhost'
···559559 width: Number(w),
560560 }
561561562562- if (isWeb) {
562562+ if (IS_WEB) {
563563 if (isSafari) {
564564 id = id.replace('AAAAC', 'AAAP1')
565565 filename = filename.replace('.gif', '.mp4')
+2-2
src/lib/styles.ts
···55 type TextStyle,
66} from 'react-native'
7788-import {isWeb} from '#/platform/detection'
88+import {IS_WEB} from '#/env'
99import {type Theme, type TypographyVariant} from './ThemeContext'
10101111// 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest
···186186 // dimensions
187187 w100pct: {width: '100%'},
188188 h100pct: {height: '100%'},
189189- hContentRegion: isWeb ? {minHeight: '100%'} : {height: '100%'},
189189+ hContentRegion: IS_WEB ? {minHeight: '100%'} : {height: '100%'},
190190 window: {
191191 width: Dimensions.get('window').width,
192192 height: Dimensions.get('window').height,
+2-2
src/logger/index.ts
···1313 type Transport,
1414} from '#/logger/types'
1515import {enabledLogLevels} from '#/logger/util'
1616-import {isNative} from '#/platform/detection'
1616+import {IS_NATIVE} from '#/env'
1717import {ENV} from '#/env'
18181919export {type MetricEvents as Metrics} from '#/logger/metrics'
···2121const TRANSPORTS: Transport[] = (function configureTransports() {
2222 switch (ENV) {
2323 case 'production': {
2424- return [sentryTransport, isNative && bitdriftTransport].filter(
2424+ return [sentryTransport, IS_NATIVE && bitdriftTransport].filter(
2525 Boolean,
2626 ) as Transport[]
2727 }
+2-2
src/logger/transports/console.ts
···2233import {LogLevel, type Transport} from '#/logger/types'
44import {prepareMetadata} from '#/logger/util'
55-import {isWeb} from '#/platform/detection'
55+import {IS_WEB} from '#/env'
6677/**
88 * Used in dev mode to nicely log to the console
···3333 msg += ` ${message.toString()}`
3434 }
35353636- if (isWeb) {
3636+ if (IS_WEB) {
3737 if (hasMetadata) {
3838 console.groupCollapsed(msg)
3939 console.log(metadata)
···2121 type NativeStackScreenProps,
2222} from '#/lib/routes/types'
2323import {logger} from '#/logger'
2424-import {isIOS} from '#/platform/detection'
2524import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
2625import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery'
2726import {useSetMinimalShellMode} from '#/state/shell'
···3837import * as Skele from '#/components/Skeleton'
3938import * as toast from '#/components/Toast'
4039import {Text} from '#/components/Typography'
4040+import {IS_IOS} from '#/env'
41414242type Props = NativeStackScreenProps<CommonNavigatorParams, 'Bookmarks'>
4343···193193 }
194194 initialNumToRender={initialNumToRender}
195195 windowSize={9}
196196- maxToRenderPerBatch={isIOS ? 5 : 1}
196196+ maxToRenderPerBatch={IS_IOS ? 5 : 1}
197197 updateCellsBatchingPeriod={40}
198198 sideBorders={false}
199199 />
+4-4
src/screens/Deactivated.tsx
···7788import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
99import {logger} from '#/logger'
1010-import {isWeb} from '#/platform/detection'
1110import {
1211 type SessionAccount,
1312 useAgent,
···2423import * as Layout from '#/components/Layout'
2524import {Loader} from '#/components/Loader'
2625import {Text} from '#/components/Typography'
2626+import {IS_WEB} from '#/env'
27272828const COL_WIDTH = 400
2929···5555 }, [setShowLoggedOut])
56565757 const onPressLogout = React.useCallback(() => {
5858- if (isWeb) {
5858+ if (IS_WEB) {
5959 // We're switching accounts, which remounts the entire app.
6060 // On mobile, this gets us Home, but on the web we also need reset the URL.
6161 // We can't change the URL via a navigate() call because the navigator
···101101 contentContainerStyle={[
102102 a.px_2xl,
103103 {
104104- paddingTop: isWeb ? 64 : insets.top + 16,
105105- paddingBottom: isWeb ? 64 : insets.bottom,
104104+ paddingTop: IS_WEB ? 64 : insets.top + 16,
105105+ paddingBottom: IS_WEB ? 64 : insets.bottom,
106106 },
107107 ]}>
108108 <View
+2-2
src/screens/FindContactsFlowScreen.tsx
···99 type AllNavigatorParams,
1010 type NativeStackScreenProps,
1111} from '#/lib/routes/types'
1212-import {isNative} from '#/platform/detection'
1312import {useSetMinimalShellMode} from '#/state/shell'
1413import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
1514import {FindContactsFlow} from '#/components/contacts/FindContactsFlow'
1615import {useFindContactsFlowState} from '#/components/contacts/state'
1716import * as Layout from '#/components/Layout'
1817import {ScreenTransition} from '#/components/ScreenTransition'
1818+import {IS_NATIVE} from '#/env'
19192020type Props = NativeStackScreenProps<AllNavigatorParams, 'FindContactsFlow'>
2121export function FindContactsFlowScreen({navigation}: Props) {
···48484949 return (
5050 <Layout.Screen>
5151- {isNative ? (
5151+ {IS_NATIVE ? (
5252 <LayoutAnimationConfig skipEntering skipExiting>
5353 <ScreenTransition key={state.step} direction={transitionDirection}>
5454 <FindContactsFlow
+2-2
src/screens/Login/LoginForm.tsx
···1818import {cleanError} from '#/lib/strings/errors'
1919import {createFullHandle} from '#/lib/strings/handles'
2020import {logger} from '#/logger'
2121-import {isIOS} from '#/platform/detection'
2221import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
2322import {useSessionApi} from '#/state/session'
2423import {useLoggedOutViewControls} from '#/state/shell/logged-out'
···3231import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticket'
3332import {Loader} from '#/components/Loader'
3433import {Text} from '#/components/Typography'
3434+import {IS_IOS} from '#/env'
3535import {FormContainer} from './FormContainer'
36363737type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
···201201 inputRef={identifierRef}
202202 label={_(msg`Username or email address`)}
203203 autoCapitalize="none"
204204- autoFocus={!isIOS}
204204+ autoFocus={!IS_IOS}
205205 autoCorrect={false}
206206 autoComplete="username"
207207 returnKeyType="next"
+3-3
src/screens/Messages/ChatList.tsx
···1313import {type MessagesTabNavigatorParams} from '#/lib/routes/types'
1414import {cleanError} from '#/lib/strings/errors'
1515import {logger} from '#/logger'
1616-import {isNative} from '#/platform/detection'
1716import {listenSoftReset} from '#/state/events'
1817import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
1918import {useMessagesEventBus} from '#/state/messages/events'
···3837import {Link} from '#/components/Link'
3938import {ListFooter} from '#/components/Lists'
4039import {Text} from '#/components/Typography'
4040+import {IS_NATIVE} from '#/env'
4141import {ChatListItem} from './components/ChatListItem'
4242import {InboxPreview} from './components/InboxPreview'
4343···222222223223 const onSoftReset = useCallback(async () => {
224224 scrollElRef.current?.scrollToOffset({
225225- animated: isNative,
225225+ animated: IS_NATIVE,
226226 offset: 0,
227227 })
228228 try {
···348348 hasNextPage={hasNextPage}
349349 />
350350 }
351351- onEndReachedThreshold={isNative ? 1.5 : 0}
351351+ onEndReachedThreshold={IS_NATIVE ? 1.5 : 0}
352352 initialNumToRender={initialNumToRender}
353353 windowSize={11}
354354 desktopFixedHeight
+2-2
src/screens/Messages/Conversation.tsx
···2121 type CommonNavigatorParams,
2222 type NavigationProp,
2323} from '#/lib/routes/types'
2424-import {isWeb} from '#/platform/detection'
2524import {type Shadow, useMaybeProfileShadow} from '#/state/cache/profile-shadow'
2625import {useEmail} from '#/state/email-verification'
2726import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo'
···4342import {Error} from '#/components/Error'
4443import * as Layout from '#/components/Layout'
4544import {Loader} from '#/components/Loader'
4545+import {IS_WEB} from '#/env'
46464747type Props = NativeStackScreenProps<
4848 CommonNavigatorParams,
···7474 useCallback(() => {
7575 setCurrentConvoId(convoId)
76767777- if (isWeb && !gtMobile) {
7777+ if (IS_WEB && !gtMobile) {
7878 setMinimalShellMode(true)
7979 } else {
8080 setMinimalShellMode(false)
+2-2
src/screens/Messages/Inbox.tsx
···2121} from '#/lib/routes/types'
2222import {cleanError} from '#/lib/strings/errors'
2323import {logger} from '#/logger'
2424-import {isNative} from '#/platform/detection'
2524import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
2625import {useMessagesEventBus} from '#/state/messages/events'
2726import {useLeftConvos} from '#/state/queries/messages/leave-conversation'
···4443import * as Layout from '#/components/Layout'
4544import {ListFooter} from '#/components/Lists'
4645import {Text} from '#/components/Typography'
4646+import {IS_NATIVE} from '#/env'
4747import {RequestListItem} from './components/RequestListItem'
48484949type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesInbox'>
···288288 hasNextPage={hasNextPage}
289289 />
290290 }
291291- onEndReachedThreshold={isNative ? 1.5 : 0}
291291+ onEndReachedThreshold={IS_NATIVE ? 1.5 : 0}
292292 initialNumToRender={initialNumToRender}
293293 windowSize={11}
294294 desktopFixedHeight
+2-2
src/screens/Messages/Settings.tsx
···55import {type NativeStackScreenProps} from '@react-navigation/native-stack'
6677import {type CommonNavigatorParams} from '#/lib/routes/types'
88-import {isNative} from '#/platform/detection'
98import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration'
109import {useProfileQuery} from '#/state/queries/profile'
1110import {useSession} from '#/state/session'
···1615import * as Toggle from '#/components/forms/Toggle'
1716import * as Layout from '#/components/Layout'
1817import {Text} from '#/components/Typography'
1818+import {IS_NATIVE} from '#/env'
1919import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
20202121type AllowIncoming = 'all' | 'none' | 'following'
···118118 you choose.
119119 </Trans>
120120 </Admonition>
121121- {isNative && (
121121+ {IS_NATIVE && (
122122 <>
123123 <Divider style={a.my_md} />
124124 <Text style={[a.text_lg, a.font_semi_bold]}>
···18181919import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
2020import {useHaptics} from '#/lib/haptics'
2121-import {isIOS, isWeb} from '#/platform/detection'
2221import {useEmail} from '#/state/email-verification'
2322import {
2423 useMessageDraft,
···2928import {android, atoms as a, useTheme} from '#/alf'
3029import {useSharedInputStyles} from '#/components/forms/TextField'
3130import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
3131+import {IS_IOS, IS_WEB} from '#/env'
3232import {useExtractEmbedFromFacets} from './MessageInputEmbed'
33333434const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
···8484 playHaptic()
8585 setEmbed(undefined)
8686 setMessage('')
8787- if (isIOS) {
8787+ if (IS_IOS) {
8888 setShouldEnforceClear(true)
8989 }
9090- if (isWeb) {
9090+ if (IS_WEB) {
9191 // Pressing the send button causes the text input to lose focus, so we need to
9292 // re-focus it after sending
9393 setTimeout(() => {
···160160 // next change and double make sure the input is cleared. It should *always* send an onChange event after
161161 // clearing via setMessage('') that happens in onSubmit()
162162 // -sfn
163163- if (isIOS && shouldEnforceClear) {
163163+ if (IS_IOS && shouldEnforceClear) {
164164 setShouldEnforceClear(false)
165165 setMessage('')
166166 return
···175175 a.px_sm,
176176 t.atoms.text,
177177 android({paddingTop: 0}),
178178- {paddingBottom: isIOS ? 5 : 0},
178178+ {paddingBottom: IS_IOS ? 5 : 0},
179179 animatedStyle,
180180 ]}
181181 keyboardAppearance={t.scheme}
+7-7
src/screens/Messages/components/MessagesList.tsx
···2424 isBskyPostUrl,
2525} from '#/lib/strings/url-helpers'
2626import {logger} from '#/logger'
2727-import {isNative} from '#/platform/detection'
2828-import {isWeb} from '#/platform/detection'
2927import {
3028 type ActiveConvoStates,
3129 isConvoActive,
···5250import {NewMessagesPill} from '#/components/dms/NewMessagesPill'
5351import {Loader} from '#/components/Loader'
5452import {Text} from '#/components/Typography'
5353+import {IS_NATIVE} from '#/env'
5454+import {IS_WEB} from '#/env'
5555import {ChatStatusInfo} from './ChatStatusInfo'
5656import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed'
5757···159159 (_: number, height: number) => {
160160 // Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
161161 // previous off whenever we add new content to the previous offset whenever we add new content to the list.
162162- if (isWeb && isAtTop.get() && hasScrolled) {
162162+ if (IS_WEB && isAtTop.get() && hasScrolled) {
163163 flatListRef.current?.scrollToOffset({
164164 offset: height - prevContentHeight.current,
165165 animated: false,
···390390 (e: LayoutChangeEvent) => {
391391 layoutHeight.set(e.nativeEvent.layout.height)
392392393393- if (isWeb || !keyboardIsOpening.get()) {
393393+ if (IS_WEB || !keyboardIsOpening.get()) {
394394 flatListRef.current?.scrollToEnd({
395395 animated: !layoutScrollWithoutAnimation.get(),
396396 })
···429429 disableVirtualization={true}
430430 style={animatedListStyle}
431431 // The extra two items account for the header and the footer components
432432- initialNumToRender={isNative ? 32 : 62}
433433- maxToRenderPerBatch={isWeb ? 32 : 62}
432432+ initialNumToRender={IS_NATIVE ? 32 : 62}
433433+ maxToRenderPerBatch={IS_WEB ? 32 : 62}
434434 keyboardDismissMode="on-drag"
435435 keyboardShouldPersistTaps="handled"
436436 maintainVisibleContentPosition={{
···468468 )}
469469 </Animated.View>
470470471471- {isWeb && (
471471+ {IS_WEB && (
472472 <EmojiPicker
473473 pinToTop
474474 state={emojiPickerState}
+2-2
src/screens/Moderation/index.tsx
···1111 type NativeStackScreenProps,
1212} from '#/lib/routes/types'
1313import {logger} from '#/logger'
1414-import {isIOS} from '#/platform/detection'
1514import {useIsBirthdateUpdateAllowed} from '#/state/birthdate'
1615import {
1716 useMyLabelersQuery,
···4544import {GlobalLabelPreference} from '#/components/moderation/LabelPreference'
4645import {Text} from '#/components/Typography'
4746import {useAgeAssurance} from '#/ageAssurance'
4747+import {IS_IOS} from '#/env'
48484949function ErrorState({error}: {error: string}) {
5050 const t = useTheme()
···182182 (optimisticAdultContent && optimisticAdultContent.enabled) ||
183183 (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled)
184184 )
185185- const adultContentUIDisabledOnIOS = isIOS && !adultContentEnabled
185185+ const adultContentUIDisabledOnIOS = IS_IOS && !adultContentEnabled
186186 let adultContentUIDisabled = adultContentUIDisabledOnIOS
187187188188 if (aa.flags.adultContentDisabled) {
···2222import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
2323import {logEvent, useGate} from '#/lib/statsig/statsig'
2424import {logger} from '#/logger'
2525-import {isWeb} from '#/platform/detection'
2625import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
2726import {getAllListMembers} from '#/state/queries/list-members'
2827import {preferencesQueryKey} from '#/state/queries/preferences'
···4746import {Button, ButtonIcon, ButtonText} from '#/components/Button'
4847import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow'
4948import {Loader} from '#/components/Loader'
4949+import {IS_WEB} from '#/env'
5050import * as bsky from '#/types/bsky'
5151import {ValuePropositionPager} from './ValuePropositionPager'
5252···305305306306 <OnboardingControls.Portal>
307307 <View style={gtMobile && [a.gap_md, a.flex_row]}>
308308- {gtMobile && (isWeb ? subStep !== 2 : true) && (
308308+ {gtMobile && (IS_WEB ? subStep !== 2 : true) && (
309309 <Button
310310 disabled={saving}
311311 color="secondary"
+3-3
src/screens/Onboarding/StepProfile/index.tsx
···1717import {logEvent, useGate} from '#/lib/statsig/statsig'
1818import {isCancelledError} from '#/lib/strings/errors'
1919import {logger} from '#/logger'
2020-import {isNative, isWeb} from '#/platform/detection'
2120import {
2221 OnboardingControls,
2322 OnboardingDescriptionText,
···3837import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
3938import {CircleInfo_Stroke2_Corner0_Rounded} from '#/components/icons/CircleInfo'
4039import {Text} from '#/components/Typography'
4040+import {IS_NATIVE, IS_WEB} from '#/env'
4141import {type AvatarColor, avatarColors, type Emoji, emojiItems} from './types'
42424343export interface Avatar {
···183183 let image = items[0]
184184 if (!image) return
185185186186- if (!isWeb) {
186186+ if (!IS_WEB) {
187187 try {
188188 image = await openCropper({
189189 imageUri: image.path,
···200200201201 // If we are on mobile, prefetching the image will load the image into memory before we try and display it,
202202 // stopping any brief flickers.
203203- if (isNative) {
203203+ if (IS_NATIVE) {
204204 await ExpoImage.prefetch(image.path)
205205 }
206206
···1010import {popularInterests, useInterestsDisplayNames} from '#/lib/interests'
1111import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
1212import {logger} from '#/logger'
1313-import {isWeb} from '#/platform/detection'
1413import {updateProfileShadow} from '#/state/cache/profile-shadow'
1514import {useLanguagePrefs} from '#/state/preferences'
1615import {useModerationOpts} from '#/state/preferences/moderation-opts'
···3130import {Loader} from '#/components/Loader'
3231import * as ProfileCard from '#/components/ProfileCard'
3332import * as toast from '#/components/Toast'
3333+import {IS_WEB} from '#/env'
3434import type * as bsky from '#/types/bsky'
3535import {bulkWriteFollows} from '../util'
3636···161161 style={[
162162 a.overflow_hidden,
163163 a.mt_sm,
164164- isWeb
164164+ IS_WEB
165165 ? [a.max_w_full, web({minHeight: '100vh'})]
166166 : {marginHorizontal: tokens.space.xl * -1},
167167 a.flex_1,
···213213 a.mt_md,
214214 a.border_y,
215215 t.atoms.border_contrast_low,
216216- isWeb && [a.border_x, a.rounded_sm, a.overflow_hidden],
216216+ IS_WEB && [a.border_x, a.rounded_sm, a.overflow_hidden],
217217 ]}>
218218 {suggestedUsers?.actors.map((user, index) => (
219219 <SuggestedProfileCard
···324324 ...interestsDisplayNames,
325325 }
326326 }
327327- gutterWidth={isWeb ? 0 : tokens.space.xl}
327327+ gutterWidth={IS_WEB ? 0 : tokens.space.xl}
328328 />
329329 )
330330}
···350350 const node = cardRef.current
351351 if (!node || hasTrackedRef.current) return
352352353353- if (isWeb && typeof IntersectionObserver !== 'undefined') {
353353+ if (IS_WEB && typeof IntersectionObserver !== 'undefined') {
354354 const observer = new IntersectionObserver(
355355 entries => {
356356 if (entries[0]?.isIntersecting && !hasTrackedRef.current) {
+2-2
src/screens/Onboarding/index.tsx
···4455import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
66import {useGate} from '#/lib/statsig/statsig'
77-import {isNative} from '#/platform/detection'
87import {useLanguagePrefs} from '#/state/preferences'
98import {
109 Layout,
···2423import {useFindContactsFlowState} from '#/components/contacts/state'
2524import {Portal} from '#/components/Portal'
2625import {ScreenTransition} from '#/components/ScreenTransition'
2626+import {IS_NATIVE} from '#/env'
2727import {ENV} from '#/env'
2828import {StepFindContacts} from './StepFindContacts'
2929import {StepFindContactsIntro} from './StepFindContactsIntro'
···5050 useIsFindContactsFeatureEnabledBasedOnGeolocation()
5151 const showFindContacts =
5252 ENV !== 'e2e' &&
5353- isNative &&
5353+ IS_NATIVE &&
5454 findContactsEnabled &&
5555 !gate('disable_onboarding_find_contacts')
5656
+2-2
src/screens/PostThread/components/GrowthHack.tsx
···33import {PrivacySensitive} from 'expo-privacy-sensitive'
4455import {useAppState} from '#/lib/hooks/useAppState'
66-import {isIOS} from '#/platform/detection'
76import {atoms as a, useTheme} from '#/alf'
87import {sizes as iconSizes} from '#/components/icons/common'
98import {Mark as Logo} from '#/components/icons/Logo'
99+import {IS_IOS} from '#/env'
10101111const ICON_SIZE = 'xl' as const
1212···25252626 const appState = useAppState()
27272828- if (!isIOS || appState !== 'active') return children
2828+ if (!IS_IOS || appState !== 'active') return children
29293030 return (
3131 <View
···55import {useNavigation} from '@react-navigation/native'
6677import {logger} from '#/logger'
88-import {isIOS} from '#/platform/detection'
98import {useProfileShadow} from '#/state/cache/profile-shadow'
109import {
1110 useProfileFollowMutationQueue,
···1716import {Button, ButtonIcon, ButtonText} from '#/components/Button'
1817import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
1918import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
1919+import {IS_IOS} from '#/env'
2020import {GrowthHack} from './GrowthHack'
21212222export function ThreadItemAnchorFollowButton({did}: {did: string}) {
2323- if (isIOS) {
2323+ if (IS_IOS) {
2424 return (
2525 <GrowthHack>
2626 <ThreadItemAnchorFollowButtonInner did={did} />
+2-2
src/screens/Profile/Header/GrowableAvatar.tsx
···77} from 'react-native-reanimated'
88import type React from 'react'
991010-import {isIOS} from '#/platform/detection'
1110import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext'
1111+import {IS_IOS} from '#/env'
12121313export function GrowableAvatar({
1414 children,
···2020 const pagerContext = usePagerHeaderContext()
21212222 // pagerContext should only be present on iOS, but better safe than sorry
2323- if (!pagerContext || !isIOS) {
2323+ if (!pagerContext || !IS_IOS) {
2424 return <View style={style}>{children}</View>
2525 }
2626
+3-3
src/screens/Profile/Header/GrowableBanner.tsx
···1515import {useIsFetching} from '@tanstack/react-query'
1616import type React from 'react'
17171818-import {isIOS} from '#/platform/detection'
1918import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs'
2019import {RQKEY_ROOT as FEED_RQKEY_ROOT} from '#/state/queries/post-feed'
2120import {RQKEY_ROOT as FEEDGEN_RQKEY_ROOT} from '#/state/queries/profile-feedgens'
2221import {RQKEY_ROOT as LIST_RQKEY_ROOT} from '#/state/queries/profile-lists'
2322import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext'
2423import {atoms as a} from '#/alf'
2424+import {IS_IOS} from '#/env'
25252626const AnimatedBlurView = Animated.createAnimatedComponent(BlurView)
2727···3939 const pagerContext = usePagerHeaderContext()
40404141 // plain non-growable mode for Android/Web
4242- if (!pagerContext || !isIOS) {
4242+ if (!pagerContext || !IS_IOS) {
4343 return (
4444 <Pressable
4545 onPress={onPress}
···164164 style={[
165165 a.absolute,
166166 a.inset_0,
167167- {top: topInset - (isIOS ? 15 : 0)},
167167+ {top: topInset - (IS_IOS ? 15 : 0)},
168168 a.justify_center,
169169 a.align_center,
170170 ]}>
+3-3
src/screens/Profile/Header/Handle.tsx
···44import {useLingui} from '@lingui/react'
5566import {isInvalidHandle, sanitizeHandle} from '#/lib/strings/handles'
77-import {isIOS, isNative} from '#/platform/detection'
87import {type Shadow} from '#/state/cache/types'
98import {atoms as a, useTheme, web} from '#/alf'
109import {NewskieDialog} from '#/components/NewskieDialog'
1110import {Text} from '#/components/Typography'
1111+import {IS_IOS, IS_NATIVE} from '#/env'
12121313export function ProfileHeaderHandle({
1414 profile,
···2424 return (
2525 <View
2626 style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]}
2727- pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}>
2727+ pointerEvents={disableTaps ? 'none' : IS_IOS ? 'auto' : 'box-none'}>
2828 <NewskieDialog profile={profile} disabled={disableTaps} />
2929 {profile.viewer?.followedBy && !blockHide ? (
3030 <View style={[t.atoms.bg_contrast_50, a.rounded_xs, a.px_sm, a.py_xs]}>
···5959 profile.handle,
6060 '@',
6161 // forceLTR handled by CSS above on web
6262- isNative,
6262+ IS_NATIVE,
6363 )}
6464 </Text>
6565 </View>
···55import {useSafeAreaInsets} from 'react-native-safe-area-context'
66import {LinearGradient} from 'expo-linear-gradient'
7788-import {isIOS} from '#/platform/detection'
98import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext'
109import {atoms as a} from '#/alf'
1010+import {IS_IOS} from '#/env'
11111212const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient)
1313···1515 const {top: topInset} = useSafeAreaInsets()
1616 const pagerContext = usePagerHeaderContext()
17171818- if (isIOS && pagerContext) {
1818+ if (IS_IOS && pagerContext) {
1919 const {scrollY} = pagerContext
2020 return <StatusBarShadowInnner scrollY={scrollY} />
2121 }
+2-2
src/screens/Profile/Header/SuggestedFollows.tsx
···22import {type AppBskyActorDefs} from '@atproto/api'
3344import {AccordionAnimation} from '#/lib/custom-animations/AccordionAnimation'
55-import {isAndroid} from '#/platform/detection'
65import {useModerationOpts} from '#/state/preferences/moderation-opts'
76import {
87 useSuggestedFollowsByActorQuery,
···109} from '#/state/queries/suggested-follows'
1110import {useBreakpoints} from '#/alf'
1211import {ProfileGrid} from '#/components/FeedInterstitials'
1212+import {IS_ANDROID} from '#/env'
13131414const DISMISS_ANIMATION_DURATION = 200
1515···210210 * This issue stems from Android not allowing dragging on clickable elements in the profile header.
211211 * Blocking the ability to scroll on Android is too much of a trade-off for now.
212212 **/
213213- if (isAndroid) return null
213213+ if (IS_ANDROID) return null
214214215215 return (
216216 <AccordionAnimation isExpanded={isExpanded}>
+2-2
src/screens/Profile/Header/index.tsx
···1717import {useIsFocused} from '@react-navigation/native'
18181919import {sanitizeHandle} from '#/lib/strings/handles'
2020-import {isNative} from '#/platform/detection'
2120import {useProfileShadow} from '#/state/cache/profile-shadow'
2221import {useModerationOpts} from '#/state/preferences/moderation-opts'
2322import {useSetLightStatusBar} from '#/state/shell/light-status-bar'
···2625import {atoms as a, useTheme} from '#/alf'
2726import {Header} from '#/components/Layout'
2827import * as ProfileCard from '#/components/ProfileCard'
2828+import {IS_NATIVE} from '#/env'
2929import {
3030 HeaderLabelerButtons,
3131 ProfileHeaderLabeler,
···83838484 return (
8585 <>
8686- {isNative && (
8686+ {IS_NATIVE && (
8787 <MinimalHeader
8888 onLayout={evt => setMinimumHeight(evt.nativeEvent.layout.height)}
8989 profile={props.profile}
+3-3
src/screens/Profile/ProfileFeed/index.tsx
···1717import {type NavigationProp} from '#/lib/routes/types'
1818import {makeRecordUri} from '#/lib/strings/url-helpers'
1919import {s} from '#/lib/styles'
2020-import {isNative} from '#/platform/detection'
2120import {listenSoftReset} from '#/state/events'
2221import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
2322import {
···4746} from '#/screens/Profile/components/ProfileFeedHeader'
4847import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag'
4948import * as Layout from '#/components/Layout'
4949+import {IS_NATIVE} from '#/env'
50505151type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'>
5252export function ProfileFeedScreen(props: Props) {
···175175176176 const onScrollToTop = useCallback(() => {
177177 scrollElRef.current?.scrollToOffset({
178178- animated: isNative,
178178+ animated: IS_NATIVE,
179179 offset: 0, // -headerHeight,
180180 })
181181 truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
···204204 const feedIsVideoMode =
205205 feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO
206206 const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode
207207- return isNative && _isVideoFeed
207207+ return IS_NATIVE && _isVideoFeed
208208 }, [feedInfo])
209209210210 return (
···33import {Trans} from '@lingui/macro'
4455import {logger} from '#/logger'
66-import {isWeb} from '#/platform/detection'
76import {
87 DEFAULT_LIMIT as RECOMMENDATIONS_COUNT,
98 useTrendingTopics,
···1716 TrendingTopicSkeleton,
1817} from '#/components/TrendingTopics'
1918import {Text} from '#/components/Typography'
1919+import {IS_WEB} from '#/env'
20202121// Note: This module is not currently used and may be removed in the future.
2222···3737 <View
3838 style={[
3939 a.flex_row,
4040- isWeb
4040+ IS_WEB
4141 ? [a.px_lg, a.py_lg, a.pt_2xl, a.gap_md]
4242 : [a.p_lg, a.pt_2xl, a.gap_md],
4343 a.border_b,
+4-4
src/screens/Settings/AboutSettings.tsx
···11111212import {STATUS_PAGE_URL} from '#/lib/constants'
1313import {type CommonNavigatorParams} from '#/lib/routes/types'
1414-import {isAndroid, isIOS, isNative} from '#/platform/detection'
1514import * as Toast from '#/view/com/util/Toast'
1615import * as SettingsList from '#/screens/Settings/components/SettingsList'
1716import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
···2221import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
2322import * as Layout from '#/components/Layout'
2423import {Loader} from '#/components/Loader'
2424+import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env'
2525import * as env from '#/env'
2626import {useDemoMode} from '#/storage/hooks/demo-mode'
2727import {useDevMode} from '#/storage/hooks/dev-mode'
···4444 return spaceDiff * -1
4545 },
4646 onSuccess: sizeDiffBytes => {
4747- if (isAndroid) {
4747+ if (IS_ANDROID) {
4848 Toast.show(
4949 _(
5050 msg({
···110110 <Trans>System log</Trans>
111111 </SettingsList.ItemText>
112112 </SettingsList.LinkItem>
113113- {isNative && (
113113+ {IS_NATIVE && (
114114 <SettingsList.PressableItem
115115 onPress={() => onClearImageCache()}
116116 label={_(msg`Clear image cache`)}
···159159 {devModeEnabled && (
160160 <>
161161 <OTAInfo />
162162- {isIOS && (
162162+ {IS_IOS && (
163163 <SettingsList.PressableItem
164164 onPress={() => {
165165 const newDemoModeEnabled = !demoModeEnabled
+2-2
src/screens/Settings/AccessibilitySettings.tsx
···33import {type NativeStackScreenProps} from '@react-navigation/native-stack'
4455import {type CommonNavigatorParams} from '#/lib/routes/types'
66-import {isNative} from '#/platform/detection'
76import {
87 useHapticsDisabled,
98 useRequireAltTextEnabled,
···2019import {Accessibility_Stroke2_Corner2_Rounded as AccessibilityIcon} from '#/components/icons/Accessibility'
2120import {Haptic_Stroke2_Corner2_Rounded as HapticIcon} from '#/components/icons/Haptic'
2221import * as Layout from '#/components/Layout'
2222+import {IS_NATIVE} from '#/env'
23232424type Props = NativeStackScreenProps<
2525 CommonNavigatorParams,
···7676 <Toggle.Platform />
7777 </Toggle.Item>
7878 </SettingsList.Group>
7979- {isNative && (
7979+ {IS_NATIVE && (
8080 <>
8181 <SettingsList.Divider />
8282 <SettingsList.Group contentContainerStyle={[a.gap_sm]}>
+3-3
src/screens/Settings/AppIconSettings/index.tsx
···88import {PressableScale} from '#/lib/custom-animations/PressableScale'
99import {type CommonNavigatorParams} from '#/lib/routes/types'
1010import {useGate} from '#/lib/statsig/statsig'
1111-import {isAndroid} from '#/platform/detection'
1211import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage'
1312import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types'
1413import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets'
···1615import * as Toggle from '#/components/forms/Toggle'
1716import * as Layout from '#/components/Layout'
1817import {Text} from '#/components/Typography'
1818+import {IS_ANDROID} from '#/env'
1919import {IS_INTERNAL} from '#/env'
20202121type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppIconSettings'>
···2929 )
30303131 const onSetAppIcon = (icon: DynamicAppIcon.IconName) => {
3232- if (isAndroid) {
3232+ if (IS_ANDROID) {
3333 const next =
3434 sets.defaults.find(i => i.id === icon) ??
3535 sets.core.find(i => i.id === icon)
···221221 accessibilityHint={_(msg`Changes app icon`)}
222222 targetScale={0.95}
223223 onPress={() => {
224224- if (isAndroid) {
224224+ if (IS_ANDROID) {
225225 Alert.alert(
226226 _(msg`Change app icon to "${icon.name}"`),
227227 _(msg`The app will be restarted`),
+2-2
src/screens/Settings/AppearanceSettings.tsx
···1212 type CommonNavigatorParams,
1313 type NativeStackScreenProps,
1414} from '#/lib/routes/types'
1515-import {isNative} from '#/platform/detection'
1615import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
1716import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem'
1817import {type Alf, atoms as a, native, useAlf, useTheme} from '#/alf'
···2423import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
2524import * as Layout from '#/components/Layout'
2625import {Text} from '#/components/Typography'
2626+import {IS_NATIVE} from '#/env'
2727import {IS_INTERNAL} from '#/env'
2828import * as SettingsList from './components/SettingsList'
2929···165165 onChange={onChangeFontScale}
166166 />
167167168168- {isNative && IS_INTERNAL && (
168168+ {IS_NATIVE && IS_INTERNAL && (
169169 <>
170170 <SettingsList.Divider />
171171 <AppIconSettingsListItem />
+2-2
src/screens/Settings/ContentAndMediaSettings.tsx
···4455import {type CommonNavigatorParams} from '#/lib/routes/types'
66import {logEvent} from '#/lib/statsig/statsig'
77-import {isNative} from '#/platform/detection'
87import {useAutoplayDisabled, useSetAutoplayDisabled} from '#/state/preferences'
98import {
109 useInAppBrowser,
···2625import {Trending2_Stroke2_Corner2_Rounded as Graph} from '#/components/icons/Trending'
2726import {Window_Stroke2_Corner2_Rounded as WindowIcon} from '#/components/icons/Window'
2827import * as Layout from '#/components/Layout'
2828+import {IS_NATIVE} from '#/env'
2929import {LiveEventFeedsSettingsToggle} from '#/features/liveEvents/components/LiveEventFeedsSettingsToggle'
30303131type Props = NativeStackScreenProps<
···9797 </SettingsList.ItemText>
9898 </SettingsList.LinkItem>
9999 <SettingsList.Divider />
100100- {isNative && (
100100+ {IS_NATIVE && (
101101 <Toggle.Item
102102 name="use_in_app_browser"
103103 label={_(msg`Use in-app browser to open links`)}
+2-2
src/screens/Settings/FindContactsSettings.tsx
···2020} from '#/lib/routes/types'
2121import {cleanError, isNetworkError} from '#/lib/strings/errors'
2222import {logger} from '#/logger'
2323-import {isNative} from '#/platform/detection'
2423import {
2524 updateProfileShadow,
2625 useProfileShadow,
···4847import * as ProfileCard from '#/components/ProfileCard'
4948import * as Toast from '#/components/Toast'
5049import {Text} from '#/components/Typography'
5050+import {IS_NATIVE} from '#/env'
5151import type * as bsky from '#/types/bsky'
5252import {bulkWriteFollows} from '../Onboarding/util'
5353···7878 </Layout.Header.Content>
7979 <Layout.Header.Slot />
8080 </Layout.Header.Outer>
8181- {isNative ? (
8181+ {IS_NATIVE ? (
8282 data ? (
8383 !data.syncStatus ? (
8484 <Intro />
···1313import {useLingui} from '@lingui/react'
1414import {useMutation} from '@tanstack/react-query'
15151616-import {isWeb} from '#/platform/detection'
1716import {useAppPasswordCreateMutation} from '#/state/queries/app-passwords'
1817import {atoms as a, native, useTheme} from '#/alf'
1918import {Admonition} from '#/components/Admonition'
···2423import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
2524import {SquareBehindSquare4_Stroke2_Corner0_Rounded as CopyIcon} from '#/components/icons/SquareBehindSquare4'
2625import {Text} from '#/components/Typography'
2626+import {IS_WEB} from '#/env'
2727import {CopyButton} from './CopyButton'
28282929export function AddAppPasswordDialog({
···181181 ) : (
182182 <Animated.View
183183 style={[a.gap_lg]}
184184- entering={isWeb ? FadeIn.delay(200) : SlideInRight}
184184+ entering={IS_WEB ? FadeIn.delay(200) : SlideInRight}
185185 key={1}>
186186 <Text style={[a.text_2xl, a.font_semi_bold]}>
187187 <Trans>Here is your app password!</Trans>
···44import {useLingui} from '@lingui/react'
5566import {cleanError} from '#/lib/strings/errors'
77-import {isNative} from '#/platform/detection'
87import {useAgent, useSession} from '#/state/session'
98import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
109import * as Toast from '#/view/com/util/Toast'
···1514import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
1615import {Loader} from '#/components/Loader'
1716import {P, Text} from '#/components/Typography'
1717+import {IS_NATIVE} from '#/env'
18181919enum Stages {
2020 Email,
···193193 </View>
194194 ) : undefined}
195195196196- {!gtMobile && isNative && <View style={{height: 40}} />}
196196+ {!gtMobile && IS_NATIVE && <View style={{height: 40}} />}
197197 </View>
198198 </Dialog.ScrollableInner>
199199 </Dialog.Outer>
+8-6
src/screens/Signup/StepCaptcha/index.tsx
···7788import {createFullHandle} from '#/lib/strings/handles'
99import {logger} from '#/logger'
1010-import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
1110import {useSignupContext} from '#/screens/Signup/state'
1211import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
1312import {atoms as a, useTheme} from '#/alf'
1413import {FormError} from '#/components/forms/FormError'
1414+import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
1515import {GCP_PROJECT_ID} from '#/env'
1616import {BackNextButtons} from '../BackNextButtons'
17171818const CAPTCHA_PATH =
1919- isWeb || GCP_PROJECT_ID === 0 ? '/gate/signup' : '/gate/signup/attempt-attest'
1919+ IS_WEB || GCP_PROJECT_ID === 0
2020+ ? '/gate/signup'
2121+ : '/gate/signup/attempt-attest'
20222123export function StepCaptcha() {
2222- if (isWeb) {
2424+ if (IS_WEB) {
2325 return <StepCaptchaInner />
2426 } else {
2527 return <StepCaptchaNative />
···3537 ;(async () => {
3638 logger.debug('trying to generate attestation token...')
3739 try {
3838- if (isIOS) {
4040+ if (IS_IOS) {
3941 logger.debug('starting to generate devicecheck token...')
4042 const token = await ReactNativeDeviceAttest.getDeviceCheckToken()
4143 setToken(token)
···8587 newUrl.searchParams.set('state', stateParam)
8688 newUrl.searchParams.set('colorScheme', theme.name)
87898888- if (isNative && token) {
9090+ if (IS_NATIVE && token) {
8991 newUrl.searchParams.set('platform', Platform.OS)
9092 newUrl.searchParams.set('token', token)
9191- if (isAndroid && payload) {
9393+ if (IS_ANDROID && payload) {
9294 newUrl.searchParams.set('payload', payload)
9395 }
9496 }
+3-3
src/screens/Signup/StepInfo/index.tsx
···7788import {isEmailMaybeInvalid} from '#/lib/strings/email'
99import {logger} from '#/logger'
1010-import {isNative} from '#/platform/detection'
1110import {useSignupContext} from '#/screens/Signup/state'
1211import {Policies} from '#/screens/Signup/StepInfo/Policies'
1312import {atoms as a, native} from '#/alf'
···3130 MIN_ACCESS_AGE,
3231 useAgeAssuranceRegionConfigWithFallback,
3332} from '#/ageAssurance/util'
3333+import {IS_NATIVE} from '#/env'
3434import {
3535 useDeviceGeolocationApi,
3636 useIsDeviceGeolocationGranted,
···325325 </Trans>
326326 )}
327327 </Admonition.Text>
328328- {isNative &&
328328+ {IS_NATIVE &&
329329 !isDeviceGeolocationGranted &&
330330 isOverAppMinAccessAge && (
331331 <Admonition.Text>
···357357 ) : undefined}
358358 </View>
359359360360- {isNative && (
360360+ {IS_NATIVE && (
361361 <DeviceLocationRequestDialog
362362 control={locationControl}
363363 onLocationAcquired={props => {
+2-2
src/screens/Signup/index.tsx
···8899import {FEEDBACK_FORM_URL} from '#/lib/constants'
1010import {logger} from '#/logger'
1111-import {isAndroid} from '#/platform/detection'
1211import {useServiceQuery} from '#/state/queries/service'
1312import {useStarterPackQuery} from '#/state/queries/starter-packs'
1413import {useActiveStarterPack} from '#/state/shell/starter-pack'
···3029import {InlineLinkText} from '#/components/Link'
3130import {ScreenTransition} from '#/components/ScreenTransition'
3231import {Text} from '#/components/Typography'
3232+import {IS_ANDROID} from '#/env'
3333import {GCP_PROJECT_ID} from '#/env'
3434import * as bsky from '#/types/bsky'
3535···108108109109 // On Android, warmup the Play Integrity API on the signup screen so it is ready by the time we get to the gate screen.
110110 useEffect(() => {
111111- if (!isAndroid) {
111111+ if (!IS_ANDROID) {
112112 return
113113 }
114114 ReactNativeDeviceAttest.warmupIntegrity(GCP_PROJECT_ID).catch(err =>
···11import React from 'react'
2233-import {isWeb} from '#/platform/detection'
43import {type DialogControlRefProps} from '#/components/Dialog'
54import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context'
55+import {IS_WEB} from '#/env'
66import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet'
7788interface IDialogContext {
···6262 const openDialogs = React.useRef<Set<string>>(new Set())
63636464 const closeAllDialogs = React.useCallback(() => {
6565- if (isWeb) {
6565+ if (IS_WEB) {
6666 openDialogs.current.forEach(id => {
6767 const dialog = activeDialogs.current.get(id)
6868 if (dialog) dialog.current.close()
+5-5
src/state/gallery.ts
···1818import {type PickerImage} from '#/lib/media/picker.shared'
1919import {getDataUriSize} from '#/lib/media/util'
2020import {isCancelledError} from '#/lib/strings/errors'
2121-import {isNative} from '#/platform/detection'
2121+import {IS_NATIVE} from '#/env'
22222323export type ImageTransformation = {
2424 crop?: ActionCrop['crop']
···5555let _imageCacheDirectory: string
56565757function getImageCacheDirectory(): string | null {
5858- if (isNative) {
5858+ if (IS_NATIVE) {
5959 return (_imageCacheDirectory ??= joinPath(cacheDirectory!, 'bsky-composer'))
6060 }
6161···120120}
121121122122export async function cropImage(img: ComposerImage): Promise<ComposerImage> {
123123- if (!isNative) {
123123+ if (!IS_NATIVE) {
124124 return img
125125 }
126126···244244}
245245246246async function moveIfNecessary(from: string) {
247247- const cacheDir = isNative && getImageCacheDirectory()
247247+ const cacheDir = IS_NATIVE && getImageCacheDirectory()
248248249249 if (cacheDir && from.startsWith(cacheDir)) {
250250 const to = joinPath(cacheDir, nanoid(36))
···260260261261/** Purge files that were created to accomodate image manipulation */
262262export async function purgeTemporaryImageFiles() {
263263- const cacheDir = isNative && getImageCacheDirectory()
263263+ const cacheDir = IS_NATIVE && getImageCacheDirectory()
264264265265 if (cacheDir) {
266266 await deleteAsync(cacheDir, {idempotent: true})
+2-2
src/state/messages/convo/agent.ts
···1616 isNetworkError,
1717} from '#/lib/strings/errors'
1818import {Logger} from '#/logger'
1919-import {isNative} from '#/platform/detection'
2019import {
2120 ACTIVE_POLL_INTERVAL,
2221 BACKGROUND_POLL_INTERVAL,
···3736} from '#/state/messages/convo/types'
3837import {type MessagesEventBus} from '#/state/messages/events/agent'
3938import {type MessagesEventBusError} from '#/state/messages/events/types'
3939+import {IS_NATIVE} from '#/env'
40404141const logger = Logger.create(Logger.Context.ConversationAgent)
4242···639639 {
640640 cursor: nextCursor,
641641 convoId: this.convoId,
642642- limit: isNative ? 30 : 60,
642642+ limit: IS_NATIVE ? 30 : 60,
643643 },
644644 {headers: DM_SERVICE_HEADERS},
645645 )
+2-2
src/state/preferences/kawaii.tsx
···11import React from 'react'
2233-import {isWeb} from '#/platform/detection'
43import * as persisted from '#/state/persisted'
44+import {IS_WEB} from '#/env'
5566type StateContext = persisted.Schema['kawaii']
77···3030 React.useEffect(() => {
3131 // dumb and stupid but it's web only so just refresh the page if you want to change it
32323333- if (isWeb) {
3333+ if (IS_WEB) {
3434 const kawaii = new URLSearchParams(window.location.search).get('kawaii')
3535 switch (kawaii) {
3636 case 'true':
+2-2
src/state/queries/usePostThread/index.ts
···11import {useCallback, useMemo, useState} from 'react'
22import {useQuery, useQueryClient} from '@tanstack/react-query'
3344-import {isWeb} from '#/platform/detection'
54import {useModerationOpts} from '#/state/preferences/moderation-opts'
65import {useThreadPreferences} from '#/state/queries/preferences/useThreadPreferences'
76import {
···3130import {useAgent, useSession} from '#/state/session'
3231import {useMergeThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
3332import {useBreakpoints} from '#/alf'
3333+import {IS_WEB} from '#/env'
34343535export * from '#/state/queries/usePostThread/context'
3636export {useUpdatePostThreadThreadgateQueryCache} from '#/state/queries/usePostThread/queryCache'
···5353 const below = useMemo(() => {
5454 return view === 'linear'
5555 ? LINEAR_VIEW_BELOW
5656- : isWeb && gtPhone
5656+ : IS_WEB && gtPhone
5757 ? TREE_VIEW_BELOW_DESKTOP
5858 : TREE_VIEW_BELOW
5959 }, [view, gtPhone])
+2-2
src/state/session/index.tsx
···11import React from 'react'
22import {type AtpSessionEvent, type BskyAgent} from '@atproto/api'
3344-import {isWeb} from '#/platform/detection'
54import * as persisted from '#/state/persisted'
65import {useCloseAllActiveElements} from '#/state/util'
76import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
77+import {IS_WEB} from '#/env'
88import {emitSessionDropped} from '../events'
99import {
1010 agentToSessionAccount,
···340340 )
341341342342 // @ts-expect-error window type is not declared, debug only
343343- if (__DEV__ && isWeb) window.agent = state.currentAgentState.agent
343343+ if (__DEV__ && IS_WEB) window.agent = state.currentAgentState.agent
344344345345 const agent = state.currentAgentState.agent as BskyAppAgent
346346 const currentAgentRef = React.useRef(agent)
+2-2
src/state/shell/logged-out.tsx
···11import React from 'react'
2233-import {isWeb} from '#/platform/detection'
43import {useSession} from '#/state/session'
54import {useActiveStarterPack} from '#/state/shell/starter-pack'
55+import {IS_WEB} from '#/env'
6677type State = {
88 showLoggedOut: boolean
···5555 const [state, setState] = React.useState<State>({
5656 showLoggedOut: shouldShowStarterPack,
5757 requestedAccountSwitchTo: shouldShowStarterPack
5858- ? isWeb
5858+ ? IS_WEB
5959 ? 'starterpack'
6060 : 'new'
6161 : undefined,
+3-3
src/state/shell/selected-feed.tsx
···11import {createContext, useCallback, useContext, useState} from 'react'
2233-import {isWeb} from '#/platform/detection'
43import {type FeedDescriptor} from '#/state/queries/post-feed'
54import {useSession} from '#/state/session'
55+import {IS_WEB} from '#/env'
66import {account} from '#/storage'
7788type StateContext = FeedDescriptor | null
···1414setContext.displayName = 'SelectedFeedSetContext'
15151616function getInitialFeed(did?: string): FeedDescriptor | null {
1717- if (isWeb) {
1717+ if (IS_WEB) {
1818 if (window.location.pathname === '/') {
1919 const params = new URLSearchParams(window.location.search)
2020 const feedFromUrl = params.get('feed')
···4949 const saveState = useCallback(
5050 (feed: FeedDescriptor) => {
5151 setState(feed)
5252- if (isWeb) {
5252+ if (IS_WEB) {
5353 try {
5454 sessionStorage.setItem('lastSelectedHomeFeed', feed)
5555 } catch {}
···7676import {cleanError} from '#/lib/strings/errors'
7777import {colors} from '#/lib/styles'
7878import {logger} from '#/logger'
7979-import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
8079import {useDialogStateControlContext} from '#/state/dialogs'
8180import {emitPostCreated} from '#/state/events'
8281import {
···130129import * as Prompt from '#/components/Prompt'
131130import * as Toast from '#/components/Toast'
132131import {Text as NewText} from '#/components/Typography'
132132+import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
133133import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
134134import {PostLanguageSelect} from './select-language/PostLanguageSelect'
135135import {
···329329 const insets = useSafeAreaInsets()
330330 const viewStyles = useMemo(
331331 () => ({
332332- paddingTop: isAndroid ? insets.top : 0,
332332+ paddingTop: IS_ANDROID ? insets.top : 0,
333333 paddingBottom:
334334 // iOS - when keyboard is closed, keep the bottom bar in the safe area
335335- (isIOS && !isKeyboardVisible) ||
335335+ (IS_IOS && !isKeyboardVisible) ||
336336 // Android - Android >=35 KeyboardAvoidingView adds double padding when
337337 // keyboard is closed, so we subtract that in the offset and add it back
338338 // here when the keyboard is open
339339- (isAndroid && isKeyboardVisible)
339339+ (IS_ANDROID && isKeyboardVisible)
340340 ? insets.bottom
341341 : 0,
342342 }),
···366366367367 // On Android, pressing Back should ask confirmation.
368368 useEffect(() => {
369369- if (!isAndroid) {
369369+ if (!IS_ANDROID) {
370370 return
371371 }
372372 const backHandler = BackHandler.addEventListener(
···671671 composerState.mutableNeedsFocusActive = false
672672 // On Android, this risks getting the cursor stuck behind the keyboard.
673673 // Not worth it.
674674- if (!isAndroid) {
674674+ if (!IS_ANDROID) {
675675 textInput.current?.focus()
676676 }
677677 }
···727727 </>
728728 )
729729730730- const isWebFooterSticky = !isNative && thread.posts.length > 1
730730+ const IS_WEBFooterSticky = !IS_NATIVE && thread.posts.length > 1
731731 return (
732732 <BottomSheetPortalProvider>
733733 <KeyboardAvoidingView
734734 testID="composePostView"
735735- behavior={isIOS ? 'padding' : 'height'}
735735+ behavior={IS_IOS ? 'padding' : 'height'}
736736 keyboardVerticalOffset={keyboardVerticalOffset}
737737 style={a.flex_1}>
738738 <View
···790790 onPublish={onComposerPostPublish}
791791 onError={setError}
792792 />
793793- {isWebFooterSticky && post.id === activePost.id && (
793793+ {IS_WEBFooterSticky && post.id === activePost.id && (
794794 <View style={styles.stickyFooterWeb}>{footer}</View>
795795 )}
796796 </React.Fragment>
797797 ))}
798798 </Animated.ScrollView>
799799- {!isWebFooterSticky && footer}
799799+ {!IS_WEBFooterSticky && footer}
800800 </View>
801801802802 <Prompt.Basic
···849849 const {data: currentProfile} = useProfileQuery({did: currentDid})
850850 const richtext = post.richtext
851851 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media
852852- const forceMinHeight = isWeb && isTextOnly && isActive
852852+ const forceMinHeight = IS_WEB && isTextOnly && isActive
853853 const selectTextInputPlaceholder = isReply
854854 ? isFirstPost
855855 ? _(msg`Write your reply`)
···889889 async (uri: string) => {
890890 if (
891891 uri.startsWith('data:video/') ||
892892- (isWeb && uri.startsWith('data:image/gif'))
892892+ (IS_WEB && uri.startsWith('data:image/gif'))
893893 ) {
894894- if (isNative) return // web only
894894+ if (IS_NATIVE) return // web only
895895 const [mimeType] = uri.slice('data:'.length).split(';')
896896 if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
897897 Toast.show(_(msg`Unsupported video type: ${mimeType}`), {
···921921 a.mb_sm,
922922 !isActive && isLastPost && a.mb_lg,
923923 !isActive && styles.inactivePost,
924924- isTextOnly && isNative && a.flex_grow,
924924+ isTextOnly && IS_NATIVE && a.flex_grow,
925925 ]}>
926926- <View style={[a.flex_row, isNative && a.flex_1]}>
926926+ <View style={[a.flex_row, IS_NATIVE && a.flex_1]}>
927927 <UserAvatar
928928 avatar={currentProfile?.avatar}
929929 size={42}
···12411241 </LayoutAnimationConfig>
12421242 {embed.quote?.uri ? (
12431243 <View
12441244- style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], isWeb && [a.pb_md]]}>
12441244+ style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], IS_WEB && [a.pb_md]]}>
12451245 <View style={[a.relative]}>
12461246 <View style={{pointerEvents: 'none'}}>
12471247 <LazyQuoteEmbed uri={embed.quote.uri} />
···16521652 const {top, bottom} = useSafeAreaInsets()
1653165316541654 // Android etc
16551655- if (!isIOS) {
16551655+ if (!IS_IOS) {
16561656 // need to account for the edge-to-edge nav bar
16571657 return bottom * -1
16581658 }
···16961696 const appState = useAppState()
1697169716981698 useEffect(() => {
16991699- if (isIOS) {
16991699+ if (IS_IOS) {
17001700 if (appState === 'inactive') {
17011701 Keyboard.dismiss()
17021702 }
···18461846 style: StyleProp<ViewStyle>
18471847 children: React.ReactNode
18481848}) {
18491849- if (isWeb) return children
18491849+ if (IS_WEB) return children
18501850 return (
18511851 <Animated.View
18521852 style={style}
+2-2
src/view/com/composer/GifAltText.tsx
···99 type EmbedPlayerParams,
1010 parseEmbedPlayerFromUrl,
1111} from '#/lib/strings/embed-player'
1212-import {isAndroid} from '#/platform/detection'
1312import {useResolveGifQuery} from '#/state/queries/resolve-link'
1413import {type Gif} from '#/state/queries/tenor'
1514import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
···2322import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
2423import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif'
2524import {Text} from '#/components/Typography'
2525+import {IS_ANDROID} from '#/env'
2626import {AltTextReminder} from './photos/Gallery'
27272828export function GifAltTextDialog({
···224224 </View>
225225 <Dialog.Close />
226226 {/* Maybe fix this later -h */}
227227- {isAndroid ? <View style={{height: 300}} /> : null}
227227+ {IS_ANDROID ? <View style={{height: 300}} /> : null}
228228 </Dialog.ScrollableInner>
229229 )
230230}
+2-2
src/view/com/composer/KeyboardAccessory.tsx
···33import {useSafeAreaInsets} from 'react-native-safe-area-context'
44import type React from 'react'
5566-import {isWeb} from '#/platform/detection'
76import {atoms as a, useTheme} from '#/alf'
77+import {IS_WEB} from '#/env'
8899export function KeyboardAccessory({children}: {children: React.ReactNode}) {
1010 const t = useTheme()
···2222 ]
23232424 // todo: when iPad support is added, it should also not use the KeyboardStickyView
2525- if (isWeb) {
2525+ if (IS_WEB) {
2626 return <View style={style}>{children}</View>
2727 }
2828
+9-9
src/view/com/composer/SelectMediaButton.tsx
···1111} from '#/lib/hooks/usePermissions'
1212import {openUnifiedPicker} from '#/lib/media/picker'
1313import {extractDataUriMime} from '#/lib/media/util'
1414-import {isNative, isWeb} from '#/platform/detection'
1514import {MAX_IMAGES} from '#/view/com/composer/state/composer'
1615import {atoms as a, useTheme} from '#/alf'
1716import {Button} from '#/components/Button'
1817import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
1918import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
2019import * as toast from '#/components/Toast'
2020+import {IS_NATIVE, IS_WEB} from '#/env'
21212222export type SelectMediaButtonProps = {
2323 disabled?: boolean
···9191 'image/svg+xml',
9292 'image/webp',
9393 'image/avif',
9494- isNative && 'image/heic',
9494+ IS_NATIVE && 'image/heic',
9595 ] as const
9696).filter(Boolean)
9797type SupportedImageMimeType = Exclude<
···261261 * We don't care too much about mimeType at this point on native,
262262 * since the `processVideo` step later on will convert to `.mp4`.
263263 */
264264- if (isWeb && !isSupportedVideoMimeType(mimeType)) {
264264+ if (IS_WEB && !isSupportedVideoMimeType(mimeType)) {
265265 errors.add(SelectedAssetError.Unsupported)
266266 continue
267267 }
···271271 * to filter out large files on web. On native, we compress these anyway,
272272 * so we only check on web.
273273 */
274274- if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
274274+ if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
275275 errors.add(SelectedAssetError.FileTooBig)
276276 continue
277277 }
···290290 * to filter out large files on web. On native, we compress GIFs as
291291 * videos anyway, so we only check on web.
292292 */
293293- if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
293293+ if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
294294 errors.add(SelectedAssetError.FileTooBig)
295295 continue
296296 }
···308308 * base64 data-uri, so we construct it here for web only.
309309 */
310310 uri:
311311- isWeb && asset.base64
311311+ IS_WEB && asset.base64
312312 ? `data:${mimeType};base64,${asset.base64}`
313313 : asset.uri,
314314 })
···327327 }
328328329329 if (supportedAssets[0].duration) {
330330- if (isWeb) {
330330+ if (IS_WEB) {
331331 /*
332332 * Web reports duration as seconds
333333 */
···432432 )
433433434434 const onPressSelectMedia = useCallback(async () => {
435435- if (isNative) {
435435+ if (IS_NATIVE) {
436436 const [photoAccess, videoAccess] = await Promise.all([
437437 requestPhotoAccessIfNeeded(),
438438 requestVideoAccessIfNeeded(),
···446446 }
447447 }
448448449449- if (isNative && Keyboard.isVisible()) {
449449+ if (IS_NATIVE && Keyboard.isVisible()) {
450450 Keyboard.dismiss()
451451 }
452452
+2-2
src/view/com/composer/labels/LabelsBtn.tsx
···99 type OtherSelfLabel,
1010 type SelfLabel,
1111} from '#/lib/moderation'
1212-import {isWeb} from '#/platform/detection'
1312import {atoms as a, useTheme, web} from '#/alf'
1413import {Button, ButtonIcon, ButtonText} from '#/components/Button'
1514import * as Dialog from '#/components/Dialog'
···1817import {TinyChevronBottom_Stroke2_Corner0_Rounded as TinyChevronIcon} from '#/components/icons/Chevron'
1918import {Shield_Stroke2_Corner0_Rounded} from '#/components/icons/Shield'
2019import {Text} from '#/components/Typography'
2020+import {IS_WEB} from '#/env'
21212222export function LabelsBtn({
2323 labels,
···218218 label={_(msg`Done`)}
219219 onPress={() => control.close()}
220220 color="primary"
221221- size={isWeb ? 'small' : 'large'}
221221+ size={IS_WEB ? 'small' : 'large'}
222222 variant="solid"
223223 testID="confirmBtn">
224224 <ButtonText>
+2-2
src/view/com/composer/photos/Gallery.tsx
···1616import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
1717import {type Dimensions} from '#/lib/media/types'
1818import {colors, s} from '#/lib/styles'
1919-import {isNative} from '#/platform/detection'
2019import {type ComposerImage, cropImage} from '#/state/gallery'
2120import {Text} from '#/view/com/util/text/Text'
2221import {tokens, useTheme} from '#/alf'
2322import * as Dialog from '#/components/Dialog'
2423import {MediaInsetBorder} from '#/components/MediaInsetBorder'
2424+import {IS_NATIVE} from '#/env'
2525import {type PostAction} from '../state/composer'
2626import {EditImageDialog} from './EditImageDialog'
2727import {ImageAltTextDialog} from './ImageAltTextDialog'
···145145 const editControl = Dialog.useDialogControl()
146146147147 const onImageEdit = () => {
148148- if (isNative) {
148148+ if (IS_NATIVE) {
149149 cropImage(image).then(next => {
150150 onChange(next)
151151 })
···8899import {isNetworkError} from '#/lib/strings/errors'
1010import {logger} from '#/logger'
1111-import {isNative} from '#/platform/detection'
1211import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings'
1312import {createPostgateRecord} from '#/state/queries/postgate/util'
1413import {usePreferencesQuery} from '#/state/queries/preferences'
···2524import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group'
2625import * as Tooltip from '#/components/Tooltip'
2726import {Text} from '#/components/Typography'
2727+import {IS_NATIVE} from '#/env'
2828import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged'
29293030export function ThreadgateBtn({
···7070 nudged: tooltipWasShown,
7171 })
72727373- if (isNative && Keyboard.isVisible()) {
7373+ if (IS_NATIVE && Keyboard.isVisible()) {
7474 Keyboard.dismiss()
7575 }
7676
+10-6
src/view/com/composer/videos/SubtitleDialog.tsx
···66import {MAX_ALT_TEXT} from '#/lib/constants'
77import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
88import {LANGUAGES} from '#/locale/languages'
99-import {isWeb} from '#/platform/detection'
109import {useLanguagePrefs} from '#/state/preferences'
1110import {atoms as a, useTheme, web} from '#/alf'
1211import {Button, ButtonIcon, ButtonText} from '#/components/Button'
···1716import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
1817import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
1918import {Text} from '#/components/Typography'
1919+import {IS_WEB} from '#/env'
2020import {SubtitleFilePicker} from './SubtitleFilePicker'
21212222const MAX_NUM_CAPTIONS = 1
···3737 return (
3838 <View style={[a.flex_row, a.my_xs]}>
3939 <Button
4040- label={isWeb ? _(msg`Captions & alt text`) : _(msg`Alt text`)}
4040+ label={IS_WEB ? _(msg`Captions & alt text`) : _(msg`Alt text`)}
4141 accessibilityHint={
4242- isWeb
4242+ IS_WEB
4343 ? _(msg`Opens captions and alt text dialog`)
4444 : _(msg`Opens alt text dialog`)
4545 }
···5252 }}>
5353 <ButtonIcon icon={CCIcon} />
5454 <ButtonText>
5555- {isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
5555+ {IS_WEB ? (
5656+ <Trans>Captions & alt text</Trans>
5757+ ) : (
5858+ <Trans>Alt text</Trans>
5959+ )}
5660 </ButtonText>
5761 </Button>
5862 <Dialog.Outer control={control}>
···134138 </Text>
135139 )}
136140137137- {isWeb && (
141141+ {IS_WEB && (
138142 <>
139143 <View
140144 style={[
···182186 <View style={web([a.flex_row, a.justify_end])}>
183187 <Button
184188 label={_(msg`Done`)}
185185- size={isWeb ? 'small' : 'large'}
189189+ size={IS_WEB ? 'small' : 'large'}
186190 color="primary"
187191 variant="solid"
188192 onPress={() => {
···44import {type ImagePickerAsset} from 'expo-image-picker'
5566import {clamp} from '#/lib/numbers'
77-import {isWeb} from '#/platform/detection'
87import {atoms as a, useTheme} from '#/alf'
88+import {IS_WEB} from '#/env'
99import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn'
1010import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'
1111···2020}) {
2121 const t = useTheme()
22222323- if (isWeb) return null
2323+ if (IS_WEB) return null
24242525 let aspectRatio = asset.width / asset.height
2626
+5-5
src/view/com/feeds/ComposerPrompt.tsx
···1111} from '#/lib/hooks/usePermissions'
1212import {openCamera, openUnifiedPicker} from '#/lib/media/picker'
1313import {logger} from '#/logger'
1414-import {isNative} from '#/platform/detection'
1514import {useCurrentAccountProfile} from '#/state/queries/useCurrentAccountProfile'
1615import {MAX_IMAGES} from '#/view/com/composer/state/composer'
1716import {UserAvatar} from '#/view/com/util/UserAvatar'
···2221import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
2322import {SubtleHover} from '#/components/SubtleHover'
2423import {Text} from '#/components/Typography'
2424+import {IS_NATIVE} from '#/env'
25252626export function ComposerPrompt() {
2727 const {_} = useLingui()
···4343 logger.metric('composerPrompt:gallery:press', {})
44444545 // On web, open the composer with the gallery picker auto-opening
4646- if (!isNative) {
4646+ if (!IS_NATIVE) {
4747 openComposer({openGallery: true})
4848 return
4949 }
···105105 return
106106 }
107107108108- if (isNative && Keyboard.isVisible()) {
108108+ if (IS_NATIVE && Keyboard.isVisible()) {
109109 Keyboard.dismiss()
110110 }
111111···122122 ]
123123124124 openComposer({
125125- imageUris: isNative ? imageUris : undefined,
125125+ imageUris: IS_NATIVE ? imageUris : undefined,
126126 })
127127 } catch (err: any) {
128128 if (!String(err).toLowerCase().includes('cancel')) {
···189189 <Trans>What's up?</Trans>
190190 </Text>
191191 <View style={[a.flex_row, a.gap_md]}>
192192- {isNative && (
192192+ {IS_NATIVE && (
193193 <Button
194194 onPress={e => {
195195 e.stopPropagation()
···11import React, {useContext} from 'react'
22import {type SharedValue} from 'react-native-reanimated'
3344-import {isNative} from '#/platform/detection'
44+import {IS_NATIVE} from '#/env'
5566export const PagerHeaderContext = React.createContext<{
77 scrollY: SharedValue<number>
···37373838export function usePagerHeaderContext() {
3939 const ctx = useContext(PagerHeaderContext)
4040- if (isNative) {
4040+ if (IS_NATIVE) {
4141 if (!ctx) {
4242 throw new Error(
4343 'usePagerHeaderContext must be used within a HeaderProvider',
+3-3
src/view/com/pager/PagerWithHeader.tsx
···18181919import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
2020import {ScrollProvider} from '#/lib/ScrollContext'
2121-import {isIOS} from '#/platform/detection'
2221import {
2322 Pager,
2423 type PagerRef,
2524 type RenderTabBarFnProps,
2625} from '#/view/com/pager/Pager'
2726import {useTheme} from '#/alf'
2727+import {IS_IOS} from '#/env'
2828import {type ListMethods} from '../util/List'
2929import {PagerHeaderProvider} from './PagerHeaderContext'
3030import {TabBar} from './TabBar'
···273273 const headerRef = useRef(null)
274274 return (
275275 <Animated.View
276276- pointerEvents={isIOS ? 'auto' : 'box-none'}
276276+ pointerEvents={IS_IOS ? 'auto' : 'box-none'}
277277 style={[styles.tabBarMobile, headerTransform, t.atoms.bg]}>
278278 <View
279279 ref={headerRef}
280280- pointerEvents={isIOS ? 'auto' : 'box-none'}
280280+ pointerEvents={IS_IOS ? 'auto' : 'box-none'}
281281 collapsable={false}>
282282 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
283283 {
+2-2
src/view/com/posts/CustomFeedEmptyState.tsx
···1313import {type NavigationProp} from '#/lib/routes/types'
1414import {s} from '#/lib/styles'
1515import {logger} from '#/logger'
1616-import {isWeb} from '#/platform/detection'
1716import {useFeedFeedbackContext} from '#/state/feed-feedback'
1817import {useSession} from '#/state/session'
1818+import {IS_WEB} from '#/env'
1919import {Button} from '../util/forms/Button'
2020import {Text} from '../util/text/Text'
2121···4444 const navigation = useNavigation<NavigationProp>()
45454646 const onPressFindAccounts = React.useCallback(() => {
4747- if (isWeb) {
4747+ if (IS_WEB) {
4848 navigation.navigate('Search', {})
4949 } else {
5050 navigation.navigate('SearchTab')
+2-2
src/view/com/posts/FollowingEmptyState.tsx
···1111import {MagnifyingGlassIcon} from '#/lib/icons'
1212import {type NavigationProp} from '#/lib/routes/types'
1313import {s} from '#/lib/styles'
1414-import {isWeb} from '#/platform/detection'
1414+import {IS_WEB} from '#/env'
1515import {Button} from '../util/forms/Button'
1616import {Text} from '../util/text/Text'
1717···2121 const navigation = useNavigation<NavigationProp>()
22222323 const onPressFindAccounts = React.useCallback(() => {
2424- if (isWeb) {
2424+ if (IS_WEB) {
2525 navigation.navigate('Search', {})
2626 } else {
2727 navigation.navigate('SearchTab')
+2-2
src/view/com/posts/FollowingEndOfFeed.tsx
···1010import {usePalette} from '#/lib/hooks/usePalette'
1111import {type NavigationProp} from '#/lib/routes/types'
1212import {s} from '#/lib/styles'
1313-import {isWeb} from '#/platform/detection'
1313+import {IS_WEB} from '#/env'
1414import {Button} from '../util/forms/Button'
1515import {Text} from '../util/text/Text'
1616···2020 const navigation = useNavigation<NavigationProp>()
21212222 const onPressFindAccounts = React.useCallback(() => {
2323- if (isWeb) {
2323+ if (IS_WEB) {
2424 navigation.navigate('Search', {})
2525 } else {
2626 navigation.navigate('SearchTab')
+4-4
src/view/com/posts/PostFeed.tsx
···3434import {logEvent, useGate} from '#/lib/statsig/statsig'
3535import {isNetworkError} from '#/lib/strings/errors'
3636import {logger} from '#/logger'
3737-import {isIOS, isNative, isWeb} from '#/platform/detection'
3837import {usePostAuthorShadowFilter} from '#/state/cache/profile-shadow'
3938import {listenPostCreated} from '#/state/events'
4039import {useFeedFeedbackContext} from '#/state/feed-feedback'
···7069} from '#/components/feeds/PostFeedVideoGridRow'
7170import {TrendingInterstitial} from '#/components/interstitials/Trending'
7271import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos'
7272+import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
7373import {DiscoverFeedLiveEventFeedsAndTrendingBanner} from '#/features/liveEvents/components/DiscoverFeedLiveEventFeedsAndTrendingBanner'
7474import {ComposerPrompt} from '../feeds/ComposerPrompt'
7575import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
···243243 const [feedType, feedUriOrActorDid, feedTab] = feed.split('|')
244244 const {gtMobile} = useBreakpoints()
245245 const {rightNavVisible} = useLayoutBreakpoints()
246246- const areVideoFeedsEnabled = isNative
246246+ const areVideoFeedsEnabled = IS_NATIVE
247247248248 const [hasPressedShowLessUris, setHasPressedShowLessUris] = useState(
249249 () => new Set<string>(),
···873873 * reach the end, so that content isn't cut off by the bottom of the
874874 * screen.
875875 */
876876- const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2)
876876+ const offset = Math.max(headerOffset, 32) * (IS_WEB ? 1 : 2)
877877878878 return isFetchingNextPage ? (
879879 <View style={[styles.feedFooter]}>
···10241024 }
10251025 initialNumToRender={initialNumToRenderOverride ?? initialNumToRender}
10261026 windowSize={9}
10271027- maxToRenderPerBatch={isIOS ? 5 : 1}
10271027+ maxToRenderPerBatch={IS_IOS ? 5 : 1}
10281028 updateCellsBatchingPeriod={40}
10291029 onItemSeen={onItemSeen}
10301030 />
+2-2
src/view/com/profile/ProfileFollows.tsx
···88import {type NavigationProp} from '#/lib/routes/types'
99import {cleanError} from '#/lib/strings/errors'
1010import {logger} from '#/logger'
1111-import {isWeb} from '#/platform/detection'
1211import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
1312import {useResolveDidQuery} from '#/state/queries/resolve-uri'
1413import {useSession} from '#/state/session'
1514import {FindContactsBannerNUX} from '#/components/contacts/FindContactsBannerNUX'
1615import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/components/icons/PeopleRemove2'
1716import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
1717+import {IS_WEB} from '#/env'
1818import {List} from '../util/List'
1919import {ProfileCardWithFollowBtn} from './ProfileCard'
2020···4949 const navigation = useNavigation<NavigationProp>()
50505151 const onPressFindAccounts = React.useCallback(() => {
5252- if (isWeb) {
5252+ if (IS_WEB) {
5353 navigation.navigate('Search', {})
5454 } else {
5555 navigation.navigate('SearchTab')
+7-5
src/view/com/profile/ProfileMenu.tsx
···1212import {shareText, shareUrl} from '#/lib/sharing'
1313import {toShareUrl} from '#/lib/strings/url-helpers'
1414import {logger} from '#/logger'
1515-import {isWeb} from '#/platform/detection'
1615import {type Shadow} from '#/state/cache/types'
1716import {useModalControls} from '#/state/modals'
1817import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
···6160import {useFullVerificationState} from '#/components/verification'
6261import {VerificationCreatePrompt} from '#/components/verification/VerificationCreatePrompt'
6362import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt'
6363+import {IS_WEB} from '#/env'
6464import {Dot} from '#/features/nuxs/components/Dot'
6565import {Gradient} from '#/features/nuxs/components/Gradient'
6666import {useDevMode} from '#/storage/hooks/dev-mode'
···266266 <Menu.Item
267267 testID="profileHeaderDropdownShareBtn"
268268 label={
269269- isWeb ? _(msg`Copy link to profile`) : _(msg`Share via...`)
269269+ IS_WEB ? _(msg`Copy link to profile`) : _(msg`Share via...`)
270270 }
271271 onPress={() => {
272272 if (showLoggedOutWarning) {
···276276 }
277277 }}>
278278 <Menu.ItemText>
279279- {isWeb ? (
279279+ {IS_WEB ? (
280280 <Trans>Copy link to profile</Trans>
281281 ) : (
282282 <Trans>Share via...</Trans>
283283 )}
284284 </Menu.ItemText>
285285- <Menu.ItemIcon icon={isWeb ? ChainLinkIcon : ArrowOutOfBoxIcon} />
285285+ <Menu.ItemIcon
286286+ icon={IS_WEB ? ChainLinkIcon : ArrowOutOfBoxIcon}
287287+ />
286288 </Menu.Item>
287289 <Menu.Item
288290 testID="profileHeaderDropdownSearchBtn"
···382384 a.flex_0,
383385 {
384386 color: t.palette.primary_500,
385385- right: isWeb ? -8 : -4,
387387+ right: IS_WEB ? -8 : -4,
386388 },
387389 ]}>
388390 <Trans>New</Trans>
+4-4
src/view/com/util/Link.tsx
···2525 linkRequiresWarning,
2626} from '#/lib/strings/url-helpers'
2727import {type TypographyVariant} from '#/lib/ThemeContext'
2828-import {isAndroid, isWeb} from '#/platform/detection'
2928import {emitSoftReset} from '#/state/events'
3029import {useModalControls} from '#/state/modals'
3130import {WebAuxClickWrapper} from '#/view/com/util/WebAuxClickWrapper'
3231import {useTheme} from '#/alf'
3332import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
3333+import {IS_ANDROID, IS_WEB} from '#/env'
3434import {router} from '../../../routes'
3535import {PressableWithHover} from './PressableWithHover'
3636import {Text} from './text/Text'
···130130 android_ripple={{
131131 color: t.atoms.bg_contrast_25.backgroundColor,
132132 }}
133133- unstable_pressDelay={isAndroid ? 90 : undefined}>
133133+ unstable_pressDelay={IS_ANDROID ? 90 : undefined}>
134134 {/* @ts-ignore web only -prf */}
135135 <View style={style} href={anchorHref}>
136136 {children ? children : <Text>{title || 'link'}</Text>}
···219219 })
220220 }
221221 if (
222222- isWeb &&
222222+ IS_WEB &&
223223 href !== '#' &&
224224 e != null &&
225225 isModifiedEvent(e as React.MouseEvent)
···323323 onBeforePress,
324324 ...props
325325}: TextLinkOnWebOnlyProps) {
326326- if (isWeb) {
326326+ if (IS_WEB) {
327327 return (
328328 <TextLink
329329 testID={testID}
+3-3
src/view/com/util/List.tsx
···1111import {useDedupe} from '#/lib/hooks/useDedupe'
1212import {useScrollHandlers} from '#/lib/ScrollContext'
1313import {addStyle} from '#/lib/styles'
1414-import {isIOS} from '#/platform/detection'
1514import {useLightbox} from '#/state/lightbox'
1615import {useTheme} from '#/alf'
1616+import {IS_IOS} from '#/env'
1717import {FlatList_INTERNAL} from './Views'
18181919export type ListMethods = FlatList_INTERNAL
···9494 }
9595 }
96969797- if (isIOS) {
9797+ if (IS_IOS) {
9898 runOnJS(dedupe)(updateActiveVideoViewAsync)
9999 }
100100 },
···184184185185// We only want to use this context value on iOS because the `scrollsToTop` prop is iOS-only
186186// removing it saves us a re-render on Android
187187-const useAllowScrollToTop = isIOS ? useAllowScrollToTopIOS : () => undefined
187187+const useAllowScrollToTop = IS_IOS ? useAllowScrollToTopIOS : () => undefined
188188function useAllowScrollToTopIOS() {
189189 const {activeLightbox} = useLightbox()
190190 return !activeLightbox
+8-8
src/view/com/util/MainScrollProvider.tsx
···99import EventEmitter from 'eventemitter3'
10101111import {ScrollProvider} from '#/lib/ScrollContext'
1212-import {isNative, isWeb} from '#/platform/detection'
1312import {useMinimalShellMode} from '#/state/shell'
1413import {useShellLayout} from '#/state/shell/shell-layout'
1414+import {IS_NATIVE, IS_WEB} from '#/env'
15151616const WEB_HIDE_SHELL_THRESHOLD = 200
1717···3535 )
36363737 useEffect(() => {
3838- if (isWeb) {
3838+ if (IS_WEB) {
3939 return listenToForcedWindowScroll(() => {
4040 startDragOffset.set(null)
4141 startMode.set(null)
···4848 (e: NativeScrollEvent) => {
4949 'worklet'
5050 const offsetY = Math.max(0, e.contentOffset.y)
5151- if (isNative) {
5151+ if (IS_NATIVE) {
5252 const startDragOffsetValue = startDragOffset.get()
5353 if (startDragOffsetValue === null) {
5454 return
···7575 (e: NativeScrollEvent) => {
7676 'worklet'
7777 const offsetY = Math.max(0, e.contentOffset.y)
7878- if (isNative) {
7878+ if (IS_NATIVE) {
7979 startDragOffset.set(offsetY)
8080 startMode.set(headerMode.get())
8181 }
···8686 const onEndDrag = useCallback(
8787 (e: NativeScrollEvent) => {
8888 'worklet'
8989- if (isNative) {
8989+ if (IS_NATIVE) {
9090 if (e.velocity && e.velocity.y !== 0) {
9191 // If we detect a velocity, wait for onMomentumEnd to snap.
9292 return
···100100 const onMomentumEnd = useCallback(
101101 (e: NativeScrollEvent) => {
102102 'worklet'
103103- if (isNative) {
103103+ if (IS_NATIVE) {
104104 snapToClosestState(e)
105105 }
106106 },
···111111 (e: NativeScrollEvent) => {
112112 'worklet'
113113 const offsetY = Math.max(0, e.contentOffset.y)
114114- if (isNative) {
114114+ if (IS_NATIVE) {
115115 const startDragOffsetValue = startDragOffset.get()
116116 const startModeValue = startMode.get()
117117 if (startDragOffsetValue === null || startModeValue === null) {
···177177178178const emitter = new EventEmitter()
179179180180-if (isWeb) {
180180+if (IS_WEB) {
181181 const originalScroll = window.scroll
182182 window.scroll = function () {
183183 emitter.emit('forced-scroll')
+3-3
src/view/com/util/PostMeta.tsx
···1212import {sanitizeDisplayName} from '#/lib/strings/display-names'
1313import {sanitizeHandle} from '#/lib/strings/handles'
1414import {niceDate} from '#/lib/strings/time'
1515-import {isAndroid} from '#/platform/detection'
1615import {useProfileShadow} from '#/state/cache/profile-shadow'
1716import {precacheProfile} from '#/state/queries/profile'
1817import {atoms as a, platform, useTheme, web} from '#/alf'
···2120import {Text} from '#/components/Typography'
2221import {useSimpleVerificationState} from '#/components/verification'
2322import {VerificationCheck} from '#/components/verification/VerificationCheck'
2323+import {IS_ANDROID} from '#/env'
2424import {TimeElapsed} from './TimeElapsed'
2525import {PreviewableUserAvatar} from './UserAvatar'
2626···152152 a.pl_xs,
153153 a.text_md,
154154 a.leading_tight,
155155- isAndroid && a.flex_grow,
155155+ IS_ANDROID && a.flex_grow,
156156 a.text_right,
157157 t.atoms.text_contrast_medium,
158158 web({
159159 whiteSpace: 'nowrap',
160160 }),
161161 ]}>
162162- {!isAndroid && (
162162+ {!IS_ANDROID && (
163163 <Text
164164 style={[
165165 a.text_md,
···1616} from '#/lib/routes/types'
1717import {cleanError} from '#/lib/strings/errors'
1818import {s} from '#/lib/styles'
1919-import {isNative, isWeb} from '#/platform/detection'
2019import {
2120 type SavedFeedItem,
2221 useGetPopularFeedsQuery,
···4645import * as Layout from '#/components/Layout'
4746import {Link} from '#/components/Link'
4847import * as ListCard from '#/components/ListCard'
4848+import {IS_NATIVE, IS_WEB} from '#/env'
49495050type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
5151···385385 const onChangeSearchFocus = React.useCallback(
386386 (focus: boolean) => {
387387 if (focus && searchBarIndex > -1) {
388388- if (isNative) {
388388+ if (IS_NATIVE) {
389389 // scrollToIndex scrolls the exact right amount, so use if available
390390 listRef.current?.scrollToIndex({
391391 index: searchBarIndex,
···681681 return (
682682 <View
683683 style={
684684- isWeb
684684+ IS_WEB
685685 ? [
686686 a.flex_row,
687687 a.px_md,
···717717 return (
718718 <View
719719 style={
720720- isWeb
720720+ IS_WEB
721721 ? [a.flex_row, a.px_md, a.pt_lg, a.pb_lg, a.gap_md]
722722 : [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md]
723723 }>
+2-2
src/view/screens/Home.tsx
···1212 type NativeStackScreenProps,
1313} from '#/lib/routes/types'
1414import {logEvent} from '#/lib/statsig/statsig'
1515-import {isWeb} from '#/platform/detection'
1615import {emitSoftReset} from '#/state/events'
1716import {
1817 type SavedFeedSourceInfo,
···3736import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
3837import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
3938import * as Layout from '#/components/Layout'
3939+import {IS_WEB} from '#/env'
4040import {useDemoMode} from '#/storage/hooks/demo-mode'
41414242type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
···4848 usePinnedFeedsInfos()
49495050 React.useEffect(() => {
5151- if (isWeb && !currentAccount) {
5151+ if (IS_WEB && !currentAccount) {
5252 const getParams = new URLSearchParams(window.location.search)
5353 const splash = getParams.get('splash')
5454 if (splash === 'true') {
+3-3
src/view/screens/Notifications.tsx
···1414} from '#/lib/routes/types'
1515import {s} from '#/lib/styles'
1616import {logger} from '#/logger'
1717-import {isNative} from '#/platform/detection'
1817import {emitSoftReset, listenSoftReset} from '#/state/events'
1918import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
2019import {useNotificationSettingsQuery} from '#/state/queries/notifications/settings'
···3938import * as Layout from '#/components/Layout'
4039import {InlineLinkText, Link} from '#/components/Link'
4140import {Loader} from '#/components/Loader'
4141+import {IS_NATIVE} from '#/env'
42424343// We don't currently persist this across reloads since
4444// you gotta visit All to clear the badge anyway.
···197197 // event handlers
198198 // =
199199 const scrollToTop = useCallback(() => {
200200- scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
200200+ scrollElRef.current?.scrollToOffset({animated: IS_NATIVE, offset: 0})
201201 setMinimalShellMode(false)
202202 }, [scrollElRef, setMinimalShellMode])
203203···227227 // on focus, check for latest, but only invalidate if the user
228228 // isnt scrolled down to avoid moving content underneath them
229229 let currentIsScrolledDown
230230- if (isNative) {
230230+ if (IS_NATIVE) {
231231 currentIsScrolledDown = isScrolledDown
232232 } else {
233233 // On the web, this isn't always updated in time so
+2-2
src/view/shell/Drawer.tsx
···1313import {type NavigationProp} from '#/lib/routes/types'
1414import {sanitizeHandle} from '#/lib/strings/handles'
1515import {colors} from '#/lib/styles'
1616-import {isWeb} from '#/platform/detection'
1716import {emitSoftReset} from '#/state/events'
1817import {useKawaiiMode} from '#/state/preferences/kawaii'
1918import {useUnreadNotifications} from '#/state/queries/notifications/unread'
···5554import {Text} from '#/components/Typography'
5655import {useSimpleVerificationState} from '#/components/verification'
5756import {VerificationCheck} from '#/components/verification/VerificationCheck'
5757+import {IS_WEB} from '#/env'
58585959const iconWidth = 26
6060···165165 (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
166166 const state = navigation.getState()
167167 setDrawerOpen(false)
168168- if (isWeb) {
168168+ if (IS_WEB) {
169169 // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
170170 if (tab === 'MyProfile') {
171171 navigation.navigate('Profile', {name: currentAccount!.handle})