Skip to content

Commit

Permalink
feat: subscription list; TimelineSelectorHeader and TimelineSelectorL…
Browse files Browse the repository at this point in the history
…ist components
  • Loading branch information
DIYgod committed Feb 13, 2025
1 parent 1327f56 commit 676e8bc
Show file tree
Hide file tree
Showing 21 changed files with 481 additions and 289 deletions.
3 changes: 1 addition & 2 deletions apps/mobile/src/modules/context-menu/entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ import {
} from "@/src/components/native/webview/EntryContentWebView"
import { openLink } from "@/src/lib/native"
import { toast } from "@/src/lib/toast"
import { useSelectedView } from "@/src/modules/screen/atoms"
import { useIsEntryStarred } from "@/src/store/collection/hooks"
import { collectionSyncService } from "@/src/store/collection/store"
import { useEntry } from "@/src/store/entry/hooks"
import { unreadSyncService } from "@/src/store/unread/store"

import { useSelectedView } from "../feed-drawer/atoms"

export const EntryItemContextMenu = ({ id, children }: PropsWithChildren<{ id: string }>) => {
const entry = useEntry(id)
const view = useSelectedView()
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/modules/context-menu/feeds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { unreadSyncService } from "@/src/store/unread/store"
export const SubscriptionFeedItemContextMenu: FC<
PropsWithChildren & {
id: string
view: FeedViewType
view?: FeedViewType
}
> = ({ id, children, view }) => {
const allCategories = useListSubscriptionCategory(view)
Expand Down
44 changes: 1 addition & 43 deletions apps/mobile/src/modules/entry-list/action.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,9 @@
import { Link } from "expo-router"
import { Text, TouchableOpacity, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"

import { AddCuteReIcon } from "@/src/icons/add_cute_re"
import { LayoutLeftbarOpenCuteReIcon } from "@/src/icons/layout_leftbar_open_cute_re"
import { accentColor } from "@/src/theme/colors"

import {
setIsLoadingArchivedEntries,
useFeedDrawer,
useIsLoadingArchivedEntries,
} from "../feed-drawer/atoms"

const useActionPadding = () => {
const insets = useSafeAreaInsets()
return { paddingLeft: insets.left + 12, paddingRight: insets.right + 12 }
}

export function HomeLeftAction() {
const { openDrawer } = useFeedDrawer()

const insets = useActionPadding()

return (
<TouchableOpacity
onPress={openDrawer}
className="flex-row items-center"
style={{ paddingLeft: insets.paddingLeft }}
>
<LayoutLeftbarOpenCuteReIcon color={accentColor} />
</TouchableOpacity>
)
}

export function HomeRightAction() {
const insets = useActionPadding()

return (
<View className="flex-row items-center" style={{ paddingRight: insets.paddingRight }}>
<Link asChild href="/add">
<TouchableOpacity className="size-6">
<AddCuteReIcon color={accentColor} />
</TouchableOpacity>
</Link>
</View>
)
}
} from "@/src/modules/screen/atoms"

export function LoadArchiveButton() {
const isLoadingArchivedEntries = useIsLoadingArchivedEntries()
Expand Down
3 changes: 1 addition & 2 deletions apps/mobile/src/modules/entry-list/entry-list-gird.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"
import { NavigationContext } from "@/src/components/common/SafeNavigationScrollView"
import { ThemedText } from "@/src/components/common/ThemedText"
import { ItemPressable } from "@/src/components/ui/pressable/item-pressable"
import { useFetchEntriesControls, useSelectedView } from "@/src/modules/screen/atoms"
import { useEntry } from "@/src/store/entry/hooks"
import { debouncedFetchEntryContentByStream } from "@/src/store/entry/store"

import { useFetchEntriesControls, useSelectedView } from "../feed-drawer/atoms"

export function EntryListContentGrid({
entryIds,
...rest
Expand Down
101 changes: 15 additions & 86 deletions apps/mobile/src/modules/entry-list/entry-list.tsx
Original file line number Diff line number Diff line change
@@ -1,140 +1,69 @@
import { FeedViewType } from "@follow/constants"
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"
import { router } from "expo-router"
import { useCallback, useContext, useMemo } from "react"
import type { NativeScrollEvent, NativeSyntheticEvent } from "react-native"
import { RefreshControl, StyleSheet, Text, useAnimatedValue, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useColor } from "react-native-uikit-colors"
import { useCallback, useMemo } from "react"
import { StyleSheet, Text, View } from "react-native"

import {
NavigationBlurEffectHeader,
NavigationContext,
} from "@/src/components/common/SafeNavigationScrollView"
import { setWebViewEntry } from "@/src/components/native/webview/EntryContentWebView"
import { ItemPressable } from "@/src/components/ui/pressable/item-pressable"
import { useDefaultHeaderHeight } from "@/src/hooks/useDefaultHeaderHeight"
import { EntryItemContextMenu } from "@/src/modules/context-menu/entry"
import { LoadArchiveButton } from "@/src/modules/entry-list/action"
import { EntryListContentGrid } from "@/src/modules/entry-list/entry-list-gird"
import {
useEntryListContext,
useFetchEntriesControls,
useSelectedFeedTitle,
useSelectedView,
} from "@/src/modules/feed-drawer/atoms"
} from "@/src/modules/screen/atoms"
import { TimelineSelectorHeader } from "@/src/modules/screen/timeline-selector-header"
import { TimelineSelectorList } from "@/src/modules/screen/timeline-selector-list"
import { useEntry } from "@/src/store/entry/hooks"
import { debouncedFetchEntryContentByStream } from "@/src/store/entry/store"

import { EntryItemContextMenu } from "../context-menu/entry"
import { ViewSelector } from "../feed-drawer/view-selector"
import { HomeLeftAction, HomeRightAction, LoadArchiveButton } from "./action"
import { EntryListContentGrid } from "./entry-list-gird"

const headerHideableBottomHeight = 58

export function EntryListScreen({ entryIds }: { entryIds: string[] }) {
const scrollY = useAnimatedValue(0)
const view = useSelectedView()
const viewTitle = useSelectedFeedTitle()
const screenType = useEntryListContext().type

const isFeed = screenType === "feed"
const isTimeline = screenType === "timeline"
return (
<NavigationContext.Provider value={useMemo(() => ({ scrollY }), [scrollY])}>
<NavigationBlurEffectHeader
headerBackTitle={isFeed ? "Subscriptions" : undefined}
headerShown
title={viewTitle}
headerLeft={useMemo(
() => (isTimeline ? () => <HomeLeftAction /> : undefined),
[isTimeline],
)}
headerRight={useMemo(
() => (isTimeline ? () => <HomeRightAction /> : undefined),
[isTimeline],
)}
headerHideableBottomHeight={isTimeline ? headerHideableBottomHeight : undefined}
headerHideableBottom={isTimeline ? ViewSelector : undefined}
/>
<TimelineSelectorHeader>
{view === FeedViewType.Pictures || view === FeedViewType.Videos ? (
<EntryListContentGrid entryIds={entryIds} />
) : (
<EntryListContent entryIds={entryIds} />
)}
</NavigationContext.Provider>
</TimelineSelectorHeader>
)
}

function EntryListContent({ entryIds }: { entryIds: string[] }) {
const screenType = useEntryListContext().type

const insets = useSafeAreaInsets()

const originalDefaultHeaderHeight = useDefaultHeaderHeight()
const headerHeight =
screenType === "timeline"
? originalDefaultHeaderHeight + headerHideableBottomHeight
: originalDefaultHeaderHeight
const scrollY = useContext(NavigationContext)?.scrollY

const { fetchNextPage, isFetching, refetch, isRefetching } = useFetchEntriesControls()

const onScroll = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollY?.setValue(e.nativeEvent.contentOffset.y)
},
[scrollY],
)

const renderItem = useCallback(
({ item: id }: ListRenderItemInfo<string>) => <EntryItem key={id} entryId={id} />,
[],
)

const tabBarHeight = useBottomTabBarHeight()

const ListFooterComponent = useMemo(
() =>
isFetching ? <EntryItemSkeleton /> : screenType === "feed" ? <LoadArchiveButton /> : null,
[isFetching, screenType],
)

const systemFill = useColor("secondaryLabel")

return (
<FlashList
refreshControl={
<RefreshControl
progressViewOffset={headerHeight}
// // FIXME: not sure why we need set tintColor manually here, otherwise we can not see the refresh indicator
tintColor={systemFill}
onRefresh={() => {
refetch()
}}
refreshing={isRefetching}
/>
}
onScroll={onScroll}
<TimelineSelectorList
onRefresh={() => {
refetch()
}}
isRefetching={isRefetching}
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={ListFooterComponent}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/modules/entry-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { FeedViewType } from "@follow/constants"
import { useIsFocused } from "@react-navigation/native"
import { useEffect } from "react"

import { useSelectedFeed, useSetDrawerSwipeDisabled } from "@/src/modules/feed-drawer/atoms"
import { useSelectedFeed, useSetDrawerSwipeDisabled } from "@/src/modules/screen/atoms"
import {
useEntryIdsByCategory,
useEntryIdsByFeedId,
Expand Down
40 changes: 40 additions & 0 deletions apps/mobile/src/modules/screen/action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Link } from "expo-router"
import { TouchableOpacity, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"

import { AddCuteReIcon } from "@/src/icons/add_cute_re"
import { LayoutLeftbarOpenCuteReIcon } from "@/src/icons/layout_leftbar_open_cute_re"
import { accentColor } from "@/src/theme/colors"

const useActionPadding = () => {
const insets = useSafeAreaInsets()
return { paddingLeft: insets.left + 12, paddingRight: insets.right + 12 }
}

export function HomeLeftAction() {
const insets = useActionPadding()

return (
<TouchableOpacity
// onPress={openDrawer}
className="flex-row items-center"
style={{ paddingLeft: insets.paddingLeft }}
>
<LayoutLeftbarOpenCuteReIcon color={accentColor} />
</TouchableOpacity>
)
}

export function HomeRightAction() {
const insets = useActionPadding()

return (
<View className="flex-row items-center" style={{ paddingRight: insets.paddingRight }}>
<Link asChild href="/add">
<TouchableOpacity className="size-6">
<AddCuteReIcon color={accentColor} />
</TouchableOpacity>
</Link>
</View>
)
}
Loading

0 comments on commit 676e8bc

Please sign in to comment.