Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-02-24 06:34:52 +00:00
committed by GitHub
parent 9b0f0a8d24
commit 73b096e443
23 changed files with 90 additions and 71 deletions
-6
View File
@@ -1,6 +0,0 @@
export default {
requestPermission: jest.fn(),
onForegroundEvent: jest.fn(),
setBadgeCount: jest.fn(),
displayNotification: jest.fn(),
}
@@ -1,9 +0,0 @@
export const CameraRoll = {
getPhotos: jest.fn().mockResolvedValue({
edges: [
{node: {image: {uri: 'path/to/image1.jpg'}}},
{node: {image: {uri: 'path/to/image2.jpg'}}},
{node: {image: {uri: 'path/to/image3.jpg'}}},
],
}),
}
@@ -1,4 +0,0 @@
export default {
configure: jest.fn().mockResolvedValue(0),
finish: jest.fn(),
}
-1
View File
@@ -1 +0,0 @@
export default {}
-10
View File
@@ -1,10 +0,0 @@
jest.mock('rn-fetch-blob', () => {
return {
__esModule: true,
default: {
fs: {
unlink: jest.fn(),
},
},
}
})
-2
View File
@@ -1,2 +0,0 @@
export const DropdownMenu = jest.fn().mockImplementation(() => {})
export const create = jest.fn().mockImplementation(() => {})
-1
View File
@@ -112,7 +112,6 @@ module.exports = function (_config) {
'zh-Hans', 'zh-Hans',
'zh-Hant', 'zh-Hant',
], ],
UIDesignRequiresCompatibility: true,
}, },
associatedDomains: ASSOCIATED_DOMAINS, associatedDomains: ASSOCIATED_DOMAINS,
entitlements: { entitlements: {
@@ -27,6 +27,19 @@ class SheetViewController: UIViewController {
return return
} }
// On iOS 26, the floaty sheet presentation adds the device bottom safe area
// on top of the custom detent value, creating visible padding inside the pill.
// Subtract it so the pill height matches our actual content.
var bottomSafeAreaAdjustment: CGFloat = 0
if #available(iOS 26.0, *) {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first {
bottomSafeAreaAdjustment = window.safeAreaInsets.bottom
}
}
let adjustedHeight = contentHeight - bottomSafeAreaAdjustment
if #available(iOS 16.0, *) { if #available(iOS 16.0, *) {
if contentHeight > screenHeight - 100 { if contentHeight > screenHeight - 100 {
sheet.detents = [ sheet.detents = [
@@ -36,7 +49,7 @@ class SheetViewController: UIViewController {
} else { } else {
sheet.detents = [ sheet.detents = [
.custom { _ in .custom { _ in
return contentHeight return adjustedHeight
} }
] ]
if !preventExpansion { if !preventExpansion {
@@ -5,6 +5,7 @@ import {
type NativeSyntheticEvent, type NativeSyntheticEvent,
Platform, Platform,
type StyleProp, type StyleProp,
useWindowDimensions,
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
@@ -21,8 +22,6 @@ import {
Context as PortalContext, Context as PortalContext,
} from './BottomSheetPortal' } from './BottomSheetPortal'
const screenHeight = Dimensions.get('screen').height
const NativeView: React.ComponentType< const NativeView: React.ComponentType<
BottomSheetViewProps & { BottomSheetViewProps & {
ref: React.RefObject<any> ref: React.RefObject<any>
@@ -94,6 +93,7 @@ export class BottomSheetNativeComponent extends React.Component<
let extraStyles let extraStyles
if (IS_IOS15 && this.state.viewHeight) { if (IS_IOS15 && this.state.viewHeight) {
const screenHeight = Dimensions.get('screen').height
const {viewHeight} = this.state const {viewHeight} = this.state
const cornerRadius = this.props.cornerRadius ?? 0 const cornerRadius = this.props.cornerRadius ?? 0
if (viewHeight < screenHeight / 2) { if (viewHeight < screenHeight / 2) {
@@ -154,6 +154,7 @@ function BottomSheetNativeComponentInner({
}) { }) {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const cornerRadius = rest.cornerRadius ?? 0 const cornerRadius = rest.cornerRadius ?? 0
const {height: screenHeight} = useWindowDimensions()
const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight
+7
View File
@@ -16,6 +16,13 @@
"expo-image-picker" "expo-image-picker"
] ]
} }
},
"install": {
"exclude": [
"react-native-reanimated",
"@sentry/react-native",
"react-native-pager-view"
]
} }
}, },
"scripts": { "scripts": {
+4
View File
@@ -10,6 +10,10 @@ const EXP_CURVE = 'cubic-bezier(0.16, 1, 0.3, 1)'
export const atoms = { export const atoms = {
...baseAtoms, ...baseAtoms,
rounded_sheet: {
borderRadius: 40,
},
h_full_vh: web({ h_full_vh: web({
height: '100vh', height: '100vh',
}), }),
+11 -7
View File
@@ -38,7 +38,7 @@ import {
type DialogOuterProps, type DialogOuterProps,
} from '#/components/Dialog/types' } from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField' import {createInput} from '#/components/forms/TextField'
import {IS_ANDROID, IS_IOS} from '#/env' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env'
import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet' import {BottomSheet, BottomSheetSnapPoint} from '../../../modules/bottom-sheet'
import { import {
type BottomSheetSnapPointChangeEvent, type BottomSheetSnapPointChangeEvent,
@@ -166,7 +166,8 @@ export function Outer({
return ( return (
<BottomSheet <BottomSheet
ref={ref} ref={ref}
cornerRadius={20} // device-bezel radius when undefined
cornerRadius={IS_LIQUID_GLASS ? undefined : 20}
backgroundColor={t.atoms.bg.backgroundColor} backgroundColor={t.atoms.bg.backgroundColor}
{...nativeOptions} {...nativeOptions}
onSnapPointChange={onSnapPointChange} onSnapPointChange={onSnapPointChange}
@@ -181,6 +182,9 @@ export function Outer({
) )
} }
/**
* @deprecated use `Dialog.ScrollableInner` instead
*/
export function Inner({children, style, header}: DialogInnerProps) { export function Inner({children, style, header}: DialogInnerProps) {
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
return ( return (
@@ -190,9 +194,9 @@ export function Inner({children, style, header}: DialogInnerProps) {
style={[ style={[
a.pt_2xl, a.pt_2xl,
a.px_xl, a.px_xl,
{ IS_LIQUID_GLASS
paddingBottom: insets.bottom + insets.top, ? a.pb_2xl
}, : {paddingBottom: insets.bottom + insets.top},
style, style,
]}> ]}>
{children} {children}
@@ -253,7 +257,7 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
<KeyboardAwareScrollView <KeyboardAwareScrollView
contentContainerStyle={[ contentContainerStyle={[
a.pt_2xl, a.pt_2xl,
a.px_xl, IS_LIQUID_GLASS ? a.px_2xl : a.px_xl,
{paddingBottom}, {paddingBottom},
contentContainerStyle, contentContainerStyle,
]} ]}
@@ -342,7 +346,7 @@ export function FlatListFooter({children}: {children: React.ReactNode}) {
a.pt_md, a.pt_md,
{ {
paddingBottom: platform({ paddingBottom: platform({
ios: tokens.space.md + bottom, ios: tokens.space.md + bottom + (IS_LIQUID_GLASS ? top : 0),
android: tokens.space.md + bottom + top, android: tokens.space.md + bottom + top,
}), }),
}, },
+8 -3
View File
@@ -8,6 +8,7 @@ import {
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_LIQUID_GLASS} from '#/env'
export function Header({ export function Header({
renderLeft, renderLeft,
@@ -35,7 +36,7 @@ export function Header({
a.flex_row, a.flex_row,
a.justify_center, a.justify_center,
a.align_center, a.align_center,
{minHeight: 50}, {minHeight: IS_LIQUID_GLASS ? 64 : 50},
a.border_b, a.border_b,
t.atoms.border_contrast_medium, t.atoms.border_contrast_medium,
t.atoms.bg, t.atoms.bg,
@@ -44,11 +45,15 @@ export function Header({
style, style,
]}> ]}>
{renderLeft && ( {renderLeft && (
<View style={[a.absolute, {left: 6}]}>{renderLeft()}</View> <View style={[a.absolute, {left: IS_LIQUID_GLASS ? 12 : 6}]}>
{renderLeft()}
</View>
)} )}
{children} {children}
{renderRight && ( {renderRight && (
<View style={[a.absolute, {right: 6}]}>{renderRight()}</View> <View style={[a.absolute, {right: IS_LIQUID_GLASS ? 12 : 6}]}>
{renderRight()}
</View>
)} )}
</View> </View>
) )
+2 -2
View File
@@ -1,7 +1,7 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {SystemBars} from 'react-native-edge-to-edge' import {SystemBars} from 'react-native-edge-to-edge'
import {IS_IOS} from '#/env' import {IS_IOS, IS_LIQUID_GLASS} from '#/env'
/** /**
* If we're calling a system API like the image picker that opens a sheet * If we're calling a system API like the image picker that opens a sheet
@@ -9,7 +9,7 @@ import {IS_IOS} from '#/env'
*/ */
export function useSheetWrapper() { export function useSheetWrapper() {
return useCallback(async <T>(promise: Promise<T>): Promise<T> => { return useCallback(async <T>(promise: Promise<T>): Promise<T> => {
if (IS_IOS) { if (IS_IOS && !IS_LIQUID_GLASS) {
const entry = SystemBars.pushStackEntry({ const entry = SystemBars.pushStackEntry({
style: { style: {
statusBar: 'light', statusBar: 'light',
+4 -2
View File
@@ -273,7 +273,8 @@ export function ContainerItem({
a.align_center, a.align_center,
a.gap_sm, a.gap_sm,
a.px_md, a.px_md,
a.rounded_md, a.rounded_lg,
a.curve_continuous,
a.border, a.border,
t.atoms.bg_contrast_25, t.atoms.bg_contrast_25,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
@@ -311,7 +312,8 @@ export function Group({children, style}: GroupProps) {
return ( return (
<View <View
style={[ style={[
a.rounded_md, a.rounded_lg,
a.curve_continuous,
a.overflow_hidden, a.overflow_hidden,
a.border, a.border,
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
@@ -17,7 +17,7 @@ import {SearchInput} from '#/components/forms/SearchInput'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env' import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
export function LanguageSelectDialog({ export function LanguageSelectDialog({
titleText, titleText,
@@ -52,7 +52,9 @@ export function LanguageSelectDialog({
return ( return (
<Dialog.Outer <Dialog.Outer
control={control} control={control}
nativeOptions={{minHeight: height - insets.top}}> nativeOptions={{
minHeight: IS_LIQUID_GLASS ? height : height - insets.top,
}}>
<Dialog.Handle /> <Dialog.Handle />
<ErrorBoundary renderError={renderErrorBoundary}> <ErrorBoundary renderError={renderErrorBoundary}>
<DialogInner <DialogInner
+8
View File
@@ -5,6 +5,12 @@ import {BUNDLE_IDENTIFIER, IS_TESTFLIGHT, RELEASE_VERSION} from '#/env/common'
export * from '#/env/common' export * from '#/env/common'
// for some reason Platform.OS === 'ios' AND Platform.Version is undefined in our CI unit tests -sfn
const iOSMajorVersion =
Platform.OS === 'ios' && typeof Platform.Version === 'string'
? parseInt(Platform.Version.split('.')[0], 10)
: 0
/** /**
* The semver version of the app, specified in our `package.json`.file. On * The semver version of the app, specified in our `package.json`.file. On
* iOs/Android, the native build version is appended to the semver version, so * iOs/Android, the native build version is appended to the semver version, so
@@ -41,3 +47,5 @@ export const IS_WEB_FIREFOX: boolean = false
* Misc * Misc
*/ */
export const IS_HIGH_DPI: boolean = true export const IS_HIGH_DPI: boolean = true
// ideally we'd use isLiquidGlassAvailable() from expo-glass-effect but checking iOS version is good enough for now
export const IS_LIQUID_GLASS: boolean = iOSMajorVersion >= 26
+1
View File
@@ -47,3 +47,4 @@ export const IS_WEB_FIREFOX: boolean = /firefox|fxios/i.test(
export const IS_HIGH_DPI: boolean = window.matchMedia( export const IS_HIGH_DPI: boolean = window.matchMedia(
'(min-resolution: 2dppx)', '(min-resolution: 2dppx)',
).matches ).matches
export const IS_LIQUID_GLASS: boolean = false
+4 -2
View File
@@ -14,7 +14,7 @@ import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {P, Text} from '#/components/Typography' import {P, Text} from '#/components/Typography'
import {IS_IOS, IS_WEB} from '#/env' import {IS_IOS, IS_LIQUID_GLASS, IS_WEB} from '#/env'
const COL_WIDTH = 400 const COL_WIDTH = 400
@@ -107,7 +107,9 @@ export function SignupQueued() {
animationType={native('slide')} animationType={native('slide')}
presentationStyle="formSheet" presentationStyle="formSheet"
style={[web(a.util_screen_outer)]}> style={[web(a.util_screen_outer)]}>
{IS_IOS && <SystemBars style={{statusBar: 'light'}} />} {IS_IOS && !IS_LIQUID_GLASS && (
<SystemBars style={{statusBar: 'light'}} />
)}
<ScrollView <ScrollView
style={[a.flex_1, t.atoms.bg]} style={[a.flex_1, t.atoms.bg]}
contentContainerStyle={{borderWidth: 0}} contentContainerStyle={{borderWidth: 0}}
+8 -10
View File
@@ -133,7 +133,7 @@ import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_ANDROID, IS_IOS, IS_NATIVE, IS_WEB} from '#/env' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
import { import {
draftToComposerPosts, draftToComposerPosts,
@@ -1522,7 +1522,7 @@ function ComposerTopBar({
<Animated.View <Animated.View
style={topBarAnimatedStyle} style={topBarAnimatedStyle}
layout={native(LinearTransition)}> layout={native(LinearTransition)}>
<View style={styles.topbarInner}> <View style={[a.flex_row, a.align_center, a.gap_xs, a.p_lg, a.pb_md]}>
<Button <Button
label={_(msg`Cancel`)} label={_(msg`Cancel`)}
variant="ghost" variant="ghost"
@@ -2138,10 +2138,15 @@ function useKeyboardVerticalOffset() {
return bottom * -1 return bottom * -1
} }
// they ditched the gap behaviour on 26
if (IS_LIQUID_GLASS) {
return top
}
// iPhone SE // iPhone SE
if (top === 20) return 40 if (top === 20) return 40
// all other iPhones // all other iPhones on <26
return top + 10 return top + 10
} }
@@ -2186,13 +2191,6 @@ function useHideKeyboardOnBackground() {
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
topbarInner: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
height: 54,
gap: 4,
},
postBtn: { postBtn: {
borderRadius: 20, borderRadius: 20,
paddingHorizontal: 20, paddingHorizontal: 20,
+4 -2
View File
@@ -1,5 +1,5 @@
import {useState} from 'react' import {useState} from 'react'
import {TouchableOpacity, View} from 'react-native' import {TouchableOpacity, useWindowDimensions, View} from 'react-native'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro' import {Plural, Trans} from '@lingui/react/macro'
@@ -69,6 +69,7 @@ export function GifAltTextDialogLoaded({
const {_} = useLingui() const {_} = useLingui()
const t = useTheme() const t = useTheme()
const [altTextDraft, setAltTextDraft] = useState(altText || vendorAltText) const [altTextDraft, setAltTextDraft] = useState(altText || vendorAltText)
const {height: minHeight} = useWindowDimensions()
return ( return (
<> <>
<TouchableOpacity <TouchableOpacity
@@ -108,7 +109,8 @@ export function GifAltTextDialogLoaded({
control={control} control={control}
onClose={() => { onClose={() => {
onSubmit(altTextDraft) onSubmit(altTextDraft)
}}> }}
nativeOptions={{minHeight}}>
<Dialog.Handle /> <Dialog.Handle />
<AltTextInner <AltTextInner
vendorAltText={vendorAltText} vendorAltText={vendorAltText}
+5 -3
View File
@@ -6,6 +6,7 @@ import {useComposerState} from '#/state/shell/composer'
import {ComposePost, useComposerCancelRef} from '#/view/com/composer/Composer' import {ComposePost, useComposerCancelRef} from '#/view/com/composer/Composer'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {SheetCompatProvider as TooltipSheetCompatProvider} from '#/components/Tooltip' import {SheetCompatProvider as TooltipSheetCompatProvider} from '#/components/Tooltip'
import {IS_LIQUID_GLASS} from '#/env'
export function Composer({}: {winHeight: number}) { export function Composer({}: {winHeight: number}) {
const {setFullyExpandedCount} = useDialogStateControlContext() const {setFullyExpandedCount} = useDialogStateControlContext()
@@ -32,8 +33,10 @@ export function Composer({}: {winHeight: number}) {
visible={open} visible={open}
presentationStyle="pageSheet" presentationStyle="pageSheet"
animationType="slide" animationType="slide"
onRequestClose={() => ref.current?.onPressCancel()}> onRequestClose={() => ref.current?.onPressCancel()}
<View style={[t.atoms.bg, a.flex_1]}> backdropColor="transparent"
style={[!IS_LIQUID_GLASS && a.rounded_sheet]}>
<View style={[a.flex_1, a.curve_continuous, t.atoms.bg]}>
<TooltipSheetCompatProvider> <TooltipSheetCompatProvider>
<ComposePost <ComposePost
cancelRef={ref} cancelRef={ref}
@@ -45,7 +48,6 @@ export function Composer({}: {winHeight: number}) {
text={state?.text} text={state?.text}
imageUris={state?.imageUris} imageUris={state?.imageUris}
videoUri={state?.videoUri} videoUri={state?.videoUri}
openGallery={state?.openGallery}
/> />
</TooltipSheetCompatProvider> </TooltipSheetCompatProvider>
</View> </View>
+3 -2
View File
@@ -43,7 +43,7 @@ import {useAgeAssurance} from '#/ageAssurance'
import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen' import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen'
import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay' import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay'
import {PassiveAnalytics} from '#/analytics/PassiveAnalytics' import {PassiveAnalytics} from '#/analytics/PassiveAnalytics'
import {IS_ANDROID, IS_IOS} from '#/env' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS} from '#/env'
import {RoutesContainer, TabsNavigator} from '#/Navigation' import {RoutesContainer, TabsNavigator} from '#/Navigation'
import {BottomSheetOutlet} from '../../../modules/bottom-sheet' import {BottomSheetOutlet} from '../../../modules/bottom-sheet'
import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView'
@@ -223,7 +223,8 @@ export function Shell() {
<SystemBars <SystemBars
style={{ style={{
statusBar: statusBar:
t.name !== 'light' || (IS_IOS && fullyExpandedCount > 0) t.name !== 'light' ||
(IS_IOS && !IS_LIQUID_GLASS && fullyExpandedCount > 0)
? 'light' ? 'light'
: 'dark', : 'dark',
navigationBar: t.name !== 'light' ? 'light' : 'dark', navigationBar: t.name !== 'light' ? 'light' : 'dark',