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

* Add platform vars to env

* Replace /platform/detection with /env
This commit is contained in:
Eric Bailey
2026-01-16 16:28:17 -06:00
committed by GitHub
parent bd510d8468
commit 0589bd7d9e
244 changed files with 789 additions and 789 deletions
+19 -19
View File
@@ -76,7 +76,6 @@ import {logEvent} from '#/lib/statsig/statsig'
import {cleanError} from '#/lib/strings/errors'
import {colors} from '#/lib/styles'
import {logger} from '#/logger'
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
import {useDialogStateControlContext} from '#/state/dialogs'
import {emitPostCreated} from '#/state/events'
import {
@@ -130,6 +129,7 @@ import {LazyQuoteEmbed} from '#/components/Post/Embed/LazyQuoteEmbed'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text as NewText} from '#/components/Typography'
import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env'
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
import {PostLanguageSelect} from './select-language/PostLanguageSelect'
import {
@@ -329,14 +329,14 @@ export const ComposePost = ({
const insets = useSafeAreaInsets()
const viewStyles = useMemo(
() => ({
paddingTop: isAndroid ? insets.top : 0,
paddingTop: IS_ANDROID ? insets.top : 0,
paddingBottom:
// iOS - when keyboard is closed, keep the bottom bar in the safe area
(isIOS && !isKeyboardVisible) ||
(IS_IOS && !isKeyboardVisible) ||
// Android - Android >=35 KeyboardAvoidingView adds double padding when
// keyboard is closed, so we subtract that in the offset and add it back
// here when the keyboard is open
(isAndroid && isKeyboardVisible)
(IS_ANDROID && isKeyboardVisible)
? insets.bottom
: 0,
}),
@@ -366,7 +366,7 @@ export const ComposePost = ({
// On Android, pressing Back should ask confirmation.
useEffect(() => {
if (!isAndroid) {
if (!IS_ANDROID) {
return
}
const backHandler = BackHandler.addEventListener(
@@ -671,7 +671,7 @@ export const ComposePost = ({
composerState.mutableNeedsFocusActive = false
// On Android, this risks getting the cursor stuck behind the keyboard.
// Not worth it.
if (!isAndroid) {
if (!IS_ANDROID) {
textInput.current?.focus()
}
}
@@ -727,12 +727,12 @@ export const ComposePost = ({
</>
)
const isWebFooterSticky = !isNative && thread.posts.length > 1
const IS_WEBFooterSticky = !IS_NATIVE && thread.posts.length > 1
return (
<BottomSheetPortalProvider>
<KeyboardAvoidingView
testID="composePostView"
behavior={isIOS ? 'padding' : 'height'}
behavior={IS_IOS ? 'padding' : 'height'}
keyboardVerticalOffset={keyboardVerticalOffset}
style={a.flex_1}>
<View
@@ -790,13 +790,13 @@ export const ComposePost = ({
onPublish={onComposerPostPublish}
onError={setError}
/>
{isWebFooterSticky && post.id === activePost.id && (
{IS_WEBFooterSticky && post.id === activePost.id && (
<View style={styles.stickyFooterWeb}>{footer}</View>
)}
</React.Fragment>
))}
</Animated.ScrollView>
{!isWebFooterSticky && footer}
{!IS_WEBFooterSticky && footer}
</View>
<Prompt.Basic
@@ -849,7 +849,7 @@ let ComposerPost = React.memo(function ComposerPost({
const {data: currentProfile} = useProfileQuery({did: currentDid})
const richtext = post.richtext
const isTextOnly = !post.embed.link && !post.embed.quote && !post.embed.media
const forceMinHeight = isWeb && isTextOnly && isActive
const forceMinHeight = IS_WEB && isTextOnly && isActive
const selectTextInputPlaceholder = isReply
? isFirstPost
? _(msg`Write your reply`)
@@ -889,9 +889,9 @@ let ComposerPost = React.memo(function ComposerPost({
async (uri: string) => {
if (
uri.startsWith('data:video/') ||
(isWeb && uri.startsWith('data:image/gif'))
(IS_WEB && uri.startsWith('data:image/gif'))
) {
if (isNative) return // web only
if (IS_NATIVE) return // web only
const [mimeType] = uri.slice('data:'.length).split(';')
if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
Toast.show(_(msg`Unsupported video type: ${mimeType}`), {
@@ -921,9 +921,9 @@ let ComposerPost = React.memo(function ComposerPost({
a.mb_sm,
!isActive && isLastPost && a.mb_lg,
!isActive && styles.inactivePost,
isTextOnly && isNative && a.flex_grow,
isTextOnly && IS_NATIVE && a.flex_grow,
]}>
<View style={[a.flex_row, isNative && a.flex_1]}>
<View style={[a.flex_row, IS_NATIVE && a.flex_1]}>
<UserAvatar
avatar={currentProfile?.avatar}
size={42}
@@ -1241,7 +1241,7 @@ function ComposerEmbeds({
</LayoutAnimationConfig>
{embed.quote?.uri ? (
<View
style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], isWeb && [a.pb_md]]}>
style={[a.pb_sm, video ? [a.pt_md] : [a.pt_xl], IS_WEB && [a.pb_md]]}>
<View style={[a.relative]}>
<View style={{pointerEvents: 'none'}}>
<LazyQuoteEmbed uri={embed.quote.uri} />
@@ -1652,7 +1652,7 @@ function useKeyboardVerticalOffset() {
const {top, bottom} = useSafeAreaInsets()
// Android etc
if (!isIOS) {
if (!IS_IOS) {
// need to account for the edge-to-edge nav bar
return bottom * -1
}
@@ -1696,7 +1696,7 @@ function useHideKeyboardOnBackground() {
const appState = useAppState()
useEffect(() => {
if (isIOS) {
if (IS_IOS) {
if (appState === 'inactive') {
Keyboard.dismiss()
}
@@ -1846,7 +1846,7 @@ function ToolbarWrapper({
style: StyleProp<ViewStyle>
children: React.ReactNode
}) {
if (isWeb) return children
if (IS_WEB) return children
return (
<Animated.View
style={style}
+2 -2
View File
@@ -9,7 +9,6 @@ import {
type EmbedPlayerParams,
parseEmbedPlayerFromUrl,
} from '#/lib/strings/embed-player'
import {isAndroid} from '#/platform/detection'
import {useResolveGifQuery} from '#/state/queries/resolve-link'
import {type Gif} from '#/state/queries/tenor'
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
@@ -23,6 +22,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico
import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif'
import {Text} from '#/components/Typography'
import {IS_ANDROID} from '#/env'
import {AltTextReminder} from './photos/Gallery'
export function GifAltTextDialog({
@@ -224,7 +224,7 @@ function AltTextInner({
</View>
<Dialog.Close />
{/* Maybe fix this later -h */}
{isAndroid ? <View style={{height: 300}} /> : null}
{IS_ANDROID ? <View style={{height: 300}} /> : null}
</Dialog.ScrollableInner>
)
}
+2 -2
View File
@@ -3,8 +3,8 @@ import {KeyboardStickyView} from 'react-native-keyboard-controller'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import type React from 'react'
import {isWeb} from '#/platform/detection'
import {atoms as a, useTheme} from '#/alf'
import {IS_WEB} from '#/env'
export function KeyboardAccessory({children}: {children: React.ReactNode}) {
const t = useTheme()
@@ -22,7 +22,7 @@ export function KeyboardAccessory({children}: {children: React.ReactNode}) {
]
// todo: when iPad support is added, it should also not use the KeyboardStickyView
if (isWeb) {
if (IS_WEB) {
return <View style={style}>{children}</View>
}
+9 -9
View File
@@ -11,13 +11,13 @@ import {
} from '#/lib/hooks/usePermissions'
import {openUnifiedPicker} from '#/lib/media/picker'
import {extractDataUriMime} from '#/lib/media/util'
import {isNative, isWeb} from '#/platform/detection'
import {MAX_IMAGES} from '#/view/com/composer/state/composer'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
import * as toast from '#/components/Toast'
import {IS_NATIVE, IS_WEB} from '#/env'
export type SelectMediaButtonProps = {
disabled?: boolean
@@ -91,7 +91,7 @@ const SUPPORTED_IMAGE_MIME_TYPES = (
'image/svg+xml',
'image/webp',
'image/avif',
isNative && 'image/heic',
IS_NATIVE && 'image/heic',
] as const
).filter(Boolean)
type SupportedImageMimeType = Exclude<
@@ -261,7 +261,7 @@ async function processImagePickerAssets(
* We don't care too much about mimeType at this point on native,
* since the `processVideo` step later on will convert to `.mp4`.
*/
if (isWeb && !isSupportedVideoMimeType(mimeType)) {
if (IS_WEB && !isSupportedVideoMimeType(mimeType)) {
errors.add(SelectedAssetError.Unsupported)
continue
}
@@ -271,7 +271,7 @@ async function processImagePickerAssets(
* to filter out large files on web. On native, we compress these anyway,
* so we only check on web.
*/
if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
errors.add(SelectedAssetError.FileTooBig)
continue
}
@@ -290,7 +290,7 @@ async function processImagePickerAssets(
* to filter out large files on web. On native, we compress GIFs as
* videos anyway, so we only check on web.
*/
if (isWeb && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
if (IS_WEB && asset.fileSize && asset.fileSize > VIDEO_MAX_SIZE) {
errors.add(SelectedAssetError.FileTooBig)
continue
}
@@ -308,7 +308,7 @@ async function processImagePickerAssets(
* base64 data-uri, so we construct it here for web only.
*/
uri:
isWeb && asset.base64
IS_WEB && asset.base64
? `data:${mimeType};base64,${asset.base64}`
: asset.uri,
})
@@ -327,7 +327,7 @@ async function processImagePickerAssets(
}
if (supportedAssets[0].duration) {
if (isWeb) {
if (IS_WEB) {
/*
* Web reports duration as seconds
*/
@@ -432,7 +432,7 @@ export function SelectMediaButton({
)
const onPressSelectMedia = useCallback(async () => {
if (isNative) {
if (IS_NATIVE) {
const [photoAccess, videoAccess] = await Promise.all([
requestPhotoAccessIfNeeded(),
requestVideoAccessIfNeeded(),
@@ -446,7 +446,7 @@ export function SelectMediaButton({
}
}
if (isNative && Keyboard.isVisible()) {
if (IS_NATIVE && Keyboard.isVisible()) {
Keyboard.dismiss()
}
+2 -2
View File
@@ -9,7 +9,6 @@ import {
type OtherSelfLabel,
type SelfLabel,
} from '#/lib/moderation'
import {isWeb} from '#/platform/detection'
import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@@ -18,6 +17,7 @@ import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {TinyChevronBottom_Stroke2_Corner0_Rounded as TinyChevronIcon} from '#/components/icons/Chevron'
import {Shield_Stroke2_Corner0_Rounded} from '#/components/icons/Shield'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
export function LabelsBtn({
labels,
@@ -218,7 +218,7 @@ function DialogInner({
label={_(msg`Done`)}
onPress={() => control.close()}
color="primary"
size={isWeb ? 'small' : 'large'}
size={IS_WEB ? 'small' : 'large'}
variant="solid"
testID="confirmBtn">
<ButtonText>
+2 -2
View File
@@ -16,12 +16,12 @@ import {useLingui} from '@lingui/react'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {type Dimensions} from '#/lib/media/types'
import {colors, s} from '#/lib/styles'
import {isNative} from '#/platform/detection'
import {type ComposerImage, cropImage} from '#/state/gallery'
import {Text} from '#/view/com/util/text/Text'
import {tokens, useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {IS_NATIVE} from '#/env'
import {type PostAction} from '../state/composer'
import {EditImageDialog} from './EditImageDialog'
import {ImageAltTextDialog} from './ImageAltTextDialog'
@@ -145,7 +145,7 @@ const GalleryItem = ({
const editControl = Dialog.useDialogControl()
const onImageEdit = () => {
if (isNative) {
if (IS_NATIVE) {
cropImage(image).then(next => {
onChange(next)
})
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {MAX_ALT_TEXT} from '#/lib/constants'
import {enforceLen} from '#/lib/strings/helpers'
import {isAndroid, isWeb} from '#/platform/detection'
import {type ComposerImage} from '#/state/gallery'
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
import {atoms as a, useTheme} from '#/alf'
@@ -16,6 +15,7 @@ import {type DialogControlProps} from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Text} from '#/components/Typography'
import {IS_ANDROID, IS_WEB} from '#/env'
type Props = {
control: Dialog.DialogOuterProps['control']
@@ -66,7 +66,7 @@ const ImageAltTextInner = ({
const windim = useWindowDimensions()
const imageStyle = React.useMemo<ImageStyle>(() => {
const maxWidth = isWeb ? 450 : windim.width
const maxWidth = IS_WEB ? 450 : windim.width
const source = image.transformed ?? image.source
if (source.height > source.width) {
@@ -165,7 +165,7 @@ const ImageAltTextInner = ({
</AltTextCounterWrapper>
</View>
{/* Maybe fix this later -h */}
{isAndroid ? <View style={{height: 300}} /> : null}
{IS_ANDROID ? <View style={{height: 300}} /> : null}
</Dialog.ScrollableInner>
)
}
@@ -7,11 +7,11 @@ import {POST_IMG_MAX} from '#/lib/constants'
import {useCameraPermission} from '#/lib/hooks/usePermissions'
import {openCamera} from '#/lib/media/picker'
import {logger} from '#/logger'
import {isMobileWeb, isNative} from '#/platform/detection'
import {type ComposerImage, createComposerImage} from '#/state/gallery'
import {atoms as a, useTheme} from '#/alf'
import {Button} from '#/components/Button'
import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera'
import {IS_NATIVE, IS_WEB_MOBILE} from '#/env'
type Props = {
disabled?: boolean
@@ -58,7 +58,7 @@ export function OpenCameraBtn({disabled, onAdd}: Props) {
requestMediaPermission,
])
const shouldShowCameraButton = isNative || isMobileWeb
const shouldShowCameraButton = IS_NATIVE || IS_WEB_MOBILE
if (!shouldShowCameraButton) {
return null
}
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {languageName} from '#/locale/helpers'
import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages'
import {isNative, isWeb} from '#/platform/detection'
import {
toPostLanguages,
useLanguagePrefs,
@@ -21,6 +20,7 @@ import {SearchInput} from '#/components/forms/SearchInput'
import * as Toggle from '#/components/forms/Toggle'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env'
export function PostLanguageSelectDialog({
control,
@@ -168,7 +168,7 @@ export function DialogInner({
const listHeader = (
<View
style={[a.pb_xs, t.atoms.bg, isNative && a.pt_2xl]}
style={[a.pb_xs, t.atoms.bg, IS_NATIVE && a.pt_2xl]}
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}>
<View style={[a.flex_row, a.w_full, a.justify_between]}>
<View>
@@ -195,7 +195,7 @@ export function DialogInner({
</Text>
</View>
{isWeb && (
{IS_WEB && (
<Button
variant="ghost"
size="small"
@@ -252,7 +252,7 @@ export function DialogInner({
ListHeaderComponent={listHeader}
stickyHeaderIndices={[0]}
contentContainerStyle={[a.gap_0]}
style={[isNative && a.px_lg, web({paddingBottom: 120})]}
style={[IS_NATIVE && a.px_lg, web({paddingBottom: 120})]}
scrollIndicatorInsets={{top: headerHeight}}
renderItem={({item, index}) => {
if (item.type === 'header') {
@@ -25,13 +25,13 @@ import {isUriImage} from '#/lib/media/util'
import {cleanError} from '#/lib/strings/errors'
import {getMentionAt, insertMentionAt} from '#/lib/strings/mention-manip'
import {useTheme} from '#/lib/ThemeContext'
import {isAndroid, isNative} from '#/platform/detection'
import {
type LinkFacetMatch,
suggestLinkCardUri,
} from '#/view/com/composer/text-input/text-input-util'
import {atoms as a, useAlf} from '#/alf'
import {normalizeTextStyles} from '#/alf/typography'
import {IS_ANDROID, IS_NATIVE} from '#/env'
import {Autocomplete} from './mobile/Autocomplete'
import {type TextInputProps} from './TextInput.types'
@@ -179,14 +179,14 @@ export function TextInput({
/**
* PasteInput doesn't like `lineHeight`, results in jumpiness
*/
if (isNative) {
if (IS_NATIVE) {
style.lineHeight = undefined
}
/*
* Android impl of `PasteInput` doesn't support the array syntax for `fontVariant`
*/
if (isAndroid) {
if (IS_ANDROID) {
// @ts-ignore
style.fontVariant = style.fontVariant
? style.fontVariant.join(' ')
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal'
import {isNetworkError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {isNative} from '#/platform/detection'
import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings'
import {createPostgateRecord} from '#/state/queries/postgate/util'
import {usePreferencesQuery} from '#/state/queries/preferences'
@@ -25,6 +24,7 @@ import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Glo
import {Group3_Stroke2_Corner0_Rounded as GroupIcon} from '#/components/icons/Group'
import * as Tooltip from '#/components/Tooltip'
import {Text} from '#/components/Typography'
import {IS_NATIVE} from '#/env'
import {useThreadgateNudged} from '#/storage/hooks/threadgate-nudged'
export function ThreadgateBtn({
@@ -70,7 +70,7 @@ export function ThreadgateBtn({
nudged: tooltipWasShown,
})
if (isNative && Keyboard.isVisible()) {
if (IS_NATIVE && Keyboard.isVisible()) {
Keyboard.dismiss()
}
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
import {MAX_ALT_TEXT} from '#/lib/constants'
import {isOverMaxGraphemeCount} from '#/lib/strings/helpers'
import {LANGUAGES} from '#/locale/languages'
import {isWeb} from '#/platform/detection'
import {useLanguagePrefs} from '#/state/preferences'
import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -17,6 +16,7 @@ import {PageText_Stroke2_Corner0_Rounded as PageTextIcon} from '#/components/ico
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import {SubtitleFilePicker} from './SubtitleFilePicker'
const MAX_NUM_CAPTIONS = 1
@@ -37,9 +37,9 @@ export function SubtitleDialogBtn(props: Props) {
return (
<View style={[a.flex_row, a.my_xs]}>
<Button
label={isWeb ? _(msg`Captions & alt text`) : _(msg`Alt text`)}
label={IS_WEB ? _(msg`Captions & alt text`) : _(msg`Alt text`)}
accessibilityHint={
isWeb
IS_WEB
? _(msg`Opens captions and alt text dialog`)
: _(msg`Opens alt text dialog`)
}
@@ -52,7 +52,11 @@ export function SubtitleDialogBtn(props: Props) {
}}>
<ButtonIcon icon={CCIcon} />
<ButtonText>
{isWeb ? <Trans>Captions & alt text</Trans> : <Trans>Alt text</Trans>}
{IS_WEB ? (
<Trans>Captions & alt text</Trans>
) : (
<Trans>Alt text</Trans>
)}
</ButtonText>
</Button>
<Dialog.Outer control={control}>
@@ -134,7 +138,7 @@ function SubtitleDialogInner({
</Text>
)}
{isWeb && (
{IS_WEB && (
<>
<View
style={[
@@ -182,7 +186,7 @@ function SubtitleDialogInner({
<View style={web([a.flex_row, a.justify_end])}>
<Button
label={_(msg`Done`)}
size={isWeb ? 'small' : 'large'}
size={IS_WEB ? 'small' : 'large'}
color="primary"
variant="solid"
onPress={() => {
@@ -4,8 +4,8 @@ import ProgressPie from 'react-native-progress/Pie'
import {type ImagePickerAsset} from 'expo-image-picker'
import {clamp} from '#/lib/numbers'
import {isWeb} from '#/platform/detection'
import {atoms as a, useTheme} from '#/alf'
import {IS_WEB} from '#/env'
import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn'
import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'
@@ -20,7 +20,7 @@ export function VideoTranscodeProgress({
}) {
const t = useTheme()
if (isWeb) return null
if (IS_WEB) return null
let aspectRatio = asset.width / asset.height