make scrollview automatically adapt to footer height
This commit is contained in:
+34
-35
@@ -232,42 +232,41 @@ function App() {
|
|||||||
* that is set up in the InnerApp component above.
|
* that is set up in the InnerApp component above.
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<Geo.Provider>
|
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
||||||
<AppConfigProvider>
|
<Geo.Provider>
|
||||||
<A11yProvider>
|
<AppConfigProvider>
|
||||||
<KeyboardControllerProvider preload={false}>
|
<A11yProvider>
|
||||||
<OnboardingProvider>
|
<KeyboardControllerProvider preload={false}>
|
||||||
<AnalyticsContext>
|
<OnboardingProvider>
|
||||||
<SessionProvider>
|
<AnalyticsContext>
|
||||||
<PrefsStateProvider>
|
<SessionProvider>
|
||||||
<I18nProvider>
|
<PrefsStateProvider>
|
||||||
<ShellStateProvider>
|
<I18nProvider>
|
||||||
<ModalStateProvider>
|
<ShellStateProvider>
|
||||||
<DialogStateProvider>
|
<ModalStateProvider>
|
||||||
<LightboxStateProvider>
|
<DialogStateProvider>
|
||||||
<PortalProvider>
|
<LightboxStateProvider>
|
||||||
<BottomSheetProvider>
|
<PortalProvider>
|
||||||
<StarterPackProvider>
|
<BottomSheetProvider>
|
||||||
<SafeAreaProvider
|
<StarterPackProvider>
|
||||||
initialMetrics={initialWindowMetrics}>
|
|
||||||
<InnerApp />
|
<InnerApp />
|
||||||
</SafeAreaProvider>
|
</StarterPackProvider>
|
||||||
</StarterPackProvider>
|
</BottomSheetProvider>
|
||||||
</BottomSheetProvider>
|
</PortalProvider>
|
||||||
</PortalProvider>
|
</LightboxStateProvider>
|
||||||
</LightboxStateProvider>
|
</DialogStateProvider>
|
||||||
</DialogStateProvider>
|
</ModalStateProvider>
|
||||||
</ModalStateProvider>
|
</ShellStateProvider>
|
||||||
</ShellStateProvider>
|
</I18nProvider>
|
||||||
</I18nProvider>
|
</PrefsStateProvider>
|
||||||
</PrefsStateProvider>
|
</SessionProvider>
|
||||||
</SessionProvider>
|
</AnalyticsContext>
|
||||||
</AnalyticsContext>
|
</OnboardingProvider>
|
||||||
</OnboardingProvider>
|
</KeyboardControllerProvider>
|
||||||
</KeyboardControllerProvider>
|
</A11yProvider>
|
||||||
</A11yProvider>
|
</AppConfigProvider>
|
||||||
</AppConfigProvider>
|
</Geo.Provider>
|
||||||
</Geo.Provider>
|
</SafeAreaProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
-50
@@ -4,7 +4,10 @@ import './style.css'
|
|||||||
|
|
||||||
import {Fragment, useEffect, useState} from 'react'
|
import {Fragment, useEffect, useState} from 'react'
|
||||||
import {KeyboardProvider as KeyboardControllerProvider} from 'react-native-keyboard-controller'
|
import {KeyboardProvider as KeyboardControllerProvider} from 'react-native-keyboard-controller'
|
||||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
import {
|
||||||
|
initialWindowMetrics,
|
||||||
|
SafeAreaProvider,
|
||||||
|
} from 'react-native-safe-area-context'
|
||||||
import {useLingui} from '@lingui/react/macro'
|
import {useLingui} from '@lingui/react/macro'
|
||||||
import * as Sentry from '@sentry/react-native'
|
import * as Sentry from '@sentry/react-native'
|
||||||
|
|
||||||
@@ -149,24 +152,22 @@ function InnerApp() {
|
|||||||
<UnreadNotifsProvider>
|
<UnreadNotifsProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<MutedThreadsProvider>
|
<MutedThreadsProvider>
|
||||||
<SafeAreaProvider>
|
<ProgressGuideProvider>
|
||||||
<ProgressGuideProvider>
|
<ServiceConfigProvider>
|
||||||
<ServiceConfigProvider>
|
<EmailVerificationProvider>
|
||||||
<EmailVerificationProvider>
|
<HideBottomBarBorderProvider>
|
||||||
<HideBottomBarBorderProvider>
|
<IntentDialogProvider>
|
||||||
<IntentDialogProvider>
|
<TranslateOnDeviceProvider>
|
||||||
<TranslateOnDeviceProvider>
|
<HotkeysProvider>
|
||||||
<HotkeysProvider>
|
<Shell />
|
||||||
<Shell />
|
<ToastOutlet />
|
||||||
<ToastOutlet />
|
</HotkeysProvider>
|
||||||
</HotkeysProvider>
|
</TranslateOnDeviceProvider>
|
||||||
</TranslateOnDeviceProvider>
|
</IntentDialogProvider>
|
||||||
</IntentDialogProvider>
|
</HideBottomBarBorderProvider>
|
||||||
</HideBottomBarBorderProvider>
|
</EmailVerificationProvider>
|
||||||
</EmailVerificationProvider>
|
</ServiceConfigProvider>
|
||||||
</ServiceConfigProvider>
|
</ProgressGuideProvider>
|
||||||
</ProgressGuideProvider>
|
|
||||||
</SafeAreaProvider>
|
|
||||||
</MutedThreadsProvider>
|
</MutedThreadsProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</UnreadNotifsProvider>
|
</UnreadNotifsProvider>
|
||||||
@@ -211,37 +212,39 @@ function App() {
|
|||||||
* that is set up in the InnerApp component above.
|
* that is set up in the InnerApp component above.
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<Geo.Provider>
|
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
||||||
<AppConfigProvider>
|
<Geo.Provider>
|
||||||
<A11yProvider>
|
<AppConfigProvider>
|
||||||
<KeyboardControllerProvider>
|
<A11yProvider>
|
||||||
<OnboardingProvider>
|
<KeyboardControllerProvider>
|
||||||
<AnalyticsContext>
|
<OnboardingProvider>
|
||||||
<SessionProvider>
|
<AnalyticsContext>
|
||||||
<PrefsStateProvider>
|
<SessionProvider>
|
||||||
<I18nProvider>
|
<PrefsStateProvider>
|
||||||
<ShellStateProvider>
|
<I18nProvider>
|
||||||
<ModalStateProvider>
|
<ShellStateProvider>
|
||||||
<DialogStateProvider>
|
<ModalStateProvider>
|
||||||
<LightboxStateProvider>
|
<DialogStateProvider>
|
||||||
<PortalProvider>
|
<LightboxStateProvider>
|
||||||
<StarterPackProvider>
|
<PortalProvider>
|
||||||
<InnerApp />
|
<StarterPackProvider>
|
||||||
</StarterPackProvider>
|
<InnerApp />
|
||||||
</PortalProvider>
|
</StarterPackProvider>
|
||||||
</LightboxStateProvider>
|
</PortalProvider>
|
||||||
</DialogStateProvider>
|
</LightboxStateProvider>
|
||||||
</ModalStateProvider>
|
</DialogStateProvider>
|
||||||
</ShellStateProvider>
|
</ModalStateProvider>
|
||||||
</I18nProvider>
|
</ShellStateProvider>
|
||||||
</PrefsStateProvider>
|
</I18nProvider>
|
||||||
</SessionProvider>
|
</PrefsStateProvider>
|
||||||
</AnalyticsContext>
|
</SessionProvider>
|
||||||
</OnboardingProvider>
|
</AnalyticsContext>
|
||||||
</KeyboardControllerProvider>
|
</OnboardingProvider>
|
||||||
</A11yProvider>
|
</KeyboardControllerProvider>
|
||||||
</AppConfigProvider>
|
</A11yProvider>
|
||||||
</Geo.Provider>
|
</AppConfigProvider>
|
||||||
|
</Geo.Provider>
|
||||||
|
</SafeAreaProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import {forwardRef, memo, useContext, useMemo} from 'react'
|
import {forwardRef, memo, useContext, useMemo} from 'react'
|
||||||
import {
|
import {
|
||||||
|
ScrollView,
|
||||||
|
type ScrollViewProps,
|
||||||
type StyleProp,
|
type StyleProp,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
@@ -10,16 +12,14 @@ import {
|
|||||||
KeyboardAwareScrollView,
|
KeyboardAwareScrollView,
|
||||||
type KeyboardAwareScrollViewProps,
|
type KeyboardAwareScrollViewProps,
|
||||||
} from 'react-native-keyboard-controller'
|
} from 'react-native-keyboard-controller'
|
||||||
import Animated, {
|
|
||||||
type AnimatedScrollViewProps,
|
|
||||||
useAnimatedProps,
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
import {useEnableMinimalShellModeForScreen} from '#/state/shell'
|
import {useEnableMinimalShellModeForScreen} from '#/state/shell'
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
import {
|
import {
|
||||||
|
android,
|
||||||
atoms as a,
|
atoms as a,
|
||||||
|
ios,
|
||||||
useBreakpoints,
|
useBreakpoints,
|
||||||
useLayoutBreakpoints,
|
useLayoutBreakpoints,
|
||||||
useTheme,
|
useTheme,
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
import {useDialogContext} from '#/components/Dialog'
|
import {useDialogContext} from '#/components/Dialog'
|
||||||
import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const'
|
import {CENTER_COLUMN_OFFSET, SCROLLBAR_OFFSET} from '#/components/Layout/const'
|
||||||
import {ScrollbarOffsetContext} from '#/components/Layout/context'
|
import {ScrollbarOffsetContext} from '#/components/Layout/context'
|
||||||
import {IS_WEB} from '#/env'
|
import {IS_IOS, IS_WEB} from '#/env'
|
||||||
|
|
||||||
export * from '#/components/Layout/const'
|
export * from '#/components/Layout/const'
|
||||||
export * as Header from '#/components/Layout/Header'
|
export * as Header from '#/components/Layout/Header'
|
||||||
@@ -63,9 +63,7 @@ export const Screen = memo(function Screen({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export type ContentProps = AnimatedScrollViewProps & {
|
export type ContentProps = ScrollViewProps & {
|
||||||
style?: StyleProp<ViewStyle>
|
|
||||||
contentContainerStyle?: StyleProp<ViewStyle>
|
|
||||||
ignoreTabletLayoutOffset?: boolean
|
ignoreTabletLayoutOffset?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +71,7 @@ export type ContentProps = AnimatedScrollViewProps & {
|
|||||||
* Default scroll view for simple pages
|
* Default scroll view for simple pages
|
||||||
*/
|
*/
|
||||||
export const Content = memo(
|
export const Content = memo(
|
||||||
forwardRef<Animated.ScrollView, ContentProps>(function Content(
|
forwardRef<ScrollView, ContentProps>(function Content(
|
||||||
{
|
{
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
@@ -85,39 +83,33 @@ export const Content = memo(
|
|||||||
) {
|
) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {footerHeight} = useShellLayout()
|
const {footerHeight} = useShellLayout()
|
||||||
const animatedProps = useAnimatedProps(() => {
|
|
||||||
return {
|
|
||||||
scrollIndicatorInsets: {
|
|
||||||
bottom: footerHeight.get(),
|
|
||||||
top: 0,
|
|
||||||
right: 1,
|
|
||||||
},
|
|
||||||
} satisfies AnimatedScrollViewProps
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.ScrollView
|
<ScrollView
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id="content"
|
id="content"
|
||||||
automaticallyAdjustsScrollIndicatorInsets={false}
|
automaticallyAdjustsScrollIndicatorInsets={false}
|
||||||
|
scrollIndicatorInsets={{
|
||||||
|
bottom: footerHeight,
|
||||||
|
top: 0,
|
||||||
|
right: 1,
|
||||||
|
}}
|
||||||
indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'}
|
indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'}
|
||||||
// sets the scroll inset to the height of the footer
|
|
||||||
animatedProps={animatedProps}
|
|
||||||
style={[scrollViewStyles.common, style]}
|
style={[scrollViewStyles.common, style]}
|
||||||
|
contentInset={ios({top: 0, left: 0, bottom: footerHeight, right: 0})}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
scrollViewStyles.contentContainer,
|
!IS_IOS && {paddingBottom: footerHeight},
|
||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
]}
|
]}
|
||||||
{...props}>
|
{...props}>
|
||||||
{IS_WEB ? (
|
{IS_WEB ? (
|
||||||
<Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}>
|
<Center ignoreTabletLayoutOffset={ignoreTabletLayoutOffset}>
|
||||||
{/* @ts-expect-error web only -esb */}
|
|
||||||
{children}
|
{children}
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
children
|
children
|
||||||
)}
|
)}
|
||||||
</Animated.ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -126,9 +118,6 @@ const scrollViewStyles = StyleSheet.create({
|
|||||||
common: {
|
common: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
contentContainer: {
|
|
||||||
paddingBottom: 100,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
|
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
|
||||||
@@ -147,11 +136,14 @@ export const KeyboardAwareContent = memo(function LayoutKeyboardAwareContent({
|
|||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
...props
|
...props
|
||||||
}: KeyboardAwareContentProps) {
|
}: KeyboardAwareContentProps) {
|
||||||
|
const {footerHeight} = useShellLayout()
|
||||||
return (
|
return (
|
||||||
<KeyboardAwareScrollView
|
<KeyboardAwareScrollView
|
||||||
style={[scrollViewStyles.common, style]}
|
style={[scrollViewStyles.common, style]}
|
||||||
|
contentInset={ios({top: 0, left: 0, bottom: footerHeight, right: 0})}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
scrollViewStyles.contentContainer,
|
android({paddingBottom: footerHeight}),
|
||||||
|
web({paddingBottom: footerHeight}),
|
||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
]}
|
]}
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ export function useMinimalShellFooterTransform() {
|
|||||||
opacity: Math.pow(1 - footerModeValue, 2),
|
opacity: Math.pow(1 - footerModeValue, 2),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: interpolate(
|
translateY: interpolate(footerModeValue, [0, 1], [0, footerHeight]),
|
||||||
footerModeValue,
|
|
||||||
[0, 1],
|
|
||||||
[0, footerHeight.get()],
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {useWindowDimensions, View} from 'react-native'
|
import {useWindowDimensions, View} from 'react-native'
|
||||||
import Animated, {useAnimatedStyle} from 'react-native-reanimated'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
import {Trans} from '@lingui/react/macro'
|
||||||
|
|
||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
@@ -69,6 +68,8 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
hasSession,
|
hasSession,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [promptHeight, setPromptHeight] = useState(43)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* One query to rule them all
|
* One query to rule them all
|
||||||
*/
|
*/
|
||||||
@@ -543,6 +544,7 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const defaultListFooterHeight = hasParents ? windowHeight - 200 : undefined
|
const defaultListFooterHeight = hasParents ? windowHeight - 200 : undefined
|
||||||
|
const hasPrompt = !gtMobile && canReply && hasSession
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostThreadContextProvider context={thread.context}>
|
<PostThreadContextProvider context={thread.context}>
|
||||||
@@ -630,29 +632,37 @@ export function PostThread({uri}: {uri: string}) {
|
|||||||
* Default: 50
|
* Default: 50
|
||||||
*/
|
*/
|
||||||
updateCellsBatchingPeriod={100}
|
updateCellsBatchingPeriod={100}
|
||||||
|
footerExtensionHeight={hasPrompt ? promptHeight : 0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!gtMobile && canReply && hasSession && (
|
{hasPrompt && (
|
||||||
<MobileComposePrompt onPressReply={onReplyToAnchor} />
|
<MobileComposePrompt
|
||||||
|
onPressReply={onReplyToAnchor}
|
||||||
|
onLayout={evt =>
|
||||||
|
setPromptHeight(Math.round(evt.nativeEvent.layout.height))
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</PostThreadContextProvider>
|
</PostThreadContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MobileComposePrompt({onPressReply}: {onPressReply: () => unknown}) {
|
function MobileComposePrompt({
|
||||||
|
onPressReply,
|
||||||
|
onLayout,
|
||||||
|
}: {
|
||||||
|
onPressReply: () => unknown
|
||||||
|
onLayout?: (event: LayoutChangeEvent) => void
|
||||||
|
}) {
|
||||||
const {footerHeight} = useShellLayout()
|
const {footerHeight} = useShellLayout()
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
|
||||||
return {
|
|
||||||
bottom: footerHeight.get(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[a.fixed, a.left_0, a.right_0, animatedStyle]}>
|
<View
|
||||||
|
style={[a.fixed, a.left_0, a.right_0, {bottom: footerHeight}]}
|
||||||
|
onLayout={onLayout}>
|
||||||
<ThreadComposePrompt onPressCompose={onPressReply} />
|
<ThreadComposePrompt onPressCompose={onPressReply} />
|
||||||
</Animated.View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import {createContext, useContext, useMemo} from 'react'
|
import {createContext, useContext, useMemo, useState} from 'react'
|
||||||
import {type SharedValue, useSharedValue} from 'react-native-reanimated'
|
import {type SharedValue, useSharedValue} from 'react-native-reanimated'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
type StateContext = {
|
import {clamp} from '#/lib/numbers'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import {atoms as a, platform, useBreakpoints} from '#/alf'
|
||||||
|
|
||||||
|
type LayoutContextValue = {
|
||||||
headerHeight: SharedValue<number>
|
headerHeight: SharedValue<number>
|
||||||
footerHeight: SharedValue<number>
|
footerHeight: number
|
||||||
|
setFooterHeight: (height: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateContext = createContext<StateContext>({
|
const LayoutContext = createContext<LayoutContextValue>({
|
||||||
headerHeight: {
|
headerHeight: {
|
||||||
value: 0,
|
value: 0,
|
||||||
addListener() {},
|
addListener() {},
|
||||||
@@ -17,34 +23,46 @@ const stateContext = createContext<StateContext>({
|
|||||||
},
|
},
|
||||||
set() {},
|
set() {},
|
||||||
},
|
},
|
||||||
footerHeight: {
|
footerHeight: 0,
|
||||||
value: 0,
|
setFooterHeight: () => {},
|
||||||
addListener() {},
|
|
||||||
removeListener() {},
|
|
||||||
modify() {},
|
|
||||||
get() {
|
|
||||||
return 0
|
|
||||||
},
|
|
||||||
set() {},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
stateContext.displayName = 'ShellLayoutContext'
|
LayoutContext.displayName = 'ShellLayoutContext'
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const headerHeight = useSharedValue(0)
|
const headerHeight = useSharedValue(0)
|
||||||
const footerHeight = useSharedValue(0)
|
const insets = useSafeAreaInsets()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const [footerHeight, setFooterHeight] = useState(() =>
|
||||||
|
platform({
|
||||||
|
// try and precisely guess the footer height, then round it to 4 decimal places
|
||||||
|
// to remove floating point imprecision. if we can guess it exactly,
|
||||||
|
// we get to skip a rerender
|
||||||
|
native: round4dp(
|
||||||
|
47 + a.border.borderWidth + clamp(insets.bottom, 15, 60),
|
||||||
|
),
|
||||||
|
web: 58,
|
||||||
|
default: 0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
headerHeight,
|
headerHeight,
|
||||||
footerHeight,
|
footerHeight: isWeb && gtMobile ? 0 : footerHeight,
|
||||||
|
setFooterHeight: (height: number) => setFooterHeight(round4dp(height)),
|
||||||
}),
|
}),
|
||||||
[headerHeight, footerHeight],
|
[headerHeight, footerHeight, setFooterHeight, gtMobile],
|
||||||
)
|
)
|
||||||
|
|
||||||
return <stateContext.Provider value={value}>{children}</stateContext.Provider>
|
return (
|
||||||
|
<LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useShellLayout() {
|
export function useShellLayout() {
|
||||||
return useContext(stateContext)
|
return useContext(LayoutContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
function round4dp(value: number) {
|
||||||
|
return Math.round(value * 10000) / 10000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import {useDedupe} from '#/lib/hooks/useDedupe'
|
|||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||||
import {addStyle} from '#/lib/styles'
|
import {addStyle} from '#/lib/styles'
|
||||||
import {useTheme} from '#/alf'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
import {ios, useTheme} from '#/alf'
|
||||||
import {useLightbox} from '#/components/Lightbox/state'
|
import {useLightbox} from '#/components/Lightbox/state'
|
||||||
import {IS_IOS} from '#/env'
|
import {IS_ANDROID, IS_IOS} from '#/env'
|
||||||
import {FlatList_INTERNAL} from './Views'
|
import {FlatList_INTERNAL} from './Views'
|
||||||
|
|
||||||
export type ListMethods = FlatList_INTERNAL
|
export type ListMethods = FlatList_INTERNAL
|
||||||
@@ -39,6 +40,7 @@ export type ListProps<ItemT = any> = Omit<
|
|||||||
disableFullWindowScroll?: boolean
|
disableFullWindowScroll?: boolean
|
||||||
sideBorders?: boolean
|
sideBorders?: boolean
|
||||||
progressViewOffset?: number
|
progressViewOffset?: number
|
||||||
|
footerExtensionHeight?: number
|
||||||
}
|
}
|
||||||
export type ListRef = React.RefObject<FlatList_INTERNAL | null>
|
export type ListRef = React.RefObject<FlatList_INTERNAL | null>
|
||||||
|
|
||||||
@@ -55,6 +57,8 @@ let List = forwardRef<ListMethods, ListProps>(
|
|||||||
style,
|
style,
|
||||||
progressViewOffset,
|
progressViewOffset,
|
||||||
automaticallyAdjustsScrollIndicatorInsets = false,
|
automaticallyAdjustsScrollIndicatorInsets = false,
|
||||||
|
contentContainerStyle,
|
||||||
|
footerExtensionHeight = 0,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -63,6 +67,7 @@ let List = forwardRef<ListMethods, ListProps>(
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const dedupe = useDedupe(400)
|
const dedupe = useDedupe(400)
|
||||||
const scrollsToTop = useAllowScrollToTop()
|
const scrollsToTop = useAllowScrollToTop()
|
||||||
|
const {footerHeight} = useShellLayout()
|
||||||
|
|
||||||
const handleScrolledDownChange = useNonReactiveCallback(
|
const handleScrolledDownChange = useNonReactiveCallback(
|
||||||
(didScrollDown: boolean) => {
|
(didScrollDown: boolean) => {
|
||||||
@@ -159,14 +164,26 @@ let List = forwardRef<ListMethods, ListProps>(
|
|||||||
onViewableItemsChanged={onViewableItemsChanged}
|
onViewableItemsChanged={onViewableItemsChanged}
|
||||||
viewabilityConfig={viewabilityConfig}
|
viewabilityConfig={viewabilityConfig}
|
||||||
{...props}
|
{...props}
|
||||||
|
contentContainerStyle={[
|
||||||
|
IS_ANDROID && {paddingBottom: footerHeight + footerExtensionHeight},
|
||||||
|
contentContainerStyle,
|
||||||
|
]}
|
||||||
automaticallyAdjustsScrollIndicatorInsets={
|
automaticallyAdjustsScrollIndicatorInsets={
|
||||||
automaticallyAdjustsScrollIndicatorInsets
|
automaticallyAdjustsScrollIndicatorInsets
|
||||||
}
|
}
|
||||||
scrollIndicatorInsets={{
|
scrollIndicatorInsets={{
|
||||||
top: headerOffset,
|
top: headerOffset,
|
||||||
right: 1,
|
right: 1,
|
||||||
|
bottom: footerHeight + footerExtensionHeight,
|
||||||
...props.scrollIndicatorInsets,
|
...props.scrollIndicatorInsets,
|
||||||
}}
|
}}
|
||||||
|
contentInset={ios({
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: footerHeight + footerExtensionHeight,
|
||||||
|
...props.contentInset,
|
||||||
|
})}
|
||||||
indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'}
|
indicatorStyle={t.scheme === 'dark' ? 'white' : 'black'}
|
||||||
contentOffset={contentOffset}
|
contentOffset={contentOffset}
|
||||||
refreshControl={refreshControl}
|
refreshControl={refreshControl}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const safeAreaInsets = useSafeAreaInsets()
|
const safeAreaInsets = useSafeAreaInsets()
|
||||||
const {footerHeight} = useShellLayout()
|
const {setFooterHeight} = useShellLayout()
|
||||||
const {isAtHome, isAtSearch, isAtNotifications, isAtMyProfile, isAtMessages} =
|
const {isAtHome, isAtSearch, isAtNotifications, isAtMyProfile, isAtMessages} =
|
||||||
useNavigationTabState()
|
useNavigationTabState()
|
||||||
const numUnreadNotifications = useUnreadNotifications()
|
const numUnreadNotifications = useUnreadNotifications()
|
||||||
@@ -154,9 +154,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||||||
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 60)},
|
{paddingBottom: clamp(safeAreaInsets.bottom, 15, 60)},
|
||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
]}
|
]}
|
||||||
onLayout={e => {
|
onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)}>
|
||||||
footerHeight.set(e.nativeEvent.layout.height)
|
|
||||||
}}>
|
|
||||||
{hasSession ? (
|
{hasSession ? (
|
||||||
<>
|
<>
|
||||||
<Btn
|
<Btn
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function BottomBarWeb() {
|
|||||||
const footerMinimalShellTransform = useMinimalShellFooterTransform()
|
const footerMinimalShellTransform = useMinimalShellFooterTransform()
|
||||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
const {footerHeight} = useShellLayout()
|
const {setFooterHeight} = useShellLayout()
|
||||||
const hideBorder = useHideBottomBarBorder()
|
const hideBorder = useHideBottomBarBorder()
|
||||||
const accountSwitchControl = useDialogControl()
|
const accountSwitchControl = useDialogControl()
|
||||||
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
const {data: profile} = useProfileQuery({did: currentAccount?.did})
|
||||||
@@ -93,7 +93,7 @@ export function BottomBarWeb() {
|
|||||||
: t.atoms.border_contrast_low,
|
: t.atoms.border_contrast_low,
|
||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
]}
|
]}
|
||||||
onLayout={event => footerHeight.set(event.nativeEvent.layout.height)}>
|
onLayout={event => setFooterHeight(event.nativeEvent.layout.height)}>
|
||||||
{hasSession ? (
|
{hasSession ? (
|
||||||
<>
|
<>
|
||||||
<NavItem routeName="Home" href="/">
|
<NavItem routeName="Home" href="/">
|
||||||
|
|||||||
Reference in New Issue
Block a user