···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 {useDisableVerifyEmailReminder} from '#/state/preferences/disable-verify-email-reminder'
4948import {useUnreadNotifications} from '#/state/queries/notifications/unread'
5049import {useSession} from '#/state/session'
···140139 EmailDialogScreenID,
141140 useEmailDialogControl,
142141} from '#/components/dialogs/EmailDialog'
142142+import {IS_NATIVE, IS_WEB} from '#/env'
143143import {router} from '#/routes'
144144import {Referrer} from '../modules/expo-bluesky-swiss-army'
145145···852852 // native, since the home tab and the home screen are defined as initial routes, we don't need to return a state
853853 // since it will be created by react-navigation.
854854 if (path.includes('intent/')) {
855855- if (isNative) return
855855+ if (IS_NATIVE) return
856856 return buildStateObject('Flat', 'Home', params)
857857 }
858858859859- if (isNative) {
859859+ if (IS_NATIVE) {
860860 if (name === 'Search') {
861861 return buildStateObject('SearchTab', 'Search', params)
862862 }
···933933 )
934934935935 async function handlePushNotificationEntry() {
936936- if (!isNative) return
936936+ if (!IS_NATIVE) return
937937938938 // deep links take precedence - on android,
939939 // getLastNotificationResponseAsync returns a "notification"
···10851085 navigationRef.dispatch(
10861086 CommonActions.reset({
10871087 index: 0,
10881088- routes: [{name: isNative ? 'HomeTab' : 'Home'}],
10881088+ routes: [{name: IS_NATIVE ? 'HomeTab' : 'Home'}],
10891089 }),
10901090 )
10911091 return Promise.race([
···11191119 initMs,
11201120 })
1121112111221122- if (isWeb) {
11221122+ if (IS_WEB) {
11231123 const referrerInfo = Referrer.getReferrerInfo()
11241124 if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
11251125 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 }
···149149 // setting a zIndex when using FullWindowOverlay on iOS
150150 // means the taps pass straight through to the underlying content (???)
151151 // so don't set it on iOS. FullWindowOverlay already does the job.
152152- !isIOS && {zIndex: 9999},
152152+ !IS_IOS && {zIndex: 9999},
153153 t.atoms.bg,
154154 gtMobile ? a.p_2xl : a.p_xl,
155155 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
+4-4
src/components/BlockedGeoOverlay.tsx
···55import {useLingui} from '@lingui/react'
6677import {logger} from '#/logger'
88-import {isWeb} from '#/platform/detection'
99-import {useDeviceGeolocationApi} from '#/state/geolocation'
88+import {useDeviceGeolocationApi} from '#/geolocation'
109import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1110import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
1211import {Button, ButtonIcon, ButtonText} from '#/components/Button'
···2019import * as Toast from '#/components/Toast'
2120import {Text} from '#/components/Typography'
2221import {BottomSheetOutlet} from '#/../modules/bottom-sheet'
2222+import {IS_WEB} from '#/env'
23232424export function BlockedGeoOverlay() {
2525 const t = useTheme()
···7070 contentContainerStyle={[
7171 a.px_2xl,
7272 {
7373- paddingTop: isWeb ? a.p_5xl.padding : insets.top + a.p_2xl.padding,
7373+ paddingTop: IS_WEB ? a.p_5xl.padding : insets.top + a.p_2xl.padding,
7474 paddingBottom: 100,
7575 },
7676 ]}>
···117117 ))}
118118 </View>
119119120120- {!isWeb && (
120120+ {!IS_WEB && (
121121 <>
122122 <View style={[a.pt_2xl]}>
123123 <Divider />
···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',
···1111} from 'react-native-reanimated'
1212import {useSafeAreaInsets} from 'react-native-safe-area-context'
13131414-import {isWeb} from '#/platform/detection'
1514import {useShellLayout} from '#/state/shell/shell-layout'
1615import {
1716 atoms as a,
···2322import {useDialogContext} from '#/components/Dialog'
2423import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const'
2524import {ScrollbarOffsetContext} from '#/components/Layout/context'
2525+import {IS_WEB} from '#/env'
26262727export * from '#/components/Layout/const'
2828export * as Header from '#/components/Layout/Header'
···4343 const {top} = useSafeAreaInsets()
4444 return (
4545 <>
4646- {isWeb && <WebCenterBorders />}
4646+ {IS_WEB && <WebCenterBorders />}
4747 <View
4848 style={[a.util_screen_outer, {paddingTop: noInsetTop ? 0 : top}, style]}
4949 {...props}
···9898 contentContainerStyle,
9999 ]}
100100 {...props}>
101101- {isWeb ? (
101101+ {IS_WEB ? (
102102 <Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}>
103103 {/* @ts-expect-error web only -esb */}
104104 {children}
···145145 ]}
146146 keyboardShouldPersistTaps="handled"
147147 {...props}>
148148- {isWeb ? <Center>{children}</Center> : children}
148148+ {IS_WEB ? <Center>{children}</Center> : children}
149149 </KeyboardAwareScrollView>
150150 )
151151})
+11-11
src/components/Link.tsx
···1818 isExternalUrl,
1919 linkRequiresWarning,
2020} from '#/lib/strings/url-helpers'
2121-import {isNative, isWeb} from '#/platform/detection'
2221import {useModalControls} from '#/state/modals'
2322import {useGoLinksEnabled} from '#/state/preferences'
2423import {atoms as a, flatten, type TextStyleProp, useTheme, web} from '#/alf'
2524import {Button, type ButtonProps} from '#/components/Button'
2625import {useInteractionState} from '#/components/hooks/useInteractionState'
2726import {Text, type TextProps} from '#/components/Typography'
2727+import {IS_NATIVE, IS_WEB} from '#/env'
2828import {router} from '#/routes'
2929import {useGlobalDialogsControlContext} from './dialogs/Context'
3030···133133 linkRequiresWarning(href, displayText),
134134 )
135135136136- if (isWeb) {
136136+ if (IS_WEB) {
137137 e.preventDefault()
138138 }
139139···166166 ]
167167168168 // does not apply to web's flat navigator
169169- if (isNative && screen !== 'NotFound') {
169169+ if (IS_NATIVE && screen !== 'NotFound') {
170170 const state = navigation.getState()
171171 // if screen is not in the current navigator, it means it's
172172 // most likely a tab screen. note: state can be undefined
···251251 (e: GestureResponderEvent) => {
252252 const exitEarlyIfFalse = outerOnLongPress?.(e)
253253 if (exitEarlyIfFalse === false) return
254254- return isNative && shareOnLongPress ? handleLongPress() : undefined
254254+ return IS_NATIVE && shareOnLongPress ? handleLongPress() : undefined
255255 },
256256 [outerOnLongPress, handleLongPress, shareOnLongPress],
257257 )
···506506 onPress,
507507 ...props
508508}: Omit<InlineLinkProps, 'onLongPress'>) {
509509- return isWeb ? (
509509+ return IS_WEB ? (
510510 <InlineLinkText {...props} to={to} onPress={onPress}>
511511 {children}
512512 </InlineLinkText>
···552552): {onPress: Exclude<BaseLinkProps['onPress'], undefined>} {
553553 return {
554554 onPress(e: GestureResponderEvent) {
555555- if (!isWeb || !isModifiedClickEvent(e)) {
555555+ if (!IS_WEB || !isModifiedClickEvent(e)) {
556556 e.preventDefault()
557557 onPressHandler(e)
558558 return false
···566566 * intends to deviate from default behavior.
567567 */
568568export function isClickEventWithMetaKey(e: GestureResponderEvent) {
569569- if (!isWeb) return false
569569+ if (!IS_WEB) return false
570570 const event = e as unknown as MouseEvent
571571 return event.metaKey || event.altKey || event.ctrlKey || event.shiftKey
572572}
···575575 * Determines if the web click target is anything other than `_self`
576576 */
577577export function isClickTargetExternal(e: GestureResponderEvent) {
578578- if (!isWeb) return false
578578+ if (!IS_WEB) return false
579579 const event = e as unknown as MouseEvent
580580 const el = event.currentTarget as HTMLAnchorElement
581581 return el && el.target && el.target !== '_self'
···587587 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button}
588588 */
589589export function isModifiedClickEvent(e: GestureResponderEvent): boolean {
590590- if (!isWeb) return false
590590+ if (!IS_WEB) return false
591591 const event = e as unknown as MouseEvent
592592 const isPrimaryButton = event.button === 0
593593 return (
···601601 * {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button}
602602 */
603603export function shouldClickOpenNewTab(e: GestureResponderEvent) {
604604- if (!isWeb) return false
604604+ if (!IS_WEB) return false
605605 const event = e as unknown as MouseEvent
606606- const isMiddleClick = isWeb && event.button === 1
606606+ const isMiddleClick = IS_WEB && event.button === 1
607607 return isClickEventWithMetaKey(e) || isClickTargetExternal(e) || isMiddleClick
608608}
+2-2
src/components/MediaInsetBorder.tsx
···11import {StyleSheet} from 'react-native'
22import type React from 'react'
3344-import {isHighDPI} from '#/lib/browser'
54import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf'
65import {Fill} from '#/components/Fill'
66+import {IS_HIGH_DPI} from '#/env'
7788/**
99 * Applies and thin border within a bounding box. Used to contrast media from
···3333 // while we generally use hairlineWidth (aka 1px),
3434 // we make an exception here for high DPI screens
3535 // as the 1px border is very noticeable -sfn
3636- web: isHighDPI ? 0.5 : StyleSheet.hairlineWidth,
3636+ web: IS_HIGH_DPI ? 0.5 : StyleSheet.hairlineWidth,
3737 }),
3838 },
3939 opaque
+5-5
src/components/Menu/index.tsx
···1010import {useLingui} from '@lingui/react'
1111import flattenReactChildren from 'react-keyed-flatten-children'
12121313-import {isAndroid, isIOS, isNative} from '#/platform/detection'
1413import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1514import {atoms as a, useTheme} from '#/alf'
1615import {Button, ButtonText} from '#/components/Button'
···3130 type TriggerProps,
3231} from '#/components/Menu/types'
3332import {Text} from '#/components/Typography'
3333+import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env'
34343535export {
3636 type DialogControlProps as MenuControlProps,
···7171 } = useInteractionState()
72727373 return children({
7474- isNative: true,
7474+ IS_NATIVE: true,
7575 control: context.control,
7676 state: {
7777 hovered: false,
···112112 <Dialog.ScrollableInner label={_(msg`Menu`)}>
113113 <View style={[a.gap_lg]}>
114114 {children}
115115- {isNative && showCancel && <Cancel />}
115115+ {IS_NATIVE && showCancel && <Cancel />}
116116 </View>
117117 </Dialog.ScrollableInner>
118118 </Context.Provider>
···138138 onFocus={onFocus}
139139 onBlur={onBlur}
140140 onPress={async e => {
141141- if (isAndroid) {
141141+ if (IS_ANDROID) {
142142 /**
143143 * Below fix for iOS doesn't work for Android, this does.
144144 */
145145 onPress?.(e)
146146 context.control.close()
147147- } else if (isIOS) {
147147+ } else if (IS_IOS) {
148148 /**
149149 * Fixes a subtle bug on iOS
150150 * {@link https://github.com/bluesky-social/social-app/pull/5849/files#diff-de516ef5e7bd9840cd639213301df38cf03acfcad5bda85a1d63efd249ba79deL124-L127}
···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
···33import {msg} from '@lingui/macro'
44import {useLingui} from '@lingui/react'
5566-import {isFirefox, isTouchDevice} from '#/lib/browser'
76import {clamp} from '#/lib/numbers'
87import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
98import {atoms as a, useTheme, web} from '#/alf'
109import {useInteractionState} from '#/components/hooks/useInteractionState'
1010+import {IS_WEB_FIREFOX, IS_WEB_TOUCH_DEVICE} from '#/env'
1111import {formatTime} from './utils'
12121313export function Scrubber({
···102102 // a pointerUp event is fired outside the element that captured the
103103 // pointer. Firefox clicks on the element the mouse is over, so we have
104104 // to make everything unclickable while seeking -sfn
105105- if (isFirefox && scrubberActive) {
105105+ if (IS_WEB_FIREFOX && scrubberActive) {
106106 document.body.classList.add('force-no-clicks')
107107108108 return () => {
···153153 <View
154154 testID="scrubber"
155155 style={[
156156- {height: isTouchDevice ? 32 : 18, width: '100%'},
156156+ {height: IS_WEB_TOUCH_DEVICE ? 32 : 18, width: '100%'},
157157 a.flex_shrink_0,
158158 a.px_xs,
159159 ]}
···11import {type RefObject, useCallback, useEffect, useRef, useState} from 'react'
2233-import {isSafari} from '#/lib/browser'
43import {logger} from '#/logger'
54import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
55+import {IS_WEB_SAFARI} from '#/env'
6677export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) {
88 const [playing, setPlaying] = useState(false)
···4141 setCurrentTime(round(ref.current.currentTime) || 0)
4242 // HACK: Safari randomly fires `stalled` events when changing between segments
4343 // let's just clear the buffering state if the video is still progressing -sfn
4444- if (isSafari) {
4444+ if (IS_WEB_SAFARI) {
4545 if (bufferingTimeout) clearTimeout(bufferingTimeout)
4646 setBuffering(false)
4747 }
···1111import {shareText, shareUrl} from '#/lib/sharing'
1212import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers'
1313import {logger} from '#/logger'
1414-import {isIOS} from '#/platform/detection'
1514import {useProfileShadow} from '#/state/cache/profile-shadow'
1615import {useShowExternalShareButtons} from '#/state/preferences/external-share-buttons'
1716import {useSession} from '#/state/session'
···2726import {SquareArrowTopRight_Stroke2_Corner0_Rounded as ExternalIcon} from '#/components/icons/SquareArrowTopRight'
2827import * as Menu from '#/components/Menu'
2928import {useAgeAssurance} from '#/ageAssurance'
2929+import {IS_IOS} from '#/env'
3030import {useDevMode} from '#/storage/hooks/dev-mode'
3131import {RecentChats} from './RecentChats'
3232import {type ShareMenuItemsProps} from './ShareMenuItems.types'
···7474 const onCopyLink = async () => {
7575 logger.metric('share:press:copyLink', {}, {statsig: true})
7676 const url = toShareUrl(href)
7777- if (isIOS) {
7777+ if (IS_IOS) {
7878 // iOS only
7979 await ExpoClipboard.setUrlAsync(url)
8080 } 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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1817import {
1918 type Gif,
···3231import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
3332import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
3433import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
3434+import {IS_WEB} from '#/env'
35353636export function GifSelectDialog({
3737 controlRef,
···152152 a.pb_sm,
153153 t.atoms.bg,
154154 ]}>
155155- {!gtMobile && isWeb && (
155155+ {!gtMobile && IS_WEB && (
156156 <Button
157157 size="small"
158158 variant="ghost"
···164164 </Button>
165165 )}
166166167167- <TextField.Root style={[!gtMobile && isWeb && a.flex_1]}>
167167+ <TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}>
168168 <TextField.Icon icon={Search} />
169169 <TextField.Input
170170 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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
109import {
1110 usePreferencesQuery,
···3332import {Loader} from '#/components/Loader'
3433import * as Prompt from '#/components/Prompt'
3534import {Text} from '#/components/Typography'
3535+import {IS_NATIVE} from '#/env'
36363737const ONE_DAY = 24 * 60 * 60 * 1000
3838···407407 )}
408408 </View>
409409410410- {isNative && <View style={{height: 20}} />}
410410+ {IS_NATIVE && <View style={{height: 20}} />}
411411 </View>
412412413413 <Dialog.Close />
···44import {useLingui} from '@lingui/react'
5566import {HITSLOP_10} from '#/lib/constants'
77-import {isNative} from '#/platform/detection'
87import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
98import {atoms as a, useTheme} from '#/alf'
109import {Button, ButtonIcon} from '#/components/Button'
1110import * as TextField from '#/components/forms/TextField'
1211import {MagnifyingGlass_Stroke2_Corner0_Rounded as MagnifyingGlassIcon} from '#/components/icons/MagnifyingGlass'
1312import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
1313+import {IS_NATIVE} from '#/env'
14141515type SearchInputProps = Omit<TextField.InputProps, 'label'> & {
1616 label?: TextField.InputProps['label']
···3939 placeholder={_(msg`Search`)}
4040 returnKeyType="search"
4141 keyboardAppearance={t.scheme}
4242- selectTextOnFocus={isNative}
4242+ selectTextOnFocus={IS_NATIVE}
4343 autoFocus={false}
4444 accessibilityRole="search"
4545 autoCorrect={false}
+2-2
src/components/forms/TextField.tsx
···11111212import {HITSLOP_20} from '#/lib/constants'
1313import {mergeRefs} from '#/lib/merge-refs'
1414-import {isWeb} from '#/platform/detection'
1514import {
1615 android,
1716 applyFonts,
···2625import {useInteractionState} from '#/components/hooks/useInteractionState'
2726import {type Props as SVGIconProps} from '#/components/icons/common'
2827import {Text} from '#/components/Typography'
2828+import {IS_WEB} from '#/env/index.web'
29293030const Context = createContext<{
3131 inputRef: React.RefObject<TextInput | null> | null
···106106 a.align_center,
107107 a.relative,
108108 a.w_full,
109109- !(hasMultiline && isWeb) && a.px_md,
109109+ !(hasMultiline && IS_WEB) && a.px_md,
110110 style,
111111 ]}
112112 {...web({
+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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1514import {
1615 atoms as a,
···2322import {useInteractionState} from '#/components/hooks/useInteractionState'
2423import {CheckThick_Stroke2_Corner0_Rounded as Checkmark} from '#/components/icons/Check'
2524import {Text} from '#/components/Typography'
2525+import {IS_NATIVE} from '#/env'
26262727export * from './Panel'
2828···564564 )
565565}
566566567567-export const Platform = isNative ? Switch : Checkbox
567567+export const Platform = IS_NATIVE ? Switch : Checkbox
+3-4
src/components/hooks/useFullscreen.ts
···66 useSyncExternalStore,
77} from 'react'
8899-import {isFirefox, isSafari} from '#/lib/browser'
1010-import {isWeb} from '#/platform/detection'
99+import {IS_WEB, IS_WEB_FIREFOX, IS_WEB_SAFARI} from '#/env'
11101211function fullscreenSubscribe(onChange: () => void) {
1312 document.addEventListener('fullscreenchange', onChange)
···1514}
16151716export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) {
1818- if (!isWeb) throw new Error("'useFullscreen' is a web-only hook")
1717+ if (!IS_WEB) throw new Error("'useFullscreen' is a web-only hook")
1918 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
2019 Boolean(document.fullscreenElement),
2120 )
···39384039 // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen
4140 // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium
4242- if (prevIsFullscreen && !isFirefox && !isSafari) {
4141+ if (prevIsFullscreen && !IS_WEB_FIREFOX && !IS_WEB_SAFARI) {
4342 setTimeout(() => {
4443 if (scrollYRef.current !== null) {
4544 window.scrollTo(0, scrollYRef.current)
···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 {
1615 maybeModifyHighQualityImage,
1716 useHighQualityImages,
···2120import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
2221import {MediaInsetBorder} from '#/components/MediaInsetBorder'
2322import {Text} from '#/components/Typography'
2323+import {IS_NATIVE} from '#/env'
24242525export function ConstrainedImage({
2626 aspectRatio,
···3939 * the height of the image.
4040 */
4141 const outerAspectRatio = useMemo<DimensionValue>(() => {
4242- const ratio = isNative
4242+ const ratio = IS_NATIVE
4343 ? Math.min(1 / aspectRatio, minMobileAspectRatio ?? 16 / 9) // 9:16 bounding box
4444 : Math.min(1 / aspectRatio, 1) // 1:1 bounding box
4545 return `${ratio * 100}%`
···1313import {Button, ButtonIcon, ButtonText} from '#/components/Button'
1414import * as Dialog from '#/components/Dialog'
1515import * as TextField from '#/components/forms/TextField'
1616-import {getLiveServiceNames} from '#/components/live/utils'
1616+import {
1717+ displayDuration,
1818+ getLiveServiceNames,
1919+ useDebouncedValue,
2020+} from '#/components/live/utils'
1721import {Loader} from '#/components/Loader'
1822import * as ProfileCard from '#/components/ProfileCard'
1923import * as Select from '#/components/Select'
···2125import type * as bsky from '#/types/bsky'
2226import {LinkPreview} from './LinkPreview'
2327import {useLiveLinkMetaQuery, useUpsertLiveStatusMutation} from './queries'
2424-import {displayDuration, useDebouncedValue} from './utils'
25282629export function GoLiveDialog({
2730 control,
···57605861 const time = useCallback(
5962 (offset: number) => {
6060- tick!
6363+ void tick
61646265 const date = new Date()
6366 date.setMinutes(date.getMinutes() + offset)
+2-2
src/components/moderation/LabelsOnMeDialog.tsx
···1212import {makeProfileLink} from '#/lib/routes/links'
1313import {sanitizeHandle} from '#/lib/strings/handles'
1414import {logger} from '#/logger'
1515-import {isAndroid} from '#/platform/detection'
1615import {useAgent, useSession} from '#/state/session'
1716import * as Toast from '#/view/com/util/Toast'
1817import {atoms as a, useBreakpoints, useTheme} from '#/alf'
···2019import * as Dialog from '#/components/Dialog'
2120import {InlineLinkText} from '#/components/Link'
2221import {Text} from '#/components/Typography'
2222+import {IS_ANDROID} from '#/env'
2323import {Admonition} from '../Admonition'
2424import {Divider} from '../Divider'
2525import {Loader} from '../Loader'
···344344 {isPending && <ButtonIcon icon={Loader} />}
345345 </Button>
346346 </View>
347347- {isAndroid && <View style={{height: 300}} />}
347347+ {IS_ANDROID && <View style={{height: 300}} />}
348348 </>
349349 )
350350}
···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'
109import * as Dialog from '#/components/Dialog'
1110import {Loader} from '#/components/Loader'
1212-import * as toast from '#/components/Toast'
1111+import * 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,
···6161 feed,
6262 metricContext,
6363 onUpdateSuccess({undoAction}) {
6464- toast.show(
6565- <toast.Outer>
6666- <toast.Icon />
6767- <toast.Text>
6464+ Toast.show(
6565+ <Toast.Outer>
6666+ <Toast.Icon />
6767+ <Toast.Text>
6868 <Trans>Your live event preferences have been updated.</Trans>
6969- </toast.Text>
6969+ </Toast.Text>
7070 {undoAction && (
7171- <toast.Action
7171+ <Toast.Action
7272 label={_(msg`Undo`)}
7373 onPress={() => {
7474 if (undoAction) {
···7676 }
7777 }}>
7878 <Trans>Undo</Trans>
7979- </toast.Action>
7979+ </Toast.Action>
8080 )}
8181- </toast.Outer>,
8282- {
8383- type: 'success',
8484- },
8181+ </Toast.Outer>,
8282+ {type: 'success'},
8583 )
86848785 /*
···148146 </ButtonText>
149147 {isHidingAllFeeds && <ButtonIcon icon={Loader} />}
150148 </Button>
151151- {isNative && (
149149+ {IS_NATIVE && (
152150 <Button
153151 label={_(msg`Cancel`)}
154152 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
···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
+1-1
src/lib/actor-status.ts
···1717 const config = useLiveNowConfig()
18181919 return useMemo(() => {
2020- tick! // revalidate every minute
2020+ void tick // revalidate every minute
21212222 if (shadowed && 'status' in shadowed && shadowed.status) {
2323 const isValid = validateStatus(shadowed.status, config)
+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}
···6767}
68686969// Copied from: https://github.com/jonschlinkert/is-plain-object
7070-export function isPlainObject(o: any): o is Object {
7070+export function isPlainObject(o: any): o is object {
7171 if (!hasObjectPrototype(o)) {
7272 return false
7373 }
+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)
···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'
2424import {mimeToExt} from './video/util'
···109109110110 // save
111111 try {
112112- if (isAndroid) {
112112+ if (IS_ANDROID) {
113113 // android triggers an annoying permission prompt if you try and move an image
114114 // between albums. therefore, we need to either create the album with the image
115115 // as the starting image, or put it directly into the album
···327327}
328328329329function normalizePath(str: string, allPlatforms = false): string {
330330- if (isAndroid || allPlatforms) {
330330+ if (IS_ANDROID || allPlatforms) {
331331 if (!str.startsWith('file://')) {
332332 return `file://${str}`
333333 }
···350350 type: string,
351351) {
352352 try {
353353- if (isIOS) {
353353+ if (IS_IOS) {
354354 await withTempFile(filename, encoded, async tmpFileUrl => {
355355 await Sharing.shareAsync(tmpFileUrl, {UTI: type})
356356 })
+1-1
src/lib/media/picker.e2e.tsx
···33 getInfoAsync,
44 readDirectoryAsync,
55} from 'expo-file-system/legacy'
66+import {type ImagePickerResult} from 'expo-image-picker'
67import ExpoImageCropTool, {
78 type OpenCropperOptions,
89} from '@bsky.app/expo-image-crop-tool'
9101011import {compressIfNeeded} from './manip'
1112import {type PickerImage} from './picker.shared'
1212-import {ImagePickerResult} from 'expo-image-picker'
13131414async function getFile() {
1515 const imagesDir = documentDirectory!
+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'
1413import {PUBLIC_BSKY_SERVICE} from './constants'
1414+import {IS_NATIVE, IS_WEB} from '#/env'
15151616declare global {
1717 interface Window {
···8888}, 2000)
89899090focusManager.setEventListener(onFocus => {
9191- if (isNative) {
9191+ if (IS_NATIVE) {
9292 const subscription = AppState.addEventListener(
9393 'change',
9494 (status: AppStateStatus) => {
···188188 }
189189 })
190190 useEffect(() => {
191191- if (isWeb) {
191191+ if (IS_WEB) {
192192 window.__TANSTACK_QUERY_CLIENT__ = queryClient
193193 }
194194 }, [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)
···5566import {logger} from '#/logger'
77import {type MetricEvents} from '#/logger/metrics'
88-import {isWeb} from '#/platform/detection'
98import * as persisted from '#/state/persisted'
1010-// import {useSession} from '../../state/session'
99+import {IS_WEB} from '#/env'
1110import * as env from '#/env'
1211import {device} from '#/storage'
1312import {timeout} from '../async/timeout'
···38373938let refSrc = ''
4039let refUrl = ''
4141-if (isWeb && typeof window !== 'undefined') {
4040+if (IS_WEB && typeof window !== 'undefined') {
4241 const params = new URLSearchParams(window.location.search)
4342 refSrc = params.get('ref_src') ?? ''
4443 refUrl = decodeURIComponent(params.get('ref_url') ?? '')
+5-6
src/lib/strings/embed-player.ts
···11import {Dimensions} from 'react-native'
2233-import {isSafari} from '#/lib/browser'
44-import {isWeb} from '#/platform/detection'
33+import {IS_WEB, IS_WEB_SAFARI} from '#/env'
5465const {height: SCREEN_HEIGHT} = Dimensions.get('window')
7688-const IFRAME_HOST = isWeb
77+const IFRAME_HOST = IS_WEB
98 ? // @ts-ignore only for web
109 window.location.host === 'localhost:8100'
1110 ? 'http://localhost:8100'
···135134 urlp.hostname === 'www.twitch.tv' ||
136135 urlp.hostname === 'm.twitch.tv'
137136 ) {
138138- const parent = isWeb
137137+ const parent = IS_WEB
139138 ? // @ts-ignore only for web
140139 window.location.hostname
141140 : 'localhost'
···570569 width: Number(w),
571570 }
572571573573- if (isWeb) {
574574- if (isSafari) {
572572+ if (IS_WEB) {
573573+ if (IS_WEB_SAFARI) {
575574 id = id.replace('AAAAC', 'AAAP1')
576575 filename = filename.replace('.gif', '.mp4')
577576 } else {
+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,
+139-139
src/locale/locales/en/messages.po
···1818msgid "\"{interestsDisplayName}\" category (active)"
1919msgstr ""
20202121-#: src/screens/Messages/components/ChatListItem.tsx:163
2121+#: src/screens/Messages/components/ChatListItem.tsx:162
2222msgid "(contains embedded content)"
2323msgstr ""
2424···163163msgid "{0} is not available"
164164msgstr ""
165165166166-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:232
166166+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:231
167167msgid "{0} joined this week"
168168msgstr ""
169169···180180msgid "{0} reacted {1}"
181181msgstr ""
182182183183-#: src/screens/Messages/components/ChatListItem.tsx:234
183183+#: src/screens/Messages/components/ChatListItem.tsx:233
184184msgid "{0} reacted {1} to {2}"
185185msgstr ""
186186···192192msgid "{0}, a list by {1}"
193193msgstr ""
194194195195-#: src/view/com/util/UserAvatar.tsx:578
196196-#: src/view/com/util/UserAvatar.tsx:596
195195+#: src/view/com/util/UserAvatar.tsx:577
196196+#: src/view/com/util/UserAvatar.tsx:595
197197msgid "{0}'s avatar"
198198msgstr ""
199199···540540msgstr ""
541541542542#. If last message does not contain text, fall back to "{user} reacted to {a message}"
543543-#: src/screens/Messages/components/ChatListItem.tsx:213
543543+#: src/screens/Messages/components/ChatListItem.tsx:212
544544msgid "a message"
545545msgstr ""
546546···714714msgid "Add a content warning"
715715msgstr ""
716716717717-#: src/components/live/GoLiveDialog.tsx:103
717717+#: src/components/live/GoLiveDialog.tsx:106
718718msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event."
719719msgstr ""
720720···738738msgid "Add alt text"
739739msgstr ""
740740741741-#: src/view/com/composer/videos/SubtitleDialog.tsx:110
741741+#: src/view/com/composer/videos/SubtitleDialog.tsx:114
742742msgid "Add alt text (optional)"
743743msgstr ""
744744···826826msgid "Add this feed to your feeds"
827827msgstr ""
828828829829-#: src/view/com/profile/ProfileMenu.tsx:340
830830-#: src/view/com/profile/ProfileMenu.tsx:343
829829+#: src/view/com/profile/ProfileMenu.tsx:342
830830+#: src/view/com/profile/ProfileMenu.tsx:345
831831msgid "Add to lists"
832832msgstr ""
833833···836836msgstr ""
837837838838#: src/components/dialogs/StarterPackDialog.tsx:176
839839-#: src/view/com/profile/ProfileMenu.tsx:331
840840-#: src/view/com/profile/ProfileMenu.tsx:334
839839+#: src/view/com/profile/ProfileMenu.tsx:333
840840+#: src/view/com/profile/ProfileMenu.tsx:336
841841msgid "Add to starter packs"
842842msgstr ""
843843···10221022#: src/view/com/composer/GifAltText.tsx:154
10231023#: src/view/com/composer/photos/ImageAltTextDialog.tsx:117
10241024#: src/view/com/composer/videos/SubtitleDialog.tsx:40
10251025-#: src/view/com/composer/videos/SubtitleDialog.tsx:55
10261026-#: src/view/com/composer/videos/SubtitleDialog.tsx:105
10251025+#: src/view/com/composer/videos/SubtitleDialog.tsx:58
10271026#: src/view/com/composer/videos/SubtitleDialog.tsx:109
10271027+#: src/view/com/composer/videos/SubtitleDialog.tsx:113
10281028msgid "Alt text"
10291029msgstr ""
10301030···10361036msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone."
10371037msgstr ""
1038103810391039-#: src/view/com/composer/videos/SubtitleDialog.tsx:133
10391039+#: src/view/com/composer/videos/SubtitleDialog.tsx:137
10401040msgid "Alt text must be less than {MAX_ALT_TEXT} characters."
10411041msgstr ""
10421042···10541054msgid "An error has occurred"
10551055msgstr ""
1056105610571057-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:422
10571057+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:421
10581058msgid "An error occurred"
10591059msgstr ""
10601060···11511151msgid "An mockup of a iPhone showing the Bluesky app open to the profile of a verified user with a blue checkmark next to their display name."
11521152msgstr ""
1153115311541154-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:166
11541154+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:164
11551155msgid "An unknown error occurred."
11561156msgstr ""
11571157···1476147614771477#: src/components/PostControls/PostMenu/PostMenuItems.tsx:818
14781478#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190
14791479-#: src/view/com/profile/ProfileMenu.tsx:548
14791479+#: src/view/com/profile/ProfileMenu.tsx:550
14801480msgid "Block"
14811481msgstr ""
14821482···14861486#: src/components/PostControls/PostMenu/PostMenuItems.tsx:705
14871487#: src/screens/Messages/components/RequestButtons.tsx:144
14881488#: src/screens/Messages/components/RequestButtons.tsx:146
14891489-#: src/view/com/profile/ProfileMenu.tsx:454
14901490-#: src/view/com/profile/ProfileMenu.tsx:461
14891489+#: src/view/com/profile/ProfileMenu.tsx:456
14901490+#: src/view/com/profile/ProfileMenu.tsx:463
14911491msgid "Block account"
14921492msgstr ""
1493149314941494#: src/components/PostControls/PostMenu/PostMenuItems.tsx:813
14951495-#: src/view/com/profile/ProfileMenu.tsx:531
14951495+#: src/view/com/profile/ProfileMenu.tsx:533
14961496msgid "Block Account?"
14971497msgstr ""
14981498···15441544msgstr ""
1545154515461546#: src/components/PostControls/PostMenu/PostMenuItems.tsx:815
15471547-#: src/view/com/profile/ProfileMenu.tsx:543
15471547+#: src/view/com/profile/ProfileMenu.tsx:545
15481548msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you."
15491549msgstr ""
15501550···15601560msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you."
15611561msgstr ""
1562156215631563-#: src/view/com/profile/ProfileMenu.tsx:540
15631563+#: src/view/com/profile/ProfileMenu.tsx:542
15641564msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you."
15651565msgstr ""
15661566···17611761#: src/components/dialogs/InAppBrowserConsent.tsx:104
17621762#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:274
17631763#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:282
17641764-#: src/components/live/GoLiveDialog.tsx:243
17651765-#: src/components/live/GoLiveDialog.tsx:249
17641764+#: src/components/live/GoLiveDialog.tsx:246
17651765+#: src/components/live/GoLiveDialog.tsx:252
17661766#: src/components/Menu/index.tsx:352
17671767#: src/components/PostControls/RepostButton.tsx:209
17681768#: src/components/Prompt.tsx:144
17691769#: src/components/Prompt.tsx:146
17701770-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:153
17711771-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:158
17701770+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:151
17711771+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:156
17721772#: src/lib/media/picker.tsx:38
17731773#: src/screens/Deactivated.tsx:149
17741774#: src/screens/Profile/Header/EditProfileDialog.tsx:218
···18211821msgid "Cannot interact with a blocked user"
18221822msgstr ""
1823182318241824-#: src/view/com/composer/videos/SubtitleDialog.tsx:148
18241824+#: src/view/com/composer/videos/SubtitleDialog.tsx:152
18251825msgid "Captions (.vtt)"
18261826msgstr ""
1827182718281828#: src/view/com/composer/videos/SubtitleDialog.tsx:40
18291829-#: src/view/com/composer/videos/SubtitleDialog.tsx:55
18291829+#: src/view/com/composer/videos/SubtitleDialog.tsx:56
18301830msgid "Captions & alt text"
18311831msgstr ""
18321832···24312431msgid "Conversation deleted"
24322432msgstr ""
2433243324342434-#: src/screens/Messages/components/ChatListItem.tsx:199
24342434+#: src/screens/Messages/components/ChatListItem.tsx:198
24352435msgid "Conversation deleted"
24362436msgstr ""
24372437···24652465msgid "Copy App Password"
24662466msgstr ""
2467246724682468-#: src/view/com/profile/ProfileMenu.tsx:491
24692469-#: src/view/com/profile/ProfileMenu.tsx:494
24682468+#: src/view/com/profile/ProfileMenu.tsx:493
24692469+#: src/view/com/profile/ProfileMenu.tsx:496
24702470msgid "Copy at:// URI"
24712471msgstr ""
24722472···24812481msgstr ""
2482248224832483#: src/screens/Settings/components/ChangeHandleDialog.tsx:502
24842484-#: src/view/com/profile/ProfileMenu.tsx:500
24852485-#: src/view/com/profile/ProfileMenu.tsx:503
24842484+#: src/view/com/profile/ProfileMenu.tsx:502
24852485+#: src/view/com/profile/ProfileMenu.tsx:505
24862486msgid "Copy DID"
24872487msgstr ""
24882488···24952495msgid "Copy link"
24962496msgstr ""
2497249724982498-#: src/components/StarterPack/ShareDialog.tsx:119
24982498+#: src/components/StarterPack/ShareDialog.tsx:120
24992499msgid "Copy Link"
25002500msgstr ""
25012501···26712671msgid "Create an account"
26722672msgstr ""
2673267326742674-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:312
26752675-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:319
26742674+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:311
26752675+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:318
26762676msgid "Create an account without using this starter pack"
26772677msgstr ""
26782678···29762976msgid "Disable replies entirely"
29772977msgstr ""
2978297829792979-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:388
29792979+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:387
29802980msgid "Disable subtitles"
29812981msgstr ""
29822982···31193119#: src/view/com/composer/labels/LabelsBtn.tsx:218
31203120#: src/view/com/composer/labels/LabelsBtn.tsx:225
31213121#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303
31223122-#: src/view/com/composer/videos/SubtitleDialog.tsx:184
31233123-#: src/view/com/composer/videos/SubtitleDialog.tsx:195
31223122+#: src/view/com/composer/videos/SubtitleDialog.tsx:188
31233123+#: src/view/com/composer/videos/SubtitleDialog.tsx:199
31243124msgid "Done"
31253125msgstr ""
31263126···31463146msgid "Double tap to like"
31473147msgstr ""
3148314831493149-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:330
31493149+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:329
31503150msgid "Download Bluesky"
31513151msgstr ""
31523152···32213221msgid "Edit"
32223222msgstr ""
3223322332243224-#: src/view/com/util/UserAvatar.tsx:440
32243224+#: src/view/com/util/UserAvatar.tsx:439
32253225#: src/view/com/util/UserBanner.tsx:121
32263226msgid "Edit avatar"
32273227msgstr ""
···32513251msgid "Edit list details"
32523252msgstr ""
3253325332543254-#: src/view/com/profile/ProfileMenu.tsx:354
32553255-#: src/view/com/profile/ProfileMenu.tsx:374
32543254+#: src/view/com/profile/ProfileMenu.tsx:356
32553255+#: src/view/com/profile/ProfileMenu.tsx:376
32563256msgid "Edit live status"
32573257msgstr ""
32583258···34083408msgid "Enable quote posts of this post"
34093409msgstr ""
3410341034113411-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:389
34113411+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:388
34123412msgid "Enable subtitles"
34133413msgstr ""
34143414···34363436msgid "End of feed"
34373437msgstr ""
3438343834393439-#: src/view/com/composer/videos/SubtitleDialog.tsx:174
34393439+#: src/view/com/composer/videos/SubtitleDialog.tsx:178
34403440msgid "Ensure you have selected a language for each subtitle file."
34413441msgstr ""
34423442···34593459msgid "Enter code"
34603460msgstr ""
3461346134623462-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:407
34623462+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:406
34633463msgid "Enter fullscreen"
34643464msgstr ""
34653465···35293529msgid "Error occurred while saving file"
35303530msgstr ""
3531353135323532-#: src/screens/Signup/StepCaptcha/index.tsx:123
35323532+#: src/screens/Signup/StepCaptcha/index.tsx:125
35333533msgid "Error receiving captcha response."
35343534msgstr ""
35353535···35653565msgid "Excludes users you follow"
35663566msgstr ""
3567356735683568-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:406
35683568+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:405
35693569msgid "Exit fullscreen"
35703570msgstr ""
35713571···41214121msgid "Follow 7 accounts"
41224122msgstr ""
4123412341244124-#: src/view/com/profile/ProfileMenu.tsx:310
41254125-#: src/view/com/profile/ProfileMenu.tsx:321
41244124+#: src/view/com/profile/ProfileMenu.tsx:312
41254125+#: src/view/com/profile/ProfileMenu.tsx:323
41264126msgid "Follow account"
41274127msgstr ""
41284128···44334433msgid "Go Home"
44344434msgstr ""
4435443544364436-#: src/view/com/profile/ProfileMenu.tsx:355
44374437-#: src/view/com/profile/ProfileMenu.tsx:376
44364436+#: src/view/com/profile/ProfileMenu.tsx:357
44374437+#: src/view/com/profile/ProfileMenu.tsx:378
44384438msgid "Go live"
44394439msgstr ""
4440444044414441-#: src/components/live/GoLiveDialog.tsx:95
44424442-#: src/components/live/GoLiveDialog.tsx:100
44434443-#: src/components/live/GoLiveDialog.tsx:228
44444444-#: src/components/live/GoLiveDialog.tsx:237
44414441+#: src/components/live/GoLiveDialog.tsx:98
44424442+#: src/components/live/GoLiveDialog.tsx:103
44434443+#: src/components/live/GoLiveDialog.tsx:231
44444444+#: src/components/live/GoLiveDialog.tsx:240
44454445msgid "Go Live"
44464446msgstr ""
4447444744484448-#: src/view/com/profile/ProfileMenu.tsx:352
44494449-#: src/view/com/profile/ProfileMenu.tsx:372
44484448+#: src/view/com/profile/ProfileMenu.tsx:354
44494449+#: src/view/com/profile/ProfileMenu.tsx:374
44504450msgid "Go live (disabled)"
44514451msgstr ""
4452445244534453-#: src/components/live/GoLiveDialog.tsx:170
44534453+#: src/components/live/GoLiveDialog.tsx:173
44544454msgid "Go live for"
44554455msgstr ""
44564456···44654465msgid "Go to account settings"
44664466msgstr ""
4467446744684468-#: src/screens/Messages/components/ChatListItem.tsx:364
44684468+#: src/screens/Messages/components/ChatListItem.tsx:363
44694469msgid "Go to conversation with {0}"
44704470msgstr ""
44714471···46444644msgid "Hide"
46454645msgstr ""
4646464646474647-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:140
46484648-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:147
46474647+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:138
46484648+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:145
46494649msgid "Hide all events"
46504650msgstr ""
46514651···46764676msgid "Hide this card"
46774677msgstr ""
4678467846794679-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:128
46804680-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:135
46794679+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:126
46804680+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:133
46814681msgid "Hide this event"
46824682msgstr ""
46834683···48304830msgid "If you believe your birthdate is incorrect, you can update it by <0>clicking here</0>."
48314831msgstr ""
4832483248334833-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:119
48334833+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:117
48344834msgid "If you choose to hide all events, you can always re-enable them from <0>Settings → Content & Media</0>."
48354835msgstr ""
48364836···50935093msgid "Jobs"
50945094msgstr ""
5095509550965096-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:210
50975097-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:216
50965096+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:209
50975097+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:215
50985098#: src/screens/StarterPack/StarterPackScreen.tsx:464
50995099#: src/screens/StarterPack/StarterPackScreen.tsx:475
51005100msgid "Join Bluesky"
···54915491msgid "Live event happening now: {0}"
54925492msgstr ""
5493549354945494-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:107
54945494+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:105
54955495msgid "Live event options"
54965496msgstr ""
5497549754985498-#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:111
54985498+#: src/features/liveEvents/components/LiveEventFeedOptionsMenu.tsx:109
54995499msgid "Live events appear occasionally when something exciting is happening. If you'd like, you can hide this particular event, or all events for this placement in your app interface."
55005500msgstr ""
55015501···5505550555065506#: src/components/live/EditLiveDialog.tsx:147
55075507#: src/components/live/EditLiveDialog.tsx:151
55085508-#: src/components/live/GoLiveDialog.tsx:126
55095509-#: src/components/live/GoLiveDialog.tsx:130
55085508+#: src/components/live/GoLiveDialog.tsx:129
55095509+#: src/components/live/GoLiveDialog.tsx:133
55105510msgid "Live link"
55115511msgstr ""
55125512···56655665msgid "Message deleted"
56665666msgstr ""
5667566756685668-#: src/screens/Messages/components/ChatListItem.tsx:200
56685668+#: src/screens/Messages/components/ChatListItem.tsx:199
56695669msgid "Message deleted"
56705670msgstr ""
56715671···58055805msgstr ""
5806580658075807#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153
58085808-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95
58085808+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:97
58095809msgctxt "video"
58105810msgid "Mute"
58115811msgstr ""
···5817581758185818#: src/components/PostControls/PostMenu/PostMenuItems.tsx:686
58195819#: src/components/PostControls/PostMenu/PostMenuItems.tsx:692
58205820-#: src/view/com/profile/ProfileMenu.tsx:433
58215821-#: src/view/com/profile/ProfileMenu.tsx:440
58205820+#: src/view/com/profile/ProfileMenu.tsx:435
58215821+#: src/view/com/profile/ProfileMenu.tsx:442
58225822msgid "Mute account"
58235823msgstr ""
58245824···59485948msgstr ""
5949594959505950#: src/screens/Search/modules/ExploreTrendingTopics.tsx:196
59515951-#: src/view/com/profile/ProfileMenu.tsx:388
59515951+#: src/view/com/profile/ProfileMenu.tsx:390
59525952msgid "New"
59535953msgstr ""
59545954···61496149msgid "No media yet"
61506150msgstr ""
6151615161526152-#: src/screens/Messages/components/ChatListItem.tsx:142
61526152+#: src/screens/Messages/components/ChatListItem.tsx:141
61536153msgid "No messages yet"
61546154msgstr ""
61556155···62936293msgid "Not Found"
62946294msgstr ""
6295629562966296-#: src/view/com/profile/ProfileMenu.tsx:555
62966296+#: src/view/com/profile/ProfileMenu.tsx:557
62976297msgid "Note about sharing"
62986298msgstr ""
62996299···64756475msgid "Open camera"
64766476msgstr ""
6477647764786478-#: src/screens/Messages/components/ChatListItem.tsx:374
64796479-#: src/screens/Messages/components/ChatListItem.tsx:378
64786478+#: src/screens/Messages/components/ChatListItem.tsx:373
64796479+#: src/screens/Messages/components/ChatListItem.tsx:377
64806480msgid "Open conversation options"
64816481msgstr ""
64826482···66236623msgid "Opens link {0}"
66246624msgstr ""
6625662566266626-#: src/view/com/util/UserAvatar.tsx:582
66266626+#: src/view/com/util/UserAvatar.tsx:581
66276627msgid "Opens live status dialog"
66286628msgstr ""
66296629···66406640msgstr ""
6641664166426642#: src/view/com/notifications/NotificationFeedItem.tsx:1027
66436643-#: src/view/com/util/UserAvatar.tsx:600
66436643+#: src/view/com/util/UserAvatar.tsx:599
66446644msgid "Opens this profile"
66456645msgstr ""
66466646···6760676067616761#: src/components/Post/Embed/ExternalEmbed/Gif.tsx:44
67626762#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:137
67636763-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:369
67636763+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:368
67646764msgid "Pause"
67656765msgstr ""
6766676667676767-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:320
67676767+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:319
67686768msgid "Pause video"
67696769msgstr ""
67706770···6871687168726872#: src/components/Post/Embed/ExternalEmbed/Gif.tsx:44
68736873#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:137
68746874-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:370
68746874+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:369
68756875msgid "Play"
68766876msgstr ""
68776877···68806880msgstr ""
6881688168826882#: src/components/Post/Embed/VideoEmbed/index.tsx:115
68836883-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:321
68836883+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:320
68846884msgid "Play video"
68856885msgstr ""
68866886···70657065msgid "Porn"
70667066msgstr ""
7067706770687068-#: src/screens/PostThread/index.tsx:532
70687068+#: src/screens/PostThread/index.tsx:531
70697069msgctxt "description"
70707070msgid "Post"
70717071msgstr ""
···75367536msgid "Remove attachment"
75377537msgstr ""
7538753875397539-#: src/view/com/util/UserAvatar.tsx:499
75407540-#: src/view/com/util/UserAvatar.tsx:502
75397539+#: src/view/com/util/UserAvatar.tsx:498
75407540+#: src/view/com/util/UserAvatar.tsx:501
75417541msgid "Remove Avatar"
75427542msgstr ""
75437543···76087608msgid "Remove repost"
76097609msgstr ""
7610761076117611-#: src/view/com/composer/videos/SubtitleDialog.tsx:278
76117611+#: src/view/com/composer/videos/SubtitleDialog.tsx:282
76127612msgid "Remove subtitle file"
76137613msgstr ""
76147614···7627762776287628#: src/components/verification/VerificationRemovePrompt.tsx:46
76297629#: src/components/verification/VerificationsDialog.tsx:252
76307630-#: src/view/com/profile/ProfileMenu.tsx:406
76317631-#: src/view/com/profile/ProfileMenu.tsx:409
76307630+#: src/view/com/profile/ProfileMenu.tsx:408
76317631+#: src/view/com/profile/ProfileMenu.tsx:411
76327632msgid "Remove verification"
76337633msgstr ""
76347634···77637763msgid "Report"
77647764msgstr ""
7765776577667766-#: src/view/com/profile/ProfileMenu.tsx:473
77677767-#: src/view/com/profile/ProfileMenu.tsx:476
77667766+#: src/view/com/profile/ProfileMenu.tsx:475
77677767+#: src/view/com/profile/ProfileMenu.tsx:478
77687768msgid "Report account"
77697769msgstr ""
77707770···80748074msgid "Save changes"
80758075msgstr ""
8076807680778077-#: src/components/StarterPack/ShareDialog.tsx:138
80788078-#: src/components/StarterPack/ShareDialog.tsx:144
80778077+#: src/components/StarterPack/ShareDialog.tsx:142
80788078+#: src/components/StarterPack/ShareDialog.tsx:148
80798079msgid "Save image"
80808080msgstr ""
80818081···82238223msgid "Search my posts"
82248224msgstr ""
8225822582268226-#: src/view/com/profile/ProfileMenu.tsx:289
82278227-#: src/view/com/profile/ProfileMenu.tsx:292
82268226+#: src/view/com/profile/ProfileMenu.tsx:291
82278227+#: src/view/com/profile/ProfileMenu.tsx:294
82288228msgid "Search posts"
82298229msgstr ""
82308230···83508350msgid "Select content languages"
83518351msgstr ""
8352835283538353-#: src/components/live/GoLiveDialog.tsx:175
83538353+#: src/components/live/GoLiveDialog.tsx:178
83548354msgid "Select duration"
83558355msgstr ""
83568356···83848384msgid "Select language"
83858385msgstr ""
8386838683878387-#: src/view/com/composer/videos/SubtitleDialog.tsx:265
83878387+#: src/view/com/composer/videos/SubtitleDialog.tsx:269
83888388msgid "Select language..."
83898389msgstr ""
83908390···86428642msgid "Share a fun fact!"
86438643msgstr ""
8644864486458645-#: src/view/com/profile/ProfileMenu.tsx:560
86458645+#: src/view/com/profile/ProfileMenu.tsx:562
86468646msgid "Share anyway"
86478647msgstr ""
86488648···86548654#: src/components/dialogs/LinkWarning.tsx:96
86558655#: src/components/dialogs/LinkWarning.tsx:104
86568656#: src/components/StarterPack/ShareDialog.tsx:113
86578657-#: src/components/StarterPack/ShareDialog.tsx:119
86578657+#: src/components/StarterPack/ShareDialog.tsx:122
86588658msgid "Share link"
86598659msgstr ""
86608660···86678667msgid "Share post at:// URI"
86688668msgstr ""
8669866986708670-#: src/components/StarterPack/ShareDialog.tsx:123
86718671-#: src/components/StarterPack/ShareDialog.tsx:133
86708670+#: src/components/StarterPack/ShareDialog.tsx:127
86718671+#: src/components/StarterPack/ShareDialog.tsx:137
86728672msgid "Share QR code"
86738673msgstr ""
86748674···89748974msgid "Someone reacted {0}"
89758975msgstr ""
8976897689778977-#: src/screens/Messages/components/ChatListItem.tsx:244
89778977+#: src/screens/Messages/components/ChatListItem.tsx:243
89788978msgid "Someone reacted {0} to {1}"
89798979msgstr ""
89808980···9404940494059405#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:186
94069406#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:391
94079407-#: src/view/com/profile/ProfileMenu.tsx:536
94079407+#: src/view/com/profile/ProfileMenu.tsx:538
94089408msgid "The account will be able to interact with you after unblocking."
94099409msgstr ""
94109410···94429442msgid "The Discover feed now knows what you like"
94439443msgstr ""
9444944494459445-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:333
94459445+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:332
94469446msgid "The experience is better in the app. Download Bluesky now and we'll pick back up where you left off."
94479447msgstr ""
94489448···94589458msgid "The following labels were applied to your content."
94599459msgstr ""
9460946094619461-#: src/components/live/GoLiveDialog.tsx:157
94619461+#: src/components/live/GoLiveDialog.tsx:160
94629462msgid "The following services are enabled for your account: {allowedServices}"
94639463msgstr ""
94649464···95519551msgstr ""
9552955295539553#: src/screens/Search/Explore.tsx:999
95549554-#: src/view/com/posts/PostFeed.tsx:759
95549554+#: src/view/com/posts/PostFeed.tsx:758
95559555msgid "There was an issue fetching posts. Tap here to try again."
95569556msgstr ""
95579557···97029702msgid "This content is not viewable without a Bluesky account."
97039703msgstr ""
9704970497059705-#: src/screens/Messages/components/ChatListItem.tsx:366
97059705+#: src/screens/Messages/components/ChatListItem.tsx:365
97069706msgid "This conversation is with a deleted or a deactivated account. Press for options"
97079707msgstr ""
97089708···97579757msgstr ""
9758975897599759#: src/components/live/EditLiveDialog.tsx:176
97609760-#: src/components/live/GoLiveDialog.tsx:150
97609760+#: src/components/live/GoLiveDialog.tsx:153
97619761msgid "This is not a valid link"
97629762msgstr ""
97639763···98179817msgid "This post's author has disabled quote posts."
98189818msgstr ""
9819981998209820-#: src/view/com/profile/ProfileMenu.tsx:557
98209820+#: src/view/com/profile/ProfileMenu.tsx:559
98219821msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in."
98229822msgstr ""
98239823···1009010090#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:394
1009110091#: src/screens/ProfileList/components/Header.tsx:171
1009210092#: src/screens/ProfileList/components/Header.tsx:178
1009310093-#: src/view/com/profile/ProfileMenu.tsx:548
1009310093+#: src/view/com/profile/ProfileMenu.tsx:550
1009410094msgid "Unblock"
1009510095msgstr ""
1009610096···10101101011010210102#: src/components/dms/ConvoMenu.tsx:261
1010310103#: src/components/dms/ConvoMenu.tsx:264
1010410104-#: src/view/com/profile/ProfileMenu.tsx:453
1010510105-#: src/view/com/profile/ProfileMenu.tsx:459
1010410104+#: src/view/com/profile/ProfileMenu.tsx:455
1010510105+#: src/view/com/profile/ProfileMenu.tsx:461
1010610106msgid "Unblock account"
1010710107msgstr ""
10108101081010910109#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:184
1011010110#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:389
1011110111-#: src/view/com/profile/ProfileMenu.tsx:530
1011110111+#: src/view/com/profile/ProfileMenu.tsx:532
1011210112msgid "Unblock Account?"
1011310113msgstr ""
1011410114···1014110141msgid "Unfollow {0}"
1014210142msgstr ""
10143101431014410144-#: src/view/com/profile/ProfileMenu.tsx:309
1014510145-#: src/view/com/profile/ProfileMenu.tsx:319
1014410144+#: src/view/com/profile/ProfileMenu.tsx:311
1014510145+#: src/view/com/profile/ProfileMenu.tsx:321
1014610146msgid "Unfollow account"
1014710147msgstr ""
1014810148···1018410184msgstr ""
10185101851018610186#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152
1018710187-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:94
1018710187+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96
1018810188msgctxt "video"
1018910189msgid "Unmute"
1019010190msgstr ""
···10201102011020210202#: src/components/PostControls/PostMenu/PostMenuItems.tsx:685
1020310203#: src/components/PostControls/PostMenu/PostMenuItems.tsx:691
1020410204-#: src/view/com/profile/ProfileMenu.tsx:432
1020510205-#: src/view/com/profile/ProfileMenu.tsx:438
1020410204+#: src/view/com/profile/ProfileMenu.tsx:434
1020510205+#: src/view/com/profile/ProfileMenu.tsx:440
1020610206msgid "Unmute account"
1020710207msgstr ""
1020810208···1022010220msgid "Unmute thread"
1022110221msgstr ""
10222102221022310223-#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:318
1022310223+#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx:317
1022410224msgid "Unmute video"
1022510225msgstr ""
1022610226···1033910339msgid "Upload a text file to:"
1034010340msgstr ""
10341103411034210342-#: src/view/com/util/UserAvatar.tsx:470
1034310343-#: src/view/com/util/UserAvatar.tsx:473
1034210342+#: src/view/com/util/UserAvatar.tsx:469
1034310343+#: src/view/com/util/UserAvatar.tsx:472
1034410344#: src/view/com/util/UserBanner.tsx:159
1034510345#: src/view/com/util/UserBanner.tsx:162
1034610346msgid "Upload from Camera"
1034710347msgstr ""
10348103481034910349-#: src/view/com/util/UserAvatar.tsx:487
1034910349+#: src/view/com/util/UserAvatar.tsx:486
1035010350#: src/view/com/util/UserBanner.tsx:176
1035110351msgid "Upload from Files"
1035210352msgstr ""
10353103531035410354-#: src/view/com/util/UserAvatar.tsx:481
1035510355-#: src/view/com/util/UserAvatar.tsx:485
1035410354+#: src/view/com/util/UserAvatar.tsx:480
1035510355+#: src/view/com/util/UserAvatar.tsx:484
1035610356#: src/view/com/util/UserBanner.tsx:170
1035710357#: src/view/com/util/UserBanner.tsx:174
1035810358msgid "Upload from Library"
···10510105101051110511#: src/components/verification/VerificationCreatePrompt.tsx:84
1051210512#: src/components/verification/VerificationCreatePrompt.tsx:86
1051310513-#: src/view/com/profile/ProfileMenu.tsx:416
1051410514-#: src/view/com/profile/ProfileMenu.tsx:419
1051310513+#: src/view/com/profile/ProfileMenu.tsx:418
1051410514+#: src/view/com/profile/ProfileMenu.tsx:421
1051510515msgid "Verify account"
1051610516msgstr ""
1051710517···1062610626msgid "Video not found."
1062710627msgstr ""
10628106281062910629-#: src/view/com/composer/videos/SubtitleDialog.tsx:102
1062910629+#: src/view/com/composer/videos/SubtitleDialog.tsx:106
1063010630msgid "Video settings"
1063110631msgstr ""
1063210632···1112011120msgstr ""
11121111211112211122#: src/components/live/EditLiveDialog.tsx:152
1112311123-#: src/components/live/GoLiveDialog.tsx:131
1112311123+#: src/components/live/GoLiveDialog.tsx:134
1112411124msgid "www.mylivestream.tv"
1112511125msgstr ""
1112611126···1146411464msgid "You reacted {0}"
1146511465msgstr ""
11466114661146711467-#: src/screens/Messages/components/ChatListItem.tsx:221
1146711467+#: src/screens/Messages/components/ChatListItem.tsx:220
1146811468msgid "You reacted {0} to {1}"
1146911469msgstr ""
1147011470···1149411494msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password."
1149511495msgstr ""
11496114961149711497-#: src/screens/Messages/components/ChatListItem.tsx:157
1149711497+#: src/screens/Messages/components/ChatListItem.tsx:156
1149811498msgid "You: {0}"
1149911499msgstr ""
11500115001150111501-#: src/screens/Messages/components/ChatListItem.tsx:186
1150111501+#: src/screens/Messages/components/ChatListItem.tsx:185
1150211502msgid "You: {defaultEmbeddedContentMessage}"
1150311503msgstr ""
11504115041150511505-#: src/screens/Messages/components/ChatListItem.tsx:179
1150511505+#: src/screens/Messages/components/ChatListItem.tsx:178
1150611506msgid "You: {short}"
1150711507msgstr ""
1150811508···1151411514msgid "You'll follow the suggested users once you finish creating your account!"
1151511515msgstr ""
11516115161151711517-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:245
1151711517+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:244
1151811518msgid "You'll follow these people and {0} others"
1151911519msgstr ""
11520115201152111521-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:243
1152111521+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:242
1152211522msgid "You'll follow these people right away"
1152311523msgstr ""
1152411524···1153011530msgid "You'll start receiving notifications for {0}!"
1153111531msgstr ""
11532115321153311533-#: src/screens/StarterPack/StarterPackLandingScreen.tsx:283
1153311533+#: src/screens/StarterPack/StarterPackLandingScreen.tsx:282
1153411534msgid "You'll stay updated with these feeds"
1153511535msgstr ""
1153611536
+2-2
src/logger/index.ts
···1111 type Transport,
1212} from '#/logger/types'
1313import {enabledLogLevels} from '#/logger/util'
1414-import {isNative} from '#/platform/detection'
1414+import {IS_NATIVE} from '#/env'
1515import {ENV} from '#/env'
1616import {bitdriftTransport} from './transports/bitdrift'
1717import {sentryTransport} from './transports/sentry'
···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,
···2524import * as Layout from '#/components/Layout'
2625import {Loader} from '#/components/Loader'
2726import {Text} from '#/components/Typography'
2727+import {IS_WEB} from '#/env'
28282929const COL_WIDTH = 400
3030···5656 }, [setShowLoggedOut])
57575858 const onPressLogout = React.useCallback(() => {
5959- if (isWeb) {
5959+ if (IS_WEB) {
6060 // We're switching accounts, which remounts the entire app.
6161 // On mobile, this gets us Home, but on the web we also need reset the URL.
6262 // We can't change the URL via a navigate() call because the navigator
···102102 contentContainerStyle={[
103103 a.px_2xl,
104104 {
105105- paddingTop: isWeb ? 64 : insets.top + 16,
106106- paddingBottom: isWeb ? 64 : insets.bottom,
105105+ paddingTop: IS_WEB ? 64 : insets.top + 16,
106106+ paddingBottom: IS_WEB ? 64 : insets.bottom,
107107 },
108108 ]}>
109109 <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
···217217 inputRef={identifierRef}
218218 label={_(msg`Username or email address`)}
219219 autoCapitalize="none"
220220- autoFocus={!isIOS}
220220+ autoFocus={!IS_IOS}
221221 autoCorrect={false}
222222 autoComplete="username"
223223 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'
···3938import {Link} from '#/components/Link'
4039import {ListFooter} from '#/components/Lists'
4140import {Text} from '#/components/Typography'
4141+import {IS_NATIVE} from '#/env'
4242import {ChatListItem} from './components/ChatListItem'
4343import {InboxPreview} from './components/InboxPreview'
4444···223223224224 const onSoftReset = useCallback(async () => {
225225 scrollElRef.current?.scrollToOffset({
226226- animated: isNative,
226226+ animated: IS_NATIVE,
227227 offset: 0,
228228 })
229229 try {
···349349 hasNextPage={hasNextPage}
350350 />
351351 }
352352- onEndReachedThreshold={isNative ? 1.5 : 0}
352352+ onEndReachedThreshold={IS_NATIVE ? 1.5 : 0}
353353 initialNumToRender={initialNumToRender}
354354 windowSize={11}
355355 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,
···3029import {android, atoms as a, useTheme} from '#/alf'
3130import {useSharedInputStyles} from '#/components/forms/TextField'
3231import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
3232+import {IS_IOS, IS_WEB} from '#/env'
3333import {useExtractEmbedFromFacets} from './MessageInputEmbed'
34343535const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
···8787 playHaptic()
8888 setEmbed(undefined)
8989 setMessage('')
9090- if (isIOS) {
9090+ if (IS_IOS) {
9191 setShouldEnforceClear(true)
9292 }
9393- if (isWeb) {
9393+ if (IS_WEB) {
9494 // Pressing the send button causes the text input to lose focus, so we need to
9595 // re-focus it after sending
9696 setTimeout(() => {
···163163 // next change and double make sure the input is cleared. It should *always* send an onChange event after
164164 // clearing via setMessage('') that happens in onSubmit()
165165 // -sfn
166166- if (isIOS && shouldEnforceClear) {
166166+ if (IS_IOS && shouldEnforceClear) {
167167 setShouldEnforceClear(false)
168168 setMessage('')
169169 return
···178178 a.px_sm,
179179 t.atoms.text,
180180 android({paddingTop: 0}),
181181- {paddingBottom: isIOS ? 5 : 0},
181181+ {paddingBottom: IS_IOS ? 5 : 0},
182182 animatedStyle,
183183 ]}
184184 keyboardAppearance={t.scheme}
···66import TextareaAutosize from 'react-textarea-autosize'
77import {countGraphemes} from 'unicode-segmenter/grapheme'
8899-import {isSafari, isTouchDevice} from '#/lib/browser'
109import {MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
1110import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
1211import {
···2524import {useSharedInputStyles} from '#/components/forms/TextField'
2625import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
2726import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
2727+import {IS_WEB_SAFARI, IS_WEB_TOUCH_DEVICE} from '#/env'
2828import {useExtractEmbedFromFacets} from './MessageInputEmbed'
29293030export function MessageInput({
···8888 // far too long of a delay, and a subsequent enter press would often just end up doing nothing. A shorter time
8989 // frame was also not great, since it was too short to be reliable (i.e. an older system might have a larger
9090 // time gap between the two events firing.
9191- if (isSafari && e.key === 'Enter' && e.keyCode === 229) {
9191+ if (IS_WEB_SAFARI && e.key === 'Enter' && e.keyCode === 229) {
9292 return
9393 }
9494···231231 onChange={onChange}
232232 // On mobile web phones, we want to keep the same behavior as the native app. Do not submit the message
233233 // in these cases.
234234- onKeyDown={isTouchDevice && isMobile ? undefined : onKeyDown}
234234+ onKeyDown={IS_WEB_TOUCH_DEVICE && isMobile ? undefined : onKeyDown}
235235 />
236236 <Pressable
237237 accessibilityRole="button"
+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,
···399399 (e: LayoutChangeEvent) => {
400400 layoutHeight.set(e.nativeEvent.layout.height)
401401402402- if (isWeb || !keyboardIsOpening.get()) {
402402+ if (IS_WEB || !keyboardIsOpening.get()) {
403403 flatListRef.current?.scrollToEnd({
404404 animated: !layoutScrollWithoutAnimation.get(),
405405 })
···438438 disableVirtualization={true}
439439 style={animatedListStyle}
440440 // The extra two items account for the header and the footer components
441441- initialNumToRender={isNative ? 32 : 62}
442442- maxToRenderPerBatch={isWeb ? 32 : 62}
441441+ initialNumToRender={IS_NATIVE ? 32 : 62}
442442+ maxToRenderPerBatch={IS_WEB ? 32 : 62}
443443 keyboardDismissMode="on-drag"
444444 keyboardShouldPersistTaps="handled"
445445 maintainVisibleContentPosition={{
···477477 )}
478478 </Animated.View>
479479480480- {isWeb && (
480480+ {IS_WEB && (
481481 <EmojiPicker
482482 pinToTop
483483 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
+7-6
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
···3333 a.relative,
3434 a.justify_center,
3535 align === 'right' ? a.align_end : a.align_start,
3636- width === undefined ? {opacity: 0} : {minWidth: width},
3636+ {minWidth: width ?? iconSizes[ICON_SIZE]},
3737 ]}>
3838 <PrivacySensitive
3939 style={[
···4646 // when finding the size of the button, we need the containing
4747 // element to have a concrete size otherwise the text will
4848 // collapse to 0 width. so set it to a really big number
4949- // and hide the entire thing (see above)
5050- width === undefined && {width: 10000},
4949+ // and just use `pointer-events: box-none` so it doesn't interfere with the UI
5050+ {width: 1000},
5151+ a.pointer_events_box_none,
5152 ]}>
5253 <View
5354 onLayout={evt => setWidth(evt.nativeEvent.layout.width)}
5455 style={[
5556 t.atoms.bg,
5656- // make sure it covers the icon! the won't always be a button
5757+ // make sure it covers the icon! children might be undefined
5758 {minWidth: iconSizes[ICON_SIZE], minHeight: iconSizes[ICON_SIZE]},
5859 ]}>
5960 {children}
···55import {useNavigation} from '@react-navigation/native'
6677import {logger} from '#/logger'
88-import {isIOS} from '#/platform/detection'
98import {useProfileShadow} from '#/state/cache/profile-shadow'
109import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1110import {
···1817import {Button, ButtonIcon, ButtonText} from '#/components/Button'
1918import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
2019import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
2020+import {IS_IOS} from '#/env'
2121import {GrowthHack} from './GrowthHack'
22222323export function ThreadItemAnchorFollowButton({did}: {did: string}) {
2424- if (isIOS) {
2424+ if (IS_IOS) {
2525 return (
2626 <GrowthHack>
2727 <ThreadItemAnchorFollowButtonInner did={did} />
···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 {useShowLinkInHandle} from '#/state/preferences/show-link-in-handle.tsx'
109import {atoms as a, useTheme, web} from '#/alf'
1110import {InlineLinkText} from '#/components/Link.tsx'
1211import {NewskieDialog} from '#/components/NewskieDialog'
1312import {Text} from '#/components/Typography'
1313+import {IS_IOS, IS_NATIVE} from '#/env'
14141515export function ProfileHeaderHandle({
1616 profile,
···2828 profile.handle,
2929 '@',
3030 // forceLTR handled by CSS above on web
3131- isNative,
3131+ IS_NATIVE,
3232 )
3333 return (
3434 <View
3535 style={[a.flex_row, a.gap_sm, a.align_center, {maxWidth: '100%'}]}
3636- pointerEvents={disableTaps ? 'none' : isIOS ? 'auto' : 'box-none'}>
3636+ pointerEvents={disableTaps ? 'none' : IS_IOS ? 'auto' : 'box-none'}>
3737 <NewskieDialog profile={profile} disabled={disableTaps} />
38383939 <Text
···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
···991010import {STATUS_PAGE_URL} from '#/lib/constants'
1111import {type CommonNavigatorParams} from '#/lib/routes/types'
1212-import {isAndroid, isIOS, isNative} from '#/platform/detection'
1312import * as Toast from '#/view/com/util/Toast'
1413import * as SettingsList from '#/screens/Settings/components/SettingsList'
1514import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
···2019import {Wrench_Stroke2_Corner2_Rounded as WrenchIcon} from '#/components/icons/Wrench'
2120import * as Layout from '#/components/Layout'
2221import {Loader} from '#/components/Loader'
2222+import {IS_ANDROID, IS_IOS, IS_NATIVE} from '#/env'
2323import * as env from '#/env'
2424import {useDemoMode} from '#/storage/hooks/demo-mode'
2525import {useDevMode} from '#/storage/hooks/dev-mode'
···4242 return spaceDiff * -1
4343 },
4444 onSuccess: sizeDiffBytes => {
4545- if (isAndroid) {
4545+ if (IS_ANDROID) {
4646 Toast.show(
4747 _(
4848 msg({
···108108 <Trans>System log</Trans>
109109 </SettingsList.ItemText>
110110 </SettingsList.LinkItem>
111111- {isNative && (
111111+ {IS_NATIVE && (
112112 <SettingsList.PressableItem
113113 onPress={() => onClearImageCache()}
114114 label={_(msg`Clear image cache`)}
···157157 {devModeEnabled && (
158158 <>
159159 <OTAInfo />
160160- {isIOS && (
160160+ {IS_IOS && (
161161 <SettingsList.PressableItem
162162 onPress={() => {
163163 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`),
···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 {pdsAgent} from '#/state/session/agent'
109import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
···1615import {Lock_Stroke2_Corner0_Rounded as Lock} from '#/components/icons/Lock'
1716import {Loader} from '#/components/Loader'
1817import {P, Text} from '#/components/Typography'
1818+import {IS_NATIVE} from '#/env'
19192020enum Stages {
2121 Email,
···194194 </View>
195195 ) : undefined}
196196197197- {!gtMobile && isNative && <View style={{height: 40}} />}
197197+ {!gtMobile && IS_NATIVE && <View style={{height: 40}} />}
198198 </View>
199199 </Dialog.ScrollableInner>
200200 </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)
···8688 newUrl.searchParams.set('state', stateParam)
8789 newUrl.searchParams.set('colorScheme', theme.name)
88908989- if (isNative && token) {
9191+ if (IS_NATIVE && token) {
9092 newUrl.searchParams.set('platform', Platform.OS)
9193 newUrl.searchParams.set('token', token)
9292- if (isAndroid && payload) {
9494+ if (IS_ANDROID && payload) {
9395 newUrl.searchParams.set('payload', payload)
9496 }
9597 }
+4-4
src/screens/Signup/StepInfo/index.tsx
···88import {DEFAULT_SERVICE} from '#/lib/constants'
99import {isEmailMaybeInvalid} from '#/lib/strings/email'
1010import {logger} from '#/logger'
1111-import {isNative, isWeb} from '#/platform/detection'
1211import {useSignupContext} from '#/screens/Signup/state'
1312import {Policies} from '#/screens/Signup/StepInfo/Policies'
1413import {atoms as a, native} from '#/alf'
···3736 MIN_ACCESS_AGE,
3837 useAgeAssuranceRegionConfigWithFallback,
3938} from '#/ageAssurance/util'
3939+import {IS_NATIVE, IS_WEB} from '#/env'
4040import {
4141 useDeviceGeolocationApi,
4242 useIsDeviceGeolocationGranted,
···215215 If you have one, sign in with an existing Bluesky account.
216216 </Trans>
217217 </Text>
218218- <View style={isWeb && [a.flex_row, a.justify_center]}>
218218+ <View style={IS_WEB && [a.flex_row, a.justify_center]}>
219219 <Button
220220 testID="signInButton"
221221 onPress={onPressSignIn}
···397397 </Trans>
398398 )}
399399 </Admonition.Text>
400400- {isNative &&
400400+ {IS_NATIVE &&
401401 !isDeviceGeolocationGranted &&
402402 isOverAppMinAccessAge && (
403403 <Admonition.Text>
···429429 ) : undefined}
430430 </View>
431431432432- {isNative && (
432432+ {IS_NATIVE && (
433433 <DeviceLocationRequestDialog
434434 control={locationControl}
435435 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···114114115115 // On Android, warmup the Play Integrity API on the signup screen so it is ready by the time we get to the gate screen.
116116 useEffect(() => {
117117- if (!isAndroid) {
117117+ if (!IS_ANDROID) {
118118 return
119119 }
120120 ReactNativeDeviceAttest.warmupIntegrity(GCP_PROJECT_ID).catch(err =>
···2222let _state: Schema = defaults
2323const _emitter = new EventEmitter()
24242525+// async, to match native implementation
2626+// eslint-disable-next-line @typescript-eslint/require-await
2527export async function init() {
2628 broadcast.onmessage = onBroadcastMessage
2729 window.onstorage = onStorage
···3739}
3840get satisfies PersistedApi['get']
39414242+// eslint-disable-next-line @typescript-eslint/require-await
4043export async function write<K extends keyof Schema>(
4144 key: K,
4245 value: Schema[K],
···8285}
8386onUpdate satisfies PersistedApi['onUpdate']
84878888+// eslint-disable-next-line @typescript-eslint/require-await
8589export async function clearStorage() {
8690 try {
8791 localStorage.removeItem(BSKY_STORAGE)
···102106 }
103107}
104108109109+// eslint-disable-next-line @typescript-eslint/require-await
105110async function onBroadcastMessage({data}: MessageEvent) {
106111 if (
107112 typeof data === 'object' &&
+1-1
src/state/preferences/index.tsx
···5151 useSetExternalEmbedPref,
5252} from './external-embeds-prefs'
5353export {useGoLinksEnabled, useSetGoLinksEnabled} from './go-links-enabled'
5454-export * from './hidden-posts'
5454+export {useHiddenPosts, useHiddenPostsApi} from './hidden-posts'
5555export {
5656 useHideFeedsPromoTab,
5757 useSetHideFeedsPromoTab,
+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':
···11import React, {useMemo} 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,
···341341 )
342342343343 // @ts-expect-error window type is not declared, debug only
344344- if (__DEV__ && isWeb) window.agent = state.currentAgentState.agent
344344+ if (__DEV__ && IS_WEB) window.agent = state.currentAgentState.agent
345345346346 const agent = state.currentAgentState.agent as BskyAppAgent
347347 const currentAgentRef = React.useRef(agent)
+3-3
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
···2626 /**
2727 * The did of the account to populate the login form with.
2828 */
2929- requestedAccount?: string | 'none' | 'new' | 'starterpack'
2929+ requestedAccount?: (string & {}) | 'none' | 'new' | 'starterpack'
3030 }) => void
3131 /**
3232 * Clears the requested account so that next time the logged out view is
···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 {
···132131import * as Prompt from '#/components/Prompt'
133132import * as Toast from '#/components/Toast'
134133import {Text as NewText} from '#/components/Typography'
134134+import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
135135import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
136136import {PostLanguageSelect} from './select-language/PostLanguageSelect'
137137import {
···331331 const insets = useSafeAreaInsets()
332332 const viewStyles = useMemo(
333333 () => ({
334334- paddingTop: isAndroid ? insets.top : 0,
334334+ paddingTop: IS_ANDROID ? insets.top : 0,
335335 paddingBottom:
336336 // iOS - when keyboard is closed, keep the bottom bar in the safe area
337337- (isIOS && !isKeyboardVisible) ||
337337+ (IS_IOS && !isKeyboardVisible) ||
338338 // Android - Android >=35 KeyboardAvoidingView adds double padding when
339339 // keyboard is closed, so we subtract that in the offset and add it back
340340 // here when the keyboard is open
341341- (isAndroid && isKeyboardVisible)
341341+ (IS_ANDROID && isKeyboardVisible)
342342 ? insets.bottom
343343 : 0,
344344 }),
···368368369369 // On Android, pressing Back should ask confirmation.
370370 useEffect(() => {
371371- if (!isAndroid) {
371371+ if (!IS_ANDROID) {
372372 return
373373 }
374374 const backHandler = BackHandler.addEventListener(
···673673 composerState.mutableNeedsFocusActive = false
674674 // On Android, this risks getting the cursor stuck behind the keyboard.
675675 // Not worth it.
676676- if (!isAndroid) {
676676+ if (!IS_ANDROID) {
677677 textInput.current?.focus()
678678 }
679679 }
···729729 </>
730730 )
731731732732- const isWebFooterSticky = !isNative && thread.posts.length > 1
732732+ const IS_WEBFooterSticky = !IS_NATIVE && thread.posts.length > 1
733733 return (
734734 <BottomSheetPortalProvider>
735735 <KeyboardAvoidingView
736736 testID="composePostView"
737737- behavior={isIOS ? 'padding' : 'height'}
737737+ behavior={IS_IOS ? 'padding' : 'height'}
738738 keyboardVerticalOffset={keyboardVerticalOffset}
739739 style={a.flex_1}>
740740 <View
···794794 onPublish={onComposerPostPublish}
795795 onError={setError}
796796 />
797797- {isWebFooterSticky && post.id === activePost.id && (
797797+ {IS_WEBFooterSticky && post.id === activePost.id && (
798798 <View style={styles.stickyFooterWeb}>{footer}</View>
799799 )}
800800 </React.Fragment>
801801 ))}
802802 </Animated.ScrollView>
803803- {!isWebFooterSticky && footer}
803803+ {!IS_WEBFooterSticky && footer}
804804 </View>
805805806806 <Prompt.Basic
···853853 const {data: currentProfile} = useProfileQuery({did: currentDid})
854854 const richtext = post.richtext
855855 const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media
856856- const forceMinHeight = isWeb && isTextOnly && isActive
856856+ const forceMinHeight = IS_WEB && isTextOnly && isActive
857857 const selectTextInputPlaceholder = isReply
858858 ? isFirstPost
859859 ? _(msg`Write your reply`)
···895895 async (uri: string) => {
896896 if (
897897 uri.startsWith('data:video/') ||
898898- (isWeb && uri.startsWith('data:image/gif'))
898898+ (IS_WEB && uri.startsWith('data:image/gif'))
899899 ) {
900900- if (isNative) return // web only
900900+ if (IS_NATIVE) return // web only
901901 const [mimeType] = uri.slice('data:'.length).split(';')
902902 if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
903903 Toast.show(_(msg`Unsupported video type: ${mimeType}`), {
···927927 a.mb_sm,
928928 !isActive && isLastPost && a.mb_lg,
929929 !isActive && styles.inactivePost,
930930- isTextOnly && isNative && a.flex_grow,
930930+ isTextOnly && IS_NATIVE && a.flex_grow,
931931 ]}>
932932- <View style={[a.flex_row, isNative && a.flex_1]}>
932932+ <View style={[a.flex_row, IS_NATIVE && a.flex_1]}>
933933 <UserAvatar
934934 avatar={currentProfile?.avatar}
935935 size={42}
···12701270 </LayoutAnimationConfig>
12711271 {embed.quote?.uri ? (
12721272 <View
12731273- style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], isWeb && [a.pb_md]]}>
12731273+ style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], IS_WEB && [a.pb_md]]}>
12741274 <View style={[a.relative]}>
12751275 <View style={{pointerEvents: 'none'}}>
12761276 <LazyQuoteEmbed uri={embed.quote.uri} />
···16831683 const {top, bottom} = useSafeAreaInsets()
1684168416851685 // Android etc
16861686- if (!isIOS) {
16861686+ if (!IS_IOS) {
16871687 // need to account for the edge-to-edge nav bar
16881688 return bottom * -1
16891689 }
···17271727 const appState = useAppState()
1728172817291729 useEffect(() => {
17301730- if (isIOS) {
17301730+ if (IS_IOS) {
17311731 if (appState === 'inactive') {
17321732 Keyboard.dismiss()
17331733 }
···18791879 style: StyleProp<ViewStyle>
18801880 children: React.ReactNode
18811881}) {
18821882- if (isWeb) return children
18821882+ if (IS_WEB) return children
18831883 return (
18841884 <Animated.View
18851885 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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1615import {MAX_IMAGES} from '#/view/com/composer/state/composer'
1716import {atoms as a, useTheme} from '#/alf'
···1918import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
2019import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
2120import * as toast from '#/components/Toast'
2121+import {IS_NATIVE, IS_WEB} from '#/env'
22222323export type SelectMediaButtonProps = {
2424 disabled?: boolean
···9292 'image/svg+xml',
9393 'image/webp',
9494 'image/avif',
9595- isNative && 'image/heic',
9595+ IS_NATIVE && 'image/heic',
9696 ] as const
9797).filter(Boolean)
9898type SupportedImageMimeType = Exclude<
···262262 * We don't care too much about mimeType at this point on native,
263263 * since the `processVideo` step later on will convert to `.mp4`.
264264 */
265265- if (isWeb && !isSupportedVideoMimeType(mimeType)) {
265265+ if (IS_WEB && !isSupportedVideoMimeType(mimeType)) {
266266 errors.add(SelectedAssetError.Unsupported)
267267 continue
268268 }
···272272 * to filter out large files on web. On native, we compress these anyway,
273273 * so we only check on web.
274274 */
275275- if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
275275+ if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
276276 errors.add(SelectedAssetError.FileTooBig)
277277 continue
278278 }
···291291 * to filter out large files on web. On native, we compress GIFs as
292292 * videos anyway, so we only check on web.
293293 */
294294- if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
294294+ if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
295295 errors.add(SelectedAssetError.FileTooBig)
296296 continue
297297 }
···309309 * base64 data-uri, so we construct it here for web only.
310310 */
311311 uri:
312312- isWeb && asset.base64
312312+ IS_WEB && asset.base64
313313 ? `data:${mimeType};base64,${asset.base64}`
314314 : asset.uri,
315315 })
···328328 }
329329330330 if (supportedAssets[0].duration) {
331331- if (isWeb) {
331331+ if (IS_WEB) {
332332 /*
333333 * Web reports duration as seconds
334334 */
···433433 )
434434435435 const onPressSelectMedia = useCallback(async () => {
436436- if (isNative) {
436436+ if (IS_NATIVE) {
437437 const [photoAccess, videoAccess] = await Promise.all([
438438 requestPhotoAccessIfNeeded(),
439439 requestVideoAccessIfNeeded(),
···447447 }
448448 }
449449450450- if (isNative && Keyboard.isVisible()) {
450450+ if (IS_NATIVE && Keyboard.isVisible()) {
451451 Keyboard.dismiss()
452452 }
453453
+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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
2221import {Text} from '#/view/com/util/text/Text'
2322import {tokens, useTheme} from '#/alf'
2423import * as Dialog from '#/components/Dialog'
2524import {MediaInsetBorder} from '#/components/MediaInsetBorder'
2525+import {IS_NATIVE} from '#/env'
2626import {type PostAction} from '../state/composer'
2727import {EditImageDialog} from './EditImageDialog'
2828import {ImageAltTextDialog} from './ImageAltTextDialog'
···148148 const enableSquareButtons = useEnableSquareButtons()
149149150150 const onImageEdit = () => {
151151- if (isNative) {
151151+ if (IS_NATIVE) {
152152 cropImage(image).then(next => {
153153 onChange(next)
154154 })
···1414import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api'
1515import PasteInput, {
1616 type PastedFile,
1717- type PasteInputRef, // @ts-expect-error no types when installing from github
1717+ type PasteInputRef,
1818+ // @ts-expect-error no types when installing from github
1919+ // eslint-disable-next-line import-x/no-unresolved
1820} from '@mattermost/react-native-paste-input'
19212022import {POST_IMG_MAX} from '#/lib/constants'
···2325import {cleanError} from '#/lib/strings/errors'
2426import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip'
2527import {useTheme} from '#/lib/ThemeContext'
2626-import {isAndroid, isNative} from '#/platform/detection'
2728import {
2829 type LinkFacetMatch,
2930 suggestLinkCardUri,
3031} from '#/view/com/composer/text-input/text-input-util'
3132import {atoms as a, useAlf} from '#/alf'
3233import {normalizeTextStyles} from '#/alf/typography'
3434+import {IS_ANDROID, IS_NATIVE} from '#/env'
3335import {Autocomplete} from './mobile/Autocomplete'
3436import {type TextInputProps} from './TextInput.types'
3537···226228 /**
227229 * PasteInput doesn't like `lineHeight`, results in jumpiness
228230 */
229229- if (isNative) {
231231+ if (IS_NATIVE) {
230232 style.lineHeight = undefined
231233 }
232234233235 /*
234236 * Android impl of `PasteInput` doesn't support the array syntax for `fontVariant`
235237 */
236236- if (isAndroid) {
238238+ if (IS_ANDROID) {
237239 // @ts-ignore
238240 style.fontVariant = style.fontVariant
239241 ? style.fontVariant.join(' ')
···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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1211import {atoms as a, useTheme, web} from '#/alf'
···1817import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
1918import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
2019import {Text} from '#/components/Typography'
2020+import {IS_WEB} from '#/env'
2121import {SubtitleFilePicker} from './SubtitleFilePicker'
22222323const MAX_NUM_CAPTIONS = 1
···3838 return (
3939 <View style={[a.flex_row, a.my_xs]}>
4040 <Button
4141- label={isWeb ? _(msg`Captions & alt text`) : _(msg`Alt text`)}
4141+ label={IS_WEB ? _(msg`Captions & alt text`) : _(msg`Alt text`)}
4242 accessibilityHint={
4343- isWeb
4343+ IS_WEB
4444 ? _(msg`Opens captions and alt text dialog`)
4545 : _(msg`Opens alt text dialog`)
4646 }
···5353 }}>
5454 <ButtonIcon icon={CCIcon} />
5555 <ButtonText>
5656- {isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
5656+ {IS_WEB ? (
5757+ <Trans>Captions & alt text</Trans>
5858+ ) : (
5959+ <Trans>Alt text</Trans>
6060+ )}
5761 </ButtonText>
5862 </Button>
5963 <Dialog.Outer control={control}>
···135139 </Text>
136140 )}
137141138138- {isWeb && (
142142+ {IS_WEB && (
139143 <>
140144 <View
141145 style={[
···183187 <View style={web([a.flex_row, a.justify_end])}>
184188 <Button
185189 label={_(msg`Done`)}
186186- size={isWeb ? 'small' : 'large'}
190190+ size={IS_WEB ? 'small' : 'large'}
187191 color="primary"
188192 variant="solid"
189193 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()
···1616import {cleanError} from '#/lib/strings/errors'
1717import {colors, gradients, s} from '#/lib/styles'
1818import {useTheme} from '#/lib/ThemeContext'
1919-import {isAndroid, isWeb} from '#/platform/detection'
2019import {useModalControls} from '#/state/modals'
2120import {useAgent, useSession, useSessionApi} from '#/state/session'
2221import {pdsAgent} from '#/state/session/agent'
2322import {atoms as a, useTheme as useNewTheme} from '#/alf'
2423import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
2524import {Text as NewText} from '#/components/Typography'
2525+import {IS_ANDROID, IS_WEB} from '#/env'
2626import {resetToTab} from '../../../Navigation'
2727import {ErrorMessage} from '../util/error/ErrorMessage'
2828import {Text} from '../util/text/Text'
2929import * as Toast from '../util/Toast'
3030import {ScrollView, TextInput} from './util'
31313232-export const snapPoints = isAndroid ? ['90%'] : ['55%']
3232+export const snapPoints = IS_ANDROID ? ['90%'] : ['55%']
33333434export function Component({}: {}) {
3535 const pal = usePalette('default')
···174174 </>
175175 )}
176176177177- <View style={[!isWeb && a.px_xl]}>
177177+ <View style={[!IS_WEB && a.px_xl]}>
178178 <View
179179 style={[
180180 a.w_full,
+6-6
src/view/com/modals/UserAddRemoveLists.tsx
···1414import {cleanError} from '#/lib/strings/errors'
1515import {sanitizeHandle} from '#/lib/strings/handles'
1616import {s} from '#/lib/styles'
1717-import {useTheme} from '#/lib/ThemeContext'
1818-import {isAndroid, isMobileWeb, isWeb} from '#/platform/detection'
1717+import {useTheme} from '#/alf'
1918import {useModalControls} from '#/state/modals'
2019import {
2120 getMembership,
···2524 useListMembershipRemoveMutation,
2625} from '#/state/queries/list-memberships'
2726import {useSession} from '#/state/session'
2727+import {IS_ANDROID, IS_WEB, IS_WEB_MOBILE} from '#/env'
2828import {MyLists} from '../lists/MyLists'
2929import {Button} from '../util/forms/Button'
3030import {Text} from '../util/text/Text'
···5757 }, [closeModal])
58585959 const listStyle = React.useMemo(() => {
6060- if (isMobileWeb) {
6060+ if (IS_WEB_MOBILE) {
6161 return [pal.border, {height: screenHeight / 2}]
6262- } else if (isWeb) {
6262+ } else if (IS_WEB) {
6363 return [pal.border, {height: screenHeight / 1.5}]
6464 }
6565···245245246246const styles = StyleSheet.create({
247247 container: {
248248- paddingHorizontal: isWeb ? 0 : 16,
248248+ paddingHorizontal: IS_WEB ? 0 : 16,
249249 },
250250 btns: {
251251 position: 'relative',
···254254 justifyContent: 'center',
255255 gap: 10,
256256 paddingTop: 10,
257257- paddingBottom: isAndroid ? 10 : 0,
257257+ paddingBottom: IS_ANDROID ? 10 : 0,
258258 borderTopWidth: StyleSheet.hairlineWidth,
259259 },
260260 footerBtn: {
+2-2
src/view/com/pager/PagerHeaderContext.tsx
···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-5
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'
···7271} from '#/components/feeds/PostFeedVideoGridRow'
7372import {TrendingInterstitial} from '#/components/interstitials/Trending'
7473import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos'
7474+import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
7575import {DiscoverFeedLiveEventFeedsAndTrendingBanner} from '#/features/liveEvents/components/DiscoverFeedLiveEventFeedsAndTrendingBanner'
7676import {ComposerPrompt} from '../feeds/ComposerPrompt'
7777import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
···314314 const [feedType, feedUriOrActorDid, feedTab] = feed.split('|')
315315 const {gtMobile} = useBreakpoints()
316316 const {rightNavVisible} = useLayoutBreakpoints()
317317- const areVideoFeedsEnabled = isNative
317317+ const areVideoFeedsEnabled = IS_NATIVE
318318319319 const [hasPressedShowLessUris, setHasPressedShowLessUris] = useState(
320320 () => new Set<string>(),
···514514 for (const page of data.pages) {
515515 for (const slice of page.slices) {
516516 const item = slice.items.find(
517517- // eslint-disable-next-line @typescript-eslint/no-shadow
518517 item => item.uri === slice.feedPostUri,
519518 )
520519 if (
···986985 * reach the end, so that content isn't cut off by the bottom of the
987986 * screen.
988987 */
989989- const offset = Math.max(headerOffset, 32) * (isWeb ? 1 : 2)
988988+ const offset = Math.max(headerOffset, 32) * (IS_WEB ? 1 : 2)
990989991990 return isFetchingNextPage ? (
992991 <View style={[styles.feedFooter]}>
···11371136 }
11381137 initialNumToRender={initialNumToRenderOverride ?? initialNumToRender}
11391138 windowSize={9}
11401140- maxToRenderPerBatch={isIOS ? 5 : 1}
11391139+ maxToRenderPerBatch={IS_IOS ? 5 : 1}
11411140 updateCellsBatchingPeriod={40}
11421141 onItemSeen={onItemSeen}
11431142 />
+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')
···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')
+4-4
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···6060 return (
6161 <View
6262 style={[
6363- isAndroid ? a.flex_1 : a.flex_shrink,
6363+ IS_ANDROID ? a.flex_1 : a.flex_shrink,
6464 a.flex_row,
6565 a.align_center,
6666 a.pb_xs,
···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,
+7-8
src/view/com/util/UserAvatar.tsx
···1616import {useQueryClient} from '@tanstack/react-query'
17171818import {useActorStatus} from '#/lib/actor-status'
1919-import {isTouchDevice} from '#/lib/browser'
2019import {useHaptics} from '#/lib/haptics'
2120import {
2221 useCameraPermission,
···3029import {isCancelledError} from '#/lib/strings/errors'
3130import {sanitizeHandle} from '#/lib/strings/handles'
3231import {logger} from '#/logger'
3333-import {isAndroid, isNative, isWeb} from '#/platform/detection'
3432import {
3533 type ComposerImage,
3634 compressImage,
···5957import {MediaInsetBorder} from '#/components/MediaInsetBorder'
6058import * as Menu from '#/components/Menu'
6159import {ProfileHoverCard} from '#/components/ProfileHoverCard'
6060+import {IS_ANDROID, IS_NATIVE, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
6261import type * as bsky from '#/types/bsky'
63626463export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler'
···9392 onBeforePress?: () => void
9493}
95949696-const BLUR_AMOUNT = isWeb ? 5 : 100
9595+const BLUR_AMOUNT = IS_WEB ? 5 : 100
97969897let DefaultAvatar = ({
9998 type,
···311310 }, [size, style])
312311313312 return avatar &&
314314- !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
313313+ !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? (
315314 <View style={containerStyle}>
316315 {usePlainRNImage ? (
317316 <RNImage
···428427 }
429428430429 try {
431431- if (isNative) {
430430+ if (IS_NATIVE) {
432431 onSelectNewAvatar(
433432 await compressIfNeeded(
434433 await openCropper({
···500499 </Menu.Trigger>
501500 <Menu.Outer showCancel>
502501 <Menu.Group>
503503- {isNative && (
502502+ {IS_NATIVE && (
504503 <Menu.Item
505504 testID="changeAvatarCameraBtn"
506505 label={_(msg`Upload from Camera`)}
···517516 label={_(msg`Upload from Library`)}
518517 onPress={onOpenLibrary}>
519518 <Menu.ItemText>
520520- {isNative ? (
519519+ {IS_NATIVE ? (
521520 <Trans>Upload from Library</Trans>
522521 ) : (
523522 <Trans>Upload from Files</Trans>
···607606 <ProfileHoverCard did={profile.did} disable={disableHoverCard}>
608607 {disableNavigation ? (
609608 avatarEl
610610- ) : status.isActive && (isNative || isTouchDevice) ? (
609609+ ) : status.isActive && (IS_NATIVE || IS_WEB_TOUCH_DEVICE) ? (
611610 <>
612611 <Button
613612 label={_(
+5-5
src/view/com/util/UserBanner.tsx
···1414import {type PickerImage} from '#/lib/media/picker.shared'
1515import {isCancelledError} from '#/lib/strings/errors'
1616import {logger} from '#/logger'
1717-import {isAndroid, isNative} from '#/platform/detection'
1817import {
1918 type ComposerImage,
2019 compressImage,
···3635import {StreamingLive_Stroke2_Corner0_Rounded as LibraryIcon} from '#/components/icons/StreamingLive'
3736import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
3837import * as Menu from '#/components/Menu'
3838+import {IS_ANDROID, IS_NATIVE} from '#/env'
39394040export function UserBanner({
4141 type,
···8080 }
81818282 try {
8383- if (isNative) {
8383+ if (IS_NATIVE) {
8484 onSelectNewBanner?.(
8585 await compressIfNeeded(
8686 await openCropper({
···163163 </Menu.Trigger>
164164 <Menu.Outer showCancel>
165165 <Menu.Group>
166166- {isNative && (
166166+ {IS_NATIVE && (
167167 <Menu.Item
168168 testID="changeBannerCameraBtn"
169169 label={_(msg`Upload from Camera`)}
···180180 label={_(msg`Upload from Library`)}
181181 onPress={onOpenLibrary}>
182182 <Menu.ItemText>
183183- {isNative ? (
183183+ {IS_NATIVE ? (
184184 <Trans>Upload from Library</Trans>
185185 ) : (
186186 <Trans>Upload from Files</Trans>
···217217 />
218218 </>
219219 ) : banner &&
220220- !((moderation?.blur && isAndroid) /* android crashes with blur */) ? (
220220+ !((moderation?.blur && IS_ANDROID) /* android crashes with blur */) ? (
221221 <Image
222222 testID="userBannerImage"
223223 style={[styles.bannerImage, t.atoms.bg_contrast_25]}
+2-2
src/view/com/util/ViewSelector.tsx
···1313import {usePalette} from '#/lib/hooks/usePalette'
1414import {clamp} from '#/lib/numbers'
1515import {colors, s} from '#/lib/styles'
1616-import {isAndroid} from '#/platform/detection'
1616+import {IS_ANDROID} from '#/env'
1717import {Text} from './text/Text'
1818import {FlatList_INTERNAL} from './Views'
1919···120120 renderItem={renderItemInternal}
121121 ListFooterComponent={ListFooterComponent}
122122 // NOTE sticky header disabled on android due to major performance issues -prf
123123- stickyHeaderIndices={isAndroid ? undefined : STICKY_HEADER_INDICES}
123123+ stickyHeaderIndices={IS_ANDROID ? undefined : STICKY_HEADER_INDICES}
124124 onScroll={onScroll}
125125 onEndReached={onEndReached}
126126 refreshControl={
+2-2
src/view/com/util/fab/FABInner.tsx
···1212import {useHaptics} from '#/lib/haptics'
1313import {useMinimalShellFabTransform} from '#/lib/hooks/useMinimalShellTransform'
1414import {clamp} from '#/lib/numbers'
1515-import {isWeb} from '#/platform/detection'
1615import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
1716import {ios, useBreakpoints, useTheme} from '#/alf'
1817import {atoms as a} from '#/alf'
1818+import {IS_WEB} from '#/env'
19192020export interface FABProps extends ComponentProps<typeof Pressable> {
2121 testID?: string
···9797 },
9898 outer: {
9999 // @ts-ignore web-only
100100- position: isWeb ? 'fixed' : 'absolute',
100100+ position: IS_WEB ? 'fixed' : 'absolute',
101101 zIndex: 1,
102102 cursor: 'pointer',
103103 },
···55import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
66import {usePalette} from '#/lib/hooks/usePalette'
77import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
88-import {isWeb} from '#/platform/detection'
98import {atoms as a} from '#/alf'
99+import {IS_WEB} from '#/env'
1010import {Text} from '../text/Text'
11111212export const LoggedOutLayout = ({
···7979 contentContainerStyle={styles.scrollViewContentContainer}
8080 keyboardShouldPersistTaps="handled"
8181 keyboardDismissMode="on-drag">
8282- <View style={[styles.contentWrapper, isWeb && a.my_auto]}>
8282+ <View style={[styles.contentWrapper, IS_WEB && a.my_auto]}>
8383 {children}
8484 </View>
8585 </ScrollView>
+4-3
src/view/com/util/text/Text.tsx
···55import {lh, s} from '#/lib/styles'
66import {type TypographyVariant, useTheme} from '#/lib/ThemeContext'
77import {logger} from '#/logger'
88-import {isIOS, isWeb} from '#/platform/detection'
98import {applyFonts, useAlf} from '#/alf'
109import {
1110 childHasEmoji,
1211 renderChildrenWithEmoji,
1312 type StringChild,
1413} from '#/alf/typography'
1414+import {IS_IOS, IS_WEB} from '#/env'
15151616export type CustomTextProps = Omit<TextProps, 'children'> & {
1717 type?: TypographyVariant
···5151 if (__DEV__) {
5252 if (!emoji && childHasEmoji(children)) {
5353 logger.warn(
5454+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-base-to-string
5455 `Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
5556 )
5657 }
···8081 }
81828283 return {
8383- uiTextView: selectable && isIOS,
8484+ uiTextView: selectable && IS_IOS,
8485 selectable,
8586 style: flattened,
8686- dataSet: isWeb
8787+ dataSet: IS_WEB
8788 ? Object.assign({tooltip: title}, dataSet || {})
8889 : undefined,
8990 ...props,
+4-4
src/view/screens/Feeds.tsx
···1616} from '#/lib/routes/types'
1717import {cleanError} from '#/lib/strings/errors'
1818import {s} from '#/lib/styles'
1919-import {isNative, isWeb} from '#/platform/detection'
2019import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
2120import {
2221 type SavedFeedItem,
···4746import * as Layout from '#/components/Layout'
4847import {Link} from '#/components/Link'
4948import * as ListCard from '#/components/ListCard'
4949+import {IS_NATIVE, IS_WEB} from '#/env'
50505151type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
5252···388388 const onChangeSearchFocus = React.useCallback(
389389 (focus: boolean) => {
390390 if (focus && searchBarIndex > -1) {
391391- if (isNative) {
391391+ if (IS_NATIVE) {
392392 // scrollToIndex scrolls the exact right amount, so use if available
393393 listRef.current?.scrollToIndex({
394394 index: searchBarIndex,
···684684 return (
685685 <View
686686 style={
687687- isWeb
687687+ IS_WEB
688688 ? [
689689 a.flex_row,
690690 a.px_md,
···720720 return (
721721 <View
722722 style={
723723- isWeb
723723+ IS_WEB
724724 ? [a.flex_row, a.px_md, a.pt_lg, a.pb_lg, a.gap_md]
725725 : [{flexDirection: 'row-reverse'}, a.p_lg, a.gap_md]
726726 }>
+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 {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
2019import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
···4039import * as Layout from '#/components/Layout'
4140import {InlineLinkText, Link} from '#/components/Link'
4241import {Loader} from '#/components/Loader'
4242+import {IS_NATIVE} from '#/env'
43434444// We don't currently persist this across reloads since
4545// you gotta visit All to clear the badge anyway.
···200200 // event handlers
201201 // =
202202 const scrollToTop = useCallback(() => {
203203- scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
203203+ scrollElRef.current?.scrollToOffset({animated: IS_NATIVE, offset: 0})
204204 setMinimalShellMode(false)
205205 }, [scrollElRef, setMinimalShellMode])
206206···230230 // on focus, check for latest, but only invalidate if the user
231231 // isnt scrolled down to avoid moving content underneath them
232232 let currentIsScrolledDown
233233- if (isNative) {
233233+ if (IS_NATIVE) {
234234 currentIsScrolledDown = isScrolledDown
235235 } else {
236236 // 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 {useDisableFollowersMetrics} from '#/state/preferences/disable-followers-metrics'
1918import {useDisableFollowingMetrics} from '#/state/preferences/disable-following-metrics'
···5857import {Text} from '#/components/Typography'
5958import {useSimpleVerificationState} from '#/components/verification'
6059import {VerificationCheck} from '#/components/verification/VerificationCheck'
6060+import {IS_WEB} from '#/env'
61616262const iconWidth = 26
6363···183183 (tab: 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile') => {
184184 const state = navigation.getState()
185185 setDrawerOpen(false)
186186- if (isWeb) {
186186+ if (IS_WEB) {
187187 // hack because we have flat navigator for web and MyProfile does not exist on the web navigator -ansh
188188 if (tab === 'MyProfile') {
189189 navigation.navigate('Profile', {name: currentAccount!.handle})