diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 2deb587f6..ed32a873a 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -22,8 +22,10 @@
"@follow/models": "workspace:*",
"@follow/shared": "workspace:*",
"@follow/utils": "workspace:*",
+ "@gorhom/portal": "1.0.14",
"@hookform/resolvers": "3.9.1",
"@react-native-cookies/cookies": "^6.2.1",
+ "@react-native-picker/picker": "2.9.0",
"@react-navigation/bottom-tabs": "^7.0.0",
"@react-navigation/native": "^7.0.0",
"@shopify/flash-list": "1.7.1",
diff --git a/apps/mobile/src/components/common/CopyButton.tsx b/apps/mobile/src/components/common/CopyButton.tsx
new file mode 100644
index 000000000..b62cd233f
--- /dev/null
+++ b/apps/mobile/src/components/common/CopyButton.tsx
@@ -0,0 +1,79 @@
+import { cn } from "@follow/utils/src/utils"
+import { useRef } from "react"
+import { Pressable } from "react-native"
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withDelay,
+ withTiming,
+} from "react-native-reanimated"
+import { useEventCallback } from "usehooks-ts"
+
+import { CheckFilledIcon } from "@/src/icons/check_filled"
+import { Copy2CuteReIcon } from "@/src/icons/copy_2_cute_re"
+
+type Size = "sm" | "md"
+interface CopyButtonProps {
+ onCopy: () => void
+ className?: string
+ size?: Size
+}
+
+const sizeClassNames = {
+ sm: "size-8",
+ md: "size-10",
+}
+
+const sizeIconSize = {
+ sm: 18,
+ md: 20,
+}
+
+export const CopyButton = ({ onCopy, className, size = "sm" }: CopyButtonProps) => {
+ const initialIconScale = useSharedValue(1)
+ const pressedIconScale = useSharedValue(0)
+ const initialStyle = useAnimatedStyle(() => ({
+ transform: [{ scale: initialIconScale.value }],
+ }))
+
+ const pressedStyle = useAnimatedStyle(() => ({
+ position: "absolute",
+ transform: [{ scale: pressedIconScale.value }],
+ }))
+
+ const animatedProgressingRef = useRef(false)
+ const handlePress = useEventCallback(() => {
+ onCopy()
+ if (animatedProgressingRef.current) return
+ animatedProgressingRef.current = true
+ initialIconScale.value = withTiming(0, { duration: 100 }, () => {
+ pressedIconScale.value = withTiming(1, { duration: 100 }, () => {
+ pressedIconScale.value = withDelay(
+ 1000,
+ withTiming(0, { duration: 100 }, () => {
+ initialIconScale.value = withTiming(1, { duration: 100 }, () => {
+ animatedProgressingRef.current = false
+ })
+ }),
+ )
+ })
+ })
+ })
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/mobile/src/components/common/HeaderBlur.tsx b/apps/mobile/src/components/common/HeaderBlur.tsx
index 8f89b60d6..06f8f0159 100644
--- a/apps/mobile/src/components/common/HeaderBlur.tsx
+++ b/apps/mobile/src/components/common/HeaderBlur.tsx
@@ -11,6 +11,6 @@ const node = (
}}
/>
)
-export const HeaderBlur = () => {
+export const BlurEffect = () => {
return node
}
diff --git a/apps/mobile/src/components/ui/form/Label.tsx b/apps/mobile/src/components/ui/form/Label.tsx
new file mode 100644
index 000000000..04fcb2d9a
--- /dev/null
+++ b/apps/mobile/src/components/ui/form/Label.tsx
@@ -0,0 +1,20 @@
+import { cn } from "@follow/utils/src/utils"
+import type { FC, PropsWithChildren } from "react"
+import type { StyleProp, ViewStyle } from "react-native"
+import { Text, View } from "react-native"
+
+export const FormLabel: FC<
+ PropsWithChildren<{
+ label: string
+ optional?: boolean
+ className?: string
+ style?: StyleProp
+ }>
+> = ({ label, optional, className, style }) => {
+ return (
+
+ {label}
+ {!optional && *}
+
+ )
+}
diff --git a/apps/mobile/src/components/ui/form/Select.tsx b/apps/mobile/src/components/ui/form/Select.tsx
new file mode 100644
index 000000000..6d93b8e01
--- /dev/null
+++ b/apps/mobile/src/components/ui/form/Select.tsx
@@ -0,0 +1,93 @@
+/* eslint-disable @eslint-react/no-array-index-key */
+import { cn } from "@follow/utils"
+import { Portal } from "@gorhom/portal"
+import { Picker } from "@react-native-picker/picker"
+import { useMemo, useState } from "react"
+import type { StyleProp, ViewStyle } from "react-native"
+import { Pressable, Text, View } from "react-native"
+import Animated, { SlideOutDown } from "react-native-reanimated"
+import { useEventCallback } from "usehooks-ts"
+
+import { MingcuteDownLineIcon } from "@/src/icons/mingcute_down_line"
+import { useColor } from "@/src/theme/colors"
+
+import { BlurEffect } from "../../common/HeaderBlur"
+
+interface SelectProps {
+ options: { label: string; value: T }[]
+
+ value: T
+ onValueChange: (value: T) => void
+
+ wrapperClassName?: string
+ wrapperStyle?: StyleProp
+}
+export function Select({
+ options,
+ value,
+ onValueChange,
+ wrapperClassName,
+ wrapperStyle,
+}: SelectProps) {
+ const [isOpen, setIsOpen] = useState(false)
+
+ const [currentValue, setCurrentValue] = useState(() => {
+ if (!value) {
+ return options[0].value
+ }
+ return value
+ })
+
+ const valueToLabelMap = useMemo(() => {
+ return options.reduce((acc, option) => {
+ acc.set(option.value, option.label)
+ return acc
+ }, new Map())
+ }, [options])
+
+ const handleChangeValue = useEventCallback((value: T) => {
+ setCurrentValue(value)
+ onValueChange(value)
+ })
+
+ const systemFill = useColor("systemFill")
+ return (
+ <>
+ {/* Trigger */}
+ setIsOpen(!isOpen)}>
+
+ {valueToLabelMap.get(currentValue)}
+
+
+
+
+
+ {/* Picker */}
+ {isOpen && (
+
+ setIsOpen(false)}
+ className="absolute inset-0 flex flex-row items-end"
+ >
+
+
+ e.stopPropagation()}>
+
+ {options.map((option, index) => (
+
+ ))}
+
+
+
+
+
+ )}
+ >
+ )
+}
diff --git a/apps/mobile/src/components/ui/form/TextField.tsx b/apps/mobile/src/components/ui/form/TextField.tsx
new file mode 100644
index 000000000..546578be1
--- /dev/null
+++ b/apps/mobile/src/components/ui/form/TextField.tsx
@@ -0,0 +1,38 @@
+import { cn } from "@follow/utils/src/utils"
+import type { FC } from "react"
+import type { StyleProp, TextInputProps, ViewStyle } from "react-native"
+import { StyleSheet, TextInput, View } from "react-native"
+
+interface TextFieldProps {
+ wrapperClassName?: string
+ wrapperStyle?: StyleProp
+}
+export const TextField: FC = ({
+ className,
+ style,
+ wrapperClassName,
+ wrapperStyle,
+ ...rest
+}) => {
+ return (
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ textField: {
+ fontSize: 16,
+ },
+})
diff --git a/apps/mobile/src/icons/mingcute_down_line.tsx b/apps/mobile/src/icons/mingcute_down_line.tsx
new file mode 100644
index 000000000..77d089efc
--- /dev/null
+++ b/apps/mobile/src/icons/mingcute_down_line.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import Svg, { G, Path } from "react-native-svg"
+
+interface MingcuteDownLineIconProps {
+ width?: number
+ height?: number
+ color?: string
+}
+
+export const MingcuteDownLineIcon = ({
+ width = 24,
+ height = 24,
+ color = "#10161F",
+}: MingcuteDownLineIconProps) => {
+ return (
+
+ )
+}
diff --git a/apps/mobile/src/modules/discover/recommendation-item.tsx b/apps/mobile/src/modules/discover/recommendation-item.tsx
index 0a9a06e97..adeb9ff9e 100644
--- a/apps/mobile/src/modules/discover/recommendation-item.tsx
+++ b/apps/mobile/src/modules/discover/recommendation-item.tsx
@@ -13,7 +13,8 @@ import { RSSHubCategoryCopyMap } from "./copy"
export const RecommendationListItem: FC<{
data: RSSHubRouteDeclaration
-}> = memo(({ data }) => {
+ routePrefix: string
+}> = memo(({ data, routePrefix }) => {
const { maintainers, categories } = useMemo(() => {
const maintainers = new Set()
const categories = new Set()
@@ -90,23 +91,26 @@ export const RecommendationListItem: FC<{
{Object.keys(data.routes).map((route) => (
- {
- router.push({
- pathname: "/rsshub-form",
- params: {
- url: data.url,
- route,
- },
- })
- }}
- key={route}
- className="bg-gray-5 h-10 flex-row items-center justify-center overflow-hidden rounded px-2"
- >
-
- {data.routes[route].name}
-
-
+
+ {
+ router.push({
+ pathname: "/rsshub-form",
+ params: {
+ routePrefix,
+ route: JSON.stringify(data.routes[route]),
+ name: data.name,
+ },
+ })
+ }}
+ className="bg-gray-5 h-10 flex-row items-center justify-center overflow-hidden rounded px-2"
+ />
+
+
+ {data.routes[route].name}
+
+
+
))}
diff --git a/apps/mobile/src/modules/discover/recommendations.tsx b/apps/mobile/src/modules/discover/recommendations.tsx
index 0834e2f89..fda55fbe1 100644
--- a/apps/mobile/src/modules/discover/recommendations.tsx
+++ b/apps/mobile/src/modules/discover/recommendations.tsx
@@ -6,7 +6,7 @@ import { useHeaderHeight } from "@react-navigation/elements"
import { FlashList } from "@shopify/flash-list"
import { useQuery } from "@tanstack/react-query"
import type { FC } from "react"
-import { useCallback, useMemo, useRef, useState } from "react"
+import { useCallback, useMemo, useRef } from "react"
import { Text, TouchableOpacity, View } from "react-native"
import type { PanGestureHandlerGestureEvent } from "react-native-gesture-handler"
import { PanGestureHandler } from "react-native-gesture-handler"
@@ -183,7 +183,7 @@ const ItemRenderer = ({
// Render item
return (
-
+
)
}
@@ -193,8 +193,6 @@ const NavigationSidebar: FC<{
alphabetGroups: (string | { key: string; data: RSSHubRouteDeclaration })[]
listRef: React.RefObject>
}> = ({ alphabetGroups, listRef }) => {
- const [activeLetter, setActiveLetter] = useState("")
-
const scrollToLetter = useCallback(
(letter: string, animated = true) => {
const index = alphabetGroups.findIndex((group) => {
@@ -237,10 +235,8 @@ const NavigationSidebar: FC<{
const firstChar = letter[0].toUpperCase()
const firstCharIsAlphabet = /[A-Z]/.test(firstChar)
if (firstCharIsAlphabet) {
- setActiveLetter(letter)
scrollToLetter(letter, false)
} else {
- setActiveLetter("#")
scrollToLetter("#", false)
}
},
@@ -256,15 +252,10 @@ const NavigationSidebar: FC<{
hitSlop={5}
key={title}
onPress={() => {
- setActiveLetter(title)
scrollToLetter(title)
}}
>
-
- {title}
-
+ {title}
))}
diff --git a/apps/mobile/src/modules/discover/search.tsx b/apps/mobile/src/modules/discover/search.tsx
index 84b071235..9e786b060 100644
--- a/apps/mobile/src/modules/discover/search.tsx
+++ b/apps/mobile/src/modules/discover/search.tsx
@@ -16,7 +16,7 @@ import {
} from "react-native"
import { useSafeAreaFrame, useSafeAreaInsets } from "react-native-safe-area-context"
-import { HeaderBlur } from "@/src/components/common/HeaderBlur"
+import { BlurEffect } from "@/src/components/common/HeaderBlur"
import { Search2CuteReIcon } from "@/src/icons/search_2_cute_re"
import { accentColor, useColor } from "@/src/theme/colors"
@@ -29,7 +29,7 @@ export const SearchHeader = () => {
return (
-
+
@@ -44,7 +44,7 @@ export const DiscoverHeader = () => {
return (
-
+
@@ -53,11 +53,11 @@ export const DiscoverHeader = () => {
}
const PlaceholerSearchBar = () => {
- const { colors } = useTheme()
const placeholderTextColor = useColor("placeholderText")
return (
{
router.push("/search")
}}
diff --git a/apps/mobile/src/modules/rsshub/preview-url.tsx b/apps/mobile/src/modules/rsshub/preview-url.tsx
new file mode 100644
index 000000000..a396fb0c6
--- /dev/null
+++ b/apps/mobile/src/modules/rsshub/preview-url.tsx
@@ -0,0 +1,38 @@
+import { cn, regexpPathToPath } from "@follow/utils"
+import type { FC } from "react"
+import { useMemo } from "react"
+import type { UseFormReturn } from "react-hook-form"
+import { Clipboard, Text, View } from "react-native"
+
+import { CopyButton } from "@/src/components/common/CopyButton"
+
+export const PreviewUrl: FC<{
+ watch: UseFormReturn["watch"]
+ path: string
+ routePrefix: string
+ className?: string
+}> = ({ watch, path, routePrefix, className }) => {
+ const data = watch()
+
+ const fullPath = useMemo(() => {
+ try {
+ return regexpPathToPath(path, data)
+ } catch (err: unknown) {
+ console.info((err as Error).message)
+ return path
+ }
+ }, [path, data])
+
+ const renderedPath = `rsshub://${routePrefix}${fullPath}`
+ return (
+
+ {renderedPath}
+ {
+ Clipboard.setString(renderedPath)
+ }}
+ className="absolute right-0 top-0"
+ />
+
+ )
+}
diff --git a/apps/mobile/src/providers/index.tsx b/apps/mobile/src/providers/index.tsx
index d7a78aa8f..d4e8afa38 100644
--- a/apps/mobile/src/providers/index.tsx
+++ b/apps/mobile/src/providers/index.tsx
@@ -1,4 +1,5 @@
import { jotaiStore } from "@follow/utils"
+import { PortalProvider } from "@gorhom/portal"
import { ThemeProvider } from "@react-navigation/native"
import { QueryClientProvider } from "@tanstack/react-query"
import { useDrizzleStudio } from "expo-drizzle-studio-plugin"
@@ -28,7 +29,9 @@ export const RootProviders = ({ children }: { children: ReactNode }) => {
- {children}
+
+ {children}
+
diff --git a/apps/mobile/src/screens/(modal)/rsshub-form.tsx b/apps/mobile/src/screens/(modal)/rsshub-form.tsx
index 9a1022398..51487aaf0 100644
--- a/apps/mobile/src/screens/(modal)/rsshub-form.tsx
+++ b/apps/mobile/src/screens/(modal)/rsshub-form.tsx
@@ -1,12 +1,162 @@
-import { Stack } from "expo-router"
+import type { RSSHubParameter, RSSHubParameterObject, RSSHubRoute } from "@follow/models/src/rsshub"
+import { parseFullPathParams, parseRegexpPathParams } from "@follow/utils"
+import { PortalProvider } from "@gorhom/portal"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { router, Stack, useLocalSearchParams } from "expo-router"
+import { useEffect, useMemo } from "react"
+import type { UseFormReturn } from "react-hook-form"
+import { useForm } from "react-hook-form"
import { View } from "react-native"
+import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
+import { z } from "zod"
import { ModalHeaderCloseButton } from "@/src/components/common/ModalSharedComponents"
+import { FormLabel } from "@/src/components/ui/form/Label"
+import { Select } from "@/src/components/ui/form/Select"
+import { TextField } from "@/src/components/ui/form/TextField"
+import { PreviewUrl } from "@/src/modules/rsshub/preview-url"
+interface RsshubFormParams {
+ route: RSSHubRoute
+ routePrefix: string
+ name: string
+}
export default function RsshubForm() {
+ const params = useLocalSearchParams()
+
+ const { route, routePrefix, name } = (params || {}) as Record
+
+ const parsedRoute = useMemo(() => {
+ if (!route) return null
+ try {
+ return typeof route === "string" ? JSON.parse(route) : route
+ } catch {
+ return null
+ }
+ }, [route])
+
+ const canBack = router.canDismiss()
+ useEffect(() => {
+ if (!parsedRoute && canBack) {
+ router.dismiss()
+ }
+ }, [canBack, parsedRoute])
+ if (!parsedRoute || !routePrefix) {
+ return null
+ }
+ return
+}
+
+function FormImpl({ route, routePrefix, name }: RsshubFormParams) {
+ const { name: routeName } = route
+ const keys = useMemo(
+ () =>
+ parseRegexpPathParams(route.path, {
+ excludeNames: [
+ "routeParams",
+ "functionalFlag",
+ "fulltext",
+ "disableEmbed",
+ "date",
+ "language",
+ "lang",
+ "sort",
+ ],
+ }),
+ [route.path],
+ )
+
+ const formPlaceholder = useMemo>(() => {
+ if (!route.example) return {}
+ return parseFullPathParams(route.example.replace(`/${routePrefix}`, ""), route.path)
+ }, [route.example, route.path, routePrefix])
+ const dynamicFormSchema = useMemo(
+ () =>
+ z.object({
+ ...Object.fromEntries(
+ keys.map((keyItem) => [
+ keyItem.name,
+ keyItem.optional ? z.string().optional().nullable() : z.string().min(1),
+ ]),
+ ),
+ }),
+ [keys],
+ )
+
+ const defaultValue = useMemo(() => {
+ const ret = {} as Record
+ if (!route.parameters) return ret
+ for (const key in route.parameters) {
+ const params = normalizeRSSHubParameters(route.parameters[key])
+ if (!params) continue
+ ret[key] = params.default
+ }
+ return ret
+ }, [route.parameters])
+
+ const form = useForm>({
+ resolver: zodResolver(dynamicFormSchema),
+ defaultValues: defaultValue,
+ mode: "all",
+ }) as UseFormReturn
+
return (
-
-
-
+
+
+
+
+
+ {/* Form */}
+
+ {keys.map((keyItem) => {
+ const parameters = normalizeRSSHubParameters(route.parameters[keyItem.name])
+ const formRegister = form.register(keyItem.name)
+
+ return (
+
+
+ {!parameters?.options && (
+ {
+ formRegister.onChange({
+ target: { value: text },
+ })
+ }}
+ />
+ )}
+
+ {!!parameters?.options && (
+
+ )
+ })}
+
+
+
)
}
+
+const normalizeRSSHubParameters = (parameters: RSSHubParameter): RSSHubParameterObject | null =>
+ parameters
+ ? typeof parameters === "string"
+ ? { description: parameters, default: null }
+ : parameters
+ : null
diff --git a/apps/mobile/src/screens/(stack)/(tabs)/_layout.tsx b/apps/mobile/src/screens/(stack)/(tabs)/_layout.tsx
index ecd8215e3..c263422ac 100644
--- a/apps/mobile/src/screens/(stack)/(tabs)/_layout.tsx
+++ b/apps/mobile/src/screens/(stack)/(tabs)/_layout.tsx
@@ -5,7 +5,7 @@ import { View } from "react-native"
import { Gesture, GestureDetector } from "react-native-gesture-handler"
import { runOnJS } from "react-native-reanimated"
-import { HeaderBlur } from "@/src/components/common/HeaderBlur"
+import { BlurEffect } from "@/src/components/common/HeaderBlur"
import { FollowIcon } from "@/src/components/ui/logo"
import { SafariCuteFi } from "@/src/icons/safari_cute_fi"
import { SafariCuteIcon } from "@/src/icons/safari_cute-re"
@@ -23,7 +23,7 @@ export default function TabLayout() {
return (
{
test("normal path", () => {
diff --git a/apps/renderer/src/lib/path-parser.ts b/packages/utils/src/path-parser.ts
similarity index 100%
rename from apps/renderer/src/lib/path-parser.ts
rename to packages/utils/src/path-parser.ts
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e12988f24..a01941711 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -427,12 +427,18 @@ importers:
'@follow/utils':
specifier: workspace:*
version: link:../../packages/utils
+ '@gorhom/portal':
+ specifier: 1.0.14
+ version: 1.0.14(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
'@hookform/resolvers':
specifier: 3.9.1
version: 3.9.1(react-hook-form@7.54.0(react@18.3.1))
'@react-native-cookies/cookies':
specifier: ^6.2.1
version: 6.2.1(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))
+ '@react-native-picker/picker':
+ specifier: 2.9.0
+ version: 2.9.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
'@react-navigation/bottom-tabs':
specifier: ^7.0.0
version: 7.2.0(36el4dfrgnbt7wqu67o6leoq5q)
@@ -806,9 +812,6 @@ importers:
ofetch:
specifier: 1.4.1
version: 1.4.1
- path-to-regexp:
- specifier: 8.2.0
- version: 8.2.0
plain-shiki:
specifier: 0.0.12
version: 0.0.12(shiki@1.24.1)
@@ -1449,6 +1452,9 @@ importers:
nanoid:
specifier: 5.0.9
version: 5.0.9
+ path-to-regexp:
+ specifier: 8.2.0
+ version: 8.2.0
tailwind-merge:
specifier: 2.5.5
version: 2.5.5
@@ -3716,6 +3722,12 @@ packages:
'@gar/promisify@1.1.3':
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
+ '@gorhom/portal@1.0.14':
+ resolution: {integrity: sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A==}
+ peerDependencies:
+ react: '*'
+ react-native: '*'
+
'@grpc/grpc-js@1.9.15':
resolution: {integrity: sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==}
engines: {node: ^8.13.0 || >=10.10.0}
@@ -4918,6 +4930,12 @@ packages:
peerDependencies:
react-native: '>= 0.60.2'
+ '@react-native-picker/picker@2.9.0':
+ resolution: {integrity: sha512-khEhIW/uhfMqq/+tvg4rEAiPGT8GX+Y6QydlP2TSMSmRHoSJK+ShXvXZXSr4Sii4imkj4BwvLunGywwtQDODqg==}
+ peerDependencies:
+ react: '*'
+ react-native: '*'
+
'@react-native/assets-registry@0.76.5':
resolution: {integrity: sha512-MN5dasWo37MirVcKWuysRkRr4BjNc81SXwUtJYstwbn8oEkfnwR9DaqdDTo/hHOnTdhafffLIa2xOOHcjDIGEw==}
engines: {node: '>=18'}
@@ -17868,6 +17886,12 @@ snapshots:
'@gar/promisify@1.1.3': {}
+ '@gorhom/portal@1.0.14(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ nanoid: 3.3.8
+ react: 18.3.1
+ react-native: 0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)
+
'@grpc/grpc-js@1.9.15':
dependencies:
'@grpc/proto-loader': 0.7.13
@@ -19417,6 +19441,11 @@ snapshots:
invariant: 2.2.4
react-native: 0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)
+ '@react-native-picker/picker@2.9.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ react-native: 0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)
+
'@react-native/assets-registry@0.76.5': {}
'@react-native/babel-plugin-codegen@0.76.5(@babel/preset-env@7.26.0(@babel/core@7.26.0))':