about summary refs log tree commit diff
path: root/src/view/com/modals
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals')
-rw-r--r--src/view/com/modals/AddAppPasswords.tsx6
-rw-r--r--src/view/com/modals/AltImage.tsx10
-rw-r--r--src/view/com/modals/BirthDateSettings.tsx4
-rw-r--r--src/view/com/modals/ChangeEmail.tsx8
-rw-r--r--src/view/com/modals/ChangeHandle.tsx9
-rw-r--r--src/view/com/modals/Confirm.tsx8
-rw-r--r--src/view/com/modals/ContentFilteringSettings.tsx9
-rw-r--r--src/view/com/modals/CreateOrEditList.tsx9
-rw-r--r--src/view/com/modals/DeleteAccount.tsx6
-rw-r--r--src/view/com/modals/EditImage.tsx8
-rw-r--r--src/view/com/modals/EditProfile.tsx10
-rw-r--r--src/view/com/modals/InviteCodes.tsx6
-rw-r--r--src/view/com/modals/LinkWarning.tsx8
-rw-r--r--src/view/com/modals/ListAddUser.tsx4
-rw-r--r--src/view/com/modals/Modal.tsx24
-rw-r--r--src/view/com/modals/Modal.web.tsx17
-rw-r--r--src/view/com/modals/ModerationDetails.tsx9
-rw-r--r--src/view/com/modals/Repost.tsx6
-rw-r--r--src/view/com/modals/SelfLabel.tsx6
-rw-r--r--src/view/com/modals/ServerInput.tsx6
-rw-r--r--src/view/com/modals/UserAddRemoveLists.tsx10
-rw-r--r--src/view/com/modals/VerifyEmail.tsx10
-rw-r--r--src/view/com/modals/Waitlist.tsx6
-rw-r--r--src/view/com/modals/crop-image/CropImage.web.tsx8
-rw-r--r--src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx8
-rw-r--r--src/view/com/modals/lang-settings/PostLanguagesSettings.tsx8
-rw-r--r--src/view/com/modals/report/Modal.tsx6
27 files changed, 127 insertions, 102 deletions
diff --git a/src/view/com/modals/AddAppPasswords.tsx b/src/view/com/modals/AddAppPasswords.tsx
index 29763620f..621c61b90 100644
--- a/src/view/com/modals/AddAppPasswords.tsx
+++ b/src/view/com/modals/AddAppPasswords.tsx
@@ -13,6 +13,7 @@ import {
 import Clipboard from '@react-native-clipboard/clipboard'
 import * as Toast from '../util/Toast'
 import {logger} from '#/logger'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['70%']
 
@@ -54,6 +55,7 @@ const shadesOfBlue: string[] = [
 export function Component({}: {}) {
   const pal = usePalette('default')
   const store = useStores()
+  const {closeModal} = useModalControls()
   const [name, setName] = useState(
     shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)],
   )
@@ -69,8 +71,8 @@ export function Component({}: {}) {
   }, [appPassword])
 
   const onDone = React.useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
 
   const createAppPassword = async () => {
     // if name is all whitespace, we don't allow it
diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx
index c084e84a3..9c377a121 100644
--- a/src/view/com/modals/AltImage.tsx
+++ b/src/view/com/modals/AltImage.tsx
@@ -17,9 +17,9 @@ import {MAX_ALT_TEXT} from 'lib/constants'
 import {useTheme} from 'lib/ThemeContext'
 import {Text} from '../util/text/Text'
 import LinearGradient from 'react-native-linear-gradient'
-import {useStores} from 'state/index'
 import {isAndroid, isWeb} from 'platform/detection'
 import {ImageModel} from 'state/models/media/image'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['fullscreen']
 
@@ -29,10 +29,10 @@ interface Props {
 
 export function Component({image}: Props) {
   const pal = usePalette('default')
-  const store = useStores()
   const theme = useTheme()
   const [altText, setAltText] = useState(image.altText)
   const windim = useWindowDimensions()
+  const {closeModal} = useModalControls()
 
   const imageStyles = useMemo<ImageStyle>(() => {
     const maxWidth = isWeb ? 450 : windim.width
@@ -53,11 +53,11 @@ export function Component({image}: Props) {
 
   const onPressSave = useCallback(() => {
     image.setAltText(altText)
-    store.shell.closeModal()
-  }, [store, image, altText])
+    closeModal()
+  }, [closeModal, image, altText])
 
   const onPressCancel = () => {
-    store.shell.closeModal()
+    closeModal()
   }
 
   return (
diff --git a/src/view/com/modals/BirthDateSettings.tsx b/src/view/com/modals/BirthDateSettings.tsx
index 6927ba8d2..7b0778f83 100644
--- a/src/view/com/modals/BirthDateSettings.tsx
+++ b/src/view/com/modals/BirthDateSettings.tsx
@@ -15,12 +15,14 @@ import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {cleanError} from 'lib/strings/errors'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['50%']
 
 export const Component = observer(function Component({}: {}) {
   const pal = usePalette('default')
   const store = useStores()
+  const {closeModal} = useModalControls()
   const [date, setDate] = useState<Date>(
     store.preferences.birthDate || new Date(),
   )
@@ -33,7 +35,7 @@ export const Component = observer(function Component({}: {}) {
     setIsProcessing(true)
     try {
       await store.preferences.setBirthDate(date)
-      store.shell.closeModal()
+      closeModal()
     } catch (e) {
       setError(cleanError(String(e)))
     } finally {
diff --git a/src/view/com/modals/ChangeEmail.tsx b/src/view/com/modals/ChangeEmail.tsx
index 012570556..ec37aeede 100644
--- a/src/view/com/modals/ChangeEmail.tsx
+++ b/src/view/com/modals/ChangeEmail.tsx
@@ -12,6 +12,7 @@ import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {cleanError} from 'lib/strings/errors'
+import {useModalControls} from '#/state/modals'
 
 enum Stages {
   InputEmail,
@@ -32,6 +33,7 @@ export const Component = observer(function Component({}: {}) {
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
   const [error, setError] = useState<string>('')
   const {isMobile} = useWebMediaQueries()
+  const {openModal, closeModal} = useModalControls()
 
   const onRequestChange = async () => {
     if (email === store.session.currentSession?.email) {
@@ -90,8 +92,8 @@ export const Component = observer(function Component({}: {}) {
   }
 
   const onVerify = async () => {
-    store.shell.closeModal()
-    store.shell.openModal({name: 'verify-email'})
+    closeModal()
+    openModal({name: 'verify-email'})
   }
 
   return (
@@ -207,7 +209,7 @@ export const Component = observer(function Component({}: {}) {
               <Button
                 testID="cancelBtn"
                 type="default"
-                onPress={() => store.shell.closeModal()}
+                onPress={() => closeModal()}
                 accessibilityLabel="Cancel"
                 accessibilityHint=""
                 label="Cancel"
diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx
index c54c1c043..6184cb3b7 100644
--- a/src/view/com/modals/ChangeHandle.tsx
+++ b/src/view/com/modals/ChangeHandle.tsx
@@ -22,6 +22,7 @@ import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {cleanError} from 'lib/strings/errors'
 import {logger} from '#/logger'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['100%']
 
@@ -30,6 +31,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
   const [error, setError] = useState<string>('')
   const pal = usePalette('default')
   const {track} = useAnalytics()
+  const {closeModal} = useModalControls()
 
   const [isProcessing, setProcessing] = useState<boolean>(false)
   const [retryDescribeTrigger, setRetryDescribeTrigger] = React.useState<any>(
@@ -85,8 +87,8 @@ export function Component({onChanged}: {onChanged: () => void}) {
   // events
   // =
   const onPressCancel = React.useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
   const onPressRetryConnect = React.useCallback(
     () => setRetryDescribeTrigger({}),
     [setRetryDescribeTrigger],
@@ -110,7 +112,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
       await store.agent.updateHandle({
         handle: newHandle,
       })
-      store.shell.closeModal()
+      closeModal()
       onChanged()
     } catch (err: any) {
       setError(cleanError(err))
@@ -127,6 +129,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
     isCustom,
     onChanged,
     track,
+    closeModal,
   ])
 
   // rendering
diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx
index c1324b1cb..6b942057b 100644
--- a/src/view/com/modals/Confirm.tsx
+++ b/src/view/com/modals/Confirm.tsx
@@ -6,13 +6,13 @@ import {
   View,
 } from 'react-native'
 import {Text} from '../util/text/Text'
-import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {cleanError} from 'lib/strings/errors'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
-import type {ConfirmModal} from 'state/models/ui/shell'
+import type {ConfirmModal} from '#/state/modals'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['50%']
 
@@ -26,7 +26,7 @@ export function Component({
   cancelBtnText,
 }: ConfirmModal) {
   const pal = usePalette('default')
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
   const [error, setError] = useState<string>('')
   const onPress = async () => {
@@ -34,7 +34,7 @@ export function Component({
     setIsProcessing(true)
     try {
       await onPressConfirm()
-      store.shell.closeModal()
+      closeModal()
       return
     } catch (e: any) {
       setError(cleanError(e))
diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx
index 9075d0272..0891a6473 100644
--- a/src/view/com/modals/ContentFilteringSettings.tsx
+++ b/src/view/com/modals/ContentFilteringSettings.tsx
@@ -16,6 +16,7 @@ import {isIOS} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import * as Toast from '../util/Toast'
 import {logger} from '#/logger'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['90%']
 
@@ -24,14 +25,15 @@ export const Component = observer(
     const store = useStores()
     const {isMobile} = useWebMediaQueries()
     const pal = usePalette('default')
+    const {closeModal} = useModalControls()
 
     React.useEffect(() => {
       store.preferences.sync()
     }, [store])
 
     const onPressDone = React.useCallback(() => {
-      store.shell.closeModal()
-    }, [store])
+      closeModal()
+    }, [closeModal])
 
     return (
       <View testID="contentFilteringModal" style={[pal.view, styles.container]}>
@@ -89,8 +91,9 @@ const AdultContentEnabledPref = observer(
   function AdultContentEnabledPrefImpl() {
     const store = useStores()
     const pal = usePalette('default')
+    const {openModal} = useModalControls()
 
-    const onSetAge = () => store.shell.openModal({name: 'birth-date-settings'})
+    const onSetAge = () => openModal({name: 'birth-date-settings'})
 
     const onToggleAdultContent = async () => {
       if (isIOS) {
diff --git a/src/view/com/modals/CreateOrEditList.tsx b/src/view/com/modals/CreateOrEditList.tsx
index 1ea12695f..cdad37770 100644
--- a/src/view/com/modals/CreateOrEditList.tsx
+++ b/src/view/com/modals/CreateOrEditList.tsx
@@ -24,6 +24,7 @@ import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {cleanError, isNetworkError} from 'lib/strings/errors'
+import {useModalControls} from '#/state/modals'
 
 const MAX_NAME = 64 // todo
 const MAX_DESCRIPTION = 300 // todo
@@ -40,6 +41,7 @@ export function Component({
   list?: ListModel
 }) {
   const store = useStores()
+  const {closeModal} = useModalControls()
   const {isMobile} = useWebMediaQueries()
   const [error, setError] = useState<string>('')
   const pal = usePalette('default')
@@ -67,8 +69,8 @@ export function Component({
   const [newAvatar, setNewAvatar] = useState<RNImage | undefined | null>()
 
   const onPressCancel = useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
 
   const onSelectNewAvatar = useCallback(
     async (img: RNImage | null) => {
@@ -123,7 +125,7 @@ export function Component({
         Toast.show(`${purposeLabel} list created`)
         onSave?.(res.uri)
       }
-      store.shell.closeModal()
+      closeModal()
     } catch (e: any) {
       if (isNetworkError(e)) {
         setError(
@@ -141,6 +143,7 @@ export function Component({
     error,
     onSave,
     store,
+    closeModal,
     activePurpose,
     isCurateList,
     purposeLabel,
diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx
index 50a4cd603..9a8a8b4b0 100644
--- a/src/view/com/modals/DeleteAccount.tsx
+++ b/src/view/com/modals/DeleteAccount.tsx
@@ -17,6 +17,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {cleanError} from 'lib/strings/errors'
 import {resetToTab} from '../../../Navigation'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['60%']
 
@@ -24,6 +25,7 @@ export function Component({}: {}) {
   const pal = usePalette('default')
   const theme = useTheme()
   const store = useStores()
+  const {closeModal} = useModalControls()
   const {isMobile} = useWebMediaQueries()
   const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
   const [confirmCode, setConfirmCode] = React.useState<string>('')
@@ -55,14 +57,14 @@ export function Component({}: {}) {
       Toast.show('Your account has been deleted')
       resetToTab('HomeTab')
       store.session.clear()
-      store.shell.closeModal()
+      closeModal()
     } catch (e: any) {
       setError(cleanError(e))
     }
     setIsProcessing(false)
   }
   const onCancel = () => {
-    store.shell.closeModal()
+    closeModal()
   }
   return (
     <View style={[styles.container, pal.view]}>
diff --git a/src/view/com/modals/EditImage.tsx b/src/view/com/modals/EditImage.tsx
index dcb6668c7..a2a458f4c 100644
--- a/src/view/com/modals/EditImage.tsx
+++ b/src/view/com/modals/EditImage.tsx
@@ -6,7 +6,6 @@ import {gradients, s} from 'lib/styles'
 import {useTheme} from 'lib/ThemeContext'
 import {Text} from '../util/text/Text'
 import LinearGradient from 'react-native-linear-gradient'
-import {useStores} from 'state/index'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import ImageEditor, {Position} from 'react-avatar-editor'
 import {TextInput} from './util'
@@ -19,6 +18,7 @@ import {Slider} from '@miblanchard/react-native-slider'
 import {MaterialIcons} from '@expo/vector-icons'
 import {observer} from 'mobx-react-lite'
 import {getKeys} from 'lib/type-assertions'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['80%']
 
@@ -52,9 +52,9 @@ export const Component = observer(function EditImageImpl({
 }: Props) {
   const pal = usePalette('default')
   const theme = useTheme()
-  const store = useStores()
   const windowDimensions = useWindowDimensions()
   const {isMobile} = useWebMediaQueries()
+  const {closeModal} = useModalControls()
 
   const {
     aspectRatio,
@@ -128,8 +128,8 @@ export const Component = observer(function EditImageImpl({
   }, [image])
 
   const onCloseModal = useCallback(() => {
-    store.shell.closeModal()
-  }, [store.shell])
+    closeModal()
+  }, [closeModal])
 
   const onPressCancel = useCallback(async () => {
     await gallery.previous(image)
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index dfd5305f5..f08bb2326 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -13,7 +13,6 @@ import LinearGradient from 'react-native-linear-gradient'
 import {Image as RNImage} from 'react-native-image-crop-picker'
 import {Text} from '../util/text/Text'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {useStores} from 'state/index'
 import {ProfileModel} from 'state/models/content/profile'
 import {s, colors, gradients} from 'lib/styles'
 import {enforceLen} from 'lib/strings/helpers'
@@ -27,6 +26,7 @@ import {useAnalytics} from 'lib/analytics/analytics'
 import {cleanError, isNetworkError} from 'lib/strings/errors'
 import Animated, {FadeOut} from 'react-native-reanimated'
 import {isWeb} from 'platform/detection'
+import {useModalControls} from '#/state/modals'
 
 const AnimatedTouchableOpacity =
   Animated.createAnimatedComponent(TouchableOpacity)
@@ -40,11 +40,11 @@ export function Component({
   profileView: ProfileModel
   onUpdate?: () => void
 }) {
-  const store = useStores()
   const [error, setError] = useState<string>('')
   const pal = usePalette('default')
   const theme = useTheme()
   const {track} = useAnalytics()
+  const {closeModal} = useModalControls()
 
   const [isProcessing, setProcessing] = useState<boolean>(false)
   const [displayName, setDisplayName] = useState<string>(
@@ -66,7 +66,7 @@ export function Component({
     RNImage | undefined | null
   >()
   const onPressCancel = () => {
-    store.shell.closeModal()
+    closeModal()
   }
   const onSelectNewAvatar = useCallback(
     async (img: RNImage | null) => {
@@ -123,7 +123,7 @@ export function Component({
       )
       Toast.show('Profile updated')
       onUpdate?.()
-      store.shell.closeModal()
+      closeModal()
     } catch (e: any) {
       if (isNetworkError(e)) {
         setError(
@@ -141,7 +141,7 @@ export function Component({
     error,
     profileView,
     onUpdate,
-    store,
+    closeModal,
     displayName,
     description,
     newUserAvatar,
diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx
index a8aa164c3..227b25275 100644
--- a/src/view/com/modals/InviteCodes.tsx
+++ b/src/view/com/modals/InviteCodes.tsx
@@ -15,6 +15,7 @@ import {ScrollView} from './util'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {useModalControls} from '#/state/modals'
 import {useInvitesState, useInvitesAPI} from '#/state/invites'
 import {UserInfoText} from '../util/UserInfoText'
 import {makeProfileLink} from '#/lib/routes/links'
@@ -25,11 +26,12 @@ export const snapPoints = ['70%']
 export function Component({}: {}) {
   const pal = usePalette('default')
   const store = useStores()
+  const {closeModal} = useModalControls()
   const {isTabletOrDesktop} = useWebMediaQueries()
 
   const onClose = React.useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
 
   if (store.me.invites.length === 0) {
     return (
diff --git a/src/view/com/modals/LinkWarning.tsx b/src/view/com/modals/LinkWarning.tsx
index 67a156af4..751c69b3f 100644
--- a/src/view/com/modals/LinkWarning.tsx
+++ b/src/view/com/modals/LinkWarning.tsx
@@ -5,12 +5,12 @@ import {observer} from 'mobx-react-lite'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {Text} from '../util/text/Text'
 import {Button} from '../util/forms/Button'
-import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['50%']
 
@@ -22,12 +22,12 @@ export const Component = observer(function Component({
   href: string
 }) {
   const pal = usePalette('default')
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const {isMobile} = useWebMediaQueries()
   const potentiallyMisleading = isPossiblyAUrl(text)
 
   const onPressVisit = () => {
-    store.shell.closeModal()
+    closeModal()
     Linking.openURL(href)
   }
 
@@ -83,7 +83,7 @@ export const Component = observer(function Component({
           <Button
             testID="cancelBtn"
             type="default"
-            onPress={() => store.shell.closeModal()}
+            onPress={() => closeModal()}
             accessibilityLabel="Cancel"
             accessibilityHint=""
             label="Cancel"
diff --git a/src/view/com/modals/ListAddUser.tsx b/src/view/com/modals/ListAddUser.tsx
index a04e2d186..8864ebc78 100644
--- a/src/view/com/modals/ListAddUser.tsx
+++ b/src/view/com/modals/ListAddUser.tsx
@@ -26,6 +26,7 @@ import {cleanError} from 'lib/strings/errors'
 import {sanitizeDisplayName} from 'lib/strings/display-names'
 import {sanitizeHandle} from 'lib/strings/handles'
 import {HITSLOP_20} from '#/lib/constants'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['90%']
 
@@ -38,6 +39,7 @@ export const Component = observer(function Component({
 }) {
   const pal = usePalette('default')
   const store = useStores()
+  const {closeModal} = useModalControls()
   const {isMobile} = useWebMediaQueries()
   const [query, setQuery] = useState('')
   const autocompleteView = useMemo<UserAutocompleteModel>(
@@ -146,7 +148,7 @@ export const Component = observer(function Component({
           <Button
             testID="doneBtn"
             type="default"
-            onPress={() => store.shell.closeModal()}
+            onPress={() => closeModal()}
             accessibilityLabel="Done"
             accessibilityHint=""
             label="Done"
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index 5aaa09e87..c1999c5d6 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<BottomSheet>(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 ede845378..65c4ee444 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 type {Modal as ModalIface} from '#/state/modals'
 
+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) => (
         <Modal key={`modal-${i}`} modal={modal} />
       ))}
     </>
@@ -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/modals/ModerationDetails.tsx b/src/view/com/modals/ModerationDetails.tsx
index c01312d69..35ddfe2a1 100644
--- a/src/view/com/modals/ModerationDetails.tsx
+++ b/src/view/com/modals/ModerationDetails.tsx
@@ -1,7 +1,6 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
 import {ModerationUI} from '@atproto/api'
-import {useStores} from 'state/index'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {s} from 'lib/styles'
 import {Text} from '../util/text/Text'
@@ -10,6 +9,7 @@ import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 import {listUriToHref} from 'lib/strings/url-helpers'
 import {Button} from '../util/forms/Button'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = [300]
 
@@ -20,7 +20,7 @@ export function Component({
   context: 'account' | 'content'
   moderation: ModerationUI
 }) {
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const {isMobile} = useWebMediaQueries()
   const pal = usePalette('default')
 
@@ -99,10 +99,7 @@ export function Component({
         {description}
       </Text>
       <View style={s.flex1} />
-      <Button
-        type="primary"
-        style={styles.btn}
-        onPress={() => store.shell.closeModal()}>
+      <Button type="primary" style={styles.btn} onPress={() => closeModal()}>
         <Text type="button-lg" style={[pal.textLight, s.textCenter, s.white]}>
           Okay
         </Text>
diff --git a/src/view/com/modals/Repost.tsx b/src/view/com/modals/Repost.tsx
index b1862ecbd..13728b62b 100644
--- a/src/view/com/modals/Repost.tsx
+++ b/src/view/com/modals/Repost.tsx
@@ -1,12 +1,12 @@
 import React from 'react'
 import {StyleSheet, TouchableOpacity, View} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
-import {useStores} from 'state/index'
 import {s, colors, gradients} from 'lib/styles'
 import {Text} from '../util/text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
 import {RepostIcon} from 'lib/icons'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = [250]
 
@@ -20,10 +20,10 @@ export function Component({
   isReposted: boolean
   // TODO: Add author into component
 }) {
-  const store = useStores()
   const pal = usePalette('default')
+  const {closeModal} = useModalControls()
   const onPress = async () => {
-    store.shell.closeModal()
+    closeModal()
   }
 
   return (
diff --git a/src/view/com/modals/SelfLabel.tsx b/src/view/com/modals/SelfLabel.tsx
index 820f2895b..242b6a38a 100644
--- a/src/view/com/modals/SelfLabel.tsx
+++ b/src/view/com/modals/SelfLabel.tsx
@@ -2,7 +2,6 @@ import React, {useState} from 'react'
 import {StyleSheet, TouchableOpacity, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
 import {Text} from '../util/text/Text'
-import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -10,6 +9,7 @@ import {isWeb} from 'platform/detection'
 import {Button} from '../util/forms/Button'
 import {SelectableBtn} from '../util/forms/SelectableBtn'
 import {ScrollView} from 'view/com/modals/util'
+import {useModalControls} from '#/state/modals'
 
 const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
 
@@ -25,7 +25,7 @@ export const Component = observer(function Component({
   onChange: (labels: string[]) => void
 }) {
   const pal = usePalette('default')
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const {isMobile} = useWebMediaQueries()
   const [selected, setSelected] = useState(labels)
 
@@ -143,7 +143,7 @@ export const Component = observer(function Component({
         <TouchableOpacity
           testID="confirmBtn"
           onPress={() => {
-            store.shell.closeModal()
+            closeModal()
           }}
           style={styles.btn}
           accessibilityRole="button"
diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx
index 13b21fe22..0f8db30b6 100644
--- a/src/view/com/modals/ServerInput.tsx
+++ b/src/view/com/modals/ServerInput.tsx
@@ -6,26 +6,26 @@ import {
 } from '@fortawesome/react-native-fontawesome'
 import {ScrollView, TextInput} from './util'
 import {Text} from '../util/text/Text'
-import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index'
 import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['80%']
 
 export function Component({onSelect}: {onSelect: (url: string) => void}) {
   const theme = useTheme()
   const pal = usePalette('default')
-  const store = useStores()
   const [customUrl, setCustomUrl] = useState<string>('')
+  const {closeModal} = useModalControls()
 
   const doSelect = (url: string) => {
     if (!url.startsWith('http://') && !url.startsWith('https://')) {
       url = `https://${url}`
     }
-    store.shell.closeModal()
+    closeModal()
     onSelect(url)
   }
 
diff --git a/src/view/com/modals/UserAddRemoveLists.tsx b/src/view/com/modals/UserAddRemoveLists.tsx
index aeec2e87f..f86e88439 100644
--- a/src/view/com/modals/UserAddRemoveLists.tsx
+++ b/src/view/com/modals/UserAddRemoveLists.tsx
@@ -21,6 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb, isAndroid} from 'platform/detection'
 import isEqual from 'lodash.isequal'
 import {logger} from '#/logger'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['fullscreen']
 
@@ -36,6 +37,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
   onRemove?: (listUri: string) => void
 }) {
   const store = useStores()
+  const {closeModal} = useModalControls()
   const pal = usePalette('default')
   const palPrimary = usePalette('primary')
   const palInverted = usePalette('inverted')
@@ -69,8 +71,8 @@ export const Component = observer(function UserAddRemoveListsImpl({
   }, [memberships, listsList, store, setSelected, setMembershipsLoaded])
 
   const onPressCancel = useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
 
   const onPressSave = useCallback(async () => {
     let changes
@@ -87,8 +89,8 @@ export const Component = observer(function UserAddRemoveListsImpl({
     for (const uri of changes.removed) {
       onRemove?.(uri)
     }
-    store.shell.closeModal()
-  }, [store, selected, memberships, onAdd, onRemove])
+    closeModal()
+  }, [closeModal, selected, memberships, onAdd, onRemove])
 
   const onToggleSelected = useCallback(
     (uri: string) => {
diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx
index 9fe8811b0..3adaffb14 100644
--- a/src/view/com/modals/VerifyEmail.tsx
+++ b/src/view/com/modals/VerifyEmail.tsx
@@ -20,6 +20,7 @@ import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {cleanError} from 'lib/strings/errors'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['90%']
 
@@ -43,6 +44,7 @@ export const Component = observer(function Component({
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
   const [error, setError] = useState<string>('')
   const {isMobile} = useWebMediaQueries()
+  const {openModal, closeModal} = useModalControls()
 
   const onSendEmail = async () => {
     setError('')
@@ -67,7 +69,7 @@ export const Component = observer(function Component({
       })
       store.session.updateLocalAccountData({emailConfirmed: true})
       Toast.show('Email verified')
-      store.shell.closeModal()
+      closeModal()
     } catch (e) {
       setError(cleanError(String(e)))
     } finally {
@@ -76,8 +78,8 @@ export const Component = observer(function Component({
   }
 
   const onEmailIncorrect = () => {
-    store.shell.closeModal()
-    store.shell.openModal({name: 'change-email'})
+    closeModal()
+    openModal({name: 'change-email'})
   }
 
   return (
@@ -224,7 +226,7 @@ export const Component = observer(function Component({
               <Button
                 testID="cancelBtn"
                 type="default"
-                onPress={() => store.shell.closeModal()}
+                onPress={() => closeModal()}
                 accessibilityLabel={
                   stage === Stages.Reminder ? 'Not right now' : 'Cancel'
                 }
diff --git a/src/view/com/modals/Waitlist.tsx b/src/view/com/modals/Waitlist.tsx
index 0fb371fe4..219bdc583 100644
--- a/src/view/com/modals/Waitlist.tsx
+++ b/src/view/com/modals/Waitlist.tsx
@@ -12,19 +12,19 @@ import {
 } from '@fortawesome/react-native-fontawesome'
 import LinearGradient from 'react-native-linear-gradient'
 import {Text} from '../util/text/Text'
-import {useStores} from 'state/index'
 import {s, gradients} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {cleanError} from 'lib/strings/errors'
+import {useModalControls} from '#/state/modals'
 
 export const snapPoints = ['80%']
 
 export function Component({}: {}) {
   const pal = usePalette('default')
   const theme = useTheme()
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const [email, setEmail] = React.useState<string>('')
   const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
   const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
@@ -54,7 +54,7 @@ export function Component({}: {}) {
     setIsProcessing(false)
   }
   const onCancel = () => {
-    store.shell.closeModal()
+    closeModal()
   }
 
   return (
diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx
index 8e35201d1..c88d002a9 100644
--- a/src/view/com/modals/crop-image/CropImage.web.tsx
+++ b/src/view/com/modals/crop-image/CropImage.web.tsx
@@ -7,10 +7,10 @@ import {Text} from 'view/com/util/text/Text'
 import {Dimensions} from 'lib/media/types'
 import {getDataUriSize} from 'lib/media/util'
 import {s, gradients} from 'lib/styles'
-import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 import {SquareIcon, RectWideIcon, RectTallIcon} from 'lib/icons'
 import {Image as RNImage} from 'react-native-image-crop-picker'
+import {useModalControls} from '#/state/modals'
 
 enum AspectRatio {
   Square = 'square',
@@ -33,7 +33,7 @@ export function Component({
   uri: string
   onSelect: (img?: RNImage) => void
 }) {
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const pal = usePalette('default')
   const [as, setAs] = React.useState<AspectRatio>(AspectRatio.Square)
   const [scale, setScale] = React.useState<number>(1)
@@ -43,7 +43,7 @@ export function Component({
 
   const onPressCancel = () => {
     onSelect(undefined)
-    store.shell.closeModal()
+    closeModal()
   }
   const onPressDone = () => {
     const canvas = editorRef.current?.getImageScaledToCanvas()
@@ -59,7 +59,7 @@ export function Component({
     } else {
       onSelect(undefined)
     }
-    store.shell.closeModal()
+    closeModal()
   }
 
   let cropperStyle
diff --git a/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx b/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx
index 659245616..d37d51e47 100644
--- a/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx
+++ b/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx
@@ -1,7 +1,6 @@
 import React from 'react'
 import {StyleSheet, View} from 'react-native'
 import {ScrollView} from '../util'
-import {useStores} from 'state/index'
 import {Text} from '../../util/text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -9,6 +8,7 @@ import {deviceLocales} from 'platform/detection'
 import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
 import {LanguageToggle} from './LanguageToggle'
 import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
+import {useModalControls} from '#/state/modals'
 import {
   useLanguagePrefs,
   useSetLanguagePrefs,
@@ -18,14 +18,14 @@ import {
 export const snapPoints = ['100%']
 
 export function Component({}: {}) {
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const langPrefs = useLanguagePrefs()
   const setLangPrefs = useSetLanguagePrefs()
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()
   const onPressDone = React.useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
 
   const languages = React.useMemo(() => {
     const langs = LANGUAGES.filter(
diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
index 435fb9e1a..4a39da752 100644
--- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
+++ b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
@@ -2,7 +2,6 @@ import React from 'react'
 import {StyleSheet, View} from 'react-native'
 import {observer} from 'mobx-react-lite'
 import {ScrollView} from '../util'
-import {useStores} from 'state/index'
 import {Text} from '../../util/text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@@ -10,6 +9,7 @@ import {deviceLocales} from 'platform/detection'
 import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
 import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
 import {ToggleButton} from 'view/com/util/forms/ToggleButton'
+import {useModalControls} from '#/state/modals'
 import {
   useLanguagePrefs,
   useSetLanguagePrefs,
@@ -20,14 +20,14 @@ import {
 export const snapPoints = ['100%']
 
 export const Component = observer(function PostLanguagesSettingsImpl() {
-  const store = useStores()
+  const {closeModal} = useModalControls()
   const langPrefs = useLanguagePrefs()
   const setLangPrefs = useSetLanguagePrefs()
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()
   const onPressDone = React.useCallback(() => {
-    store.shell.closeModal()
-  }, [store])
+    closeModal()
+  }, [closeModal])
 
   const languages = React.useMemo(() => {
     const langs = LANGUAGES.filter(
diff --git a/src/view/com/modals/report/Modal.tsx b/src/view/com/modals/report/Modal.tsx
index 98aa2d471..8dc3f53f7 100644
--- a/src/view/com/modals/report/Modal.tsx
+++ b/src/view/com/modals/report/Modal.tsx
@@ -14,6 +14,7 @@ import {SendReportButton} from './SendReportButton'
 import {InputIssueDetails} from './InputIssueDetails'
 import {ReportReasonOptions} from './ReasonOptions'
 import {CollectionId} from './types'
+import {useModalControls} from '#/state/modals'
 
 const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright'
 
@@ -37,6 +38,7 @@ type ReportComponentProps =
 
 export function Component(content: ReportComponentProps) {
   const store = useStores()
+  const {closeModal} = useModalControls()
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()
   const [isProcessing, setIsProcessing] = useState(false)
@@ -60,7 +62,7 @@ export function Component(content: ReportComponentProps) {
     try {
       if (issue === '__copyright__') {
         Linking.openURL(DMCA_LINK)
-        store.shell.closeModal()
+        closeModal()
         return
       }
       const $type = !isAccountReport
@@ -76,7 +78,7 @@ export function Component(content: ReportComponentProps) {
       })
       Toast.show("Thank you for your report! We'll look into it promptly.")
 
-      store.shell.closeModal()
+      closeModal()
       return
     } catch (e: any) {
       setError(cleanError(e))