refactor: improve navigation and component rendering in mobile app
- Update SafeNavigationScrollView to support custom headerLeft prop - Optimize EntryListScreen with memoized header actions and simplified conditions - Simplify EntryListContent by removing BottomTabBarHeightContext.Consumer - Add Link to Terms of Service in login screen - Wrap Feed screen with BottomTabBarHeightContext.Provider Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
f5618ddb3a
commit
ebea9d824f
|
|
@ -143,6 +143,7 @@ export const NavigationBlurEffectHeader = ({
|
|||
}))
|
||||
|
||||
const hideableBottom = headerHideableBottom?.()
|
||||
const { headerLeft, ...rest } = props
|
||||
|
||||
return (
|
||||
<Stack.Screen
|
||||
|
|
@ -154,13 +155,15 @@ export const NavigationBlurEffectHeader = ({
|
|||
),
|
||||
headerTransparent: true,
|
||||
|
||||
headerLeft: canBack
|
||||
? () => (
|
||||
<TouchableOpacity hitSlop={10} onPress={() => router.back()}>
|
||||
<MingcuteLeftLineIcon height={20} width={20} color={label} />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: undefined,
|
||||
headerLeft:
|
||||
headerLeft ??
|
||||
(canBack
|
||||
? () => (
|
||||
<TouchableOpacity hitSlop={10} onPress={() => router.back()}>
|
||||
<MingcuteLeftLineIcon height={20} width={20} color={label} />
|
||||
</TouchableOpacity>
|
||||
)
|
||||
: undefined),
|
||||
|
||||
header: headerHideableBottom
|
||||
? ({ options }) => {
|
||||
|
|
@ -176,7 +179,7 @@ export const NavigationBlurEffectHeader = ({
|
|||
}
|
||||
: undefined,
|
||||
|
||||
...props,
|
||||
...rest,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { BottomTabBarHeightContext } from "@react-navigation/bottom-tabs"
|
||||
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"
|
||||
import type { ListRenderItemInfo } from "@shopify/flash-list"
|
||||
import { FlashList } from "@shopify/flash-list"
|
||||
import { Image } from "expo-image"
|
||||
|
|
@ -38,24 +38,18 @@ export function EntryListScreen({ entryIds }: { entryIds: string[] }) {
|
|||
const viewTitle = useSelectedFeedTitle()
|
||||
const screenType = useEntryListContext().type
|
||||
|
||||
const isFeed = screenType === "feed"
|
||||
const isTimeline = screenType === "timeline"
|
||||
return (
|
||||
<NavigationContext.Provider value={useMemo(() => ({ scrollY }), [scrollY])}>
|
||||
<NavigationBlurEffectHeader
|
||||
headerBackTitle={screenType === "feed" ? "Subscriptions" : undefined}
|
||||
headerBackTitle={isFeed ? "Subscriptions" : undefined}
|
||||
headerShown
|
||||
headerTitle={viewTitle}
|
||||
headerLeft={useCallback(
|
||||
() => (screenType === "timeline" ? <LeftAction /> : null),
|
||||
[screenType],
|
||||
)}
|
||||
headerRight={useCallback(
|
||||
() => (screenType === "timeline" ? <RightAction /> : null),
|
||||
[screenType],
|
||||
)}
|
||||
headerHideableBottomHeight={
|
||||
screenType === "timeline" ? headerHideableBottomHeight : undefined
|
||||
}
|
||||
headerHideableBottom={screenType === "timeline" ? ViewSelector : undefined}
|
||||
headerLeft={useMemo(() => (isTimeline ? () => <LeftAction /> : undefined), [isTimeline])}
|
||||
headerRight={useMemo(() => (isTimeline ? () => <RightAction /> : undefined), [isTimeline])}
|
||||
headerHideableBottomHeight={isTimeline ? headerHideableBottomHeight : undefined}
|
||||
headerHideableBottom={isTimeline ? ViewSelector : undefined}
|
||||
/>
|
||||
{view === FeedViewType.Pictures || view === FeedViewType.Videos ? (
|
||||
<EntryListContentGrid entryIds={entryIds} />
|
||||
|
|
@ -66,7 +60,7 @@ export function EntryListScreen({ entryIds }: { entryIds: string[] }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function EntryListContent({ entryIds }: { entryIds: string[] }) {
|
||||
function EntryListContent({ entryIds }: { entryIds: string[] }) {
|
||||
const screenType = useEntryListContext().type
|
||||
|
||||
const insets = useSafeAreaInsets()
|
||||
|
|
@ -92,34 +86,31 @@ export function EntryListContent({ entryIds }: { entryIds: string[] }) {
|
|||
[],
|
||||
)
|
||||
|
||||
const tabBarHeight = useBottomTabBarHeight()
|
||||
return (
|
||||
<BottomTabBarHeightContext.Consumer>
|
||||
{(tabBarHeight) => (
|
||||
<FlashList
|
||||
onScroll={onScroll}
|
||||
data={entryIds}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(id) => id}
|
||||
onEndReached={() => {
|
||||
fetchNextPage()
|
||||
}}
|
||||
onViewableItemsChanged={({ viewableItems }) => {
|
||||
debouncedFetchEntryContentByStream(viewableItems.map((item) => item.key))
|
||||
}}
|
||||
scrollIndicatorInsets={{
|
||||
top: headerHeight - insets.top,
|
||||
bottom: tabBarHeight ? tabBarHeight - insets.bottom : undefined,
|
||||
}}
|
||||
estimatedItemSize={100}
|
||||
contentContainerStyle={{
|
||||
paddingTop: headerHeight,
|
||||
paddingBottom: tabBarHeight,
|
||||
}}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
ListFooterComponent={isFetchingNextPage ? <EntryItemSkeleton /> : null}
|
||||
/>
|
||||
)}
|
||||
</BottomTabBarHeightContext.Consumer>
|
||||
<FlashList
|
||||
onScroll={onScroll}
|
||||
data={entryIds}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={(id) => id}
|
||||
onEndReached={() => {
|
||||
fetchNextPage()
|
||||
}}
|
||||
onViewableItemsChanged={({ viewableItems }) => {
|
||||
debouncedFetchEntryContentByStream(viewableItems.map((item) => item.key))
|
||||
}}
|
||||
scrollIndicatorInsets={{
|
||||
top: headerHeight - insets.top,
|
||||
bottom: tabBarHeight ? tabBarHeight - insets.bottom : undefined,
|
||||
}}
|
||||
estimatedItemSize={100}
|
||||
contentContainerStyle={{
|
||||
paddingTop: headerHeight,
|
||||
paddingBottom: tabBarHeight,
|
||||
}}
|
||||
ItemSeparatorComponent={ItemSeparator}
|
||||
ListFooterComponent={isFetchingNextPage ? <EntryItemSkeleton /> : null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { router } from "expo-router"
|
||||
import { Link, router } from "expo-router"
|
||||
import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from "react"
|
||||
import { TouchableWithoutFeedback, View } from "react-native"
|
||||
import BouncyCheckbox from "react-native-bouncy-checkbox"
|
||||
|
|
@ -128,7 +128,11 @@ const TermsText = () => {
|
|||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger className="overflow-hidden rounded-full">
|
||||
<ThemedText className="text-secondary-label text-sm">
|
||||
I agree to the Terms of Service and Privacy Policy
|
||||
I agree to the{" "}
|
||||
<Link href="/terms" className="text-primary-label">
|
||||
Terms of Service
|
||||
</Link>{" "}
|
||||
and Privacy Policy
|
||||
</ThemedText>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { BottomTabBarHeightContext } from "@react-navigation/bottom-tabs"
|
||||
import { useLocalSearchParams } from "expo-router"
|
||||
import { useMemo } from "react"
|
||||
|
||||
|
|
@ -11,9 +12,11 @@ export default function Feed() {
|
|||
const entryIdsByCategory = useEntryIdsByCategory(feedIdOrCategory as string)
|
||||
return (
|
||||
<EntryListContext.Provider value={useMemo(() => ({ type: "feed" }), [])}>
|
||||
<EntryListScreen
|
||||
entryIds={entryIdsByFeedId.length > 0 ? entryIdsByFeedId : entryIdsByCategory}
|
||||
/>
|
||||
<BottomTabBarHeightContext.Provider value={0}>
|
||||
<EntryListScreen
|
||||
entryIds={entryIdsByFeedId.length > 0 ? entryIdsByFeedId : entryIdsByCategory}
|
||||
/>
|
||||
</BottomTabBarHeightContext.Provider>
|
||||
</EntryListContext.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue