diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index 2c33b5bdcb..b7e3c78d5e 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -18,7 +18,7 @@ import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomShee export const Context = createContext({ close: () => {}, - IS_NATIVEDialog: false, + isNativeDialog: false, nativeSnapPoint: BottomSheetSnapPoint.Hidden, disableDrag: false, setDisableDrag: () => {}, diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index cdc3d657bc..7cb870d86d 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -1,20 +1,16 @@ import React, {useImperativeHandle} from 'react' import { + type LayoutChangeEvent, type NativeScrollEvent, type NativeSyntheticEvent, Pressable, - type ScrollView, + ScrollView, type StyleProp, TextInput, View, type ViewStyle, } from 'react-native' -import { - KeyboardAwareScrollView, - type KeyboardAwareScrollViewRef, - useKeyboardHandler, - useReanimatedKeyboardAnimation, -} from 'react-native-keyboard-controller' +import {useReanimatedKeyboardAnimation} from 'react-native-keyboard-controller' import Animated, { runOnJS, type ScrollEvent, @@ -29,7 +25,7 @@ import {logger} from '#/logger' import {useA11y} from '#/state/a11y' import {useDialogStateControlContext} from '#/state/dialogs' import {List, type ListMethods, type ListProps} from '#/view/com/util/List' -import {atoms as a, ios, platform, tokens, useTheme} from '#/alf' +import {android, atoms as a, ios, platform, tokens, useTheme} from '#/alf' import {useThemeName} from '#/alf/util/useColorModeTheme' import {Context, useDialogContext} from '#/components/Dialog/context' import { @@ -154,7 +150,7 @@ export function Outer({ const context = React.useMemo( () => ({ close, - IS_NATIVEDialog: true, + isNativeDialog: true, nativeSnapPoint: snapPoint, disableDrag, setDisableDrag, @@ -212,33 +208,17 @@ export const ScrollableInner = React.forwardRef( ) { const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() const insets = useSafeAreaInsets() - - const [keyboardHeight, setKeyboardHeight] = React.useState(0) - - // note: iOS-only. keyboard-controller doesn't seem to work inside the sheets on Android - useKeyboardHandler( - { - onEnd: e => { - 'worklet' - runOnJS(setKeyboardHeight)(e.height) - }, - }, - [], - ) + const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full let paddingBottom = 0 if (IS_IOS) { - paddingBottom += keyboardHeight / 4 - if (nativeSnapPoint === BottomSheetSnapPoint.Full) { - paddingBottom += insets.bottom + tokens.space.md - } - paddingBottom = Math.max(paddingBottom, tokens.space._2xl) + paddingBottom = tokens.space._2xl } else { - if (nativeSnapPoint === BottomSheetSnapPoint.Full) { + paddingBottom = + Math.max(insets.bottom, tokens.space._5xl) + tokens.space._2xl + if (isAtMaxSnapPoint) { paddingBottom += insets.top } - paddingBottom += - Math.max(insets.bottom, tokens.space._5xl) + tokens.space._2xl } const onScroll = (e: NativeSyntheticEvent) => { @@ -254,18 +234,21 @@ export const ScrollableInner = React.forwardRef( } return ( - } + ref={ref} showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} + contentInsetAdjustmentBehavior={ + isAtMaxSnapPoint ? 'automatic' : 'never' + } + automaticallyAdjustKeyboardInsets={isAtMaxSnapPoint} {...props} - bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} - bottomOffset={30} + bounces={isAtMaxSnapPoint} scrollEventThrottle={50} onScroll={IS_ANDROID ? onScroll : undefined} keyboardShouldPersistTaps="handled" @@ -275,7 +258,7 @@ export const ScrollableInner = React.forwardRef( stickyHeaderIndices={ios(header ? [0] : undefined)}> {header} {children} - + ) }, ) @@ -287,10 +270,15 @@ export const InnerFlatList = React.forwardRef< webInnerContentContainerStyle?: StyleProp footer?: React.ReactNode } ->(function InnerFlatList({footer, style, ...props}, ref) { +>(function InnerFlatList( + {headerOffset, footer, style, contentContainerStyle, ...props}, + ref, +) { const insets = useSafeAreaInsets() const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() + const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full + const onScroll = (e: ScrollEvent) => { 'worklet' if (!IS_ANDROID) { @@ -308,19 +296,36 @@ export const InnerFlatList = React.forwardRef< } + contentInsetAdjustmentBehavior={ + isAtMaxSnapPoint ? 'automatic' : 'never' + } + automaticallyAdjustKeyboardInsets={isAtMaxSnapPoint} + scrollIndicatorInsets={{top: headerOffset}} + bounces={isAtMaxSnapPoint} ref={ref} showsVerticalScrollIndicator={IS_ANDROID ? false : undefined} {...props} style={[a.h_full, style]} + contentContainerStyle={[ + {paddingTop: headerOffset}, + android({ + paddingBottom: insets.top + insets.bottom + tokens.space.xl, + }), + contentContainerStyle, + ]} /> {footer} ) }) -export function FlatListFooter({children}: {children: React.ReactNode}) { +export function FlatListFooter({ + children, + onLayout, +}: { + children: React.ReactNode + onLayout?: (event: LayoutChangeEvent) => void +}) { const t = useTheme() const {top, bottom} = useSafeAreaInsets() const {height} = useReanimatedKeyboardAnimation() @@ -334,6 +339,7 @@ export function FlatListFooter({children}: {children: React.ReactNode}) { return ( ({ close, - IS_NATIVEDialog: false, + isNativeDialog: false, nativeSnapPoint: 0, disableDrag: false, setDisableDrag: () => {}, @@ -253,11 +254,18 @@ export const InnerFlatList = React.forwardRef< ) }) -export function FlatListFooter({children}: {children: React.ReactNode}) { +export function FlatListFooter({ + children, + onLayout, +}: { + children: React.ReactNode + onLayout?: (event: LayoutChangeEvent) => void +}) { const t = useTheme() return ( > diff --git a/src/components/dialogs/LanguageSelectDialog.tsx b/src/components/dialogs/LanguageSelectDialog.tsx index 96cd485588..c744aad368 100644 --- a/src/components/dialogs/LanguageSelectDialog.tsx +++ b/src/components/dialogs/LanguageSelectDialog.tsx @@ -10,7 +10,7 @@ import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' import {useLanguagePrefs} from '#/state/preferences/languages' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' -import {atoms as a, useTheme, web} from '#/alf' +import {atoms as a, tokens, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {SearchInput} from '#/components/forms/SearchInput' @@ -84,6 +84,7 @@ export function DialogInner({ }) { const control = Dialog.useDialogContext() const [headerHeight, setHeaderHeight] = useState(0) + const [footerHeight, setFooterHeight] = useState(0) const allowedLanguages = useMemo(() => { const uniqueLanguagesMap = LANGUAGES.filter(lang => !!lang.code2).reduce( @@ -249,6 +250,8 @@ export function DialogInner({ ...displayedLanguages.all.map(lang => ({type: 'item', lang})), ] + const numItems = flatListData.length + return ( { if (item.type === 'header') { return ( @@ -283,6 +289,8 @@ export function DialogInner({ } const lang = item.lang + const isLastItem = index === numItems - 1 + return ( + setFooterHeight(evt.nativeEvent.layout.height)}> - +