diff --git a/apps/mobile/src/modules/screen/action.tsx b/apps/mobile/src/modules/screen/action.tsx
index 8c37b9306..495b31727 100644
--- a/apps/mobile/src/modules/screen/action.tsx
+++ b/apps/mobile/src/modules/screen/action.tsx
@@ -1,4 +1,5 @@
import { cn } from "@follow/utils"
+import { router } from "expo-router"
import type { PropsWithChildren } from "react"
import { TouchableOpacity, View } from "react-native"
@@ -27,7 +28,7 @@ export function HomeLeftAction() {
if (!user) return null
return (
-
+ router.push("/profile")}>
diff --git a/apps/mobile/src/modules/settings/SettingsList.tsx b/apps/mobile/src/modules/settings/SettingsList.tsx
index 05aa0d107..929c9d339 100644
--- a/apps/mobile/src/modules/settings/SettingsList.tsx
+++ b/apps/mobile/src/modules/settings/SettingsList.tsx
@@ -25,7 +25,6 @@ import { SafeLockFilledIcon } from "@/src/icons/safe_lock_filled"
import { Settings1CuteFiIcon } from "@/src/icons/settings_1_cute_fi"
import { StarCuteFiIcon } from "@/src/icons/star_cute_fi"
import { TrophyCuteFiIcon } from "@/src/icons/trophy_cute_fi"
-import { User3CuteFiIcon } from "@/src/icons/user_3_cute_fi"
import { UserSettingCuteFiIcon } from "@/src/icons/user_setting_cute_fi"
import { signOut } from "@/src/lib/auth"
@@ -43,17 +42,17 @@ interface GroupNavigationLink {
todo?: boolean
}
const UserGroupNavigationLinks: GroupNavigationLink[] = [
- {
- label: "Profile",
- icon: User3CuteFiIcon,
- onPress: (navigation, scrollRef) => {
- scrollRef.current?.scrollTo({ y: 0, animated: true })
- setTimeout(() => {
- navigation.navigate("Profile")
- }, 100)
- },
- iconBackgroundColor: "#4F46E5",
- },
+ // {
+ // label: "Profile",
+ // icon: User3CuteFiIcon,
+ // onPress: (navigation, scrollRef) => {
+ // scrollRef.current?.scrollTo({ y: 0, animated: true })
+ // setTimeout(() => {
+ // navigation.navigate("Profile")
+ // }, 100)
+ // },
+ // iconBackgroundColor: "#4F46E5",
+ // },
{
label: "Achievement",
icon: TrophyCuteFiIcon,
diff --git a/apps/mobile/src/modules/settings/routes/Profile.tsx b/apps/mobile/src/modules/settings/routes/Profile.tsx
index 9bc8761cb..1de43741e 100644
--- a/apps/mobile/src/modules/settings/routes/Profile.tsx
+++ b/apps/mobile/src/modules/settings/routes/Profile.tsx
@@ -35,6 +35,9 @@ import { useShareSubscription } from "../hooks/useShareSubscription"
import { UserHeaderBanner } from "../UserHeaderBanner"
type Subscription = Awaited>["data"][number]
+/**
+ * @deprecated
+ */
export const ProfileScreen = () => {
const scrollY = useSharedValue(0)
const { data: subscriptions, isLoading, isError } = useShareSubscription()
diff --git a/apps/mobile/src/modules/settings/routes/index.tsx b/apps/mobile/src/modules/settings/routes/index.tsx
index 57fca3c19..812d8d60c 100644
--- a/apps/mobile/src/modules/settings/routes/index.tsx
+++ b/apps/mobile/src/modules/settings/routes/index.tsx
@@ -12,11 +12,10 @@ import { ListsScreen } from "./Lists"
import { ManageListScreen } from "./ManageList"
import { NotificationsScreen } from "./Notifications"
import { PrivacyScreen } from "./Privacy"
-import { ProfileScreen } from "./Profile"
export const SettingRoutes = (Stack: TypedNavigator) => {
return [
- ,
+ // ,
,
,
,
diff --git a/apps/mobile/src/screens/(modal)/_layout.tsx b/apps/mobile/src/screens/(modal)/_layout.tsx
index aaf78fa4d..6c0d24337 100644
--- a/apps/mobile/src/screens/(modal)/_layout.tsx
+++ b/apps/mobile/src/screens/(modal)/_layout.tsx
@@ -27,6 +27,13 @@ export default function ModalLayout() {
title: "List",
}}
/>
+
)
}
diff --git a/apps/mobile/src/screens/(modal)/profile.tsx b/apps/mobile/src/screens/(modal)/profile.tsx
new file mode 100644
index 000000000..b57d77ccf
--- /dev/null
+++ b/apps/mobile/src/screens/(modal)/profile.tsx
@@ -0,0 +1,261 @@
+import type { FeedViewType } from "@follow/constants"
+import { cn } from "@follow/utils"
+import { getDefaultHeaderHeight } from "@react-navigation/elements"
+import { Fragment, useCallback, useEffect, useMemo } from "react"
+import {
+ ActivityIndicator,
+ FlatList,
+ Image,
+ Share,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from "react-native"
+import Animated, { useAnimatedScrollHandler, useSharedValue } from "react-native-reanimated"
+import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context"
+
+import { ReAnimatedScrollView } from "@/src/components/common/AnimatedComponents"
+import { BlurEffect } from "@/src/components/common/BlurEffect"
+import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
+import type { FeedIconRequiredFeed } from "@/src/components/ui/icon/feed-icon"
+import { FeedIcon } from "@/src/components/ui/icon/feed-icon"
+import { Share3CuteReIcon } from "@/src/icons/share_3_cute_re"
+import type { apiClient } from "@/src/lib/api-fetch"
+import { toast } from "@/src/lib/toast"
+import { useShareSubscription } from "@/src/modules/settings/hooks/useShareSubscription"
+import { UserHeaderBanner } from "@/src/modules/settings/UserHeaderBanner"
+import { ItemSeparator } from "@/src/modules/subscription/ItemSeparator"
+import type { FeedModel } from "@/src/store/feed/types"
+import type { ListModel } from "@/src/store/list/store"
+import { useWhoami } from "@/src/store/user/hooks"
+import { useColor } from "@/src/theme/colors"
+
+type Subscription = Awaited>["data"][number]
+export default function ProfileScreen() {
+ const scrollY = useSharedValue(0)
+ const { data: subscriptions, isLoading, isError } = useShareSubscription()
+
+ const headerOpacity = useSharedValue(0)
+ const scrollHandler = useAnimatedScrollHandler((event) => {
+ scrollY.value = event.contentOffset.y
+ headerOpacity.value = scrollY.value / 100
+ })
+
+ const whoami = useWhoami()
+
+ useEffect(() => {
+ if (isError) {
+ toast.error("Failed to fetch subscriptions")
+ }
+ }, [isError])
+
+ const insets = useSafeAreaInsets()
+
+ const textLabelColor = useColor("label")
+ const openShareUrl = useCallback(() => {
+ if (!whoami?.id) return
+ Share.share({
+ url: `https://app.follow.is/share/users/${whoami.id}`,
+ title: `Follow | ${whoami.name}'s Profile`,
+ })
+ }, [whoami?.id, whoami?.name])
+
+ const frame = useSafeAreaFrame()
+ const headerHeight = getDefaultHeaderHeight(frame, false, 0)
+
+ return (
+
+
+
+
+ {isLoading && }
+ {!isLoading && subscriptions && }
+
+
+
+
+
+
+
+
+ {/* Header */}
+
+
+
+
+ {whoami?.name}'s Profile
+
+
+
+
+
+
+
+ )
+}
+
+type PickedListModel = Pick & {
+ customTitle?: string | null
+}
+type PickedFeedModel = Pick<
+ FeedModel,
+ "id" | "title" | "description" | "siteUrl" | "url" | "image"
+> & {
+ customTitle?: string | null
+ view: FeedViewType
+}
+const SubscriptionList = ({ subscriptions }: { subscriptions: Subscription[] }) => {
+ const { lists, feeds, groupedFeeds } = useMemo(() => {
+ const lists = [] as PickedListModel[]
+ const feeds = [] as PickedFeedModel[]
+
+ const groupedFeeds = {} as Record
+
+ for (const subscription of subscriptions) {
+ if ("listId" in subscription) {
+ lists.push({
+ id: subscription.listId,
+ title: subscription.lists.title!,
+ image: subscription.lists.image!,
+ description: subscription.lists.description!,
+ view: subscription.lists.view,
+ customTitle: subscription.title,
+ })
+ continue
+ }
+
+ if ("feedId" in subscription && "feeds" in subscription) {
+ const feed = {
+ id: subscription.feedId,
+ title: subscription.feeds.title!,
+ image: subscription.feeds.image!,
+ description: subscription.feeds.description!,
+ siteUrl: subscription.feeds.siteUrl!,
+ url: subscription.feeds.url!,
+ view: subscription.view as FeedViewType,
+ customTitle: subscription.title,
+ }
+
+ if (subscription.category) {
+ groupedFeeds[subscription.category] = [
+ ...(groupedFeeds[subscription.category] || []),
+ feed,
+ ]
+ } else {
+ feeds.push(feed)
+ }
+ }
+ }
+ return { lists, feeds, groupedFeeds }
+ }, [subscriptions])
+
+ const hasFeeds = Object.keys(groupedFeeds).length > 0 || feeds.length > 0
+ return (
+
+ {lists.length > 0 && (
+
+
+
+
+
+ )}
+ {hasFeeds && (
+
+
+ {Object.entries(groupedFeeds).map(([category, feeds], index) => (
+
+
+ {category}
+
+
+
+ ))}
+
+
+ Uncategorized Feeds
+
+
+
+ )}
+
+ )
+}
+const renderListItems = ({ item }: { item: PickedListModel }) => (
+
+
+ {!!item.image && (
+
+ )}
+ {!item.image && }
+
+
+ {item.title}
+
+)
+
+const renderFeedItems = ({ item }: { item: PickedFeedModel }) => (
+
+
+
+
+ {item.title}
+
+)
+
+const SectionHeader = ({ title }: { title: string }) => (
+
+
+ {title}
+
+
+)