1
+ import { useActionSheet } from "@expo/react-native-action-sheet"
1
2
import { FeedViewType } from "@follow/constants"
2
3
import { zodResolver } from "@hookform/resolvers/zod"
3
- import { Stack , useLocalSearchParams } from "expo-router"
4
- import { memo } from "react"
4
+ import { router , Stack , useLocalSearchParams } from "expo-router"
5
+ import { memo , useState } from "react"
5
6
import { Controller , useForm } from "react-hook-form"
6
7
import { View } from "react-native"
7
8
import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
@@ -14,32 +15,58 @@ import {
14
15
import { FormProvider , useFormContext } from "@/src/components/ui/form/FormProvider"
15
16
import { FormLabel } from "@/src/components/ui/form/Label"
16
17
import { NumberField , TextField } from "@/src/components/ui/form/TextField"
17
- import { GroupedInsetListCard } from "@/src/components/ui/grouped/GroupedList"
18
+ import {
19
+ GroupedInsetButtonCell ,
20
+ GroupedInsetListCard ,
21
+ } from "@/src/components/ui/grouped/GroupedList"
18
22
import { PowerIcon } from "@/src/icons/power"
23
+ import { getBizFetchErrorMessage } from "@/src/lib/api-fetch"
24
+ import { toast } from "@/src/lib/toast"
19
25
import { FeedViewSelector } from "@/src/modules/feed/view-selector"
26
+ import { getList } from "@/src/store/list/getters"
20
27
import { useList } from "@/src/store/list/hooks"
28
+ import type { ListModel } from "@/src/store/list/store"
29
+ import { listSyncServices } from "@/src/store/list/store"
30
+ import type { CreateListModel } from "@/src/store/list/types"
21
31
import { accentColor } from "@/src/theme/colors"
22
32
23
33
const listSchema = z . object ( {
24
34
title : z . string ( ) . min ( 1 ) ,
25
- description : z . string ( ) . min ( 1 ) ,
26
- image : z . string ( ) . url ( ) ,
35
+ description : z . string ( ) . nullable ( ) . optional ( ) ,
36
+ image : z
37
+ . string ( )
38
+ . url ( )
39
+ . nullable ( )
40
+ . optional ( )
41
+ . transform ( ( val ) => ( val === "" ? null : val ) ) ,
42
+
27
43
fee : z . number ( ) . min ( 0 ) ,
28
- view : z . nativeEnum ( FeedViewType ) ,
44
+ view : z . number ( ) . int ( ) ,
29
45
} )
46
+
47
+ const defaultValues = {
48
+ title : "" ,
49
+ description : null ,
50
+ image : null ,
51
+ fee : 0 ,
52
+ view : FeedViewType . Articles ,
53
+ } as ListModel
30
54
export default function ListScreen ( ) {
31
55
const listId = useLocalSearchParams < { id ?: string } > ( ) . id
32
56
33
57
const list = useList ( listId || "" )
34
58
const form = useForm ( {
35
- defaultValues : list ,
59
+ defaultValues : list || defaultValues ,
36
60
resolver : zodResolver ( listSchema ) ,
37
61
mode : "all" ,
38
62
} )
63
+ const isEditing = ! ! listId
64
+ const { showActionSheetWithOptions } = useActionSheet ( )
65
+
39
66
return (
40
67
< FormProvider form = { form } >
41
68
< KeyboardAwareScrollView className = "bg-system-grouped-background flex-1 pb-safe" >
42
- < ScreenOptions title = { list ?. title } />
69
+ < ScreenOptions title = { list ?. title } listId = { listId } />
43
70
44
71
< GroupedInsetListCard showSeparator = { false } className = "mt-6 px-3 py-6" >
45
72
< Controller
@@ -88,11 +115,14 @@ export default function ListScreen() {
88
115
control = { form . control }
89
116
render = { ( { field : { onChange, onBlur, ref, value } } ) => (
90
117
< TextField
118
+ autoCapitalize = "none"
91
119
label = "Image"
92
120
wrapperClassName = "mt-2"
93
121
placeholder = "https://"
94
122
onBlur = { onBlur }
95
- onChangeText = { onChange }
123
+ onChangeText = { ( val ) => {
124
+ onChange ( val === "" ? null : val )
125
+ } }
96
126
defaultValue = { list ?. image ?? "" }
97
127
value = { value ?? "" }
98
128
ref = { ref }
@@ -132,26 +162,96 @@ export default function ListScreen() {
132
162
/>
133
163
</ View >
134
164
</ GroupedInsetListCard >
165
+
166
+ { isEditing && (
167
+ < GroupedInsetListCard className = "mt-6" >
168
+ < GroupedInsetButtonCell
169
+ label = "Delete"
170
+ style = "destructive"
171
+ onPress = { ( ) => {
172
+ showActionSheetWithOptions (
173
+ {
174
+ options : [ "Delete" , "Cancel" ] ,
175
+ cancelButtonIndex : 1 ,
176
+ destructiveButtonIndex : 0 ,
177
+ } ,
178
+ async ( index ) => {
179
+ if ( index === 0 ) {
180
+ await listSyncServices . deleteList ( { listId : listId ! } )
181
+ router . dismiss ( )
182
+ }
183
+ } ,
184
+ )
185
+ } }
186
+ />
187
+ </ GroupedInsetListCard >
188
+ ) }
135
189
</ KeyboardAwareScrollView >
136
190
</ FormProvider >
137
191
)
138
192
}
139
193
140
194
interface ScreenOptionsProps {
141
195
title ?: string
196
+ listId ?: string
142
197
}
143
- const ScreenOptions = memo ( ( { title } : ScreenOptionsProps ) => {
198
+ const ScreenOptions = memo ( ( { title, listId } : ScreenOptionsProps ) => {
144
199
const form = useFormContext ( )
145
200
201
+ const { isValid, isDirty } = form . formState
202
+
203
+ const isEditing = ! ! listId
204
+ const [ isLoading , setIsLoading ] = useState ( false )
205
+
146
206
return (
147
207
< Stack . Screen
148
208
options = { {
149
209
headerLeft : ModalHeaderCloseButton ,
150
- gestureEnabled : ! form . formState . isDirty ,
210
+ gestureEnabled : ! isDirty ,
151
211
152
212
headerRight : ( ) => (
153
213
< FormProvider form = { form } >
154
- < ModalHeaderSubmitButton isValid onPress = { ( ) => { } } />
214
+ < ModalHeaderSubmitButton
215
+ isValid = { isValid }
216
+ isLoading = { isLoading }
217
+ onPress = { form . handleSubmit ( ( values ) => {
218
+ if ( ! isEditing ) {
219
+ setIsLoading ( true )
220
+ listSyncServices
221
+ . createList ( {
222
+ list : values as CreateListModel ,
223
+ } )
224
+ . catch ( ( error ) => {
225
+ toast . error ( getBizFetchErrorMessage ( error ) )
226
+ console . error ( error )
227
+ } )
228
+ . finally ( ( ) => {
229
+ setIsLoading ( false )
230
+ router . dismiss ( )
231
+ } )
232
+ return
233
+ }
234
+ const list = getList ( listId ! )
235
+ if ( ! list ) return
236
+ setIsLoading ( true )
237
+ listSyncServices
238
+ . updateList ( {
239
+ listId : listId ! ,
240
+ list : {
241
+ ...list ,
242
+ ...values ,
243
+ } ,
244
+ } )
245
+ . catch ( ( error ) => {
246
+ toast . error ( getBizFetchErrorMessage ( error ) )
247
+ console . error ( error )
248
+ } )
249
+ . finally ( ( ) => {
250
+ setIsLoading ( false )
251
+ router . dismiss ( )
252
+ } )
253
+ } ) }
254
+ />
155
255
</ FormProvider >
156
256
) ,
157
257
0 commit comments