From 67152ff06dfaeb6b7b74ff84f34c2be4510b9c79 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 26 Feb 2025 23:27:19 +0800 Subject: [PATCH] refactor(mobile): improve entry content header actions - Refactor header right actions with centralized action items - Add type definition for action items - Extract action handlers into separate functions - Simplify dropdown menu and action bar rendering - Add missing type imports for menu and icon configurations Signed-off-by: Innei --- .../EntryContentHeaderRightActions.tsx | 244 +++++++++--------- 1 file changed, 125 insertions(+), 119 deletions(-) diff --git a/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx b/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx index e697ef7f5..d6058acdd 100644 --- a/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx +++ b/apps/mobile/src/modules/entry-content/EntryContentHeaderRightActions.tsx @@ -4,6 +4,7 @@ import { Clipboard, Share, TouchableOpacity, View } from "react-native" import type { SharedValue } from "react-native-reanimated" import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated" import { useColor } from "react-native-uikit-colors" +import type { MenuItemIconProps } from "zeego/lib/typescript/menu" import { ActionBarItem } from "@/src/components/ui/action-bar/ActionBarItem" import { DropdownMenu } from "@/src/components/ui/context-menu" @@ -23,6 +24,17 @@ import { summaryActions, summarySyncService } from "@/src/store/summary/store" import { useEntryContentContext } from "./ctx" +type ActionItem = { + key: string + title: string + icon: React.JSX.Element + iconIOS: MenuItemIconProps["ios"] + onPress: () => void + active?: boolean + iconColor?: string + isCheckbox?: boolean +} + export function EntryContentHeaderRightActions(props: HeaderRightActionsProps) { return } @@ -40,70 +52,105 @@ const HeaderRightActionsImpl = ({ }: HeaderRightActionsProps) => { const labelColor = useColor("label") const isStarred = useIsEntryStarred(entryId) + const { showAISummaryAtom } = useEntryContentContext() + const [showAISummary, setShowAISummary] = useAtom(showAISummaryAtom) + const [extraActionContainerWidth, setExtraActionContainerWidth] = useState(0) - const entry = useEntry(entryId, (entry) => { - if (!entry) return - return { - url: entry.url, - feedId: entry.feedId, - title: entry.title, - } - }) - const feed = useFeed(entry?.feedId as string, (feed) => { - return { - feedId: feed.id, - } - }) + const entry = useEntry( + entryId, + (entry) => + entry && { + url: entry.url, + feedId: entry.feedId, + title: entry.title, + }, + ) + + const feed = useFeed(entry?.feedId as string, (feed) => feed && { feedId: feed.id }) const subscription = useSubscription(feed?.feedId as string) const handleToggleStar = () => { - if (!entry) return - if (!feed) return - if (!subscription) return - if (isStarred) collectionSyncService.unstarEntry(entryId) - else - collectionSyncService.starEntry({ - entryId, - feedId: feed.feedId, - view: subscription.view, - }) + if (!entry || !feed || !subscription) return + + isStarred + ? collectionSyncService.unstarEntry(entryId) + : collectionSyncService.starEntry({ + entryId, + feedId: feed.feedId, + view: subscription.view, + }) } const handleShare = () => { - if (!entry) return - Share.share({ - title: entry.title!, - url: entry.url!, - }) + if (!entry?.title || !entry?.url) return + Share.share({ title: entry.title, url: entry.url }) } - const { showAISummaryAtom } = useEntryContentContext() - const [showAISummary, setShowAISummary] = useAtom(showAISummaryAtom) + const handleAISummary = () => { if (!entry) return const getCachedOrGenerateSummary = async () => { const hasSummary = await summaryActions.getSummary(entryId) if (hasSummary) return + const hideGlowEffect = showIntelligenceGlowEffect() await summarySyncService.generateSummary(entryId) hideGlowEffect() } + setShowAISummary((prev) => { - const n = !prev - if (n) { - getCachedOrGenerateSummary() - } - return n + const newValue = !prev + if (newValue) getCachedOrGenerateSummary() + return newValue }) } + const handleCopyLink = () => { + if (!entry?.url) return + Clipboard.setString(entry.url) + toast.info("Link copied to clipboard") + } + + const handleOpenInBrowser = () => { + if (!entry?.url) return + openLink(entry.url) + } + useEffect(() => { - return () => { - hideIntelligenceGlowEffect() - } + return () => hideIntelligenceGlowEffect() }, []) - const [extraActionContainerWidth, setExtraActionContainerWidth] = useState(0) + // Define action items for reuse + const actionItems = [ + subscription && { + key: "Star", + title: isStarred ? "Unstar" : "Star", + icon: isStarred ? : , + iconIOS: { + name: isStarred ? "star.fill" : "star", + paletteColors: isStarred ? ["#facc15"] : undefined, + }, + onPress: handleToggleStar, + active: isStarred, + iconColor: isStarred ? "#facc15" : undefined, + }, + { + key: "GenerateSummary", + title: "Generate Summary", + icon: , + iconIOS: { name: "sparkles" }, + onPress: handleAISummary, + active: showAISummary, + isCheckbox: true, + }, + { + key: "Share", + title: "Share", + icon: , + iconIOS: { name: "square.and.arrow.up" }, + onPress: handleShare, + }, + ].filter(Boolean) as ActionItem[] return ( @@ -112,34 +159,26 @@ const HeaderRightActionsImpl = ({ )} { - setExtraActionContainerWidth(e.nativeEvent.layout.width) - }} - style={useAnimatedStyle(() => { - return { - opacity: interpolate(titleOpacityShareValue.value, [0, 1], [1, 0]), - } - })} + onLayout={(e) => setExtraActionContainerWidth(e.nativeEvent.layout.width)} + style={useAnimatedStyle(() => ({ + opacity: interpolate(titleOpacityShareValue.value, [0, 1], [1, 0]), + }))} className="absolute right-[32px] z-10 flex-row gap-2" > - {!!subscription && ( - - {isStarred ? : } - + {actionItems.map( + (item) => + item && ( + + {item.icon} + + ), )} - - - - - - - - @@ -152,67 +191,34 @@ const HeaderRightActionsImpl = ({ {isHeaderTitleVisible && ( - {!!subscription && ( - - {isStarred ? "Unstar" : "Star"} - - + {actionItems.map( + (item) => + item && + (item.isCheckbox ? ( + + {item.title} + + + ) : ( + + {item.title} + + + )), )} - - Generate Summary - - - - Share - - )} - { - if (!entry?.url) return - Clipboard.setString(entry.url) - toast.info("Link copied to clipboard") - }} - > + Copy Link - + - { - if (!entry?.url) return - openLink(entry.url) - }} - > + Open in Browser - +