From a97f68036be4df8a5ee211442ae85a1bd14456bc Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 7 Nov 2023 15:33:25 -0600 Subject: [PATCH 1/6] Add modal state provider, replace usage except methods --- src/App.native.tsx | 31 +-- src/App.web.tsx | 31 +-- src/state/modals/index.tsx | 267 +++++++++++++++++++++++++ src/state/models/ui/shell.ts | 230 ++------------------- src/view/com/composer/Composer.tsx | 6 +- src/view/com/modals/Modal.tsx | 24 +-- src/view/com/modals/Modal.web.tsx | 15 +- src/view/com/profile/ProfileHeader.tsx | 22 +- 8 files changed, 349 insertions(+), 277 deletions(-) create mode 100644 src/state/modals/index.tsx diff --git a/src/App.native.tsx b/src/App.native.tsx index a99dbc9515..7551870ff8 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -20,6 +20,7 @@ import * as Toast from 'view/com/util/Toast' import {queryClient} from 'lib/react-query' import {TestCtrls} from 'view/com/testing/TestCtrls' import {Provider as ShellStateProvider} from 'state/shell' +import {Provider as ModalStateProvider} from 'state/modals' SplashScreen.preventAutoHideAsync() @@ -46,20 +47,22 @@ const App = observer(function AppImpl() { } return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ) }) diff --git a/src/App.web.tsx b/src/App.web.tsx index 6bbc2065db..be5c3a7ad1 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -15,6 +15,7 @@ import {ToastContainer} from 'view/com/util/Toast.web' import {ThemeProvider} from 'lib/ThemeContext' import {queryClient} from 'lib/react-query' import {Provider as ShellStateProvider} from 'state/shell' +import {Provider as ModalStateProvider} from 'state/modals' const App = observer(function AppImpl() { const [rootStore, setRootStore] = useState( @@ -36,20 +37,22 @@ const App = observer(function AppImpl() { return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ) }) diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx new file mode 100644 index 0000000000..1eb82e7d12 --- /dev/null +++ b/src/state/modals/index.tsx @@ -0,0 +1,267 @@ +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 + onPressCancel?: () => void | Promise + confirmBtnText?: string + confirmBtnStyle?: StyleProp + 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: () => {}, +}) + +export function Provider({children}: React.PropsWithChildren<{}>) { + const [isModalActive, setIsModalActive] = React.useState(false) + const [activeModals, setActiveModals] = React.useState([]) + + const openModal = React.useCallback( + (modal: Modal) => { + DeviceEventEmitter.emit('navigation') + setActiveModals(activeModals => [...activeModals, modal]) + setIsModalActive(true) + }, + [setIsModalActive, setActiveModals], + ) + + const closeModal = React.useCallback(() => { + let totalActiveModals = 0 + setActiveModals(activeModals => { + activeModals.pop() + totalActiveModals = activeModals.length + return activeModals + }) + setIsModalActive(totalActiveModals > 0) + }, [setIsModalActive, setActiveModals]) + + return ( + + + {children} + + + ) +} + +export function useModals() { + return React.useContext(ModalContext) +} + +export function useModalControls() { + return React.useContext(ModalControlContext) +} diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index d690b9331a..418a2a909c 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -1,13 +1,8 @@ -import {AppBskyEmbedRecord, AppBskyActorDefs, ModerationUI} from '@atproto/api' +import {AppBskyEmbedRecord} from '@atproto/api' import {RootStoreModel} from '../root-store' import {makeAutoObservable, runInAction} from 'mobx' import {ProfileModel} from '../content/profile' import {isObj, hasProp} from 'lib/type-guards' -import {Image as RNImage} from 'react-native-image-crop-picker' -import {ImageModel} from '../media/image' -import {ListModel} from '../content/list' -import {GalleryModel} from '../media/gallery' -import {StyleProp, ViewStyle} from 'react-native' import {isWeb} from 'platform/detection' export type ColorMode = 'system' | 'light' | 'dark' @@ -16,200 +11,6 @@ export function isColorMode(v: unknown): v is ColorMode { return v === 'system' || v === 'light' || v === 'dark' } -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' - 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 - interface LightboxModel {} export class ProfileImageLightbox implements LightboxModel { @@ -266,8 +67,6 @@ export interface ComposerOpts { export class ShellUiModel { colorMode: ColorMode = 'system' - isModalActive = false - activeModals: Modal[] = [] isLightboxActive = false activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null isComposerActive = false @@ -319,10 +118,11 @@ export class ShellUiModel { this.closeLightbox() return true } - if (this.isModalActive) { - this.closeModal() - return true - } + // if (this.isModalActive) { + // // TODO + // this.closeModal() + // return true + // } if (this.isComposerActive) { this.closeComposer() return true @@ -337,24 +137,18 @@ export class ShellUiModel { if (this.isLightboxActive) { this.closeLightbox() } - while (this.isModalActive) { - this.closeModal() - } + // while (this.isModalActive) { + // // TODO + // this.closeModal() + // } if (this.isComposerActive) { this.closeComposer() } } - openModal(modal: Modal) { - this.rootStore.emitNavigation() - this.isModalActive = true - this.activeModals.push(modal) - } + openModal() {} - closeModal() { - this.activeModals.pop() - this.isModalActive = this.activeModals.length > 0 - } + closeModal() {} openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) { this.rootStore.emitNavigation() diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index e44a0ce010..65adb47560 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -49,6 +49,7 @@ import {LabelsBtn} from './labels/LabelsBtn' import {SelectLangBtn} from './select-language/SelectLangBtn' import {EmojiPickerButton} from './text-input/web/EmojiPicker.web' import {insertMentionAt} from 'lib/strings/mention-manip' +import {useModals} from '#/state/modals' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -57,6 +58,7 @@ export const ComposePost = observer(function ComposePost({ quote: initQuote, mention: initMention, }: Props) { + const {activeModals} = useModals() const {track} = useAnalytics() const pal = usePalette('default') const {isDesktop, isMobile} = useWebMediaQueries() @@ -108,7 +110,7 @@ export const ComposePost = observer(function ComposePost({ const onPressCancel = useCallback(() => { if (graphemeLength > 0 || !gallery.isEmpty) { - if (store.shell.activeModals.some(modal => modal.name === 'confirm')) { + if (activeModals.some(modal => modal.name === 'confirm')) { store.shell.closeModal() } if (Keyboard) { @@ -128,7 +130,7 @@ export const ComposePost = observer(function ComposePost({ } else { onClose() } - }, [store, onClose, graphemeLength, gallery]) + }, [store, activeModals, onClose, graphemeLength, gallery]) // android back button useEffect(() => { if (!isAndroid) { diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 5aaa09e875..c1999c5d63 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -3,13 +3,13 @@ import {StyleSheet} from 'react-native' import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context' import {observer} from 'mobx-react-lite' import BottomSheet from '@gorhom/bottom-sheet' -import {useStores} from 'state/index' import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {usePalette} from 'lib/hooks/usePalette' import {timeout} from 'lib/async/timeout' import {navigate} from '../../../Navigation' import once from 'lodash.once' +import {useModals, useModalControls} from '#/state/modals' import * as ConfirmModal from './Confirm' import * as EditProfileModal from './EditProfile' import * as ProfilePreviewModal from './ProfilePreview' @@ -41,17 +41,17 @@ const DEFAULT_SNAPPOINTS = ['90%'] const HANDLE_HEIGHT = 24 export const ModalsContainer = observer(function ModalsContainer() { - const store = useStores() + const {isModalActive, activeModals} = useModals() + const {closeModal} = useModalControls() const bottomSheetRef = useRef(null) const pal = usePalette('default') const safeAreaInsets = useSafeAreaInsets() - const activeModal = - store.shell.activeModals[store.shell.activeModals.length - 1] + const activeModal = activeModals[activeModals.length - 1] const navigateOnce = once(navigate) - const onBottomSheetAnimate = (fromIndex: number, toIndex: number) => { + const onBottomSheetAnimate = (_fromIndex: number, toIndex: number) => { if (activeModal?.name === 'profile-preview' && toIndex === 1) { // begin loading the profile screen behind the scenes navigateOnce('Profile', {name: activeModal.did}) @@ -59,7 +59,7 @@ export const ModalsContainer = observer(function ModalsContainer() { } const onBottomSheetChange = async (snapPoint: number) => { if (snapPoint === -1) { - store.shell.closeModal() + closeModal() } else if (activeModal?.name === 'profile-preview' && snapPoint === 1) { await navigateOnce('Profile', {name: activeModal.did}) // There is no particular callback for when the view has actually been presented. @@ -67,21 +67,21 @@ export const ModalsContainer = observer(function ModalsContainer() { // It's acceptable because the data is already being fetched + it usually takes longer anyway. // TODO: Figure out why avatar/cover don't always show instantly from cache. await timeout(200) - store.shell.closeModal() + closeModal() } } const onClose = () => { bottomSheetRef.current?.close() - store.shell.closeModal() + closeModal() } useEffect(() => { - if (store.shell.isModalActive) { + if (isModalActive) { bottomSheetRef.current?.expand() } else { bottomSheetRef.current?.close() } - }, [store.shell.isModalActive, bottomSheetRef, activeModal?.name]) + }, [isModalActive, bottomSheetRef, activeModal?.name]) let needsSafeTopInset = false let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS @@ -184,12 +184,12 @@ export const ModalsContainer = observer(function ModalsContainer() { snapPoints={snapPoints} topInset={topInset} handleHeight={HANDLE_HEIGHT} - index={store.shell.isModalActive ? 0 : -1} + index={isModalActive ? 0 : -1} enablePanDownToClose android_keyboardInputMode="adjustResize" keyboardBlurBehavior="restore" backdropComponent={ - store.shell.isModalActive ? createCustomBackdrop(onClose) : undefined + isModalActive ? createCustomBackdrop(onClose) : undefined } handleIndicatorStyle={{backgroundColor: pal.text.color}} handleStyle={[styles.handle, pal.view]} diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index ede8453789..248d93b000 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -1,11 +1,11 @@ import React from 'react' import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' -import {useStores} from 'state/index' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import type {Modal as ModalIface} from 'state/models/ui/shell' +import {useModals, useModalControls} from '#/state/modals' import * as ConfirmModal from './Confirm' import * as EditProfileModal from './EditProfile' import * as ProfilePreviewModal from './ProfilePreview' @@ -34,15 +34,15 @@ import * as ChangeEmailModal from './ChangeEmail' import * as LinkWarningModal from './LinkWarning' export const ModalsContainer = observer(function ModalsContainer() { - const store = useStores() + const {isModalActive, activeModals} = useModals() - if (!store.shell.isModalActive) { + if (!isModalActive) { return null } return ( <> - {store.shell.activeModals.map((modal, i) => ( + {activeModals.map((modal, i) => ( ))} @@ -50,11 +50,12 @@ export const ModalsContainer = observer(function ModalsContainer() { }) function Modal({modal}: {modal: ModalIface}) { - const store = useStores() + const {isModalActive} = useModals() + const {closeModal} = useModalControls() const pal = usePalette('default') const {isMobile} = useWebMediaQueries() - if (!store.shell.isModalActive) { + if (!isModalActive) { return null } @@ -62,7 +63,7 @@ function Modal({modal}: {modal: ModalIface}) { if (modal.name === 'crop-image' || modal.name === 'edit-image') { return // dont close on mask presses during crop } - store.shell.closeModal() + closeModal() } const onInnerPress = () => { // TODO: can we use prevent default? diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 1a1d38e4b7..1ee2097856 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -40,6 +40,7 @@ import {makeProfileLink} from 'lib/routes/links' import {Link} from '../util/Link' import {ProfileHeaderSuggestedFollows} from './ProfileHeaderSuggestedFollows' import {logger} from '#/logger' +import {useModalControls} from '#/state/modals' interface Props { view: ProfileModel @@ -113,6 +114,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({ const pal = usePalette('default') const palInverted = usePalette('inverted') const store = useStores() + const {openModal} = useModalControls() const navigation = useNavigation() const {track} = useAnalytics() const invalidHandle = isInvalidHandle(view.handle) @@ -157,12 +159,12 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({ const onPressEditProfile = React.useCallback(() => { track('ProfileHeader:EditProfileButtonClicked') - store.shell.openModal({ + openModal({ name: 'edit-profile', profileView: view, onUpdate: onRefreshAll, }) - }, [track, store, view, onRefreshAll]) + }, [track, openModal, view, onRefreshAll]) const trackPress = React.useCallback( (f: 'Followers' | 'Follows') => { @@ -181,12 +183,12 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({ const onPressAddRemoveLists = React.useCallback(() => { track('ProfileHeader:AddToListsButtonClicked') - store.shell.openModal({ + openModal({ name: 'user-add-remove-lists', subject: view.did, displayName: view.displayName || view.handle, }) - }, [track, view, store]) + }, [track, view, openModal]) const onPressMuteAccount = React.useCallback(async () => { track('ProfileHeader:MuteAccountButtonClicked') @@ -212,7 +214,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({ const onPressBlockAccount = React.useCallback(async () => { track('ProfileHeader:BlockAccountButtonClicked') - store.shell.openModal({ + openModal({ name: 'confirm', title: 'Block Account', message: @@ -228,11 +230,11 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({ } }, }) - }, [track, view, store, onRefreshAll]) + }, [track, view, openModal, onRefreshAll]) const onPressUnblockAccount = React.useCallback(async () => { track('ProfileHeader:UnblockAccountButtonClicked') - store.shell.openModal({ + openModal({ name: 'confirm', title: 'Unblock Account', message: @@ -248,15 +250,15 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({ } }, }) - }, [track, view, store, onRefreshAll]) + }, [track, view, openModal, onRefreshAll]) const onPressReportAccount = React.useCallback(() => { track('ProfileHeader:ReportAccountButtonClicked') - store.shell.openModal({ + openModal({ name: 'report', did: view.did, }) - }, [track, store, view]) + }, [track, openModal, view]) const isMe = React.useMemo( () => store.me.did === view.did, From 04fe8ca2aa176f76f7729c55cfa9d0a65813a6e6 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 7 Nov 2023 16:55:55 -0600 Subject: [PATCH 2/6] Replace easy spots --- src/lib/hooks/useOTAUpdate.ts | 8 ++--- src/lib/media/alt-text.ts | 12 ------- src/state/models/ui/shell.ts | 2 -- src/view/com/auth/create/Step2.tsx | 8 ++--- src/view/com/auth/login/Login.tsx | 8 +++-- src/view/com/composer/Composer.tsx | 11 ++++--- src/view/com/composer/labels/LabelsBtn.tsx | 6 ++-- src/view/com/composer/photos/Gallery.tsx | 15 ++++++--- .../select-language/SelectLangBtn.tsx | 6 ++-- src/view/com/feeds/FeedSourceCard.tsx | 8 ++--- src/view/com/lists/ListItems.tsx | 8 ++--- src/view/com/modals/AddAppPasswords.tsx | 6 ++-- src/view/com/modals/AltImage.tsx | 10 +++--- src/view/com/modals/BirthDateSettings.tsx | 4 ++- src/view/com/modals/ChangeEmail.tsx | 8 +++-- src/view/com/modals/ChangeHandle.tsx | 9 ++++-- src/view/com/modals/Confirm.tsx | 8 ++--- .../com/modals/ContentFilteringSettings.tsx | 9 ++++-- src/view/com/modals/CreateOrEditList.tsx | 9 ++++-- src/view/com/modals/DeleteAccount.tsx | 6 ++-- src/view/com/modals/EditImage.tsx | 8 ++--- src/view/com/modals/EditProfile.tsx | 10 +++--- src/view/com/modals/InviteCodes.tsx | 6 ++-- src/view/com/modals/LinkWarning.tsx | 8 ++--- src/view/com/modals/ListAddUser.tsx | 4 ++- src/view/com/modals/ModerationDetails.tsx | 9 ++---- src/view/com/modals/Repost.tsx | 6 ++-- src/view/com/modals/SelfLabel.tsx | 6 ++-- src/view/com/modals/ServerInput.tsx | 6 ++-- src/view/com/modals/UserAddRemoveLists.tsx | 10 +++--- src/view/com/modals/VerifyEmail.tsx | 10 +++--- src/view/com/modals/Waitlist.tsx | 6 ++-- .../com/modals/crop-image/CropImage.web.tsx | 8 ++--- .../ContentLanguagesSettings.tsx | 6 ++-- .../lang-settings/PostLanguagesSettings.tsx | 6 ++-- src/view/com/modals/report/Modal.tsx | 6 ++-- src/view/com/posts/FeedErrorMessage.tsx | 8 +++-- src/view/com/testing/TestCtrls.e2e.tsx | 4 ++- src/view/com/util/Link.tsx | 28 ++++++++++------ src/view/com/util/UserPreviewLink.tsx | 6 ++-- src/view/com/util/forms/PostDropdownBtn.tsx | 8 ++--- src/view/com/util/moderation/ContentHider.tsx | 8 ++--- src/view/com/util/moderation/PostAlerts.tsx | 6 ++-- src/view/com/util/moderation/PostHider.tsx | 6 ++-- .../util/moderation/ProfileHeaderAlerts.tsx | 6 ++-- src/view/com/util/moderation/ScreenHider.tsx | 6 ++-- src/view/com/util/post-ctrls/PostCtrls.tsx | 9 ++++-- src/view/com/util/post-ctrls/RepostButton.tsx | 8 ++--- src/view/screens/AppPasswords.tsx | 11 ++++--- src/view/screens/LanguageSettings.tsx | 6 ++-- src/view/screens/Lists.tsx | 6 ++-- src/view/screens/Moderation.tsx | 8 ++--- src/view/screens/ModerationModlists.tsx | 6 ++-- src/view/screens/ProfileFeed.tsx | 6 ++-- src/view/screens/ProfileList.tsx | 32 ++++++++++--------- src/view/screens/Settings.tsx | 25 +++++++-------- src/view/shell/Drawer.tsx | 6 ++-- src/view/shell/bottom-bar/BottomBar.tsx | 6 ++-- src/view/shell/desktop/RightNav.tsx | 6 ++-- 59 files changed, 277 insertions(+), 220 deletions(-) delete mode 100644 src/lib/media/alt-text.ts diff --git a/src/lib/hooks/useOTAUpdate.ts b/src/lib/hooks/useOTAUpdate.ts index 0ce97a4c8b..a3584fc9db 100644 --- a/src/lib/hooks/useOTAUpdate.ts +++ b/src/lib/hooks/useOTAUpdate.ts @@ -1,15 +1,15 @@ import * as Updates from 'expo-updates' import {useCallback, useEffect} from 'react' import {AppState} from 'react-native' -import {useStores} from 'state/index' import {logger} from '#/logger' +import {useModalControls} from '#/state/modals' export function useOTAUpdate() { - const store = useStores() + const {openModal} = useModalControls() // HELPER FUNCTIONS const showUpdatePopup = useCallback(() => { - store.shell.openModal({ + openModal({ name: 'confirm', title: 'Update Available', message: @@ -20,7 +20,7 @@ export function useOTAUpdate() { }) }, }) - }, [store.shell]) + }, [openModal]) const checkForUpdate = useCallback(async () => { logger.debug('useOTAUpdate: Checking for update...') try { diff --git a/src/lib/media/alt-text.ts b/src/lib/media/alt-text.ts deleted file mode 100644 index 4109f667a0..0000000000 --- a/src/lib/media/alt-text.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {RootStoreModel} from 'state/index' -import {ImageModel} from 'state/models/media/image' - -export async function openAltTextModal( - store: RootStoreModel, - image: ImageModel, -) { - store.shell.openModal({ - name: 'alt-text-image', - image, - }) -} diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts index 418a2a909c..6c458ed78e 100644 --- a/src/state/models/ui/shell.ts +++ b/src/state/models/ui/shell.ts @@ -148,8 +148,6 @@ export class ShellUiModel { openModal() {} - closeModal() {} - openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) { this.rootStore.emitNavigation() this.isLightboxActive = true diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index 60e1975648..b2054150b3 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -10,8 +10,8 @@ import {usePalette} from 'lib/hooks/usePalette' import {TextInput} from '../util/TextInput' import {Policies} from './Policies' import {ErrorMessage} from 'view/com/util/error/ErrorMessage' -import {useStores} from 'state/index' import {isWeb} from 'platform/detection' +import {useModalControls} from '#/state/modals' /** STEP 2: Your account * @field Invite code or waitlist @@ -28,11 +28,11 @@ export const Step2 = observer(function Step2Impl({ model: CreateAccountModel }) { const pal = usePalette('default') - const store = useStores() + const {openModal} = useModalControls() const onPressWaitlist = React.useCallback(() => { - store.shell.openModal({name: 'waitlist'}) - }, [store]) + openModal({name: 'waitlist'}) + }, [openModal]) return ( diff --git a/src/view/com/auth/login/Login.tsx b/src/view/com/auth/login/Login.tsx index acc05b6ca3..24a657c66c 100644 --- a/src/view/com/auth/login/Login.tsx +++ b/src/view/com/auth/login/Login.tsx @@ -31,6 +31,7 @@ import {useTheme} from 'lib/ThemeContext' import {cleanError} from 'lib/strings/errors' import {isWeb} from 'platform/detection' import {logger} from '#/logger' +import {useModalControls} from '#/state/modals' enum Forms { Login, @@ -303,9 +304,10 @@ const LoginForm = ({ const [identifier, setIdentifier] = useState(initialHandle) const [password, setPassword] = useState('') const passwordInputRef = useRef(null) + const {openModal} = useModalControls() const onPressSelectService = () => { - store.shell.openModal({ + openModal({ name: 'server-input', initialService: serviceUrl, onSelect: setServiceUrl, @@ -528,7 +530,6 @@ const LoginForm = ({ } const ForgotPasswordForm = ({ - store, error, serviceUrl, serviceDescription, @@ -551,13 +552,14 @@ const ForgotPasswordForm = ({ const [isProcessing, setIsProcessing] = useState(false) const [email, setEmail] = useState('') const {screen} = useAnalytics() + const {openModal} = useModalControls() useEffect(() => { screen('Signin:ForgotPassword') }, [screen]) const onPressSelectService = () => { - store.shell.openModal({ + openModal({ name: 'server-input', initialService: serviceUrl, onSelect: setServiceUrl, diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 65adb47560..277d789d8c 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -49,7 +49,7 @@ import {LabelsBtn} from './labels/LabelsBtn' import {SelectLangBtn} from './select-language/SelectLangBtn' import {EmojiPickerButton} from './text-input/web/EmojiPicker.web' import {insertMentionAt} from 'lib/strings/mention-manip' -import {useModals} from '#/state/modals' +import {useModals, useModalControls} from '#/state/modals' type Props = ComposerOpts export const ComposePost = observer(function ComposePost({ @@ -59,6 +59,7 @@ export const ComposePost = observer(function ComposePost({ mention: initMention, }: Props) { const {activeModals} = useModals() + const {openModal, closeModal} = useModalControls() const {track} = useAnalytics() const pal = usePalette('default') const {isDesktop, isMobile} = useWebMediaQueries() @@ -111,17 +112,17 @@ export const ComposePost = observer(function ComposePost({ const onPressCancel = useCallback(() => { if (graphemeLength > 0 || !gallery.isEmpty) { if (activeModals.some(modal => modal.name === 'confirm')) { - store.shell.closeModal() + closeModal() } if (Keyboard) { Keyboard.dismiss() } - store.shell.openModal({ + openModal({ name: 'confirm', title: 'Discard draft', onPressConfirm: onClose, onPressCancel: () => { - store.shell.closeModal() + closeModal() }, message: "Are you sure you'd like to discard this draft?", confirmBtnText: 'Discard', @@ -130,7 +131,7 @@ export const ComposePost = observer(function ComposePost({ } else { onClose() } - }, [store, activeModals, onClose, graphemeLength, gallery]) + }, [openModal, closeModal, activeModals, onClose, graphemeLength, gallery]) // android back button useEffect(() => { if (!isAndroid) { diff --git a/src/view/com/composer/labels/LabelsBtn.tsx b/src/view/com/composer/labels/LabelsBtn.tsx index 96908d47fa..4b6ad81c72 100644 --- a/src/view/com/composer/labels/LabelsBtn.tsx +++ b/src/view/com/composer/labels/LabelsBtn.tsx @@ -3,11 +3,11 @@ import {Keyboard, StyleSheet} from 'react-native' import {observer} from 'mobx-react-lite' import {Button} from 'view/com/util/forms/Button' import {usePalette} from 'lib/hooks/usePalette' -import {useStores} from 'state/index' import {ShieldExclamation} from 'lib/icons' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' import {isNative} from 'platform/detection' +import {useModalControls} from '#/state/modals' export const LabelsBtn = observer(function LabelsBtn({ labels, @@ -19,7 +19,7 @@ export const LabelsBtn = observer(function LabelsBtn({ onChange: (v: string[]) => void }) { const pal = usePalette('default') - const store = useStores() + const {openModal} = useModalControls() return (