Skip to content

Commit

Permalink
Merge branch 'dev' into feat/mobile-masonry-grid
Browse files Browse the repository at this point in the history
  • Loading branch information
hyoban authored Jan 22, 2025
2 parents cd89c77 + 041318a commit 2c3b585
Show file tree
Hide file tree
Showing 15 changed files with 388 additions and 86 deletions.
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"react-hook-form": "7.54.0",
"react-native": "0.76.5",
"react-native-bouncy-checkbox": "4.1.2",
"react-native-color-matrix-image-filters": "^7.0.2",
"react-native-context-menu-view": "1.16.0",
"react-native-gesture-handler": "~2.20.2",
"react-native-image-colors": "2.4.0",
Expand Down
20 changes: 12 additions & 8 deletions apps/mobile/src/components/common/SafeNavigationScrollView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"
import { useHeaderHeight } from "@react-navigation/elements"
import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"
import { router, Stack, useNavigation } from "expo-router"
import type { FC, PropsWithChildren } from "react"
import { createContext, useContext, useEffect, useMemo, useState } from "react"
Expand Down Expand Up @@ -28,7 +29,7 @@ type SafeNavigationScrollViewProps = Omit<ScrollViewProps, "onScroll"> & {
withTopInset?: boolean
withBottomInset?: boolean
} & PropsWithChildren
const NavigationContext = createContext<{
export const NavigationContext = createContext<{

Check warning on line 32 in apps/mobile/src/components/common/SafeNavigationScrollView.tsx

View workflow job for this annotation

GitHub Actions / auto-fix

Fast refresh only works when a file only exports components. Move your React context(s) to a separate file

Check warning on line 32 in apps/mobile/src/components/common/SafeNavigationScrollView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Fast refresh only works when a file only exports components. Move your React context(s) to a separate file
scrollY: RNAnimated.Value
} | null>(null)

Expand Down Expand Up @@ -66,10 +67,12 @@ export const SafeNavigationScrollView: FC<SafeNavigationScrollViewProps> = ({
)
}

export interface NavigationBlurEffectHeaderProps {
title?: string
}
export const NavigationBlurEffectHeader = (props: NavigationBlurEffectHeaderProps) => {
export const NavigationBlurEffectHeader = ({
blurThreshold = 0,
...props
}: NativeStackNavigationOptions & {
blurThreshold?: number
}) => {
const label = useColor("label")

const canBack = useNavigation().canGoBack()
Expand All @@ -82,13 +85,13 @@ export const NavigationBlurEffectHeader = (props: NavigationBlurEffectHeaderProp

useEffect(() => {
const id = scrollY.addListener(({ value }) => {
setOpacity(Math.min(1, Math.max(0, Math.min(1, value / 10))))
setOpacity(Math.min(1, Math.max(0, Math.min(1, (value + blurThreshold) / 10))))
})

return () => {
scrollY.removeListener(id)
}
}, [scrollY])
}, [blurThreshold, scrollY])

return (
<Stack.Screen
Expand All @@ -112,7 +115,8 @@ export const NavigationBlurEffectHeader = (props: NavigationBlurEffectHeaderProp
</TouchableOpacity>
)
: undefined,
title: props.title,

...props,
}}
/>
)
Expand Down
13 changes: 10 additions & 3 deletions apps/mobile/src/components/ui/icon/fallback-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { getBackgroundGradient, isCJKChar } from "@follow/utils"
import { Image } from "expo-image"
import { LinearGradient } from "expo-linear-gradient"
import { useMemo, useState } from "react"
import type { StyleProp, TextStyle, ViewStyle } from "react-native"
import type { DimensionValue, StyleProp, TextStyle, ViewStyle } from "react-native"
import { StyleSheet, Text, View } from "react-native"
import { useColor } from "react-native-uikit-colors"

export const FallbackIcon = ({
title,
Expand All @@ -13,14 +14,16 @@ export const FallbackIcon = ({
style,
textClassName,
textStyle,
gray,
}: {
title: string
url?: string
size: number
size: DimensionValue
className?: string
style?: StyleProp<ViewStyle>
textClassName?: string
textStyle?: StyleProp<TextStyle>
gray?: boolean
}) => {
const colors = useMemo(() => getBackgroundGradient(title || url || ""), [title, url])
const sizeStyle = useMemo(() => ({ width: size, height: size }), [size])
Expand All @@ -36,10 +39,14 @@ export const FallbackIcon = ({
)
}, [title, textStyle, textClassName])

const grayColor = useColor("gray2")

return (
<LinearGradient
className={className}
colors={[bgAccent!, bgAccentLight!, bgAccentUltraLight!]}
colors={
gray ? [grayColor, grayColor, grayColor] : [bgAccent!, bgAccentLight!, bgAccentUltraLight!]
}
locations={[0, 0.99, 1]}
style={[sizeStyle, styles.container, style]}
>
Expand Down
7 changes: 2 additions & 5 deletions apps/mobile/src/components/ui/pressable/item-pressable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cn, composeEventHandlers } from "@follow/utils"
import { composeEventHandlers } from "@follow/utils"
import { memo, useEffect, useState } from "react"
import type { Pressable } from "react-native"
import { StyleSheet } from "react-native"
Expand Down Expand Up @@ -49,10 +49,7 @@ export const ItemPressable: typeof Pressable = memo(({ children, ...props }) =>
onPressOut={composeEventHandlers(props.onPressOut, () => setIsPressing(false))}
onHoverIn={composeEventHandlers(props.onHoverIn, () => setIsPressing(true))}
onHoverOut={composeEventHandlers(props.onHoverOut, () => setIsPressing(false))}
className={cn(
// isPressing ? "bg-system-fill" : "bg-secondary-system-grouped-background",
props.className,
)}
className={props.className}
style={StyleSheet.flatten([colorStyle, props.style])}
>
{children}
Expand Down
115 changes: 84 additions & 31 deletions apps/mobile/src/modules/entry-list/entry-list.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import type { FeedViewType } from "@follow/constants"
import { useTypeScriptHappyCallback } from "@follow/hooks"
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs"
import { useHeaderHeight } from "@react-navigation/elements"
import { useIsFocused } from "@react-navigation/native"
import { router, Stack } from "expo-router"
import { useCallback, useEffect } from "react"
import { Image, Text, View } from "react-native"
import { FlashList } from "@shopify/flash-list"
import { router } from "expo-router"
import { useCallback, useEffect, useMemo } from "react"
import { Image, StyleSheet, Text, useAnimatedValue, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"

import { BlurEffect } from "@/src/components/common/BlurEffect"
import { SafeNavigationScrollView } from "@/src/components/common/SafeNavigationScrollView"
import {
NavigationBlurEffectHeader,
NavigationContext,
} from "@/src/components/common/SafeNavigationScrollView"
import { ItemPressable } from "@/src/components/ui/pressable/item-pressable"
import {
useSelectedFeed,
Expand All @@ -16,9 +23,12 @@ import {
useEntry,
useEntryIdsByCategory,
useEntryIdsByFeedId,
useEntryIdsByInboxId,
useEntryIdsByView,
} from "@/src/store/entry/hooks"
import { FEED_COLLECTION_LIST } from "@/src/store/entry/utils"
import { useFeed } from "@/src/store/feed/hooks"
import { useInbox } from "@/src/store/inbox/hooks"
import { useList } from "@/src/store/list/hooks"

import { LeftAction, RightAction } from "./action"
Expand Down Expand Up @@ -49,23 +59,25 @@ export function EntryList() {
case "list": {
return <ListEntryList listId={selectedFeed.listId} />
}
// case "inbox": {
// return <InboxEntryList inboxId={selectedFeed.inboxId} />
// }
case "inbox": {
return <InboxEntryList inboxId={selectedFeed.inboxId} />
}
// No default
}
}

function ViewEntryList({ viewId }: { viewId: FeedViewType }) {
const entryIds = useEntryIdsByView(viewId)
const viewDef = useViewDefinition(viewId)

return <EntryListScreen title={viewDef.name} entryIds={entryIds} />
}

function FeedEntryList({ feedId }: { feedId: string }) {
const feed = useFeed(feedId)
const entryIds = useEntryIdsByFeedId(feedId)
return <EntryListScreen title={feed?.title ?? ""} entryIds={entryIds} />
const title = feedId === FEED_COLLECTION_LIST ? "Collections" : (feed?.title ?? "")
return <EntryListScreen title={title} entryIds={entryIds} />
}

function CategoryEntryList({ categoryName }: { categoryName: string }) {
Expand All @@ -80,28 +92,72 @@ function ListEntryList({ listId }: { listId: string }) {
return <EntryListScreen title={list.title} entryIds={list.entryIds ?? []} />
}

function InboxEntryList({ inboxId }: { inboxId: string }) {
const inbox = useInbox(inboxId)
const entryIds = useEntryIdsByInboxId(inboxId)
return <EntryListScreen title={inbox?.title ?? "Inbox"} entryIds={entryIds} />
}

function EntryListScreen({ title, entryIds }: { title: string; entryIds: string[] }) {
const scrollY = useAnimatedValue(0)
const insets = useSafeAreaInsets()
const tabBarHeight = useBottomTabBarHeight()
const headerHeight = useHeaderHeight()
return (
<>
<Stack.Screen
options={{
headerShown: true,
headerTitle: title,
headerLeft: LeftAction,
headerRight: RightAction,
headerTransparent: true,
headerBackground: BlurEffect,
<NavigationContext.Provider value={useMemo(() => ({ scrollY }), [scrollY])}>
<NavigationBlurEffectHeader
headerShown
title={title}
headerLeft={useCallback(
() => (
<LeftAction />
),
[],
)}
headerRight={useCallback(
() => (
<RightAction />
),
[],
)}
/>
<FlashList
onScroll={useTypeScriptHappyCallback(
(e) => {
scrollY.setValue(e.nativeEvent.contentOffset.y)
},
[scrollY],
)}
data={entryIds}
renderItem={useTypeScriptHappyCallback(
({ item: id }) => (
<EntryItem key={id} entryId={id} />
),
[],
)}
scrollIndicatorInsets={{
top: headerHeight - insets.top,
bottom: tabBarHeight - insets.bottom,
}}
estimatedItemSize={100}
contentContainerStyle={{
paddingTop: headerHeight,
paddingBottom: tabBarHeight,
}}
ItemSeparatorComponent={ItemSeparator}
/>
</NavigationContext.Provider>
)
}

<SafeNavigationScrollView contentInsetAdjustmentBehavior="automatic" withTopInset>
<View className="flex">
{entryIds.map((id) => (
<EntryItem key={id} entryId={id} />
))}
</View>
</SafeNavigationScrollView>
</>
const ItemSeparator = () => {
return (
<View
className="bg-opaque-separator mx-4"
style={{
height: StyleSheet.hairlineWidth,
}}
/>
)
}

Expand All @@ -122,10 +178,7 @@ function EntryItem({ entryId }: { entryId: string }) {
const image = media?.[0]?.url

return (
<ItemPressable
className="bg-system-background flex flex-row items-center p-4"
onPress={handlePress}
>
<ItemPressable className="flex flex-row items-center p-4" onPress={handlePress}>
<View className="flex-1 space-y-2">
<Text numberOfLines={2} className="text-lg font-semibold text-zinc-900 dark:text-zinc-100">
{title}
Expand All @@ -148,7 +201,7 @@ function EntryItem({ entryId }: { entryId: string }) {

function EntryItemSkeleton() {
return (
<View className="bg-system-background flex flex-row items-center p-4">
<View className="bg-secondary-system-grouped-background flex flex-row items-center p-4">
<View className="flex flex-1 flex-col justify-between">
{/* Title skeleton */}
<View className="h-6 w-3/4 animate-pulse rounded-md bg-zinc-200 dark:bg-zinc-800" />
Expand Down
14 changes: 9 additions & 5 deletions apps/mobile/src/modules/feed-drawer/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function useSelectedCollection() {
}
export const selectCollection = (state: CollectionPanelState) => {
jotaiStore.set(collectionPanelStateAtom, state)
if (state.type === "view") {
if (state.type === "view" || state.type === "list") {
jotaiStore.set(selectedFeedAtom, state)
}
}
Expand All @@ -83,6 +83,10 @@ export type SelectedFeed =
type: "list"
listId: string
}
| {
type: "inbox"
inboxId: string
}

const selectedFeedAtom = atom<SelectedFeed>({
type: "view",
Expand All @@ -109,10 +113,10 @@ export function useSelectedFeed() {
payload = { listId: selectedFeed.listId }
break
}
// case "inbox": {
// payload = { inboxId: selectedFeed.inboxId }
// break
// }
case "inbox": {
payload = { inboxId: selectedFeed.inboxId }
break
}
// No default
}
usePrefetchEntries(payload)
Expand Down
Loading

0 comments on commit 2c3b585

Please sign in to comment.