Skip to content

Commit

Permalink
feat(rn): init setting page
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Jan 14, 2025
1 parent 225e470 commit 5f44d24
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 74 deletions.
2 changes: 2 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"@follow/utils": "workspace:*",
"@gorhom/portal": "1.0.14",
"@hookform/resolvers": "3.9.1",
"@infinite-list/data-model": "2.2.10",
"@infinite-list/react-native": "2.2.10",
"@react-native-cookies/cookies": "^6.2.1",
"@react-native-picker/picker": "2.9.0",
"@react-navigation/bottom-tabs": "^7.0.0",
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/src/components/common/ThemedBlurView.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { BlurViewProps } from "expo-blur"
import { BlurView } from "expo-blur"
import { useColorScheme } from "nativewind"
import type { FC } from "react"
import { forwardRef } from "react"

export const ThemedBlurView: FC<BlurViewProps> = ({ tint, ...rest }) => {
export const ThemedBlurView = forwardRef<BlurView, BlurViewProps>(({ tint, ...rest }, ref) => {
const { colorScheme } = useColorScheme()
return (
<BlurView
ref={ref}
tint={colorScheme === "light" ? "systemMaterialLight" : "systemMaterialDark"}
{...rest}
/>
)
}
})
61 changes: 61 additions & 0 deletions apps/mobile/src/components/ui/grouped/GroupedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { cn } from "@follow/utils"
import type { FC, PropsWithChildren } from "react"
import type { ViewProps } from "react-native"
import { Pressable, Text, View } from "react-native"

import { RightCuteReIcon } from "@/src/icons/right_cute_re"
import { useColor } from "@/src/theme/colors"

export const GroupedInsetListCard: FC<PropsWithChildren> = ({ children }) => {
return (
<View className="bg-secondary-system-grouped-background mx-4 flex-1 overflow-hidden rounded-[10px]">
{children}
</View>
)
}

export const GroupedInsetListSectionHeader: FC<{
label: string
}> = ({ label }) => {
return (
<View className="mx-4 h-[23px] px-5">
<Text className="text-secondary-label" ellipsizeMode="tail" numberOfLines={1}>
{label}
</Text>
</View>
)
}

export const GroupedInsetListItem: FC<PropsWithChildren & ViewProps> = ({ children, ...props }) => {
return (
<View {...props} className={cn("px-5 py-4", props.className)}>
{children}
</View>
)
}

export const GroupedInsetListNavigationLink: FC<{
label: string
icon?: React.ReactNode
onPress: () => void
}> = ({ label, icon, onPress }) => {
const tertiaryLabelColor = useColor("tertiaryLabel")

return (
<Pressable onPress={onPress}>
{({ pressed }) => (
<GroupedInsetListItem className={cn(pressed && "bg-system-fill")}>
<View className={"flex-row items-center"}>
<View className="flex-row items-center">
{icon}
<Text>{label}</Text>
</View>
<View className="-mr-2 ml-auto">
<RightCuteReIcon height={20} width={20} color={tertiaryLabelColor} />
</View>
</View>
</GroupedInsetListItem>
)}
</Pressable>
)
}
10 changes: 10 additions & 0 deletions apps/mobile/src/contexts/TabBarBackgroundContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createContext } from "react"
import type { SharedValue } from "react-native-reanimated"

interface TabBarBackgroundContextType {
opacity: SharedValue<number>
}

export const TabBarBackgroundContext = createContext<TabBarBackgroundContextType>({
opacity: null!,
})
25 changes: 25 additions & 0 deletions apps/mobile/src/modules/settings/SettingsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { View } from "react-native"

import {
GroupedInsetListCard,
GroupedInsetListNavigationLink,
} from "@/src/components/ui/grouped/GroupedList"

import { useSettingsNavigation } from "./hooks"

export const SettingsList = () => {
const navigation = useSettingsNavigation()

return (
<View className="bg-system-grouped-background flex-1 py-4">
<GroupedInsetListCard>
<GroupedInsetListNavigationLink
label="Account"
onPress={() => {
navigation.navigate("Account")
}}
/>
</GroupedInsetListCard>
</View>
)
}
28 changes: 28 additions & 0 deletions apps/mobile/src/modules/settings/UserHeaderBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Image, Text, View } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"

import { useWhoami } from "@/src/store/user/hooks"

export const UserHeaderBanner = () => {
const whoami = useWhoami()
const insets = useSafeAreaInsets()

if (!whoami) return null
return (
<View className="h-[200px] items-center justify-center" style={{ marginTop: -insets.top }}>
<View
className="bg-system-background overflow-hidden rounded-full"
style={{ marginTop: insets.top }}
>
{!!whoami.image && (
<Image source={{ uri: whoami.image, height: 60, width: 60 }} resizeMode="cover" />
)}
</View>

<View className="mt-2">
<Text className="text-2xl font-bold">{whoami.name}</Text>
{!!whoami.handle && <Text className="text-secondary-label">@{whoami.handle}</Text>}
</View>
</View>
)
}
10 changes: 10 additions & 0 deletions apps/mobile/src/modules/settings/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useNavigation } from "@react-navigation/native"
import type { NativeStackNavigationProp } from "@react-navigation/native-stack"

type RootStackParamList = {
Account: undefined
}

export const useSettingsNavigation = () => {
return useNavigation<NativeStackNavigationProp<RootStackParamList>>()
}
9 changes: 9 additions & 0 deletions apps/mobile/src/modules/settings/routes/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Text, View } from "react-native"

export const AccountScreen = () => {
return (
<View>
<Text>Account</Text>
</View>
)
}
7 changes: 7 additions & 0 deletions apps/mobile/src/modules/settings/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { createNativeStackNavigator } from "@react-navigation/native-stack"

import { AccountScreen } from "./Account"

export const SettingRoutes = (Stack: ReturnType<typeof createNativeStackNavigator>) => {
return [<Stack.Screen key="Account" name="Account" component={AccountScreen} />]
}
162 changes: 95 additions & 67 deletions apps/mobile/src/screens/(stack)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { FeedViewType } from "@follow/constants"
import { PlatformPressable } from "@react-navigation/elements/src/PlatformPressable"
import { router, Tabs } from "expo-router"
import { Easing, View } from "react-native"
import { useContext, useMemo } from "react"
import { Easing, StyleSheet, View } from "react-native"
import { Gesture, GestureDetector } from "react-native-gesture-handler"
import { runOnJS } from "react-native-reanimated"
import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from "react-native-reanimated"

import { BlurEffect } from "@/src/components/common/HeaderBlur"
import { ThemedBlurView } from "@/src/components/common/ThemedBlurView"
import { FollowIcon } from "@/src/components/ui/logo"
import { TabBarBackgroundContext } from "@/src/contexts/TabBarBackgroundContext"
import { SafariCuteFi } from "@/src/icons/safari_cute_fi"
import { SafariCuteIcon } from "@/src/icons/safari_cute-re"
import { Setting7CuteFi } from "@/src/icons/setting_7_cute_fi"
import { Settings7CuteReIcon } from "@/src/icons/settings_7_cute_re"
import { FeedDrawer } from "@/src/modules/feed-drawer/drawer"
import { setCurrentView } from "@/src/modules/subscription/atoms"
import { useColor } from "@/src/theme/colors"

const doubleTap = Gesture.Tap()
.numberOfTaps(2)
Expand All @@ -27,76 +30,101 @@ const fifthTap = Gesture.Tap()
})

export default function TabLayout() {
const opacity = useSharedValue(1)
return (
<FeedDrawer>
<Tabs
screenOptions={{
tabBarBackground: BlurEffect,
tabBarStyle: {
position: "absolute",
},
animation: "fade",
transitionSpec: {
animation: "timing",
config: {
duration: 50,
easing: Easing.ease,
<TabBarBackgroundContext.Provider value={useMemo(() => ({ opacity }), [opacity])}>
<Tabs
screenOptions={{
tabBarBackground: TabBarBackground,
tabBarStyle: {
position: "absolute",
borderTopWidth: 0,
},
},
}}
>
<Tabs.Screen
name="subscription"
options={{
title: "Subscriptions",
headerShown: false,
tabBarIcon: ({ color }) => (
<FollowIcon color={color} style={{ width: 20, height: 20 }} />
),
tabBarButton(props) {
return (
<GestureDetector gesture={doubleTap}>
<View className="flex-1">
<PlatformPressable {...props} />
</View>
</GestureDetector>
)
animation: "fade",
transitionSpec: {
animation: "timing",
config: {
duration: 50,
easing: Easing.ease,
},
},
}}
/>
<Tabs.Screen
name="discover"
options={{
title: "Discover",
headerShown: false,
tabBarIcon: ({ color, focused }) => {
const Icon = !focused ? SafariCuteIcon : SafariCuteFi
return <Icon color={color} width={24} height={24} />
},
}}
/>
>
<Tabs.Screen
name="subscription"
options={{
title: "Subscriptions",
headerShown: false,
tabBarIcon: ({ color }) => (
<FollowIcon color={color} style={{ width: 20, height: 20 }} />
),
tabBarButton(props) {
return (
<GestureDetector gesture={doubleTap}>
<View className="flex-1">
<PlatformPressable {...props} />
</View>
</GestureDetector>
)
},
}}
/>
<Tabs.Screen
name="discover"
options={{
title: "Discover",
headerShown: false,
tabBarIcon: ({ color, focused }) => {
const Icon = !focused ? SafariCuteIcon : SafariCuteFi
return <Icon color={color} width={24} height={24} />
},
}}
/>

<Tabs.Screen
name="settings"
options={{
title: "Settings",
headerShown: false,
tabBarButton(props) {
return (
<GestureDetector gesture={fifthTap}>
<View className="flex-1">
<PlatformPressable {...props} />
</View>
</GestureDetector>
)
},
tabBarIcon: ({ color, focused }) => {
const Icon = !focused ? Settings7CuteReIcon : Setting7CuteFi
return <Icon color={color} width={24} height={24} />
},
}}
/>
</Tabs>
<Tabs.Screen
name="settings"
options={{
title: "Settings",
headerShown: false,
tabBarButton(props) {
return (
<GestureDetector gesture={fifthTap}>
<View className="flex-1">
<PlatformPressable {...props} />
</View>
</GestureDetector>
)
},
tabBarIcon: ({ color, focused }) => {
const Icon = !focused ? Settings7CuteReIcon : Setting7CuteFi
return <Icon color={color} width={24} height={24} />
},
}}
/>
</Tabs>
</TabBarBackgroundContext.Provider>
</FeedDrawer>
)
}

const AnimatedThemedBlurView = Animated.createAnimatedComponent(ThemedBlurView)
const TabBarBackground = () => {
const { opacity } = useContext(TabBarBackgroundContext)

const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
...styles.blurEffect,
}))
const borderColor = useColor("opaqueSeparator")
return <AnimatedThemedBlurView style={[styles.blurEffect, animatedStyle, { borderColor }]} />
}

const styles = StyleSheet.create({
blurEffect: {
...StyleSheet.absoluteFillObject,
overflow: "hidden",
backgroundColor: "transparent",
borderTopWidth: StyleSheet.hairlineWidth,
},
})
Loading

0 comments on commit 5f44d24

Please sign in to comment.