refactor(mobile): simplify SafeModalScrollView and RSSHub form handling

- Replace KeyboardAwareScrollView with standard ScrollView
- Update form error handling to use JSON parsing
- Modify ScreenOptions and ModalHeaderSubmitButtonImpl to pass form errors
- Improve type safety and component props

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-02-28 13:38:04 +08:00
parent 8e33327a31
commit 7d23f2fda5
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
2 changed files with 42 additions and 28 deletions

View File

@ -8,21 +8,21 @@
* ```
*/
import { useHeaderHeight } from "@react-navigation/elements"
import type { KeyboardAwareScrollViewProps } from "react-native-keyboard-controller"
import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
import type { ScrollViewProps } from "react-native"
import { ScrollView } from "react-native"
import { useSafeAreaInsets } from "react-native-safe-area-context"
interface SafeModalScrollViewProps extends KeyboardAwareScrollViewProps {}
interface SafeModalScrollViewProps extends ScrollViewProps {}
export const SafeModalScrollView = (props: SafeModalScrollViewProps) => {
const headerHeight = useHeaderHeight()
const insets = useSafeAreaInsets()
return (
<KeyboardAwareScrollView
<ScrollView
{...props}
scrollIndicatorInsets={{ top: headerHeight, bottom: insets.bottom }}
contentContainerStyle={{ paddingTop: headerHeight, paddingBottom: insets.bottom }}
>
{props.children}
</KeyboardAwareScrollView>
</ScrollView>
)
}

View File

@ -8,6 +8,7 @@ import {
import { zodResolver } from "@hookform/resolvers/zod"
import { router, Stack, useLocalSearchParams } from "expo-router"
import { memo, useEffect, useMemo, useState } from "react"
import type { FieldErrors } from "react-hook-form"
import { Controller, useForm } from "react-hook-form"
import { Linking, Text, TouchableOpacity, View } from "react-native"
import { z } from "zod"
@ -111,6 +112,9 @@ function FormImpl({ route, routePrefix, name }: RsshubFormParams) {
mode: "all",
})
// eslint-disable-next-line unicorn/prefer-structured-clone
const nextErrors = JSON.parse(JSON.stringify(form.formState.errors))
return (
<FormProvider form={form}>
<ScreenOptions
@ -118,6 +122,7 @@ function FormImpl({ route, routePrefix, name }: RsshubFormParams) {
routeName={routeName}
route={route.path}
routePrefix={routePrefix}
errors={nextErrors}
/>
<PortalHost>
@ -231,33 +236,40 @@ type ScreenOptionsProps = {
routeName: string
route: string
routePrefix: string
errors: FieldErrors
}
const ScreenOptions = memo(({ name, routeName, route, routePrefix }: ScreenOptionsProps) => {
const form = useFormContext()
const ScreenOptions = memo(
({ name, routeName, route, routePrefix, errors }: ScreenOptionsProps) => {
const form = useFormContext()
return (
<Stack.Screen
options={{
headerLeft: ModalHeaderCloseButton,
gestureEnabled: !form.formState.isDirty,
headerBackground: BlurEffectWithBottomBorder,
headerTransparent: true,
return (
<Stack.Screen
options={{
headerLeft: ModalHeaderCloseButton,
gestureEnabled: !form.formState.isDirty,
headerBackground: BlurEffectWithBottomBorder,
headerTransparent: true,
headerRight: () => (
<FormProvider form={form}>
<ModalHeaderSubmitButtonImpl routePrefix={routePrefix} route={route} />
</FormProvider>
),
headerRight: () => (
<FormProvider form={form}>
<ModalHeaderSubmitButtonImpl
errors={errors}
routePrefix={routePrefix}
route={route}
/>
</FormProvider>
),
headerTitle: () => (
<Title name={name} routeName={routeName} route={route} routePrefix={routePrefix} />
),
}}
/>
)
})
headerTitle: () => (
<Title name={name} routeName={routeName} route={route} routePrefix={routePrefix} />
),
}}
/>
)
},
)
const Title = ({ name, routeName, route, routePrefix }: ScreenOptionsProps) => {
const Title = ({ name, routeName, route, routePrefix }: Omit<ScreenOptionsProps, "errors">) => {
return (
<HeaderTitleExtra subText={`rsshub://${routePrefix}${route}`}>
{`${name} - ${routeName}`}
@ -270,12 +282,14 @@ const routeParamsKeyPrefix = "route-params-"
const ModalHeaderSubmitButtonImpl = ({
routePrefix,
route,
errors,
}: {
routePrefix: string
route: string
errors: FieldErrors
}) => {
const form = useFormContext()
const { isValid } = form.formState
const isValid = Object.keys(errors).length === 0
const [isLoading, setIsLoading] = useState(false)