From 665cf95afbad540f22e98573c085489fe42ef679 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Feb 2025 21:31:17 +0800 Subject: [PATCH] feat(mobile): add feed or rsshub feed - Add multiple new SVG icons in the mobile app's icons directory - Create a new Overlay component in the UI components - Update search module and other related components - Refactor and clean up various mobile app files Signed-off-by: Innei --- .../src/components/ui/overlay/Overlay.tsx | 15 +++ apps/mobile/src/icons/search_2_cute_re.tsx | 26 +++++ apps/mobile/src/modules/discover/search.tsx | 5 +- apps/mobile/src/modules/feed/FollowFeed.tsx | 34 +++++-- apps/mobile/src/modules/screen/action.tsx | 96 +++++++++++++++++-- apps/mobile/src/screens/(modal)/add.tsx | 47 --------- apps/mobile/src/screens/(modal)/follow.tsx | 7 +- apps/mobile/src/store/feed/hooks.ts | 8 ++ apps/mobile/src/store/feed/store.ts | 21 +++- icons/mgc/search_2_cute_re.svg | 1 + 10 files changed, 194 insertions(+), 66 deletions(-) create mode 100644 apps/mobile/src/components/ui/overlay/Overlay.tsx create mode 100644 apps/mobile/src/icons/search_2_cute_re.tsx delete mode 100644 apps/mobile/src/screens/(modal)/add.tsx create mode 100644 icons/mgc/search_2_cute_re.svg diff --git a/apps/mobile/src/components/ui/overlay/Overlay.tsx b/apps/mobile/src/components/ui/overlay/Overlay.tsx new file mode 100644 index 000000000..e2d91b3b1 --- /dev/null +++ b/apps/mobile/src/components/ui/overlay/Overlay.tsx @@ -0,0 +1,15 @@ +import type { FC } from "react" +import { FadeIn, FadeOut } from "react-native-reanimated" + +import { ReAnimatedPressable } from "../../common/AnimatedComponents" + +export const Overlay: FC<{ onPress: () => void }> = ({ onPress }) => { + return ( + + ) +} diff --git a/apps/mobile/src/icons/search_2_cute_re.tsx b/apps/mobile/src/icons/search_2_cute_re.tsx new file mode 100644 index 000000000..fa803559d --- /dev/null +++ b/apps/mobile/src/icons/search_2_cute_re.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import Svg, { Path } from "react-native-svg" + +interface Search2CuteReIconProps { + width?: number + height?: number + color?: string +} + +export const Search2CuteReIcon = ({ + width = 24, + height = 24, + color = "#10161F", +}: Search2CuteReIconProps) => { + return ( + + + + + ) +} diff --git a/apps/mobile/src/modules/discover/search.tsx b/apps/mobile/src/modules/discover/search.tsx index 0048baca2..93288076c 100644 --- a/apps/mobile/src/modules/discover/search.tsx +++ b/apps/mobile/src/modules/discover/search.tsx @@ -20,6 +20,7 @@ import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-cont import { BlurEffect } from "@/src/components/common/BlurEffect" import { TabBar } from "@/src/components/ui/tabview/TabBar" +import { Search2CuteReIcon } from "@/src/icons/search_2_cute_re" import { Search3CuteReIcon } from "@/src/icons/search_3_cute_re" import { accentColor, useColor } from "@/src/theme/colors" @@ -215,7 +216,7 @@ const SearchInput = () => { }} className="absolute inset-y-0 left-3 flex flex-row items-center justify-center" > - + {!searchValue && !tempSearchValue && ( Search @@ -251,7 +252,7 @@ const SearchInput = () => { className="absolute inset-0 flex flex-row items-center justify-center" pointerEvents="none" > - + Search diff --git a/apps/mobile/src/modules/feed/FollowFeed.tsx b/apps/mobile/src/modules/feed/FollowFeed.tsx index ce036d195..caf9e7682 100644 --- a/apps/mobile/src/modules/feed/FollowFeed.tsx +++ b/apps/mobile/src/modules/feed/FollowFeed.tsx @@ -1,7 +1,7 @@ import { FeedViewType } from "@follow/constants" import { zodResolver } from "@hookform/resolvers/zod" import { StackActions } from "@react-navigation/native" -import { router, Stack, useLocalSearchParams, useNavigation } from "expo-router" +import { router, Stack, useNavigation } from "expo-router" import { useState } from "react" import { Controller, useForm } from "react-hook-form" import { ActivityIndicator, ScrollView, Text, View } from "react-native" @@ -20,7 +20,7 @@ import { GroupedInsetListCard } from "@/src/components/ui/grouped/GroupedList" import { FeedIcon } from "@/src/components/ui/icon/feed-icon" import { useIsRouteOnlyOne } from "@/src/hooks/useIsRouteOnlyOne" import { FeedViewSelector } from "@/src/modules/feed/view-selector" -import { useFeed, usePrefetchFeed } from "@/src/store/feed/hooks" +import { useFeed, usePrefetchFeed, usePrefetchFeedByUrl } from "@/src/store/feed/hooks" import { useSubscriptionByFeedId } from "@/src/store/subscription/hooks" import { subscriptionSyncService } from "@/src/store/subscription/store" import type { SubscriptionForm } from "@/src/store/subscription/types" @@ -45,10 +45,31 @@ export function FollowFeed(props: { id: string }) { ) } - return + return } -function FollowImpl() { - const { id } = useLocalSearchParams() + +export function FollowUrl(props: { url: string }) { + const { url } = props + + const { isLoading, data, error } = usePrefetchFeedByUrl(url) + + if (isLoading) { + return ( + + + + ) + } + + if (!data) { + return {error?.message} + } + + return +} + +function FollowImpl(props: { feedId: string }) { + const { feedId: id } = props const feed = useFeed(id as string)! const isSubscribed = useSubscriptionByFeedId(feed?.id || "") @@ -129,7 +150,7 @@ function FollowImpl() { {/* Group 2 */} - + ( { const insets = useSafeAreaInsets() @@ -28,11 +33,86 @@ export function HomeRightAction() { return ( - - - - - + ) } + +const AddFeedButton = () => { + const [isOpen, setIsOpen] = useState(false) + const label = useColor("label") + const handleClose = () => setIsOpen(false) + + const valueRef = useRef("") + const handleAdd = () => { + handleClose() + + const value = valueRef.current + if (!value) return + router.push({ + pathname: "/follow", + params: { + url: value, + type: "url", + }, + }) + } + return ( + <> + setIsOpen((s) => !s)}> + + + + + {isOpen && ( + <> + + + + + + + + + Enter Feed URL or RSSHub URL + + + (valueRef.current = text)} + autoFocus + enterKeyHint="done" + cursorColor={accentColor} + selectionColor={accentColor} + onSubmitEditing={handleAdd} + className="bg-system-background dark:bg-secondary-system-fill/30 text-text my-3 rounded-xl" + placeholder="https:// or rsshub://" + /> + + + + + Cancel + + + + Add + + + + + + )} + + + ) +} diff --git a/apps/mobile/src/screens/(modal)/add.tsx b/apps/mobile/src/screens/(modal)/add.tsx deleted file mode 100644 index 5780f2759..000000000 --- a/apps/mobile/src/screens/(modal)/add.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Stack } from "expo-router" -import { useState } from "react" -import { Text, TextInput, TouchableOpacity, View } from "react-native" - -import { ModalHeaderCloseButton } from "@/src/components/common/ModalSharedComponents" -import { Search3CuteReIcon } from "@/src/icons/search_3_cute_re" -import { accentColor, useColor } from "@/src/theme/colors" - -export default function Add() { - const [url, setUrl] = useState("") - - const label = useColor("label") - // @ts-expect-error FIXME: use the right color - const disabled = useColor("disabled") - return ( - - ( - { - if (!url) return - - // TODO impl search - }} - > - - - ), - }} - /> - - Feed URL - - - - ) -} diff --git a/apps/mobile/src/screens/(modal)/follow.tsx b/apps/mobile/src/screens/(modal)/follow.tsx index 6a4d549c0..f2616e5e1 100644 --- a/apps/mobile/src/screens/(modal)/follow.tsx +++ b/apps/mobile/src/screens/(modal)/follow.tsx @@ -1,10 +1,10 @@ import { useLocalSearchParams } from "expo-router" -import { FollowFeed } from "@/src/modules/feed/FollowFeed" +import { FollowFeed, FollowUrl } from "@/src/modules/feed/FollowFeed" import { FollowList } from "@/src/modules/list/FollowList" export default function Follow() { - const { id, type = "feed" } = useLocalSearchParams() + const { id, type = "feed", url } = useLocalSearchParams() switch (type) { case "feed": { @@ -13,6 +13,9 @@ export default function Follow() { case "list": { return } + case "url": { + return + } default: { return } diff --git a/apps/mobile/src/store/feed/hooks.ts b/apps/mobile/src/store/feed/hooks.ts index 7a9a65a3f..11233b3c8 100644 --- a/apps/mobile/src/store/feed/hooks.ts +++ b/apps/mobile/src/store/feed/hooks.ts @@ -31,3 +31,11 @@ export const usePrefetchFeed = (id: string, options?: { enabled?: boolean }) => ...options, }) } + +export const usePrefetchFeedByUrl = (url: string, options?: { enabled?: boolean }) => { + return useQuery({ + queryKey: ["feed", url], + queryFn: () => feedSyncServices.fetchFeedByUrl({ url }), + ...options, + }) +} diff --git a/apps/mobile/src/store/feed/store.ts b/apps/mobile/src/store/feed/store.ts index 79f723f7f..79606e580 100644 --- a/apps/mobile/src/store/feed/store.ts +++ b/apps/mobile/src/store/feed/store.ts @@ -39,7 +39,7 @@ class FeedActions { }) tx.persist(async () => { - await FeedService.upsertMany(feeds) + await FeedService.upsertMany(feeds.filter((feed) => !("nonce" in feed))) }) tx.run() @@ -78,6 +78,25 @@ class FeedSyncServices { return !finalData.id ? { ...finalData, id: nonce } : finalData } + + async fetchFeedByUrl({ url }: FeedQueryParams) { + const res = await apiClient.feeds.$get({ + query: { + url, + }, + }) + + const nonce = Math.random().toString(36).slice(2, 15) + + const finalData = res.data.feed as FeedModel + if (!finalData.id) { + finalData["nonce"] = nonce + finalData["id"] = nonce + } + feedActions.upsertMany([finalData]) + + return finalData + } } export const feedSyncServices = new FeedSyncServices() export const feedActions = new FeedActions() diff --git a/icons/mgc/search_2_cute_re.svg b/icons/mgc/search_2_cute_re.svg new file mode 100644 index 000000000..7c5e90731 --- /dev/null +++ b/icons/mgc/search_2_cute_re.svg @@ -0,0 +1 @@ + \ No newline at end of file