Add modal state provider, replace usage except methods (#1833)

* Add modal state provider, replace usage except methods

* Replace easy spots

* Fix sticky spots

* Replace final usages

* Memorize context objects

* Add more warnings
This commit is contained in:
Eric Bailey 2023-11-08 12:34:10 -06:00 committed by GitHub
parent 5eadadffbf
commit f18b15241a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 634 additions and 498 deletions

284
src/state/modals/index.tsx Normal file
View file

@ -0,0 +1,284 @@
import React from 'react'
import {AppBskyActorDefs, ModerationUI} from '@atproto/api'
import {StyleProp, ViewStyle, DeviceEventEmitter} from 'react-native'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {ProfileModel} from '#/state/models/content/profile'
import {ImageModel} from '#/state/models/media/image'
import {ListModel} from '#/state/models/content/list'
import {GalleryModel} from '#/state/models/media/gallery'
export interface ConfirmModal {
name: 'confirm'
title: string
message: string | (() => JSX.Element)
onPressConfirm: () => void | Promise<void>
onPressCancel?: () => void | Promise<void>
confirmBtnText?: string
confirmBtnStyle?: StyleProp<ViewStyle>
cancelBtnText?: string
}
export interface EditProfileModal {
name: 'edit-profile'
profileView: ProfileModel
onUpdate?: () => void
}
export interface ProfilePreviewModal {
name: 'profile-preview'
did: string
}
export interface ServerInputModal {
name: 'server-input'
initialService: string
onSelect: (url: string) => void
}
export interface ModerationDetailsModal {
name: 'moderation-details'
context: 'account' | 'content'
moderation: ModerationUI
}
export type ReportModal = {
name: 'report'
} & (
| {
uri: string
cid: string
}
| {did: string}
)
export interface CreateOrEditListModal {
name: 'create-or-edit-list'
purpose?: string
list?: ListModel
onSave?: (uri: string) => void
}
export interface UserAddRemoveListsModal {
name: 'user-add-remove-lists'
subject: string
displayName: string
onAdd?: (listUri: string) => void
onRemove?: (listUri: string) => void
}
export interface ListAddUserModal {
name: 'list-add-user'
list: ListModel
onAdd?: (profile: AppBskyActorDefs.ProfileViewBasic) => void
}
export interface EditImageModal {
name: 'edit-image'
image: ImageModel
gallery: GalleryModel
}
export interface CropImageModal {
name: 'crop-image'
uri: string
onSelect: (img?: RNImage) => void
}
export interface AltTextImageModal {
name: 'alt-text-image'
image: ImageModel
}
export interface DeleteAccountModal {
name: 'delete-account'
}
export interface RepostModal {
name: 'repost'
onRepost: () => void
onQuote: () => void
isReposted: boolean
}
export interface SelfLabelModal {
name: 'self-label'
labels: string[]
hasMedia: boolean
onChange: (labels: string[]) => void
}
export interface ChangeHandleModal {
name: 'change-handle'
onChanged: () => void
}
export interface WaitlistModal {
name: 'waitlist'
}
export interface InviteCodesModal {
name: 'invite-codes'
}
export interface AddAppPasswordModal {
name: 'add-app-password'
}
export interface ContentFilteringSettingsModal {
name: 'content-filtering-settings'
}
export interface ContentLanguagesSettingsModal {
name: 'content-languages-settings'
}
export interface PostLanguagesSettingsModal {
name: 'post-languages-settings'
}
export interface BirthDateSettingsModal {
name: 'birth-date-settings'
}
export interface VerifyEmailModal {
name: 'verify-email'
showReminder?: boolean
}
export interface ChangeEmailModal {
name: 'change-email'
}
export interface SwitchAccountModal {
name: 'switch-account'
}
export interface LinkWarningModal {
name: 'link-warning'
text: string
href: string
}
export type Modal =
// Account
| AddAppPasswordModal
| ChangeHandleModal
| DeleteAccountModal
| EditProfileModal
| ProfilePreviewModal
| BirthDateSettingsModal
| VerifyEmailModal
| ChangeEmailModal
| SwitchAccountModal
// Curation
| ContentFilteringSettingsModal
| ContentLanguagesSettingsModal
| PostLanguagesSettingsModal
// Moderation
| ModerationDetailsModal
| ReportModal
// Lists
| CreateOrEditListModal
| UserAddRemoveListsModal
| ListAddUserModal
// Posts
| AltTextImageModal
| CropImageModal
| EditImageModal
| ServerInputModal
| RepostModal
| SelfLabelModal
// Bluesky access
| WaitlistModal
| InviteCodesModal
// Generic
| ConfirmModal
| LinkWarningModal
const ModalContext = React.createContext<{
isModalActive: boolean
activeModals: Modal[]
}>({
isModalActive: false,
activeModals: [],
})
const ModalControlContext = React.createContext<{
openModal: (modal: Modal) => void
closeModal: () => void
}>({
openModal: () => {},
closeModal: () => {},
})
/**
* @deprecated DO NOT USE THIS unless you have no other choice.
*/
export let unstable__openModal: (modal: Modal) => void = () => {
throw new Error(`ModalContext is not initialized`)
}
export function Provider({children}: React.PropsWithChildren<{}>) {
const [isModalActive, setIsModalActive] = React.useState(false)
const [activeModals, setActiveModals] = React.useState<Modal[]>([])
const openModal = React.useCallback(
(modal: Modal) => {
DeviceEventEmitter.emit('navigation')
setActiveModals(activeModals => [...activeModals, modal])
setIsModalActive(true)
},
[setIsModalActive, setActiveModals],
)
unstable__openModal = openModal
const closeModal = React.useCallback(() => {
let totalActiveModals = 0
setActiveModals(activeModals => {
activeModals.pop()
totalActiveModals = activeModals.length
return activeModals
})
setIsModalActive(totalActiveModals > 0)
}, [setIsModalActive, setActiveModals])
const state = React.useMemo(
() => ({
isModalActive,
activeModals,
}),
[isModalActive, activeModals],
)
const methods = React.useMemo(
() => ({
openModal,
closeModal,
}),
[openModal, closeModal],
)
return (
<ModalContext.Provider value={state}>
<ModalControlContext.Provider value={methods}>
{children}
</ModalControlContext.Provider>
</ModalContext.Provider>
)
}
export function useModals() {
return React.useContext(ModalContext)
}
export function useModalControls() {
return React.useContext(ModalControlContext)
}