mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {Image as RNImage} from 'react-native-image-crop-picker'
3import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
4
5import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
6import {GalleryModel} from '#/state/models/media/gallery'
7import {ImageModel} from '#/state/models/media/image'
8
9export interface EditProfileModal {
10 name: 'edit-profile'
11 profile: AppBskyActorDefs.ProfileViewDetailed
12 onUpdate?: () => void
13}
14
15export interface CreateOrEditListModal {
16 name: 'create-or-edit-list'
17 purpose?: string
18 list?: AppBskyGraphDefs.ListView
19 onSave?: (uri: string) => void
20}
21
22export interface UserAddRemoveListsModal {
23 name: 'user-add-remove-lists'
24 subject: string
25 handle: string
26 displayName: string
27 onAdd?: (listUri: string) => void
28 onRemove?: (listUri: string) => void
29}
30
31export interface ListAddRemoveUsersModal {
32 name: 'list-add-remove-users'
33 list: AppBskyGraphDefs.ListView
34 onChange?: (
35 type: 'add' | 'remove',
36 profile: AppBskyActorDefs.ProfileViewBasic,
37 ) => void
38}
39
40export interface EditImageModal {
41 name: 'edit-image'
42 image: ImageModel
43 gallery: GalleryModel
44}
45
46export interface CropImageModal {
47 name: 'crop-image'
48 uri: string
49 dimensions?: {width: number; height: number}
50 onSelect: (img?: RNImage) => void
51}
52
53export interface AltTextImageModal {
54 name: 'alt-text-image'
55 image: ImageModel
56}
57
58export interface DeleteAccountModal {
59 name: 'delete-account'
60}
61
62export interface SelfLabelModal {
63 name: 'self-label'
64 labels: string[]
65 hasMedia: boolean
66 onChange: (labels: string[]) => void
67}
68
69export interface ChangeHandleModal {
70 name: 'change-handle'
71 onChanged: () => void
72}
73
74export interface WaitlistModal {
75 name: 'waitlist'
76}
77
78export interface InviteCodesModal {
79 name: 'invite-codes'
80}
81
82export interface AddAppPasswordModal {
83 name: 'add-app-password'
84}
85
86export interface ContentLanguagesSettingsModal {
87 name: 'content-languages-settings'
88}
89
90export interface PostLanguagesSettingsModal {
91 name: 'post-languages-settings'
92}
93
94export interface VerifyEmailModal {
95 name: 'verify-email'
96 showReminder?: boolean
97 onSuccess?: () => void
98}
99
100export interface ChangeEmailModal {
101 name: 'change-email'
102}
103
104export interface ChangePasswordModal {
105 name: 'change-password'
106}
107
108export interface LinkWarningModal {
109 name: 'link-warning'
110 text: string
111 href: string
112 share?: boolean
113}
114
115export interface InAppBrowserConsentModal {
116 name: 'in-app-browser-consent'
117 href: string
118}
119
120export type Modal =
121 // Account
122 | AddAppPasswordModal
123 | ChangeHandleModal
124 | DeleteAccountModal
125 | EditProfileModal
126 | VerifyEmailModal
127 | ChangeEmailModal
128 | ChangePasswordModal
129
130 // Curation
131 | ContentLanguagesSettingsModal
132 | PostLanguagesSettingsModal
133
134 // Lists
135 | CreateOrEditListModal
136 | UserAddRemoveListsModal
137 | ListAddRemoveUsersModal
138
139 // Posts
140 | AltTextImageModal
141 | CropImageModal
142 | EditImageModal
143 | SelfLabelModal
144
145 // Bluesky access
146 | WaitlistModal
147 | InviteCodesModal
148
149 // Generic
150 | LinkWarningModal
151 | InAppBrowserConsentModal
152
153const ModalContext = React.createContext<{
154 isModalActive: boolean
155 activeModals: Modal[]
156}>({
157 isModalActive: false,
158 activeModals: [],
159})
160
161const ModalControlContext = React.createContext<{
162 openModal: (modal: Modal) => void
163 closeModal: () => boolean
164 closeAllModals: () => boolean
165}>({
166 openModal: () => {},
167 closeModal: () => false,
168 closeAllModals: () => false,
169})
170
171/**
172 * @deprecated DO NOT USE THIS unless you have no other choice.
173 */
174export let unstable__openModal: (modal: Modal) => void = () => {
175 throw new Error(`ModalContext is not initialized`)
176}
177
178/**
179 * @deprecated DO NOT USE THIS unless you have no other choice.
180 */
181export let unstable__closeModal: () => boolean = () => {
182 throw new Error(`ModalContext is not initialized`)
183}
184
185export function Provider({children}: React.PropsWithChildren<{}>) {
186 const [activeModals, setActiveModals] = React.useState<Modal[]>([])
187
188 const openModal = useNonReactiveCallback((modal: Modal) => {
189 setActiveModals(modals => [...modals, modal])
190 })
191
192 const closeModal = useNonReactiveCallback(() => {
193 let wasActive = activeModals.length > 0
194 setActiveModals(modals => {
195 return modals.slice(0, -1)
196 })
197 return wasActive
198 })
199
200 const closeAllModals = useNonReactiveCallback(() => {
201 let wasActive = activeModals.length > 0
202 setActiveModals([])
203 return wasActive
204 })
205
206 unstable__openModal = openModal
207 unstable__closeModal = closeModal
208
209 const state = React.useMemo(
210 () => ({
211 isModalActive: activeModals.length > 0,
212 activeModals,
213 }),
214 [activeModals],
215 )
216
217 const methods = React.useMemo(
218 () => ({
219 openModal,
220 closeModal,
221 closeAllModals,
222 }),
223 [openModal, closeModal, closeAllModals],
224 )
225
226 return (
227 <ModalContext.Provider value={state}>
228 <ModalControlContext.Provider value={methods}>
229 {children}
230 </ModalControlContext.Provider>
231 </ModalContext.Provider>
232 )
233}
234
235export function useModals() {
236 return React.useContext(ModalContext)
237}
238
239export function useModalControls() {
240 return React.useContext(ModalControlContext)
241}