diff --git a/apps/mobile/src/components/common/ModalSharedComponents.tsx b/apps/mobile/src/components/common/ModalSharedComponents.tsx
index f6dcc3160..af3df0adc 100644
--- a/apps/mobile/src/components/common/ModalSharedComponents.tsx
+++ b/apps/mobile/src/components/common/ModalSharedComponents.tsx
@@ -30,16 +30,16 @@ const ModalHeaderCloseButtonImpl = () => {
)
}
-export interface ModalHeaderShubmitButtonProps {
+export interface ModalHeaderSubmitButtonProps {
isValid: boolean
onPress: () => void
isLoading?: boolean
}
-export const ModalHeaderShubmitButton = ({
+export const ModalHeaderSubmitButton = ({
isValid,
onPress,
isLoading,
-}: ModalHeaderShubmitButtonProps) => {
+}: ModalHeaderSubmitButtonProps) => {
return
}
@@ -47,8 +47,9 @@ const ModalHeaderShubmitButtonImpl = ({
isValid,
onPress,
isLoading,
-}: ModalHeaderShubmitButtonProps) => {
+}: ModalHeaderSubmitButtonProps) => {
const label = useColor("label")
+
return (
{isLoading ? (
diff --git a/apps/mobile/src/components/common/SwipeableItem.tsx b/apps/mobile/src/components/common/SwipeableItem.tsx
index e6f53d6ed..1e064afe7 100644
--- a/apps/mobile/src/components/common/SwipeableItem.tsx
+++ b/apps/mobile/src/components/common/SwipeableItem.tsx
@@ -118,6 +118,10 @@ export const SwipeableItem: React.FC = ({
const renderRightActions = (progress: Animated.AnimatedInterpolation) => {
const width = rightActions?.length ? rightActions.length * 74 : 74
+ const parallaxX = progress.interpolate({
+ inputRange: [0, 1, 1.2],
+ outputRange: [0, 0, 10],
+ })
return (
<>
= ({
onPress={action.onPress}
>
{action.icon}
- {action.label}
+
+ {action.label}
+
)
diff --git a/apps/mobile/src/components/ui/form/TextField.tsx b/apps/mobile/src/components/ui/form/TextField.tsx
index 2ee4ca30b..fd04e30ea 100644
--- a/apps/mobile/src/components/ui/form/TextField.tsx
+++ b/apps/mobile/src/components/ui/form/TextField.tsx
@@ -7,25 +7,36 @@ import { accentColor } from "@/src/theme/colors"
import { FormLabel } from "./Label"
-interface TextFieldProps {
+interface BaseFieldProps {
wrapperClassName?: string
wrapperStyle?: StyleProp
-
label?: string
description?: string
required?: boolean
+
+ inputPostfixElement?: React.ReactNode
}
-export const TextField = forwardRef(
+const BaseField = forwardRef(
(
- { className, style, wrapperClassName, wrapperStyle, label, description, required, ...rest },
+ {
+ className,
+ style,
+ wrapperClassName,
+ wrapperStyle,
+ label,
+ description,
+ required,
+ inputPostfixElement,
+ ...rest
+ },
ref,
) => {
return (
- <>
- {!!label && }
+
+ {!!label && }
{!!description && (
- {description}
+ {description}
)}
(
+ {inputPostfixElement}
- >
+
)
},
)
+export const TextField = forwardRef((props, ref) => (
+
+))
+
+interface NumberFieldProps extends BaseFieldProps {
+ value?: number
+ onChangeNumber?: (value: number) => void
+ defaultValue?: number
+}
+
+export const NumberField = forwardRef<
+ TextInput,
+ Omit &
+ NumberFieldProps
+>(({ value, onChangeNumber, defaultValue, ...rest }, ref) => (
+ onChangeNumber?.(Math.min(Number(text), Number.MAX_SAFE_INTEGER))}
+ defaultValue={defaultValue?.toString()}
+ />
+))
+
const styles = StyleSheet.create({
textField: {
fontSize: 16,
diff --git a/apps/mobile/src/components/ui/grouped/GroupedList.tsx b/apps/mobile/src/components/ui/grouped/GroupedList.tsx
index 552e4b276..bd9cc69f6 100644
--- a/apps/mobile/src/components/ui/grouped/GroupedList.tsx
+++ b/apps/mobile/src/components/ui/grouped/GroupedList.tsx
@@ -9,11 +9,13 @@ import { MingcuteRightLine } from "@/src/icons/mingcute_right_line"
import { RightCuteReIcon } from "@/src/icons/right_cute_re"
import { useColor } from "@/src/theme/colors"
-export const GroupedInsetListCard: FC = ({
- children,
- className,
- ...props
-}) => {
+type GroupedInsetListCardProps = {
+ showSeparator?: boolean
+}
+
+export const GroupedInsetListCard: FC<
+ PropsWithChildren & ViewProps & GroupedInsetListCardProps
+> = ({ children, className, showSeparator = true, ...props }) => {
return (
= ({
className,
)}
>
- {React.Children.map(children, (child, index) => {
- const isLast = index === React.Children.count(children) - 1
+ {showSeparator
+ ? React.Children.map(children, (child, index) => {
+ const isLast = index === React.Children.count(children) - 1
- const isNavigationLink =
- React.isValidElement(child) &&
- // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
- (child.type as Function).name === GroupedInsetListNavigationLink.name
+ const isNavigationLink =
+ React.isValidElement(child) &&
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
+ (child.type as Function).name === GroupedInsetListNavigationLink.name
- return (
-
- {child}
- {!isLast && (
-
- )}
-
- )
- })}
+ return (
+
+ {child}
+ {!isLast && (
+
+ )}
+
+ )
+ })
+ : children}
)
}
diff --git a/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx b/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx
index e50ea7988..03b86c0fa 100644
--- a/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx
+++ b/apps/mobile/src/components/ui/typography/MarkdownWeb.tsx
@@ -34,7 +34,7 @@ const MarkdownWeb: WebComponent<{
overscroll-behavior: none;
}
body, html {
- overflow: hidden;
+ overflow-y: hidden;
}
`,
}}
diff --git a/apps/mobile/src/modules/feed/FollowFeed.tsx b/apps/mobile/src/modules/feed/FollowFeed.tsx
index d7fa06ae3..73348aaed 100644
--- a/apps/mobile/src/modules/feed/FollowFeed.tsx
+++ b/apps/mobile/src/modules/feed/FollowFeed.tsx
@@ -10,7 +10,7 @@ import { z } from "zod"
import {
ModalHeaderCloseButton,
- ModalHeaderShubmitButton,
+ ModalHeaderSubmitButton,
} from "@/src/components/common/ModalSharedComponents"
import { FormProvider } from "@/src/components/ui/form/FormProvider"
import { FormLabel } from "@/src/components/ui/form/Label"
@@ -108,7 +108,7 @@ function FollowImpl() {
headerLeft: ModalHeaderCloseButton,
gestureEnabled: !isDirty,
headerRight: () => (
-
{view.name}
diff --git a/apps/mobile/src/modules/list/FollowList.tsx b/apps/mobile/src/modules/list/FollowList.tsx
index 60a26ae27..687158a21 100644
--- a/apps/mobile/src/modules/list/FollowList.tsx
+++ b/apps/mobile/src/modules/list/FollowList.tsx
@@ -8,7 +8,7 @@ import { z } from "zod"
import {
ModalHeaderCloseButton,
- ModalHeaderShubmitButton,
+ ModalHeaderSubmitButton,
} from "@/src/components/common/ModalSharedComponents"
import { FormProvider } from "@/src/components/ui/form/FormProvider"
import { FormLabel } from "@/src/components/ui/form/Label"
@@ -118,7 +118,7 @@ const Impl = (props: { id: string }) => {
headerLeft: ModalHeaderCloseButton,
gestureEnabled: !isDirty,
headerRight: () => (
- {
const AddListButton = () => {
const labelColor = useColor("label")
return (
-
+ router.push("/list")}>
)
@@ -109,7 +110,7 @@ const ListItemCell: ListRenderItem = ({ item: list
{
label: "Edit",
onPress: () => {
- // TODO
+ router.push(`/list?id=${list.id}`)
},
backgroundColor: "#0ea5e9",
},
diff --git a/apps/mobile/src/screens/(modal)/_layout.tsx b/apps/mobile/src/screens/(modal)/_layout.tsx
index 556f4460e..aaf78fa4d 100644
--- a/apps/mobile/src/screens/(modal)/_layout.tsx
+++ b/apps/mobile/src/screens/(modal)/_layout.tsx
@@ -21,6 +21,12 @@ export default function ModalLayout() {
title: "Follow",
}}
/>
+
)
}
diff --git a/apps/mobile/src/screens/(modal)/list.tsx b/apps/mobile/src/screens/(modal)/list.tsx
new file mode 100644
index 000000000..ba31735ba
--- /dev/null
+++ b/apps/mobile/src/screens/(modal)/list.tsx
@@ -0,0 +1,162 @@
+import { FeedViewType } from "@follow/constants"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { Stack, useLocalSearchParams } from "expo-router"
+import { memo } from "react"
+import { Controller, useForm } from "react-hook-form"
+import { View } from "react-native"
+import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
+import { z } from "zod"
+
+import {
+ ModalHeaderCloseButton,
+ ModalHeaderSubmitButton,
+} from "@/src/components/common/ModalSharedComponents"
+import { FormProvider, useFormContext } from "@/src/components/ui/form/FormProvider"
+import { FormLabel } from "@/src/components/ui/form/Label"
+import { NumberField, TextField } from "@/src/components/ui/form/TextField"
+import { GroupedInsetListCard } from "@/src/components/ui/grouped/GroupedList"
+import { PowerIcon } from "@/src/icons/power"
+import { FeedViewSelector } from "@/src/modules/feed/view-selector"
+import { useList } from "@/src/store/list/hooks"
+import { accentColor } from "@/src/theme/colors"
+
+const listSchema = z.object({
+ title: z.string().min(1),
+ description: z.string().min(1),
+ image: z.string().url(),
+ fee: z.number().min(0),
+ view: z.nativeEnum(FeedViewType),
+})
+export default function ListScreen() {
+ const listId = useLocalSearchParams<{ id?: string }>().id
+
+ const list = useList(listId || "")
+ const form = useForm({
+ defaultValues: list,
+ resolver: zodResolver(listSchema),
+ mode: "all",
+ })
+ return (
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+
+ )}
+ />
+
+
+
+ (
+
+ )}
+ />
+
+
+
+
+ (
+
+ )}
+ />
+
+
+
+ (
+ }
+ ref={ref}
+ />
+ )}
+ />
+
+
+
+
+ )
+}
+
+interface ScreenOptionsProps {
+ title?: string
+}
+const ScreenOptions = memo(({ title }: ScreenOptionsProps) => {
+ const form = useFormContext()
+
+ return (
+ (
+
+ {}} />
+
+ ),
+
+ headerTitle: title ? `Edit List - ${title}` : "Create List",
+ }}
+ />
+ )
+})
diff --git a/apps/mobile/src/screens/(modal)/rsshub-form.tsx b/apps/mobile/src/screens/(modal)/rsshub-form.tsx
index c72abe4c0..77544ad63 100644
--- a/apps/mobile/src/screens/(modal)/rsshub-form.tsx
+++ b/apps/mobile/src/screens/(modal)/rsshub-form.tsx
@@ -18,7 +18,7 @@ import { z } from "zod"
import { HeaderTitleExtra } from "@/src/components/common/HeaderTitleExtra"
import {
ModalHeaderCloseButton,
- ModalHeaderShubmitButton,
+ ModalHeaderSubmitButton,
} from "@/src/components/common/ModalSharedComponents"
import { FormProvider, useFormContext } from "@/src/components/ui/form/FormProvider"
import { Select } from "@/src/components/ui/form/Select"
@@ -247,7 +247,7 @@ const ScreenOptions = memo(({ name, routeName, route, routePrefix }: ScreenOptio
headerRight: () => (
-
+
),
@@ -267,17 +267,15 @@ const Title = ({ name, routeName, route, routePrefix }: ScreenOptionsProps) => {
)
}
-type ModalHeaderSubmitButtonProps = {
- routePrefix: string
- route: string
-}
-const ModalHeaderSubmitButton = ({ routePrefix, route }: ModalHeaderSubmitButtonProps) => {
- return
-}
-
const routeParamsKeyPrefix = "route-params-"
-const ModalHeaderSubmitButtonImpl = ({ routePrefix, route }: ModalHeaderSubmitButtonProps) => {
+const ModalHeaderSubmitButtonImpl = ({
+ routePrefix,
+ route,
+}: {
+ routePrefix: string
+ route: string
+}) => {
const form = useFormContext()
const { isValid } = form.formState
@@ -339,5 +337,5 @@ const ModalHeaderSubmitButtonImpl = ({ routePrefix, route }: ModalHeaderSubmitBu
}
})
- return
+ return
}
diff --git a/packages/utils/src/color.ts b/packages/utils/src/color.ts
index e6032e3e2..2bc50a289 100644
--- a/packages/utils/src/color.ts
+++ b/packages/utils/src/color.ts
@@ -148,7 +148,7 @@ export const isRGBColor = (color: string) => {
return /^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$/.test(color)
}
export const isRGBAColor = (color: string) => {
- return /^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*0?\.\d+\)$/.test(color)
+ return /^rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*(?:0?\.\d+|1(?:\.0+)?)\)$/.test(color)
}
export const withOpacity = (color: string, opacity: number) => {