import React from 'react' import {AppBskyActorDefs, AppBskyGraphDefs, ModerationUI} from '@atproto/api' import {StyleProp, ViewStyle} from 'react-native' import {Image as RNImage} from 'react-native-image-crop-picker' import {ImageModel} from '#/state/models/media/image' import {GalleryModel} from '#/state/models/media/gallery' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {EmbedPlayerSource} from '#/lib/strings/embed-player' import {ThreadgateSetting} from '../queries/threadgate' export interface ConfirmModal { name: 'confirm' title: string message: string | (() => JSX.Element) onPressConfirm: () => void | Promise onPressCancel?: () => void | Promise confirmBtnText?: string confirmBtnStyle?: StyleProp cancelBtnText?: string } export interface EditProfileModal { name: 'edit-profile' profile: AppBskyActorDefs.ProfileViewDetailed 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 type AppealLabelModal = { name: 'appeal-label' } & ( | { uri: string cid: string } | {did: string} ) export interface CreateOrEditListModal { name: 'create-or-edit-list' purpose?: string list?: AppBskyGraphDefs.ListView onSave?: (uri: string) => void } export interface UserAddRemoveListsModal { name: 'user-add-remove-lists' subject: string handle: string displayName: string onAdd?: (listUri: string) => void onRemove?: (listUri: string) => void } export interface ListAddRemoveUsersModal { name: 'list-add-remove-users' list: AppBskyGraphDefs.ListView onChange?: ( type: 'add' | 'remove', 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 ThreadgateModal { name: 'threadgate' settings: ThreadgateSetting[] onChange: (settings: ThreadgateSetting[]) => 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 ChangePasswordModal { name: 'change-password' } export interface SwitchAccountModal { name: 'switch-account' } export interface LinkWarningModal { name: 'link-warning' text: string href: string } export interface EmbedConsentModal { name: 'embed-consent' source: EmbedPlayerSource onAccept: () => void } export interface InAppBrowserConsentModal { name: 'in-app-browser-consent' href: string } export type Modal = // Account | AddAppPasswordModal | ChangeHandleModal | DeleteAccountModal | EditProfileModal | ProfilePreviewModal | BirthDateSettingsModal | VerifyEmailModal | ChangeEmailModal | ChangePasswordModal | SwitchAccountModal // Curation | ContentFilteringSettingsModal | ContentLanguagesSettingsModal | PostLanguagesSettingsModal // Moderation | ModerationDetailsModal | ReportModal | AppealLabelModal // Lists | CreateOrEditListModal | UserAddRemoveListsModal | ListAddRemoveUsersModal // Posts | AltTextImageModal | CropImageModal | EditImageModal | ServerInputModal | RepostModal | SelfLabelModal | ThreadgateModal // Bluesky access | WaitlistModal | InviteCodesModal // Generic | ConfirmModal | LinkWarningModal | EmbedConsentModal | InAppBrowserConsentModal const ModalContext = React.createContext<{ isModalActive: boolean activeModals: Modal[] }>({ isModalActive: false, activeModals: [], }) const ModalControlContext = React.createContext<{ openModal: (modal: Modal) => void closeModal: () => boolean closeAllModals: () => void }>({ openModal: () => {}, closeModal: () => false, closeAllModals: () => {}, }) /** * @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`) } /** * @deprecated DO NOT USE THIS unless you have no other choice. */ export let unstable__closeModal: () => boolean = () => { throw new Error(`ModalContext is not initialized`) } export function Provider({children}: React.PropsWithChildren<{}>) { const [activeModals, setActiveModals] = React.useState([]) const openModal = useNonReactiveCallback((modal: Modal) => { setActiveModals(modals => [...modals, modal]) }) const closeModal = useNonReactiveCallback(() => { let wasActive = activeModals.length > 0 setActiveModals(modals => { return modals.slice(0, -1) }) return wasActive }) const closeAllModals = useNonReactiveCallback(() => { setActiveModals([]) }) unstable__openModal = openModal unstable__closeModal = closeModal const state = React.useMemo( () => ({ isModalActive: activeModals.length > 0, activeModals, }), [activeModals], ) const methods = React.useMemo( () => ({ openModal, closeModal, closeAllModals, }), [openModal, closeModal, closeAllModals], ) return ( {children} ) } export function useModals() { return React.useContext(ModalContext) } export function useModalControls() { return React.useContext(ModalControlContext) }