iOS 26 (#9047)
Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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(),
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export default {}
|
||||
@@ -1,10 +0,0 @@
|
||||
jest.mock('rn-fetch-blob', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: {
|
||||
fs: {
|
||||
unlink: jest.fn(),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -1,2 +0,0 @@
|
||||
export const DropdownMenu = jest.fn().mockImplementation(() => {})
|
||||
export const create = jest.fn().mockImplementation(() => {})
|
||||
@@ -112,7 +112,6 @@ module.exports = function (_config) {
|
||||
'zh-Hans',
|
||||
'zh-Hant',
|
||||
],
|
||||
UIDesignRequiresCompatibility: true,
|
||||
},
|
||||
associatedDomains: ASSOCIATED_DOMAINS,
|
||||
entitlements: {
|
||||
|
||||
@@ -27,6 +27,19 @@ class SheetViewController: UIViewController {
|
||||
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 contentHeight > screenHeight - 100 {
|
||||
sheet.detents = [
|
||||
@@ -36,7 +49,7 @@ class SheetViewController: UIViewController {
|
||||
} else {
|
||||
sheet.detents = [
|
||||
.custom { _ in
|
||||
return contentHeight
|
||||
return adjustedHeight
|
||||
}
|
||||
]
|
||||
if !preventExpansion {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type NativeSyntheticEvent,
|
||||
Platform,
|
||||
type StyleProp,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
@@ -21,8 +22,6 @@ import {
|
||||
Context as PortalContext,
|
||||
} from './BottomSheetPortal'
|
||||
|
||||
const screenHeight = Dimensions.get('screen').height
|
||||
|
||||
const NativeView: React.ComponentType<
|
||||
BottomSheetViewProps & {
|
||||
ref: React.RefObject<any>
|
||||
@@ -94,6 +93,7 @@ export class BottomSheetNativeComponent extends React.Component<
|
||||
|
||||
let extraStyles
|
||||
if (IS_IOS15 && this.state.viewHeight) {
|
||||
const screenHeight = Dimensions.get('screen').height
|
||||
const {viewHeight} = this.state
|
||||
const cornerRadius = this.props.cornerRadius ?? 0
|
||||
if (viewHeight < screenHeight / 2) {
|
||||
@@ -154,6 +154,7 @@ function BottomSheetNativeComponentInner({
|
||||
}) {
|
||||
const insets = useSafeAreaInsets()
|
||||
const cornerRadius = rest.cornerRadius ?? 0
|
||||
const {height: screenHeight} = useWindowDimensions()
|
||||
|
||||
const sheetHeight = IS_IOS ? screenHeight - insets.top : screenHeight
|
||||
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
"expo-image-picker"
|
||||
]
|
||||
}
|
||||
},
|
||||
"install": {
|
||||
"exclude": [
|
||||
"react-native-reanimated",
|
||||
"@sentry/react-native",
|
||||
"react-native-pager-view"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -10,6 +10,10 @@ const EXP_CURVE = 'cubic-bezier(0.16, 1, 0.3, 1)'
|
||||
export const atoms = {
|
||||
...baseAtoms,
|
||||
|
||||
rounded_sheet: {
|
||||
borderRadius: 40,
|
||||
},
|
||||
|
||||
h_full_vh: web({
|
||||
height: '100vh',
|
||||
}),
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
type DialogOuterProps,
|
||||
} from '#/components/Dialog/types'
|
||||
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 {
|
||||
type BottomSheetSnapPointChangeEvent,
|
||||
@@ -166,7 +166,8 @@ export function Outer({
|
||||
return (
|
||||
<BottomSheet
|
||||
ref={ref}
|
||||
cornerRadius={20}
|
||||
// device-bezel radius when undefined
|
||||
cornerRadius={IS_LIQUID_GLASS ? undefined : 20}
|
||||
backgroundColor={t.atoms.bg.backgroundColor}
|
||||
{...nativeOptions}
|
||||
onSnapPointChange={onSnapPointChange}
|
||||
@@ -181,6 +182,9 @@ export function Outer({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `Dialog.ScrollableInner` instead
|
||||
*/
|
||||
export function Inner({children, style, header}: DialogInnerProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
return (
|
||||
@@ -190,9 +194,9 @@ export function Inner({children, style, header}: DialogInnerProps) {
|
||||
style={[
|
||||
a.pt_2xl,
|
||||
a.px_xl,
|
||||
{
|
||||
paddingBottom: insets.bottom + insets.top,
|
||||
},
|
||||
IS_LIQUID_GLASS
|
||||
? a.pb_2xl
|
||||
: {paddingBottom: insets.bottom + insets.top},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
@@ -253,7 +257,7 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
<KeyboardAwareScrollView
|
||||
contentContainerStyle={[
|
||||
a.pt_2xl,
|
||||
a.px_xl,
|
||||
IS_LIQUID_GLASS ? a.px_2xl : a.px_xl,
|
||||
{paddingBottom},
|
||||
contentContainerStyle,
|
||||
]}
|
||||
@@ -342,7 +346,7 @@ export function FlatListFooter({children}: {children: React.ReactNode}) {
|
||||
a.pt_md,
|
||||
{
|
||||
paddingBottom: platform({
|
||||
ios: tokens.space.md + bottom,
|
||||
ios: tokens.space.md + bottom + (IS_LIQUID_GLASS ? top : 0),
|
||||
android: tokens.space.md + bottom + top,
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {IS_LIQUID_GLASS} from '#/env'
|
||||
|
||||
export function Header({
|
||||
renderLeft,
|
||||
@@ -35,7 +36,7 @@ export function Header({
|
||||
a.flex_row,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
{minHeight: 50},
|
||||
{minHeight: IS_LIQUID_GLASS ? 64 : 50},
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg,
|
||||
@@ -44,11 +45,15 @@ export function Header({
|
||||
style,
|
||||
]}>
|
||||
{renderLeft && (
|
||||
<View style={[a.absolute, {left: 6}]}>{renderLeft()}</View>
|
||||
<View style={[a.absolute, {left: IS_LIQUID_GLASS ? 12 : 6}]}>
|
||||
{renderLeft()}
|
||||
</View>
|
||||
)}
|
||||
{children}
|
||||
{renderRight && (
|
||||
<View style={[a.absolute, {right: 6}]}>{renderRight()}</View>
|
||||
<View style={[a.absolute, {right: IS_LIQUID_GLASS ? 12 : 6}]}>
|
||||
{renderRight()}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useCallback} from 'react'
|
||||
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
|
||||
@@ -9,7 +9,7 @@ import {IS_IOS} from '#/env'
|
||||
*/
|
||||
export function useSheetWrapper() {
|
||||
return useCallback(async <T>(promise: Promise<T>): Promise<T> => {
|
||||
if (IS_IOS) {
|
||||
if (IS_IOS && !IS_LIQUID_GLASS) {
|
||||
const entry = SystemBars.pushStackEntry({
|
||||
style: {
|
||||
statusBar: 'light',
|
||||
|
||||
@@ -273,7 +273,8 @@ export function ContainerItem({
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
a.px_md,
|
||||
a.rounded_md,
|
||||
a.rounded_lg,
|
||||
a.curve_continuous,
|
||||
a.border,
|
||||
t.atoms.bg_contrast_25,
|
||||
t.atoms.border_contrast_low,
|
||||
@@ -311,7 +312,8 @@ export function Group({children, style}: GroupProps) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.rounded_lg,
|
||||
a.curve_continuous,
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
|
||||
@@ -17,7 +17,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'
|
||||
import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
|
||||
|
||||
export function LanguageSelectDialog({
|
||||
titleText,
|
||||
@@ -52,7 +52,9 @@ export function LanguageSelectDialog({
|
||||
return (
|
||||
<Dialog.Outer
|
||||
control={control}
|
||||
nativeOptions={{minHeight: height - insets.top}}>
|
||||
nativeOptions={{
|
||||
minHeight: IS_LIQUID_GLASS ? height : height - insets.top,
|
||||
}}>
|
||||
<Dialog.Handle />
|
||||
<ErrorBoundary renderError={renderErrorBoundary}>
|
||||
<DialogInner
|
||||
|
||||
Vendored
+8
@@ -5,6 +5,12 @@ import {BUNDLE_IDENTIFIER, IS_TESTFLIGHT, RELEASE_VERSION} 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
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
|
||||
Vendored
+1
@@ -47,3 +47,4 @@ export const IS_WEB_FIREFOX: boolean = /firefox|fxios/i.test(
|
||||
export const IS_HIGH_DPI: boolean = window.matchMedia(
|
||||
'(min-resolution: 2dppx)',
|
||||
).matches
|
||||
export const IS_LIQUID_GLASS: boolean = false
|
||||
|
||||
@@ -14,7 +14,7 @@ import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {Loader} from '#/components/Loader'
|
||||
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
|
||||
|
||||
@@ -107,7 +107,9 @@ export function SignupQueued() {
|
||||
animationType={native('slide')}
|
||||
presentationStyle="formSheet"
|
||||
style={[web(a.util_screen_outer)]}>
|
||||
{IS_IOS && <SystemBars style={{statusBar: 'light'}} />}
|
||||
{IS_IOS && !IS_LIQUID_GLASS && (
|
||||
<SystemBars style={{statusBar: 'light'}} />
|
||||
)}
|
||||
<ScrollView
|
||||
style={[a.flex_1, t.atoms.bg]}
|
||||
contentContainerStyle={{borderWidth: 0}}
|
||||
|
||||
@@ -133,7 +133,7 @@ import * as Prompt from '#/components/Prompt'
|
||||
import * as Toast from '#/components/Toast'
|
||||
import {Text} from '#/components/Typography'
|
||||
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 {
|
||||
draftToComposerPosts,
|
||||
@@ -1522,7 +1522,7 @@ function ComposerTopBar({
|
||||
<Animated.View
|
||||
style={topBarAnimatedStyle}
|
||||
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
|
||||
label={_(msg`Cancel`)}
|
||||
variant="ghost"
|
||||
@@ -2138,10 +2138,15 @@ function useKeyboardVerticalOffset() {
|
||||
return bottom * -1
|
||||
}
|
||||
|
||||
// they ditched the gap behaviour on 26
|
||||
if (IS_LIQUID_GLASS) {
|
||||
return top
|
||||
}
|
||||
|
||||
// iPhone SE
|
||||
if (top === 20) return 40
|
||||
|
||||
// all other iPhones
|
||||
// all other iPhones on <26
|
||||
return top + 10
|
||||
}
|
||||
|
||||
@@ -2186,13 +2191,6 @@ function useHideKeyboardOnBackground() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topbarInner: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
height: 54,
|
||||
gap: 4,
|
||||
},
|
||||
postBtn: {
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 20,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useState} from 'react'
|
||||
import {TouchableOpacity, View} from 'react-native'
|
||||
import {TouchableOpacity, useWindowDimensions, View} from 'react-native'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Plural, Trans} from '@lingui/react/macro'
|
||||
@@ -69,6 +69,7 @@ export function GifAltTextDialogLoaded({
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const [altTextDraft, setAltTextDraft] = useState(altText || vendorAltText)
|
||||
const {height: minHeight} = useWindowDimensions()
|
||||
return (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
@@ -108,7 +109,8 @@ export function GifAltTextDialogLoaded({
|
||||
control={control}
|
||||
onClose={() => {
|
||||
onSubmit(altTextDraft)
|
||||
}}>
|
||||
}}
|
||||
nativeOptions={{minHeight}}>
|
||||
<Dialog.Handle />
|
||||
<AltTextInner
|
||||
vendorAltText={vendorAltText}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {useComposerState} from '#/state/shell/composer'
|
||||
import {ComposePost, useComposerCancelRef} from '#/view/com/composer/Composer'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {SheetCompatProvider as TooltipSheetCompatProvider} from '#/components/Tooltip'
|
||||
import {IS_LIQUID_GLASS} from '#/env'
|
||||
|
||||
export function Composer({}: {winHeight: number}) {
|
||||
const {setFullyExpandedCount} = useDialogStateControlContext()
|
||||
@@ -32,8 +33,10 @@ export function Composer({}: {winHeight: number}) {
|
||||
visible={open}
|
||||
presentationStyle="pageSheet"
|
||||
animationType="slide"
|
||||
onRequestClose={() => ref.current?.onPressCancel()}>
|
||||
<View style={[t.atoms.bg, a.flex_1]}>
|
||||
onRequestClose={() => ref.current?.onPressCancel()}
|
||||
backdropColor="transparent"
|
||||
style={[!IS_LIQUID_GLASS && a.rounded_sheet]}>
|
||||
<View style={[a.flex_1, a.curve_continuous, t.atoms.bg]}>
|
||||
<TooltipSheetCompatProvider>
|
||||
<ComposePost
|
||||
cancelRef={ref}
|
||||
@@ -45,7 +48,6 @@ export function Composer({}: {winHeight: number}) {
|
||||
text={state?.text}
|
||||
imageUris={state?.imageUris}
|
||||
videoUri={state?.videoUri}
|
||||
openGallery={state?.openGallery}
|
||||
/>
|
||||
</TooltipSheetCompatProvider>
|
||||
</View>
|
||||
|
||||
@@ -43,7 +43,7 @@ import {useAgeAssurance} from '#/ageAssurance'
|
||||
import {NoAccessScreen} from '#/ageAssurance/components/NoAccessScreen'
|
||||
import {RedirectOverlay} from '#/ageAssurance/components/RedirectOverlay'
|
||||
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 {BottomSheetOutlet} from '../../../modules/bottom-sheet'
|
||||
import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView'
|
||||
@@ -223,7 +223,8 @@ export function Shell() {
|
||||
<SystemBars
|
||||
style={{
|
||||
statusBar:
|
||||
t.name !== 'light' || (IS_IOS && fullyExpandedCount > 0)
|
||||
t.name !== 'light' ||
|
||||
(IS_IOS && !IS_LIQUID_GLASS && fullyExpandedCount > 0)
|
||||
? 'light'
|
||||
: 'dark',
|
||||
navigationBar: t.name !== 'light' ? 'light' : 'dark',
|
||||
|
||||
Reference in New Issue
Block a user