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
zio/stable
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

View File

@ -22,6 +22,7 @@ import * as Toast from 'view/com/util/Toast'
import {queryClient} from 'lib/react-query' import {queryClient} from 'lib/react-query'
import {TestCtrls} from 'view/com/testing/TestCtrls' import {TestCtrls} from 'view/com/testing/TestCtrls'
import {Provider as ShellStateProvider} from 'state/shell' import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as PrefsStateProvider} from 'state/preferences'
@ -84,7 +85,9 @@ function App() {
<PrefsStateProvider> <PrefsStateProvider>
<MutedThreadsProvider> <MutedThreadsProvider>
<InvitesStateProvider> <InvitesStateProvider>
<InnerApp /> <ModalStateProvider>
<InnerApp />
</ModalStateProvider>
</InvitesStateProvider> </InvitesStateProvider>
</MutedThreadsProvider> </MutedThreadsProvider>
</PrefsStateProvider> </PrefsStateProvider>

View File

@ -17,6 +17,7 @@ import {ToastContainer} from 'view/com/util/Toast.web'
import {ThemeProvider} from 'lib/ThemeContext' import {ThemeProvider} from 'lib/ThemeContext'
import {queryClient} from 'lib/react-query' import {queryClient} from 'lib/react-query'
import {Provider as ShellStateProvider} from 'state/shell' import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as MutedThreadsProvider} from 'state/muted-threads' import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites' import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences' import {Provider as PrefsStateProvider} from 'state/preferences'
@ -74,7 +75,9 @@ function App() {
<PrefsStateProvider> <PrefsStateProvider>
<MutedThreadsProvider> <MutedThreadsProvider>
<InvitesStateProvider> <InvitesStateProvider>
<InnerApp /> <ModalStateProvider>
<InnerApp />
</ModalStateProvider>
</InvitesStateProvider> </InvitesStateProvider>
</MutedThreadsProvider> </MutedThreadsProvider>
</PrefsStateProvider> </PrefsStateProvider>

View File

@ -7,6 +7,7 @@ import {AccountData} from 'state/models/session'
import {reset as resetNavigation} from '../../Navigation' import {reset as resetNavigation} from '../../Navigation'
import * as Toast from 'view/com/util/Toast' import * as Toast from 'view/com/util/Toast'
import {useSetDrawerOpen} from '#/state/shell/drawer-open' import {useSetDrawerOpen} from '#/state/shell/drawer-open'
import {useModalControls} from '#/state/modals'
export function useAccountSwitcher(): [ export function useAccountSwitcher(): [
boolean, boolean,
@ -16,6 +17,7 @@ export function useAccountSwitcher(): [
const {track} = useAnalytics() const {track} = useAnalytics()
const store = useStores() const store = useStores()
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const {closeModal} = useModalControls()
const [isSwitching, setIsSwitching] = useState(false) const [isSwitching, setIsSwitching] = useState(false)
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
@ -25,6 +27,7 @@ export function useAccountSwitcher(): [
setIsSwitching(true) setIsSwitching(true)
const success = await store.session.resumeSession(acct) const success = await store.session.resumeSession(acct)
setDrawerOpen(false) setDrawerOpen(false)
closeModal()
store.shell.closeAllActiveElements() store.shell.closeAllActiveElements()
if (success) { if (success) {
resetNavigation() resetNavigation()
@ -36,7 +39,7 @@ export function useAccountSwitcher(): [
store.session.clear() store.session.clear()
} }
}, },
[track, setIsSwitching, navigation, store, setDrawerOpen], [track, setIsSwitching, navigation, store, setDrawerOpen, closeModal],
) )
return [isSwitching, setIsSwitching, onPressSwitchAccount] return [isSwitching, setIsSwitching, onPressSwitchAccount]

View File

@ -1,15 +1,15 @@
import * as Updates from 'expo-updates' import * as Updates from 'expo-updates'
import {useCallback, useEffect} from 'react' import {useCallback, useEffect} from 'react'
import {AppState} from 'react-native' import {AppState} from 'react-native'
import {useStores} from 'state/index'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
export function useOTAUpdate() { export function useOTAUpdate() {
const store = useStores() const {openModal} = useModalControls()
// HELPER FUNCTIONS // HELPER FUNCTIONS
const showUpdatePopup = useCallback(() => { const showUpdatePopup = useCallback(() => {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Update Available', title: 'Update Available',
message: message:
@ -20,7 +20,7 @@ export function useOTAUpdate() {
}) })
}, },
}) })
}, [store.shell]) }, [openModal])
const checkForUpdate = useCallback(async () => { const checkForUpdate = useCallback(async () => {
logger.debug('useOTAUpdate: Checking for update...') logger.debug('useOTAUpdate: Checking for update...')
try { try {

View File

@ -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,
})
}

View File

@ -4,6 +4,7 @@ import {CameraOpts, CropperOptions} from './types'
import {RootStoreModel} from 'state/index' import {RootStoreModel} from 'state/index'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
export {openPicker} from './picker.shared' export {openPicker} from './picker.shared'
import {unstable__openModal} from '#/state/modals'
export async function openCamera( export async function openCamera(
_store: RootStoreModel, _store: RootStoreModel,
@ -14,12 +15,12 @@ export async function openCamera(
} }
export async function openCropper( export async function openCropper(
store: RootStoreModel, _store: RootStoreModel,
opts: CropperOptions, opts: CropperOptions,
): Promise<RNImage> { ): Promise<RNImage> {
// TODO handle more opts // TODO handle more opts
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
store.shell.openModal({ unstable__openModal({
name: 'crop-image', name: 'crop-image',
uri: opts.path, uri: opts.path,
onSelect: (img?: RNImage) => { onSelect: (img?: RNImage) => {

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)
}

View File

@ -4,7 +4,6 @@ import {ImageModel} from './image'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {openPicker} from 'lib/media/picker' import {openPicker} from 'lib/media/picker'
import {getImageDim} from 'lib/media/manip' import {getImageDim} from 'lib/media/manip'
import {isNative} from 'platform/detection'
export class GalleryModel { export class GalleryModel {
images: ImageModel[] = [] images: ImageModel[] = []
@ -42,18 +41,6 @@ export class GalleryModel {
} }
} }
async edit(image: ImageModel) {
if (isNative) {
this.crop(image)
} else {
this.rootStore.shell.openModal({
name: 'edit-image',
image,
gallery: this,
})
}
}
async paste(uri: string) { async paste(uri: string) {
if (this.size >= 4) { if (this.size >= 4) {
return return

View File

@ -1,16 +1,12 @@
import {AppBskyEmbedRecord, AppBskyActorDefs, ModerationUI} from '@atproto/api' import {AppBskyEmbedRecord} from '@atproto/api'
import {RootStoreModel} from '../root-store' import {RootStoreModel} from '../root-store'
import {makeAutoObservable, runInAction} from 'mobx' import {makeAutoObservable, runInAction} from 'mobx'
import {ProfileModel} from '../content/profile' import {ProfileModel} from '../content/profile'
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 { import {
shouldRequestEmailConfirmation, shouldRequestEmailConfirmation,
setEmailConfirmationRequested, setEmailConfirmationRequested,
} from '#/state/shell/reminders' } from '#/state/shell/reminders'
import {unstable__openModal} from '#/state/modals'
export type ColorMode = 'system' | 'light' | 'dark' export type ColorMode = 'system' | 'light' | 'dark'
@ -18,200 +14,6 @@ export function isColorMode(v: unknown): v is ColorMode {
return v === 'system' || v === 'light' || v === 'dark' return v === 'system' || v === 'light' || v === 'dark'
} }
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
interface LightboxModel {} interface LightboxModel {}
export class ProfileImageLightbox implements LightboxModel { export class ProfileImageLightbox implements LightboxModel {
@ -267,8 +69,6 @@ export interface ComposerOpts {
} }
export class ShellUiModel { export class ShellUiModel {
isModalActive = false
activeModals: Modal[] = []
isLightboxActive = false isLightboxActive = false
activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
isComposerActive = false isComposerActive = false
@ -293,10 +93,6 @@ export class ShellUiModel {
this.closeLightbox() this.closeLightbox()
return true return true
} }
if (this.isModalActive) {
this.closeModal()
return true
}
if (this.isComposerActive) { if (this.isComposerActive) {
this.closeComposer() this.closeComposer()
return true return true
@ -311,25 +107,11 @@ export class ShellUiModel {
if (this.isLightboxActive) { if (this.isLightboxActive) {
this.closeLightbox() this.closeLightbox()
} }
while (this.isModalActive) {
this.closeModal()
}
if (this.isComposerActive) { if (this.isComposerActive) {
this.closeComposer() this.closeComposer()
} }
} }
openModal(modal: Modal) {
this.rootStore.emitNavigation()
this.isModalActive = true
this.activeModals.push(modal)
}
closeModal() {
this.activeModals.pop()
this.isModalActive = this.activeModals.length > 0
}
openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) { openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {
this.rootStore.emitNavigation() this.rootStore.emitNavigation()
this.isLightboxActive = true this.isLightboxActive = true
@ -363,7 +145,7 @@ export class ShellUiModel {
setupLoginModals() { setupLoginModals() {
this.rootStore.onSessionReady(() => { this.rootStore.onSessionReady(() => {
if (shouldRequestEmailConfirmation(this.rootStore.session)) { if (shouldRequestEmailConfirmation(this.rootStore.session)) {
this.openModal({name: 'verify-email', showReminder: true}) unstable__openModal({name: 'verify-email', showReminder: true})
setEmailConfirmationRequested() setEmailConfirmationRequested()
} }
}) })

View File

@ -10,8 +10,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {TextInput} from '../util/TextInput' import {TextInput} from '../util/TextInput'
import {Policies} from './Policies' import {Policies} from './Policies'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {useStores} from 'state/index'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useModalControls} from '#/state/modals'
/** STEP 2: Your account /** STEP 2: Your account
* @field Invite code or waitlist * @field Invite code or waitlist
@ -28,11 +28,11 @@ export const Step2 = observer(function Step2Impl({
model: CreateAccountModel model: CreateAccountModel
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const {openModal} = useModalControls()
const onPressWaitlist = React.useCallback(() => { const onPressWaitlist = React.useCallback(() => {
store.shell.openModal({name: 'waitlist'}) openModal({name: 'waitlist'})
}, [store]) }, [openModal])
return ( return (
<View> <View>

View File

@ -31,6 +31,7 @@ import {useTheme} from 'lib/ThemeContext'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
enum Forms { enum Forms {
Login, Login,
@ -303,9 +304,10 @@ const LoginForm = ({
const [identifier, setIdentifier] = useState<string>(initialHandle) const [identifier, setIdentifier] = useState<string>(initialHandle)
const [password, setPassword] = useState<string>('') const [password, setPassword] = useState<string>('')
const passwordInputRef = useRef<TextInput>(null) const passwordInputRef = useRef<TextInput>(null)
const {openModal} = useModalControls()
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal({ openModal({
name: 'server-input', name: 'server-input',
initialService: serviceUrl, initialService: serviceUrl,
onSelect: setServiceUrl, onSelect: setServiceUrl,
@ -528,7 +530,6 @@ const LoginForm = ({
} }
const ForgotPasswordForm = ({ const ForgotPasswordForm = ({
store,
error, error,
serviceUrl, serviceUrl,
serviceDescription, serviceDescription,
@ -551,13 +552,14 @@ const ForgotPasswordForm = ({
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [email, setEmail] = useState<string>('') const [email, setEmail] = useState<string>('')
const {screen} = useAnalytics() const {screen} = useAnalytics()
const {openModal} = useModalControls()
useEffect(() => { useEffect(() => {
screen('Signin:ForgotPassword') screen('Signin:ForgotPassword')
}, [screen]) }, [screen])
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal({ openModal({
name: 'server-input', name: 'server-input',
initialService: serviceUrl, initialService: serviceUrl,
onSelect: setServiceUrl, onSelect: setServiceUrl,

View File

@ -49,6 +49,7 @@ import {LabelsBtn} from './labels/LabelsBtn'
import {SelectLangBtn} from './select-language/SelectLangBtn' import {SelectLangBtn} from './select-language/SelectLangBtn'
import {EmojiPickerButton} from './text-input/web/EmojiPicker.web' import {EmojiPickerButton} from './text-input/web/EmojiPicker.web'
import {insertMentionAt} from 'lib/strings/mention-manip' import {insertMentionAt} from 'lib/strings/mention-manip'
import {useModals, useModalControls} from '#/state/modals'
import {useRequireAltTextEnabled} from '#/state/preferences' import {useRequireAltTextEnabled} from '#/state/preferences'
import { import {
useLanguagePrefs, useLanguagePrefs,
@ -64,6 +65,8 @@ export const ComposePost = observer(function ComposePost({
quote: initQuote, quote: initQuote,
mention: initMention, mention: initMention,
}: Props) { }: Props) {
const {activeModals} = useModals()
const {openModal, closeModal} = useModalControls()
const {track} = useAnalytics() const {track} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
const {isDesktop, isMobile} = useWebMediaQueries() const {isDesktop, isMobile} = useWebMediaQueries()
@ -118,18 +121,18 @@ export const ComposePost = observer(function ComposePost({
const onPressCancel = useCallback(() => { const onPressCancel = useCallback(() => {
if (graphemeLength > 0 || !gallery.isEmpty) { if (graphemeLength > 0 || !gallery.isEmpty) {
if (store.shell.activeModals.some(modal => modal.name === 'confirm')) { if (activeModals.some(modal => modal.name === 'confirm')) {
store.shell.closeModal() closeModal()
} }
if (Keyboard) { if (Keyboard) {
Keyboard.dismiss() Keyboard.dismiss()
} }
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Discard draft', title: 'Discard draft',
onPressConfirm: onClose, onPressConfirm: onClose,
onPressCancel: () => { onPressCancel: () => {
store.shell.closeModal() closeModal()
}, },
message: "Are you sure you'd like to discard this draft?", message: "Are you sure you'd like to discard this draft?",
confirmBtnText: 'Discard', confirmBtnText: 'Discard',
@ -138,7 +141,7 @@ export const ComposePost = observer(function ComposePost({
} else { } else {
onClose() onClose()
} }
}, [store, onClose, graphemeLength, gallery]) }, [openModal, closeModal, activeModals, onClose, graphemeLength, gallery])
// android back button // android back button
useEffect(() => { useEffect(() => {
if (!isAndroid) { if (!isAndroid) {

View File

@ -3,11 +3,11 @@ import {Keyboard, StyleSheet} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {ShieldExclamation} from 'lib/icons' import {ShieldExclamation} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {isNative} from 'platform/detection' import {isNative} from 'platform/detection'
import {useModalControls} from '#/state/modals'
export const LabelsBtn = observer(function LabelsBtn({ export const LabelsBtn = observer(function LabelsBtn({
labels, labels,
@ -19,7 +19,7 @@ export const LabelsBtn = observer(function LabelsBtn({
onChange: (v: string[]) => void onChange: (v: string[]) => void
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const {openModal} = useModalControls()
return ( return (
<Button <Button
@ -34,7 +34,7 @@ export const LabelsBtn = observer(function LabelsBtn({
Keyboard.dismiss() Keyboard.dismiss()
} }
} }
store.shell.openModal({name: 'self-label', labels, hasMedia, onChange}) openModal({name: 'self-label', labels, hasMedia, onChange})
}}> }}>
<ShieldExclamation style={pal.link} size={26} /> <ShieldExclamation style={pal.link} size={26} />
{labels.length > 0 ? ( {labels.length > 0 ? (

View File

@ -7,11 +7,11 @@ import {s, colors} from 'lib/styles'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {openAltTextModal} from 'lib/media/alt-text'
import {Dimensions} from 'lib/media/types' import {Dimensions} from 'lib/media/types'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useModalControls} from '#/state/modals'
import {isNative} from 'platform/detection'
const IMAGE_GAP = 8 const IMAGE_GAP = 8
@ -47,9 +47,9 @@ const GalleryInner = observer(function GalleryImpl({
gallery, gallery,
containerInfo, containerInfo,
}: GalleryInnerProps) { }: GalleryInnerProps) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls()
let side: number let side: number
@ -117,7 +117,10 @@ const GalleryInner = observer(function GalleryImpl({
accessibilityHint="" accessibilityHint=""
onPress={() => { onPress={() => {
Keyboard.dismiss() Keyboard.dismiss()
openAltTextModal(store, image) openModal({
name: 'alt-text-image',
image,
})
}} }}
style={[styles.altTextControl, altTextControlStyle]}> style={[styles.altTextControl, altTextControlStyle]}>
<Text style={styles.altTextControlLabel} accessible={false}> <Text style={styles.altTextControlLabel} accessible={false}>
@ -137,7 +140,17 @@ const GalleryInner = observer(function GalleryImpl({
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel="Edit image" accessibilityLabel="Edit image"
accessibilityHint="" accessibilityHint=""
onPress={() => gallery.edit(image)} onPress={() => {
if (isNative) {
gallery.crop(image)
} else {
openModal({
name: 'edit-image',
image,
gallery,
})
}
}}
style={styles.imageControl}> style={styles.imageControl}>
<FontAwesomeIcon <FontAwesomeIcon
icon="pen" icon="pen"
@ -165,7 +178,10 @@ const GalleryInner = observer(function GalleryImpl({
accessibilityHint="" accessibilityHint=""
onPress={() => { onPress={() => {
Keyboard.dismiss() Keyboard.dismiss()
openAltTextModal(store, image) openModal({
name: 'alt-text-image',
image,
})
}} }}
style={styles.altTextHiddenRegion} style={styles.altTextHiddenRegion}
/> />

View File

@ -12,9 +12,9 @@ import {
DropdownItemButton, DropdownItemButton,
} from 'view/com/util/forms/DropdownButton' } from 'view/com/util/forms/DropdownButton'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {isNative} from 'platform/detection' import {isNative} from 'platform/detection'
import {codeToLanguageName} from '../../../../locale/helpers' import {codeToLanguageName} from '../../../../locale/helpers'
import {useModalControls} from '#/state/modals'
import { import {
useLanguagePrefs, useLanguagePrefs,
useSetLanguagePrefs, useSetLanguagePrefs,
@ -24,7 +24,7 @@ import {
export const SelectLangBtn = observer(function SelectLangBtn() { export const SelectLangBtn = observer(function SelectLangBtn() {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const {openModal} = useModalControls()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const setLangPrefs = useSetLanguagePrefs() const setLangPrefs = useSetLanguagePrefs()
@ -34,8 +34,8 @@ export const SelectLangBtn = observer(function SelectLangBtn() {
Keyboard.dismiss() Keyboard.dismiss()
} }
} }
store.shell.openModal({name: 'post-languages-settings'}) openModal({name: 'post-languages-settings'})
}, [store]) }, [openModal])
const postLanguagesPref = toPostLanguages(langPrefs.postLanguage) const postLanguagesPref = toPostLanguages(langPrefs.postLanguage)
const items: DropdownItem[] = useMemo(() => { const items: DropdownItem[] = useMemo(() => {

View File

@ -10,12 +10,12 @@ import {observer} from 'mobx-react-lite'
import {FeedSourceModel} from 'state/models/content/feed-source' import {FeedSourceModel} from 'state/models/content/feed-source'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {useStores} from 'state/index'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {AtUri} from '@atproto/api' import {AtUri} from '@atproto/api'
import * as Toast from 'view/com/util/Toast' import * as Toast from 'view/com/util/Toast'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
export const FeedSourceCard = observer(function FeedSourceCardImpl({ export const FeedSourceCard = observer(function FeedSourceCardImpl({
item, item,
@ -30,13 +30,13 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
showDescription?: boolean showDescription?: boolean
showLikes?: boolean showLikes?: boolean
}) { }) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
const onToggleSaved = React.useCallback(async () => { const onToggleSaved = React.useCallback(async () => {
if (item.isSaved) { if (item.isSaved) {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Remove from my feeds', title: 'Remove from my feeds',
message: `Remove ${item.displayName} from my feeds?`, message: `Remove ${item.displayName} from my feeds?`,
@ -59,7 +59,7 @@ export const FeedSourceCard = observer(function FeedSourceCardImpl({
logger.error('Failed to save feed', {error: e}) logger.error('Failed to save feed', {error: e})
} }
} }
}, [store, item]) }, [openModal, item])
return ( return (
<Pressable <Pressable

View File

@ -17,11 +17,11 @@ import {Button} from '../util/forms/Button'
import {ListModel} from 'state/models/content/list' import {ListModel} from 'state/models/content/list'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll' import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_ITEM = {_reactKey: '__empty__'} const EMPTY_ITEM = {_reactKey: '__empty__'}
@ -54,10 +54,10 @@ export const ListItems = observer(function ListItemsImpl({
desktopFixedHeightOffset?: number desktopFixedHeightOffset?: number
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores()
const {track} = useAnalytics() const {track} = useAnalytics()
const [isRefreshing, setIsRefreshing] = React.useState(false) const [isRefreshing, setIsRefreshing] = React.useState(false)
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls()
const data = React.useMemo(() => { const data = React.useMemo(() => {
let items: any[] = [] let items: any[] = []
@ -115,7 +115,7 @@ export const ListItems = observer(function ListItemsImpl({
const onPressEditMembership = React.useCallback( const onPressEditMembership = React.useCallback(
(profile: AppBskyActorDefs.ProfileViewBasic) => { (profile: AppBskyActorDefs.ProfileViewBasic) => {
store.shell.openModal({ openModal({
name: 'user-add-remove-lists', name: 'user-add-remove-lists',
subject: profile.did, subject: profile.did,
displayName: profile.displayName || profile.handle, displayName: profile.displayName || profile.handle,
@ -131,7 +131,7 @@ export const ListItems = observer(function ListItemsImpl({
}, },
}) })
}, },
[store, list], [openModal, list],
) )
// rendering // rendering

View File

@ -13,6 +13,7 @@ import {
import Clipboard from '@react-native-clipboard/clipboard' import Clipboard from '@react-native-clipboard/clipboard'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['70%'] export const snapPoints = ['70%']
@ -54,6 +55,7 @@ const shadesOfBlue: string[] = [
export function Component({}: {}) { export function Component({}: {}) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const [name, setName] = useState( const [name, setName] = useState(
shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)], shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)],
) )
@ -69,8 +71,8 @@ export function Component({}: {}) {
}, [appPassword]) }, [appPassword])
const onDone = React.useCallback(() => { const onDone = React.useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
const createAppPassword = async () => { const createAppPassword = async () => {
// if name is all whitespace, we don't allow it // if name is all whitespace, we don't allow it

View File

@ -17,9 +17,9 @@ import {MAX_ALT_TEXT} from 'lib/constants'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {useStores} from 'state/index'
import {isAndroid, isWeb} from 'platform/detection' import {isAndroid, isWeb} from 'platform/detection'
import {ImageModel} from 'state/models/media/image' import {ImageModel} from 'state/models/media/image'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['fullscreen'] export const snapPoints = ['fullscreen']
@ -29,10 +29,10 @@ interface Props {
export function Component({image}: Props) { export function Component({image}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores()
const theme = useTheme() const theme = useTheme()
const [altText, setAltText] = useState(image.altText) const [altText, setAltText] = useState(image.altText)
const windim = useWindowDimensions() const windim = useWindowDimensions()
const {closeModal} = useModalControls()
const imageStyles = useMemo<ImageStyle>(() => { const imageStyles = useMemo<ImageStyle>(() => {
const maxWidth = isWeb ? 450 : windim.width const maxWidth = isWeb ? 450 : windim.width
@ -53,11 +53,11 @@ export function Component({image}: Props) {
const onPressSave = useCallback(() => { const onPressSave = useCallback(() => {
image.setAltText(altText) image.setAltText(altText)
store.shell.closeModal() closeModal()
}, [store, image, altText]) }, [closeModal, image, altText])
const onPressCancel = () => { const onPressCancel = () => {
store.shell.closeModal() closeModal()
} }
return ( return (

View File

@ -15,12 +15,14 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['50%'] export const snapPoints = ['50%']
export const Component = observer(function Component({}: {}) { export const Component = observer(function Component({}: {}) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const [date, setDate] = useState<Date>( const [date, setDate] = useState<Date>(
store.preferences.birthDate || new Date(), store.preferences.birthDate || new Date(),
) )
@ -33,7 +35,7 @@ export const Component = observer(function Component({}: {}) {
setIsProcessing(true) setIsProcessing(true)
try { try {
await store.preferences.setBirthDate(date) await store.preferences.setBirthDate(date)
store.shell.closeModal() closeModal()
} catch (e) { } catch (e) {
setError(cleanError(String(e))) setError(cleanError(String(e)))
} finally { } finally {

View File

@ -12,6 +12,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {useModalControls} from '#/state/modals'
enum Stages { enum Stages {
InputEmail, InputEmail,
@ -32,6 +33,7 @@ export const Component = observer(function Component({}: {}) {
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal, closeModal} = useModalControls()
const onRequestChange = async () => { const onRequestChange = async () => {
if (email === store.session.currentSession?.email) { if (email === store.session.currentSession?.email) {
@ -90,8 +92,8 @@ export const Component = observer(function Component({}: {}) {
} }
const onVerify = async () => { const onVerify = async () => {
store.shell.closeModal() closeModal()
store.shell.openModal({name: 'verify-email'}) openModal({name: 'verify-email'})
} }
return ( return (
@ -207,7 +209,7 @@ export const Component = observer(function Component({}: {}) {
<Button <Button
testID="cancelBtn" testID="cancelBtn"
type="default" type="default"
onPress={() => store.shell.closeModal()} onPress={() => closeModal()}
accessibilityLabel="Cancel" accessibilityLabel="Cancel"
accessibilityHint="" accessibilityHint=""
label="Cancel" label="Cancel"

View File

@ -22,6 +22,7 @@ import {useTheme} from 'lib/ThemeContext'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['100%'] export const snapPoints = ['100%']
@ -30,6 +31,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const pal = usePalette('default') const pal = usePalette('default')
const {track} = useAnalytics() const {track} = useAnalytics()
const {closeModal} = useModalControls()
const [isProcessing, setProcessing] = useState<boolean>(false) const [isProcessing, setProcessing] = useState<boolean>(false)
const [retryDescribeTrigger, setRetryDescribeTrigger] = React.useState<any>( const [retryDescribeTrigger, setRetryDescribeTrigger] = React.useState<any>(
@ -85,8 +87,8 @@ export function Component({onChanged}: {onChanged: () => void}) {
// events // events
// = // =
const onPressCancel = React.useCallback(() => { const onPressCancel = React.useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
const onPressRetryConnect = React.useCallback( const onPressRetryConnect = React.useCallback(
() => setRetryDescribeTrigger({}), () => setRetryDescribeTrigger({}),
[setRetryDescribeTrigger], [setRetryDescribeTrigger],
@ -110,7 +112,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
await store.agent.updateHandle({ await store.agent.updateHandle({
handle: newHandle, handle: newHandle,
}) })
store.shell.closeModal() closeModal()
onChanged() onChanged()
} catch (err: any) { } catch (err: any) {
setError(cleanError(err)) setError(cleanError(err))
@ -127,6 +129,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
isCustom, isCustom,
onChanged, onChanged,
track, track,
closeModal,
]) ])
// rendering // rendering

View File

@ -6,13 +6,13 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' 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%'] export const snapPoints = ['50%']
@ -26,7 +26,7 @@ export function Component({
cancelBtnText, cancelBtnText,
}: ConfirmModal) { }: ConfirmModal) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const {closeModal} = useModalControls()
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const onPress = async () => { const onPress = async () => {
@ -34,7 +34,7 @@ export function Component({
setIsProcessing(true) setIsProcessing(true)
try { try {
await onPressConfirm() await onPressConfirm()
store.shell.closeModal() closeModal()
return return
} catch (e: any) { } catch (e: any) {
setError(cleanError(e)) setError(cleanError(e))

View File

@ -16,6 +16,7 @@ import {isIOS} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['90%'] export const snapPoints = ['90%']
@ -24,14 +25,15 @@ export const Component = observer(
const store = useStores() const store = useStores()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const {closeModal} = useModalControls()
React.useEffect(() => { React.useEffect(() => {
store.preferences.sync() store.preferences.sync()
}, [store]) }, [store])
const onPressDone = React.useCallback(() => { const onPressDone = React.useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
return ( return (
<View testID="contentFilteringModal" style={[pal.view, styles.container]}> <View testID="contentFilteringModal" style={[pal.view, styles.container]}>
@ -89,8 +91,9 @@ const AdultContentEnabledPref = observer(
function AdultContentEnabledPrefImpl() { function AdultContentEnabledPrefImpl() {
const store = useStores() const store = useStores()
const pal = usePalette('default') 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 () => { const onToggleAdultContent = async () => {
if (isIOS) { if (isIOS) {

View File

@ -24,6 +24,7 @@ import {useTheme} from 'lib/ThemeContext'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError, isNetworkError} from 'lib/strings/errors' import {cleanError, isNetworkError} from 'lib/strings/errors'
import {useModalControls} from '#/state/modals'
const MAX_NAME = 64 // todo const MAX_NAME = 64 // todo
const MAX_DESCRIPTION = 300 // todo const MAX_DESCRIPTION = 300 // todo
@ -40,6 +41,7 @@ export function Component({
list?: ListModel list?: ListModel
}) { }) {
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const pal = usePalette('default') const pal = usePalette('default')
@ -67,8 +69,8 @@ export function Component({
const [newAvatar, setNewAvatar] = useState<RNImage | undefined | null>() const [newAvatar, setNewAvatar] = useState<RNImage | undefined | null>()
const onPressCancel = useCallback(() => { const onPressCancel = useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
const onSelectNewAvatar = useCallback( const onSelectNewAvatar = useCallback(
async (img: RNImage | null) => { async (img: RNImage | null) => {
@ -123,7 +125,7 @@ export function Component({
Toast.show(`${purposeLabel} list created`) Toast.show(`${purposeLabel} list created`)
onSave?.(res.uri) onSave?.(res.uri)
} }
store.shell.closeModal() closeModal()
} catch (e: any) { } catch (e: any) {
if (isNetworkError(e)) { if (isNetworkError(e)) {
setError( setError(
@ -141,6 +143,7 @@ export function Component({
error, error,
onSave, onSave,
store, store,
closeModal,
activePurpose, activePurpose,
isCurateList, isCurateList,
purposeLabel, purposeLabel,

View File

@ -17,6 +17,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {resetToTab} from '../../../Navigation' import {resetToTab} from '../../../Navigation'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['60%'] export const snapPoints = ['60%']
@ -24,6 +25,7 @@ export function Component({}: {}) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false) const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
const [confirmCode, setConfirmCode] = React.useState<string>('') const [confirmCode, setConfirmCode] = React.useState<string>('')
@ -55,14 +57,14 @@ export function Component({}: {}) {
Toast.show('Your account has been deleted') Toast.show('Your account has been deleted')
resetToTab('HomeTab') resetToTab('HomeTab')
store.session.clear() store.session.clear()
store.shell.closeModal() closeModal()
} catch (e: any) { } catch (e: any) {
setError(cleanError(e)) setError(cleanError(e))
} }
setIsProcessing(false) setIsProcessing(false)
} }
const onCancel = () => { const onCancel = () => {
store.shell.closeModal() closeModal()
} }
return ( return (
<View style={[styles.container, pal.view]}> <View style={[styles.container, pal.view]}>

View File

@ -6,7 +6,6 @@ import {gradients, s} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import ImageEditor, {Position} from 'react-avatar-editor' import ImageEditor, {Position} from 'react-avatar-editor'
import {TextInput} from './util' import {TextInput} from './util'
@ -19,6 +18,7 @@ import {Slider} from '@miblanchard/react-native-slider'
import {MaterialIcons} from '@expo/vector-icons' import {MaterialIcons} from '@expo/vector-icons'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {getKeys} from 'lib/type-assertions' import {getKeys} from 'lib/type-assertions'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%'] export const snapPoints = ['80%']
@ -52,9 +52,9 @@ export const Component = observer(function EditImageImpl({
}: Props) { }: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const store = useStores()
const windowDimensions = useWindowDimensions() const windowDimensions = useWindowDimensions()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {closeModal} = useModalControls()
const { const {
aspectRatio, aspectRatio,
@ -128,8 +128,8 @@ export const Component = observer(function EditImageImpl({
}, [image]) }, [image])
const onCloseModal = useCallback(() => { const onCloseModal = useCallback(() => {
store.shell.closeModal() closeModal()
}, [store.shell]) }, [closeModal])
const onPressCancel = useCallback(async () => { const onPressCancel = useCallback(async () => {
await gallery.previous(image) await gallery.previous(image)

View File

@ -13,7 +13,6 @@ import LinearGradient from 'react-native-linear-gradient'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {useStores} from 'state/index'
import {ProfileModel} from 'state/models/content/profile' import {ProfileModel} from 'state/models/content/profile'
import {s, colors, gradients} from 'lib/styles' import {s, colors, gradients} from 'lib/styles'
import {enforceLen} from 'lib/strings/helpers' import {enforceLen} from 'lib/strings/helpers'
@ -27,6 +26,7 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {cleanError, isNetworkError} from 'lib/strings/errors' import {cleanError, isNetworkError} from 'lib/strings/errors'
import Animated, {FadeOut} from 'react-native-reanimated' import Animated, {FadeOut} from 'react-native-reanimated'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useModalControls} from '#/state/modals'
const AnimatedTouchableOpacity = const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity) Animated.createAnimatedComponent(TouchableOpacity)
@ -40,11 +40,11 @@ export function Component({
profileView: ProfileModel profileView: ProfileModel
onUpdate?: () => void onUpdate?: () => void
}) { }) {
const store = useStores()
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const {track} = useAnalytics() const {track} = useAnalytics()
const {closeModal} = useModalControls()
const [isProcessing, setProcessing] = useState<boolean>(false) const [isProcessing, setProcessing] = useState<boolean>(false)
const [displayName, setDisplayName] = useState<string>( const [displayName, setDisplayName] = useState<string>(
@ -66,7 +66,7 @@ export function Component({
RNImage | undefined | null RNImage | undefined | null
>() >()
const onPressCancel = () => { const onPressCancel = () => {
store.shell.closeModal() closeModal()
} }
const onSelectNewAvatar = useCallback( const onSelectNewAvatar = useCallback(
async (img: RNImage | null) => { async (img: RNImage | null) => {
@ -123,7 +123,7 @@ export function Component({
) )
Toast.show('Profile updated') Toast.show('Profile updated')
onUpdate?.() onUpdate?.()
store.shell.closeModal() closeModal()
} catch (e: any) { } catch (e: any) {
if (isNetworkError(e)) { if (isNetworkError(e)) {
setError( setError(
@ -141,7 +141,7 @@ export function Component({
error, error,
profileView, profileView,
onUpdate, onUpdate,
store, closeModal,
displayName, displayName,
description, description,
newUserAvatar, newUserAvatar,

View File

@ -15,6 +15,7 @@ import {ScrollView} from './util'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useModalControls} from '#/state/modals'
import {useInvitesState, useInvitesAPI} from '#/state/invites' import {useInvitesState, useInvitesAPI} from '#/state/invites'
import {UserInfoText} from '../util/UserInfoText' import {UserInfoText} from '../util/UserInfoText'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
@ -25,11 +26,12 @@ export const snapPoints = ['70%']
export function Component({}: {}) { export function Component({}: {}) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
const onClose = React.useCallback(() => { const onClose = React.useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
if (store.me.invites.length === 0) { if (store.me.invites.length === 0) {
return ( return (

View File

@ -5,12 +5,12 @@ import {observer} from 'mobx-react-lite'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers' import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['50%'] export const snapPoints = ['50%']
@ -22,12 +22,12 @@ export const Component = observer(function Component({
href: string href: string
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const potentiallyMisleading = isPossiblyAUrl(text) const potentiallyMisleading = isPossiblyAUrl(text)
const onPressVisit = () => { const onPressVisit = () => {
store.shell.closeModal() closeModal()
Linking.openURL(href) Linking.openURL(href)
} }
@ -83,7 +83,7 @@ export const Component = observer(function Component({
<Button <Button
testID="cancelBtn" testID="cancelBtn"
type="default" type="default"
onPress={() => store.shell.closeModal()} onPress={() => closeModal()}
accessibilityLabel="Cancel" accessibilityLabel="Cancel"
accessibilityHint="" accessibilityHint=""
label="Cancel" label="Cancel"

View File

@ -26,6 +26,7 @@ import {cleanError} from 'lib/strings/errors'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {HITSLOP_20} from '#/lib/constants' import {HITSLOP_20} from '#/lib/constants'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['90%'] export const snapPoints = ['90%']
@ -38,6 +39,7 @@ export const Component = observer(function Component({
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const autocompleteView = useMemo<UserAutocompleteModel>( const autocompleteView = useMemo<UserAutocompleteModel>(
@ -146,7 +148,7 @@ export const Component = observer(function Component({
<Button <Button
testID="doneBtn" testID="doneBtn"
type="default" type="default"
onPress={() => store.shell.closeModal()} onPress={() => closeModal()}
accessibilityLabel="Done" accessibilityLabel="Done"
accessibilityHint="" accessibilityHint=""
label="Done" label="Done"

View File

@ -3,13 +3,13 @@ import {StyleSheet} from 'react-native'
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context' import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import BottomSheet from '@gorhom/bottom-sheet' import BottomSheet from '@gorhom/bottom-sheet'
import {useStores} from 'state/index'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {timeout} from 'lib/async/timeout' import {timeout} from 'lib/async/timeout'
import {navigate} from '../../../Navigation' import {navigate} from '../../../Navigation'
import once from 'lodash.once' import once from 'lodash.once'
import {useModals, useModalControls} from '#/state/modals'
import * as ConfirmModal from './Confirm' import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
import * as ProfilePreviewModal from './ProfilePreview' import * as ProfilePreviewModal from './ProfilePreview'
@ -41,17 +41,17 @@ const DEFAULT_SNAPPOINTS = ['90%']
const HANDLE_HEIGHT = 24 const HANDLE_HEIGHT = 24
export const ModalsContainer = observer(function ModalsContainer() { export const ModalsContainer = observer(function ModalsContainer() {
const store = useStores() const {isModalActive, activeModals} = useModals()
const {closeModal} = useModalControls()
const bottomSheetRef = useRef<BottomSheet>(null) const bottomSheetRef = useRef<BottomSheet>(null)
const pal = usePalette('default') const pal = usePalette('default')
const safeAreaInsets = useSafeAreaInsets() const safeAreaInsets = useSafeAreaInsets()
const activeModal = const activeModal = activeModals[activeModals.length - 1]
store.shell.activeModals[store.shell.activeModals.length - 1]
const navigateOnce = once(navigate) const navigateOnce = once(navigate)
const onBottomSheetAnimate = (fromIndex: number, toIndex: number) => { const onBottomSheetAnimate = (_fromIndex: number, toIndex: number) => {
if (activeModal?.name === 'profile-preview' && toIndex === 1) { if (activeModal?.name === 'profile-preview' && toIndex === 1) {
// begin loading the profile screen behind the scenes // begin loading the profile screen behind the scenes
navigateOnce('Profile', {name: activeModal.did}) navigateOnce('Profile', {name: activeModal.did})
@ -59,7 +59,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
} }
const onBottomSheetChange = async (snapPoint: number) => { const onBottomSheetChange = async (snapPoint: number) => {
if (snapPoint === -1) { if (snapPoint === -1) {
store.shell.closeModal() closeModal()
} else if (activeModal?.name === 'profile-preview' && snapPoint === 1) { } else if (activeModal?.name === 'profile-preview' && snapPoint === 1) {
await navigateOnce('Profile', {name: activeModal.did}) await navigateOnce('Profile', {name: activeModal.did})
// There is no particular callback for when the view has actually been presented. // 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. // 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. // TODO: Figure out why avatar/cover don't always show instantly from cache.
await timeout(200) await timeout(200)
store.shell.closeModal() closeModal()
} }
} }
const onClose = () => { const onClose = () => {
bottomSheetRef.current?.close() bottomSheetRef.current?.close()
store.shell.closeModal() closeModal()
} }
useEffect(() => { useEffect(() => {
if (store.shell.isModalActive) { if (isModalActive) {
bottomSheetRef.current?.expand() bottomSheetRef.current?.expand()
} else { } else {
bottomSheetRef.current?.close() bottomSheetRef.current?.close()
} }
}, [store.shell.isModalActive, bottomSheetRef, activeModal?.name]) }, [isModalActive, bottomSheetRef, activeModal?.name])
let needsSafeTopInset = false let needsSafeTopInset = false
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
@ -184,12 +184,12 @@ export const ModalsContainer = observer(function ModalsContainer() {
snapPoints={snapPoints} snapPoints={snapPoints}
topInset={topInset} topInset={topInset}
handleHeight={HANDLE_HEIGHT} handleHeight={HANDLE_HEIGHT}
index={store.shell.isModalActive ? 0 : -1} index={isModalActive ? 0 : -1}
enablePanDownToClose enablePanDownToClose
android_keyboardInputMode="adjustResize" android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore" keyboardBlurBehavior="restore"
backdropComponent={ backdropComponent={
store.shell.isModalActive ? createCustomBackdrop(onClose) : undefined isModalActive ? createCustomBackdrop(onClose) : undefined
} }
handleIndicatorStyle={{backgroundColor: pal.text.color}} handleIndicatorStyle={{backgroundColor: pal.text.color}}
handleStyle={[styles.handle, pal.view]} handleStyle={[styles.handle, pal.view]}

View File

@ -1,11 +1,11 @@
import React from 'react' import React from 'react'
import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native' import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 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 ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
import * as ProfilePreviewModal from './ProfilePreview' import * as ProfilePreviewModal from './ProfilePreview'
@ -34,15 +34,15 @@ import * as ChangeEmailModal from './ChangeEmail'
import * as LinkWarningModal from './LinkWarning' import * as LinkWarningModal from './LinkWarning'
export const ModalsContainer = observer(function ModalsContainer() { export const ModalsContainer = observer(function ModalsContainer() {
const store = useStores() const {isModalActive, activeModals} = useModals()
if (!store.shell.isModalActive) { if (!isModalActive) {
return null return null
} }
return ( return (
<> <>
{store.shell.activeModals.map((modal, i) => ( {activeModals.map((modal, i) => (
<Modal key={`modal-${i}`} modal={modal} /> <Modal key={`modal-${i}`} modal={modal} />
))} ))}
</> </>
@ -50,11 +50,12 @@ export const ModalsContainer = observer(function ModalsContainer() {
}) })
function Modal({modal}: {modal: ModalIface}) { function Modal({modal}: {modal: ModalIface}) {
const store = useStores() const {isModalActive} = useModals()
const {closeModal} = useModalControls()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
if (!store.shell.isModalActive) { if (!isModalActive) {
return null return null
} }
@ -62,7 +63,7 @@ function Modal({modal}: {modal: ModalIface}) {
if (modal.name === 'crop-image' || modal.name === 'edit-image') { if (modal.name === 'crop-image' || modal.name === 'edit-image') {
return // dont close on mask presses during crop return // dont close on mask presses during crop
} }
store.shell.closeModal() closeModal()
} }
const onInnerPress = () => { const onInnerPress = () => {
// TODO: can we use prevent default? // TODO: can we use prevent default?

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {ModerationUI} from '@atproto/api' import {ModerationUI} from '@atproto/api'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
@ -10,6 +9,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {listUriToHref} from 'lib/strings/url-helpers' import {listUriToHref} from 'lib/strings/url-helpers'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
import {useModalControls} from '#/state/modals'
export const snapPoints = [300] export const snapPoints = [300]
@ -20,7 +20,7 @@ export function Component({
context: 'account' | 'content' context: 'account' | 'content'
moderation: ModerationUI moderation: ModerationUI
}) { }) {
const store = useStores() const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
@ -99,10 +99,7 @@ export function Component({
{description} {description}
</Text> </Text>
<View style={s.flex1} /> <View style={s.flex1} />
<Button <Button type="primary" style={styles.btn} onPress={() => closeModal()}>
type="primary"
style={styles.btn}
onPress={() => store.shell.closeModal()}>
<Text type="button-lg" style={[pal.textLight, s.textCenter, s.white]}> <Text type="button-lg" style={[pal.textLight, s.textCenter, s.white]}>
Okay Okay
</Text> </Text>

View File

@ -1,12 +1,12 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {useStores} from 'state/index'
import {s, colors, gradients} from 'lib/styles' import {s, colors, gradients} from 'lib/styles'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {RepostIcon} from 'lib/icons' import {RepostIcon} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useModalControls} from '#/state/modals'
export const snapPoints = [250] export const snapPoints = [250]
@ -20,10 +20,10 @@ export function Component({
isReposted: boolean isReposted: boolean
// TODO: Add author into component // TODO: Add author into component
}) { }) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {closeModal} = useModalControls()
const onPress = async () => { const onPress = async () => {
store.shell.closeModal() closeModal()
} }
return ( return (

View File

@ -2,7 +2,6 @@ import React, {useState} from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -10,6 +9,7 @@ import {isWeb} from 'platform/detection'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
import {SelectableBtn} from '../util/forms/SelectableBtn' import {SelectableBtn} from '../util/forms/SelectableBtn'
import {ScrollView} from 'view/com/modals/util' import {ScrollView} from 'view/com/modals/util'
import {useModalControls} from '#/state/modals'
const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
@ -25,7 +25,7 @@ export const Component = observer(function Component({
onChange: (labels: string[]) => void onChange: (labels: string[]) => void
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [selected, setSelected] = useState(labels) const [selected, setSelected] = useState(labels)
@ -143,7 +143,7 @@ export const Component = observer(function Component({
<TouchableOpacity <TouchableOpacity
testID="confirmBtn" testID="confirmBtn"
onPress={() => { onPress={() => {
store.shell.closeModal() closeModal()
}} }}
style={styles.btn} style={styles.btn}
accessibilityRole="button" accessibilityRole="button"

View File

@ -6,26 +6,26 @@ import {
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {ScrollView, TextInput} from './util' import {ScrollView, TextInput} from './util'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index' import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index'
import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags' import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%'] export const snapPoints = ['80%']
export function Component({onSelect}: {onSelect: (url: string) => void}) { export function Component({onSelect}: {onSelect: (url: string) => void}) {
const theme = useTheme() const theme = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores()
const [customUrl, setCustomUrl] = useState<string>('') const [customUrl, setCustomUrl] = useState<string>('')
const {closeModal} = useModalControls()
const doSelect = (url: string) => { const doSelect = (url: string) => {
if (!url.startsWith('http://') && !url.startsWith('https://')) { if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `https://${url}` url = `https://${url}`
} }
store.shell.closeModal() closeModal()
onSelect(url) onSelect(url)
} }

View File

@ -21,6 +21,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb, isAndroid} from 'platform/detection' import {isWeb, isAndroid} from 'platform/detection'
import isEqual from 'lodash.isequal' import isEqual from 'lodash.isequal'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['fullscreen'] export const snapPoints = ['fullscreen']
@ -36,6 +37,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
onRemove?: (listUri: string) => void onRemove?: (listUri: string) => void
}) { }) {
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const pal = usePalette('default') const pal = usePalette('default')
const palPrimary = usePalette('primary') const palPrimary = usePalette('primary')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
@ -69,8 +71,8 @@ export const Component = observer(function UserAddRemoveListsImpl({
}, [memberships, listsList, store, setSelected, setMembershipsLoaded]) }, [memberships, listsList, store, setSelected, setMembershipsLoaded])
const onPressCancel = useCallback(() => { const onPressCancel = useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
const onPressSave = useCallback(async () => { const onPressSave = useCallback(async () => {
let changes let changes
@ -87,8 +89,8 @@ export const Component = observer(function UserAddRemoveListsImpl({
for (const uri of changes.removed) { for (const uri of changes.removed) {
onRemove?.(uri) onRemove?.(uri)
} }
store.shell.closeModal() closeModal()
}, [store, selected, memberships, onAdd, onRemove]) }, [closeModal, selected, memberships, onAdd, onRemove])
const onToggleSelected = useCallback( const onToggleSelected = useCallback(
(uri: string) => { (uri: string) => {

View File

@ -20,6 +20,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['90%'] export const snapPoints = ['90%']
@ -43,6 +44,7 @@ export const Component = observer(function Component({
const [isProcessing, setIsProcessing] = useState<boolean>(false) const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [error, setError] = useState<string>('') const [error, setError] = useState<string>('')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal, closeModal} = useModalControls()
const onSendEmail = async () => { const onSendEmail = async () => {
setError('') setError('')
@ -67,7 +69,7 @@ export const Component = observer(function Component({
}) })
store.session.updateLocalAccountData({emailConfirmed: true}) store.session.updateLocalAccountData({emailConfirmed: true})
Toast.show('Email verified') Toast.show('Email verified')
store.shell.closeModal() closeModal()
} catch (e) { } catch (e) {
setError(cleanError(String(e))) setError(cleanError(String(e)))
} finally { } finally {
@ -76,8 +78,8 @@ export const Component = observer(function Component({
} }
const onEmailIncorrect = () => { const onEmailIncorrect = () => {
store.shell.closeModal() closeModal()
store.shell.openModal({name: 'change-email'}) openModal({name: 'change-email'})
} }
return ( return (
@ -224,7 +226,7 @@ export const Component = observer(function Component({
<Button <Button
testID="cancelBtn" testID="cancelBtn"
type="default" type="default"
onPress={() => store.shell.closeModal()} onPress={() => closeModal()}
accessibilityLabel={ accessibilityLabel={
stage === Stages.Reminder ? 'Not right now' : 'Cancel' stage === Stages.Reminder ? 'Not right now' : 'Cancel'
} }

View File

@ -12,19 +12,19 @@ import {
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import LinearGradient from 'react-native-linear-gradient' import LinearGradient from 'react-native-linear-gradient'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s, gradients} from 'lib/styles' import {s, gradients} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%'] export const snapPoints = ['80%']
export function Component({}: {}) { export function Component({}: {}) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const store = useStores() const {closeModal} = useModalControls()
const [email, setEmail] = React.useState<string>('') const [email, setEmail] = React.useState<string>('')
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false) const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
const [isProcessing, setIsProcessing] = React.useState<boolean>(false) const [isProcessing, setIsProcessing] = React.useState<boolean>(false)
@ -54,7 +54,7 @@ export function Component({}: {}) {
setIsProcessing(false) setIsProcessing(false)
} }
const onCancel = () => { const onCancel = () => {
store.shell.closeModal() closeModal()
} }
return ( return (

View File

@ -7,10 +7,10 @@ import {Text} from 'view/com/util/text/Text'
import {Dimensions} from 'lib/media/types' import {Dimensions} from 'lib/media/types'
import {getDataUriSize} from 'lib/media/util' import {getDataUriSize} from 'lib/media/util'
import {s, gradients} from 'lib/styles' import {s, gradients} from 'lib/styles'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {SquareIcon, RectWideIcon, RectTallIcon} from 'lib/icons' import {SquareIcon, RectWideIcon, RectTallIcon} from 'lib/icons'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {useModalControls} from '#/state/modals'
enum AspectRatio { enum AspectRatio {
Square = 'square', Square = 'square',
@ -33,7 +33,7 @@ export function Component({
uri: string uri: string
onSelect: (img?: RNImage) => void onSelect: (img?: RNImage) => void
}) { }) {
const store = useStores() const {closeModal} = useModalControls()
const pal = usePalette('default') const pal = usePalette('default')
const [as, setAs] = React.useState<AspectRatio>(AspectRatio.Square) const [as, setAs] = React.useState<AspectRatio>(AspectRatio.Square)
const [scale, setScale] = React.useState<number>(1) const [scale, setScale] = React.useState<number>(1)
@ -43,7 +43,7 @@ export function Component({
const onPressCancel = () => { const onPressCancel = () => {
onSelect(undefined) onSelect(undefined)
store.shell.closeModal() closeModal()
} }
const onPressDone = () => { const onPressDone = () => {
const canvas = editorRef.current?.getImageScaledToCanvas() const canvas = editorRef.current?.getImageScaledToCanvas()
@ -59,7 +59,7 @@ export function Component({
} else { } else {
onSelect(undefined) onSelect(undefined)
} }
store.shell.closeModal() closeModal()
} }
let cropperStyle let cropperStyle

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {ScrollView} from '../util' import {ScrollView} from '../util'
import {useStores} from 'state/index'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -9,6 +8,7 @@ import {deviceLocales} from 'platform/detection'
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
import {LanguageToggle} from './LanguageToggle' import {LanguageToggle} from './LanguageToggle'
import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
import {useModalControls} from '#/state/modals'
import { import {
useLanguagePrefs, useLanguagePrefs,
useSetLanguagePrefs, useSetLanguagePrefs,
@ -18,14 +18,14 @@ import {
export const snapPoints = ['100%'] export const snapPoints = ['100%']
export function Component({}: {}) { export function Component({}: {}) {
const store = useStores() const {closeModal} = useModalControls()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const setLangPrefs = useSetLanguagePrefs() const setLangPrefs = useSetLanguagePrefs()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const onPressDone = React.useCallback(() => { const onPressDone = React.useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
const languages = React.useMemo(() => { const languages = React.useMemo(() => {
const langs = LANGUAGES.filter( const langs = LANGUAGES.filter(

View File

@ -2,7 +2,6 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {ScrollView} from '../util' import {ScrollView} from '../util'
import {useStores} from 'state/index'
import {Text} from '../../util/text/Text' import {Text} from '../../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -10,6 +9,7 @@ import {deviceLocales} from 'platform/detection'
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {useModalControls} from '#/state/modals'
import { import {
useLanguagePrefs, useLanguagePrefs,
useSetLanguagePrefs, useSetLanguagePrefs,
@ -20,14 +20,14 @@ import {
export const snapPoints = ['100%'] export const snapPoints = ['100%']
export const Component = observer(function PostLanguagesSettingsImpl() { export const Component = observer(function PostLanguagesSettingsImpl() {
const store = useStores() const {closeModal} = useModalControls()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const setLangPrefs = useSetLanguagePrefs() const setLangPrefs = useSetLanguagePrefs()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const onPressDone = React.useCallback(() => { const onPressDone = React.useCallback(() => {
store.shell.closeModal() closeModal()
}, [store]) }, [closeModal])
const languages = React.useMemo(() => { const languages = React.useMemo(() => {
const langs = LANGUAGES.filter( const langs = LANGUAGES.filter(

View File

@ -14,6 +14,7 @@ import {SendReportButton} from './SendReportButton'
import {InputIssueDetails} from './InputIssueDetails' import {InputIssueDetails} from './InputIssueDetails'
import {ReportReasonOptions} from './ReasonOptions' import {ReportReasonOptions} from './ReasonOptions'
import {CollectionId} from './types' import {CollectionId} from './types'
import {useModalControls} from '#/state/modals'
const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright' const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright'
@ -37,6 +38,7 @@ type ReportComponentProps =
export function Component(content: ReportComponentProps) { export function Component(content: ReportComponentProps) {
const store = useStores() const store = useStores()
const {closeModal} = useModalControls()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [isProcessing, setIsProcessing] = useState(false) const [isProcessing, setIsProcessing] = useState(false)
@ -60,7 +62,7 @@ export function Component(content: ReportComponentProps) {
try { try {
if (issue === '__copyright__') { if (issue === '__copyright__') {
Linking.openURL(DMCA_LINK) Linking.openURL(DMCA_LINK)
store.shell.closeModal() closeModal()
return return
} }
const $type = !isAccountReport 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.") Toast.show("Thank you for your report! We'll look into it promptly.")
store.shell.closeModal() closeModal()
return return
} catch (e: any) { } catch (e: any) {
setError(cleanError(e)) setError(cleanError(e))

View File

@ -11,6 +11,7 @@ import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
const MESSAGES = { const MESSAGES = {
[KnownError.Unknown]: '', [KnownError.Unknown]: '',
@ -57,13 +58,14 @@ function FeedgenErrorMessage({
const msg = MESSAGES[knownError] const msg = MESSAGES[knownError]
const uri = (feed.params as GetCustomFeed.QueryParams).feed const uri = (feed.params as GetCustomFeed.QueryParams).feed
const [ownerDid] = safeParseFeedgenUri(uri) const [ownerDid] = safeParseFeedgenUri(uri)
const {openModal, closeModal} = useModalControls()
const onViewProfile = React.useCallback(() => { const onViewProfile = React.useCallback(() => {
navigation.navigate('Profile', {name: ownerDid}) navigation.navigate('Profile', {name: ownerDid})
}, [navigation, ownerDid]) }, [navigation, ownerDid])
const onRemoveFeed = React.useCallback(async () => { const onRemoveFeed = React.useCallback(async () => {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Remove feed', title: 'Remove feed',
message: 'Remove this feed from your saved feeds?', message: 'Remove this feed from your saved feeds?',
@ -78,10 +80,10 @@ function FeedgenErrorMessage({
} }
}, },
onPressCancel() { onPressCancel() {
store.shell.closeModal() closeModal()
}, },
}) })
}, [store, uri]) }, [store, openModal, closeModal, uri])
return ( return (
<View <View

View File

@ -40,6 +40,7 @@ import {makeProfileLink} from 'lib/routes/links'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {ProfileHeaderSuggestedFollows} from './ProfileHeaderSuggestedFollows' import {ProfileHeaderSuggestedFollows} from './ProfileHeaderSuggestedFollows'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
interface Props { interface Props {
view: ProfileModel view: ProfileModel
@ -113,6 +114,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
const pal = usePalette('default') const pal = usePalette('default')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const store = useStores() const store = useStores()
const {openModal} = useModalControls()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics() const {track} = useAnalytics()
const invalidHandle = isInvalidHandle(view.handle) const invalidHandle = isInvalidHandle(view.handle)
@ -157,12 +159,12 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
const onPressEditProfile = React.useCallback(() => { const onPressEditProfile = React.useCallback(() => {
track('ProfileHeader:EditProfileButtonClicked') track('ProfileHeader:EditProfileButtonClicked')
store.shell.openModal({ openModal({
name: 'edit-profile', name: 'edit-profile',
profileView: view, profileView: view,
onUpdate: onRefreshAll, onUpdate: onRefreshAll,
}) })
}, [track, store, view, onRefreshAll]) }, [track, openModal, view, onRefreshAll])
const trackPress = React.useCallback( const trackPress = React.useCallback(
(f: 'Followers' | 'Follows') => { (f: 'Followers' | 'Follows') => {
@ -181,12 +183,12 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
const onPressAddRemoveLists = React.useCallback(() => { const onPressAddRemoveLists = React.useCallback(() => {
track('ProfileHeader:AddToListsButtonClicked') track('ProfileHeader:AddToListsButtonClicked')
store.shell.openModal({ openModal({
name: 'user-add-remove-lists', name: 'user-add-remove-lists',
subject: view.did, subject: view.did,
displayName: view.displayName || view.handle, displayName: view.displayName || view.handle,
}) })
}, [track, view, store]) }, [track, view, openModal])
const onPressMuteAccount = React.useCallback(async () => { const onPressMuteAccount = React.useCallback(async () => {
track('ProfileHeader:MuteAccountButtonClicked') track('ProfileHeader:MuteAccountButtonClicked')
@ -212,7 +214,7 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
const onPressBlockAccount = React.useCallback(async () => { const onPressBlockAccount = React.useCallback(async () => {
track('ProfileHeader:BlockAccountButtonClicked') track('ProfileHeader:BlockAccountButtonClicked')
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Block Account', title: 'Block Account',
message: message:
@ -228,11 +230,11 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
} }
}, },
}) })
}, [track, view, store, onRefreshAll]) }, [track, view, openModal, onRefreshAll])
const onPressUnblockAccount = React.useCallback(async () => { const onPressUnblockAccount = React.useCallback(async () => {
track('ProfileHeader:UnblockAccountButtonClicked') track('ProfileHeader:UnblockAccountButtonClicked')
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Unblock Account', title: 'Unblock Account',
message: message:
@ -248,15 +250,15 @@ const ProfileHeaderLoaded = observer(function ProfileHeaderLoadedImpl({
} }
}, },
}) })
}, [track, view, store, onRefreshAll]) }, [track, view, openModal, onRefreshAll])
const onPressReportAccount = React.useCallback(() => { const onPressReportAccount = React.useCallback(() => {
track('ProfileHeader:ReportAccountButtonClicked') track('ProfileHeader:ReportAccountButtonClicked')
store.shell.openModal({ openModal({
name: 'report', name: 'report',
did: view.did, did: view.did,
}) })
}, [track, store, view]) }, [track, openModal, view])
const isMe = React.useMemo( const isMe = React.useMemo(
() => store.me.did === view.did, () => store.me.did === view.did,

View File

@ -2,6 +2,7 @@ import React from 'react'
import {Pressable, View} from 'react-native' import {Pressable, View} from 'react-native'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {navigate} from '../../../Navigation' import {navigate} from '../../../Navigation'
import {useModalControls} from '#/state/modals'
/** /**
* This utility component is only included in the test simulator * This utility component is only included in the test simulator
@ -13,6 +14,7 @@ const BTN = {height: 1, width: 1, backgroundColor: 'red'}
export function TestCtrls() { export function TestCtrls() {
const store = useStores() const store = useStores()
const {openModal} = useModalControls()
const onPressSignInAlice = async () => { const onPressSignInAlice = async () => {
await store.session.login({ await store.session.login({
service: 'http://localhost:3000', service: 'http://localhost:3000',
@ -85,7 +87,7 @@ export function TestCtrls() {
/> />
<Pressable <Pressable
testID="e2eOpenInviteCodesModal" testID="e2eOpenInviteCodesModal"
onPress={() => store.shell.openModal({name: 'invite-codes'})} onPress={() => openModal({name: 'invite-codes'})}
accessibilityRole="button" accessibilityRole="button"
style={BTN} style={BTN}
/> />

View File

@ -21,7 +21,6 @@ import {Text} from './text/Text'
import {TypographyVariant} from 'lib/ThemeContext' import {TypographyVariant} from 'lib/ThemeContext'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {router} from '../../../routes' import {router} from '../../../routes'
import {useStores, RootStoreModel} from 'state/index'
import { import {
convertBskyAppUrlIfNeeded, convertBskyAppUrlIfNeeded,
isExternalUrl, isExternalUrl,
@ -31,6 +30,7 @@ import {isAndroid, isWeb} from 'platform/detection'
import {sanitizeUrl} from '@braintree/sanitize-url' import {sanitizeUrl} from '@braintree/sanitize-url'
import {PressableWithHover} from './PressableWithHover' import {PressableWithHover} from './PressableWithHover'
import FixedTouchableHighlight from '../pager/FixedTouchableHighlight' import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
import {useModalControls} from '#/state/modals'
type Event = type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.MouseEvent<HTMLAnchorElement, MouseEvent>
@ -60,17 +60,17 @@ export const Link = memo(function Link({
anchorNoUnderline, anchorNoUnderline,
...props ...props
}: Props) { }: Props) {
const store = useStores() const {closeModal} = useModalControls()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const onPress = React.useCallback( const onPress = React.useCallback(
(e?: Event) => { (e?: Event) => {
if (typeof href === 'string') { if (typeof href === 'string') {
return onPressInner(store, navigation, sanitizeUrl(href), e) return onPressInner(closeModal, navigation, sanitizeUrl(href), e)
} }
}, },
[store, navigation, href], [closeModal, navigation, href],
) )
if (noFeedback) { if (noFeedback) {
@ -160,8 +160,8 @@ export const TextLink = memo(function TextLink({
warnOnMismatchingLabel?: boolean warnOnMismatchingLabel?: boolean
} & TextProps) { } & TextProps) {
const {...props} = useLinkProps({to: sanitizeUrl(href)}) const {...props} = useLinkProps({to: sanitizeUrl(href)})
const store = useStores()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal, closeModal} = useModalControls()
if (warnOnMismatchingLabel && typeof text !== 'string') { if (warnOnMismatchingLabel && typeof text !== 'string') {
console.error('Unable to detect mismatching label') console.error('Unable to detect mismatching label')
@ -174,7 +174,7 @@ export const TextLink = memo(function TextLink({
linkRequiresWarning(href, typeof text === 'string' ? text : '') linkRequiresWarning(href, typeof text === 'string' ? text : '')
if (requiresWarning) { if (requiresWarning) {
e?.preventDefault?.() e?.preventDefault?.()
store.shell.openModal({ openModal({
name: 'link-warning', name: 'link-warning',
text: typeof text === 'string' ? text : '', text: typeof text === 'string' ? text : '',
href, href,
@ -185,9 +185,17 @@ export const TextLink = memo(function TextLink({
// @ts-ignore function signature differs by platform -prf // @ts-ignore function signature differs by platform -prf
return onPress() return onPress()
} }
return onPressInner(store, navigation, sanitizeUrl(href), e) return onPressInner(closeModal, navigation, sanitizeUrl(href), e)
}, },
[onPress, store, navigation, href, text, warnOnMismatchingLabel], [
onPress,
closeModal,
openModal,
navigation,
href,
text,
warnOnMismatchingLabel,
],
) )
const hrefAttrs = useMemo(() => { const hrefAttrs = useMemo(() => {
const isExternal = isExternalUrl(href) const isExternal = isExternalUrl(href)
@ -285,7 +293,7 @@ export const TextLinkOnWebOnly = memo(function DesktopWebTextLink({
// needed customizations // needed customizations
// -prf // -prf
function onPressInner( function onPressInner(
store: RootStoreModel, closeModal = () => {},
navigation: NavigationProp, navigation: NavigationProp,
href: string, href: string,
e?: Event, e?: Event,
@ -318,7 +326,7 @@ function onPressInner(
if (newTab || href.startsWith('http') || href.startsWith('mailto')) { if (newTab || href.startsWith('http') || href.startsWith('mailto')) {
Linking.openURL(href) Linking.openURL(href)
} else { } else {
store.shell.closeModal() // close any active modals closeModal() // close any active modals
// @ts-ignore we're not able to type check on this one -prf // @ts-ignore we're not able to type check on this one -prf
navigation.dispatch(StackActions.push(...router.matchPath(href))) navigation.dispatch(StackActions.push(...router.matchPath(href)))

View File

@ -1,9 +1,9 @@
import React from 'react' import React from 'react'
import {Pressable, StyleProp, ViewStyle} from 'react-native' import {Pressable, StyleProp, ViewStyle} from 'react-native'
import {useStores} from 'state/index'
import {Link} from './Link' import {Link} from './Link'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {useModalControls} from '#/state/modals'
interface UserPreviewLinkProps { interface UserPreviewLinkProps {
did: string did: string
@ -13,7 +13,7 @@ interface UserPreviewLinkProps {
export function UserPreviewLink( export function UserPreviewLink(
props: React.PropsWithChildren<UserPreviewLinkProps>, props: React.PropsWithChildren<UserPreviewLinkProps>,
) { ) {
const store = useStores() const {openModal} = useModalControls()
if (isWeb) { if (isWeb) {
return ( return (
@ -29,7 +29,7 @@ export function UserPreviewLink(
return ( return (
<Pressable <Pressable
onPress={() => onPress={() =>
store.shell.openModal({ openModal({
name: 'profile-preview', name: 'profile-preview',
did: props.did, did: props.did,
}) })

View File

@ -2,7 +2,6 @@ import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native' import {StyleProp, View, ViewStyle} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {useStores} from 'state/index'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
import { import {
@ -10,6 +9,7 @@ import {
DropdownItem as NativeDropdownItem, DropdownItem as NativeDropdownItem,
} from './NativeDropdown' } from './NativeDropdown'
import {EventStopper} from '../EventStopper' import {EventStopper} from '../EventStopper'
import {useModalControls} from '#/state/modals'
export function PostDropdownBtn({ export function PostDropdownBtn({
testID, testID,
@ -37,9 +37,9 @@ export function PostDropdownBtn({
onDeletePost: () => void onDeletePost: () => void
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const store = useStores()
const theme = useTheme() const theme = useTheme()
const defaultCtrlColor = theme.palette.default.postCtrl const defaultCtrlColor = theme.palette.default.postCtrl
const {openModal} = useModalControls()
const dropdownItems: NativeDropdownItem[] = [ const dropdownItems: NativeDropdownItem[] = [
{ {
@ -108,7 +108,7 @@ export function PostDropdownBtn({
!isAuthor && { !isAuthor && {
label: 'Report post', label: 'Report post',
onPress() { onPress() {
store.shell.openModal({ openModal({
name: 'report', name: 'report',
uri: itemUri, uri: itemUri,
cid: itemCid, cid: itemCid,
@ -129,7 +129,7 @@ export function PostDropdownBtn({
isAuthor && { isAuthor && {
label: 'Delete post', label: 'Delete post',
onPress() { onPress() {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Delete this post?', title: 'Delete this post?',
message: 'Are you sure? This can not be undone.', message: 'Are you sure? This can not be undone.',

View File

@ -6,7 +6,7 @@ import {ModerationUI} from '@atproto/api'
import {Text} from '../text/Text' import {Text} from '../text/Text'
import {ShieldExclamation} from 'lib/icons' import {ShieldExclamation} from 'lib/icons'
import {describeModerationCause} from 'lib/moderation' import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index' import {useModalControls} from '#/state/modals'
export function ContentHider({ export function ContentHider({
testID, testID,
@ -22,10 +22,10 @@ export function ContentHider({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
childContainerStyle?: StyleProp<ViewStyle> childContainerStyle?: StyleProp<ViewStyle>
}>) { }>) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [override, setOverride] = React.useState(false) const [override, setOverride] = React.useState(false)
const {openModal} = useModalControls()
if (!moderation.blur || (ignoreMute && moderation.cause?.type === 'muted')) { if (!moderation.blur || (ignoreMute && moderation.cause?.type === 'muted')) {
return ( return (
@ -43,7 +43,7 @@ export function ContentHider({
if (!moderation.noOverride) { if (!moderation.noOverride) {
setOverride(v => !v) setOverride(v => !v)
} else { } else {
store.shell.openModal({ openModal({
name: 'moderation-details', name: 'moderation-details',
context: 'content', context: 'content',
moderation, moderation,
@ -62,7 +62,7 @@ export function ContentHider({
]}> ]}>
<Pressable <Pressable
onPress={() => { onPress={() => {
store.shell.openModal({ openModal({
name: 'moderation-details', name: 'moderation-details',
context: 'content', context: 'content',
moderation, moderation,

View File

@ -5,7 +5,7 @@ import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {ShieldExclamation} from 'lib/icons' import {ShieldExclamation} from 'lib/icons'
import {describeModerationCause} from 'lib/moderation' import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index' import {useModalControls} from '#/state/modals'
export function PostAlerts({ export function PostAlerts({
moderation, moderation,
@ -15,8 +15,8 @@ export function PostAlerts({
includeMute?: boolean includeMute?: boolean
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {openModal} = useModalControls()
const shouldAlert = !!moderation.cause && moderation.alert const shouldAlert = !!moderation.cause && moderation.alert
if (!shouldAlert) { if (!shouldAlert) {
@ -27,7 +27,7 @@ export function PostAlerts({
return ( return (
<Pressable <Pressable
onPress={() => { onPress={() => {
store.shell.openModal({ openModal({
name: 'moderation-details', name: 'moderation-details',
context: 'content', context: 'content',
moderation, moderation,

View File

@ -8,7 +8,7 @@ import {Text} from '../text/Text'
import {addStyle} from 'lib/styles' import {addStyle} from 'lib/styles'
import {describeModerationCause} from 'lib/moderation' import {describeModerationCause} from 'lib/moderation'
import {ShieldExclamation} from 'lib/icons' import {ShieldExclamation} from 'lib/icons'
import {useStores} from 'state/index' import {useModalControls} from '#/state/modals'
interface Props extends ComponentProps<typeof Link> { interface Props extends ComponentProps<typeof Link> {
// testID?: string // testID?: string
@ -25,10 +25,10 @@ export function PostHider({
children, children,
...props ...props
}: Props) { }: Props) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const [override, setOverride] = React.useState(false) const [override, setOverride] = React.useState(false)
const {openModal} = useModalControls()
if (!moderation.blur) { if (!moderation.blur) {
return ( return (
@ -63,7 +63,7 @@ export function PostHider({
]}> ]}>
<Pressable <Pressable
onPress={() => { onPress={() => {
store.shell.openModal({ openModal({
name: 'moderation-details', name: 'moderation-details',
context: 'content', context: 'content',
moderation, moderation,

View File

@ -8,7 +8,7 @@ import {
describeModerationCause, describeModerationCause,
getProfileModerationCauses, getProfileModerationCauses,
} from 'lib/moderation' } from 'lib/moderation'
import {useStores} from 'state/index' import {useModalControls} from '#/state/modals'
export function ProfileHeaderAlerts({ export function ProfileHeaderAlerts({
moderation, moderation,
@ -17,8 +17,8 @@ export function ProfileHeaderAlerts({
moderation: ProfileModeration moderation: ProfileModeration
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {openModal} = useModalControls()
const causes = getProfileModerationCauses(moderation) const causes = getProfileModerationCauses(moderation)
if (!causes.length) { if (!causes.length) {
@ -34,7 +34,7 @@ export function ProfileHeaderAlerts({
testID="profileHeaderAlert" testID="profileHeaderAlert"
key={desc.name} key={desc.name}
onPress={() => { onPress={() => {
store.shell.openModal({ openModal({
name: 'moderation-details', name: 'moderation-details',
context: 'content', context: 'content',
moderation: {cause}, moderation: {cause},

View File

@ -18,7 +18,7 @@ import {NavigationProp} from 'lib/routes/types'
import {Text} from '../text/Text' import {Text} from '../text/Text'
import {Button} from '../forms/Button' import {Button} from '../forms/Button'
import {describeModerationCause} from 'lib/moderation' import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index' import {useModalControls} from '#/state/modals'
export function ScreenHider({ export function ScreenHider({
testID, testID,
@ -34,12 +34,12 @@ export function ScreenHider({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
containerStyle?: StyleProp<ViewStyle> containerStyle?: StyleProp<ViewStyle>
}>) { }>) {
const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const [override, setOverride] = React.useState(false) const [override, setOverride] = React.useState(false)
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls()
if (!moderation.blur || override) { if (!moderation.blur || override) {
return ( return (
@ -72,7 +72,7 @@ export function ScreenHider({
.{' '} .{' '}
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={() => { onPress={() => {
store.shell.openModal({ openModal({
name: 'moderation-details', name: 'moderation-details',
context: 'account', context: 'account',
moderation, moderation,

View File

@ -16,6 +16,7 @@ import {useStores} from 'state/index'
import {RepostButton} from './RepostButton' import {RepostButton} from './RepostButton'
import {Haptics} from 'lib/haptics' import {Haptics} from 'lib/haptics'
import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
import {useModalControls} from '#/state/modals'
interface PostCtrlsOpts { interface PostCtrlsOpts {
itemUri: string itemUri: string
@ -51,6 +52,7 @@ interface PostCtrlsOpts {
export function PostCtrls(opts: PostCtrlsOpts) { export function PostCtrls(opts: PostCtrlsOpts) {
const store = useStores() const store = useStores()
const theme = useTheme() const theme = useTheme()
const {closeModal} = useModalControls()
const defaultCtrlColor = React.useMemo( const defaultCtrlColor = React.useMemo(
() => ({ () => ({
color: theme.palette.default.postCtrl, color: theme.palette.default.postCtrl,
@ -58,17 +60,17 @@ export function PostCtrls(opts: PostCtrlsOpts) {
[theme], [theme],
) as StyleProp<ViewStyle> ) as StyleProp<ViewStyle>
const onRepost = useCallback(() => { const onRepost = useCallback(() => {
store.shell.closeModal() closeModal()
if (!opts.isReposted) { if (!opts.isReposted) {
Haptics.default() Haptics.default()
opts.onPressToggleRepost().catch(_e => undefined) opts.onPressToggleRepost().catch(_e => undefined)
} else { } else {
opts.onPressToggleRepost().catch(_e => undefined) opts.onPressToggleRepost().catch(_e => undefined)
} }
}, [opts, store.shell]) }, [opts, closeModal])
const onQuote = useCallback(() => { const onQuote = useCallback(() => {
store.shell.closeModal() closeModal()
store.shell.openComposer({ store.shell.openComposer({
quote: { quote: {
uri: opts.itemUri, uri: opts.itemUri,
@ -86,6 +88,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
opts.itemUri, opts.itemUri,
opts.text, opts.text,
store.shell, store.shell,
closeModal,
]) ])
const onPressToggleLikeWrapper = async () => { const onPressToggleLikeWrapper = async () => {

View File

@ -5,8 +5,8 @@ import {s, colors} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {Text} from '../text/Text' import {Text} from '../text/Text'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {useStores} from 'state/index'
import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
import {useModalControls} from '#/state/modals'
interface Props { interface Props {
isReposted: boolean isReposted: boolean
@ -23,8 +23,8 @@ export const RepostButton = ({
onRepost, onRepost,
onQuote, onQuote,
}: Props) => { }: Props) => {
const store = useStores()
const theme = useTheme() const theme = useTheme()
const {openModal} = useModalControls()
const defaultControlColor = React.useMemo( const defaultControlColor = React.useMemo(
() => ({ () => ({
@ -34,13 +34,13 @@ export const RepostButton = ({
) )
const onPressToggleRepostWrapper = useCallback(() => { const onPressToggleRepostWrapper = useCallback(() => {
store.shell.openModal({ openModal({
name: 'repost', name: 'repost',
onRepost: onRepost, onRepost: onRepost,
onQuote: onQuote, onQuote: onQuote,
isReposted, isReposted,
}) })
}, [onRepost, onQuote, isReposted, store.shell]) }, [onRepost, onQuote, isReposted, openModal])
return ( return (
<TouchableOpacity <TouchableOpacity

View File

@ -17,6 +17,7 @@ import {useFocusEffect} from '@react-navigation/native'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
@ -27,6 +28,7 @@ export const AppPasswords = withAuthRequired(
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {screen} = useAnalytics() const {screen} = useAnalytics()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
const {openModal} = useModalControls()
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
@ -36,8 +38,8 @@ export const AppPasswords = withAuthRequired(
) )
const onAdd = React.useCallback(async () => { const onAdd = React.useCallback(async () => {
store.shell.openModal({name: 'add-app-password'}) openModal({name: 'add-app-password'})
}, [store]) }, [openModal])
// no app passwords (empty) state // no app passwords (empty) state
if (store.me.appPasswords.length === 0) { if (store.me.appPasswords.length === 0) {
@ -162,10 +164,11 @@ function AppPassword({
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {openModal} = useModalControls()
const {contentLanguages} = useLanguagePrefs() const {contentLanguages} = useLanguagePrefs()
const onDelete = React.useCallback(async () => { const onDelete = React.useCallback(async () => {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Delete App Password', title: 'Delete App Password',
message: `Are you sure you want to delete the app password "${name}"?`, message: `Are you sure you want to delete the app password "${name}"?`,
@ -174,7 +177,7 @@ function AppPassword({
Toast.show('App password deleted') Toast.show('App password deleted')
}, },
}) })
}, [store, name]) }, [store, openModal, name])
const primaryLocale = const primaryLocale =
contentLanguages.length > 0 ? contentLanguages[0] : 'en-US' contentLanguages.length > 0 ? contentLanguages[0] : 'en-US'

View File

@ -2,7 +2,6 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {Text} from '../com/util/text/Text' import {Text} from '../com/util/text/Text'
import {useStores} from 'state/index'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -19,6 +18,7 @@ import {useFocusEffect} from '@react-navigation/native'
import {LANGUAGES} from 'lib/../locale/languages' import {LANGUAGES} from 'lib/../locale/languages'
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {useLanguagePrefs, useSetLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs, useSetLanguagePrefs} from '#/state/preferences'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
@ -27,12 +27,12 @@ export const LanguageSettingsScreen = observer(function LanguageSettingsImpl(
_: Props, _: Props,
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const setLangPrefs = useSetLanguagePrefs() const setLangPrefs = useSetLanguagePrefs()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {openModal} = useModalControls()
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
@ -43,8 +43,8 @@ export const LanguageSettingsScreen = observer(function LanguageSettingsImpl(
const onPressContentLanguages = React.useCallback(() => { const onPressContentLanguages = React.useCallback(() => {
track('Settings:ContentlanguagesButtonClicked') track('Settings:ContentlanguagesButtonClicked')
store.shell.openModal({name: 'content-languages-settings'}) openModal({name: 'content-languages-settings'})
}, [track, store]) }, [track, openModal])
const onChangePrimaryLanguage = React.useCallback( const onChangePrimaryLanguage = React.useCallback(
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => { (value: Parameters<PickerSelectProps['onValueChange']>[0]) => {

View File

@ -17,6 +17,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
export const ListsScreen = withAuthRequired( export const ListsScreen = withAuthRequired(
@ -26,6 +27,7 @@ export const ListsScreen = withAuthRequired(
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
const listsLists: ListsListModel = React.useMemo( const listsLists: ListsListModel = React.useMemo(
() => new ListsListModel(store, 'my-curatelists'), () => new ListsListModel(store, 'my-curatelists'),
@ -40,7 +42,7 @@ export const ListsScreen = withAuthRequired(
) )
const onPressNewList = React.useCallback(() => { const onPressNewList = React.useCallback(() => {
store.shell.openModal({ openModal({
name: 'create-or-edit-list', name: 'create-or-edit-list',
purpose: 'app.bsky.graph.defs#curatelist', purpose: 'app.bsky.graph.defs#curatelist',
onSave: (uri: string) => { onSave: (uri: string) => {
@ -53,7 +55,7 @@ export const ListsScreen = withAuthRequired(
} catch {} } catch {}
}, },
}) })
}, [store, navigation]) }, [openModal, navigation])
return ( return (
<View style={s.hContentRegion} testID="listsScreen"> <View style={s.hContentRegion} testID="listsScreen">

View File

@ -8,7 +8,6 @@ import {
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {withAuthRequired} from 'view/com/auth/withAuthRequired' import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {useStores} from 'state/index'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {CenteredView} from '../com/util/Views' import {CenteredView} from '../com/util/Views'
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
@ -18,15 +17,16 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
export const ModerationScreen = withAuthRequired( export const ModerationScreen = withAuthRequired(
observer(function Moderation({}: Props) { observer(function Moderation({}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
const {openModal} = useModalControls()
useFocusEffect( useFocusEffect(
React.useCallback(() => { React.useCallback(() => {
@ -37,8 +37,8 @@ export const ModerationScreen = withAuthRequired(
const onPressContentFiltering = React.useCallback(() => { const onPressContentFiltering = React.useCallback(() => {
track('Moderation:ContentfilteringButtonClicked') track('Moderation:ContentfilteringButtonClicked')
store.shell.openModal({name: 'content-filtering-settings'}) openModal({name: 'content-filtering-settings'})
}, [track, store]) }, [track, openModal])
return ( return (
<CenteredView <CenteredView

View File

@ -17,6 +17,7 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
export const ModerationModlistsScreen = withAuthRequired( export const ModerationModlistsScreen = withAuthRequired(
@ -26,6 +27,7 @@ export const ModerationModlistsScreen = withAuthRequired(
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
const mutelists: ListsListModel = React.useMemo( const mutelists: ListsListModel = React.useMemo(
() => new ListsListModel(store, 'my-modlists'), () => new ListsListModel(store, 'my-modlists'),
@ -40,7 +42,7 @@ export const ModerationModlistsScreen = withAuthRequired(
) )
const onPressNewList = React.useCallback(() => { const onPressNewList = React.useCallback(() => {
store.shell.openModal({ openModal({
name: 'create-or-edit-list', name: 'create-or-edit-list',
purpose: 'app.bsky.graph.defs#modlist', purpose: 'app.bsky.graph.defs#modlist',
onSave: (uri: string) => { onSave: (uri: string) => {
@ -53,7 +55,7 @@ export const ModerationModlistsScreen = withAuthRequired(
} catch {} } catch {}
}, },
}) })
}, [store, navigation]) }, [openModal, navigation])
return ( return (
<View style={s.hContentRegion} testID="moderationModlistsScreen"> <View style={s.hContentRegion} testID="moderationModlistsScreen">

View File

@ -47,6 +47,7 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {ComposeIcon2} from 'lib/icons' import {ComposeIcon2} from 'lib/icons'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
const SECTION_TITLES = ['Posts', 'About'] const SECTION_TITLES = ['Posts', 'About']
@ -137,6 +138,7 @@ export const ProfileFeedScreenInner = observer(
route, route,
feedOwnerDid, feedOwnerDid,
}: Props & {feedOwnerDid: string}) { }: Props & {feedOwnerDid: string}) {
const {openModal} = useModalControls()
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const {track} = useAnalytics() const {track} = useAnalytics()
@ -210,12 +212,12 @@ export const ProfileFeedScreenInner = observer(
const onPressReport = React.useCallback(() => { const onPressReport = React.useCallback(() => {
if (!feedInfo) return if (!feedInfo) return
store.shell.openModal({ openModal({
name: 'report', name: 'report',
uri: feedInfo.uri, uri: feedInfo.uri,
cid: feedInfo.cid, cid: feedInfo.cid,
}) })
}, [store, feedInfo]) }, [openModal, feedInfo])
const onCurrentPageSelected = React.useCallback( const onCurrentPageSelected = React.useCallback(
(index: number) => { (index: number) => {

View File

@ -46,6 +46,7 @@ import {ComposeIcon2} from 'lib/icons'
import {ListItems} from 'view/com/lists/ListItems' import {ListItems} from 'view/com/lists/ListItems'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
const SECTION_TITLES_CURATE = ['Posts', 'About'] const SECTION_TITLES_CURATE = ['Posts', 'About']
const SECTION_TITLES_MOD = ['About'] const SECTION_TITLES_MOD = ['About']
@ -110,6 +111,7 @@ export const ProfileListScreenInner = observer(
const {rkey} = route.params const {rkey} = route.params
const feedSectionRef = React.useRef<SectionRef>(null) const feedSectionRef = React.useRef<SectionRef>(null)
const aboutSectionRef = React.useRef<SectionRef>(null) const aboutSectionRef = React.useRef<SectionRef>(null)
const {openModal} = useModalControls()
const list: ListModel = useMemo(() => { const list: ListModel = useMemo(() => {
const model = new ListModel( const model = new ListModel(
@ -136,7 +138,7 @@ export const ProfileListScreenInner = observer(
) )
const onPressAddUser = useCallback(() => { const onPressAddUser = useCallback(() => {
store.shell.openModal({ openModal({
name: 'list-add-user', name: 'list-add-user',
list, list,
onAdd() { onAdd() {
@ -145,7 +147,7 @@ export const ProfileListScreenInner = observer(
} }
}, },
}) })
}, [store, list, feed]) }, [openModal, list, feed])
const onCurrentPageSelected = React.useCallback( const onCurrentPageSelected = React.useCallback(
(index: number) => { (index: number) => {
@ -268,8 +270,8 @@ const Header = observer(function HeaderImpl({
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const store = useStores()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {openModal, closeModal} = useModalControls()
const onTogglePinned = useCallback(async () => { const onTogglePinned = useCallback(async () => {
Haptics.default() Haptics.default()
@ -280,7 +282,7 @@ const Header = observer(function HeaderImpl({
}, [list]) }, [list])
const onSubscribeMute = useCallback(() => { const onSubscribeMute = useCallback(() => {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Mute these accounts?', title: 'Mute these accounts?',
message: message:
@ -297,10 +299,10 @@ const Header = observer(function HeaderImpl({
} }
}, },
onPressCancel() { onPressCancel() {
store.shell.closeModal() closeModal()
}, },
}) })
}, [store, list]) }, [openModal, closeModal, list])
const onUnsubscribeMute = useCallback(async () => { const onUnsubscribeMute = useCallback(async () => {
try { try {
@ -314,7 +316,7 @@ const Header = observer(function HeaderImpl({
}, [list]) }, [list])
const onSubscribeBlock = useCallback(() => { const onSubscribeBlock = useCallback(() => {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Block these accounts?', title: 'Block these accounts?',
message: message:
@ -331,10 +333,10 @@ const Header = observer(function HeaderImpl({
} }
}, },
onPressCancel() { onPressCancel() {
store.shell.closeModal() closeModal()
}, },
}) })
}, [store, list]) }, [openModal, closeModal, list])
const onUnsubscribeBlock = useCallback(async () => { const onUnsubscribeBlock = useCallback(async () => {
try { try {
@ -348,17 +350,17 @@ const Header = observer(function HeaderImpl({
}, [list]) }, [list])
const onPressEdit = useCallback(() => { const onPressEdit = useCallback(() => {
store.shell.openModal({ openModal({
name: 'create-or-edit-list', name: 'create-or-edit-list',
list, list,
onSave() { onSave() {
list.refresh() list.refresh()
}, },
}) })
}, [store, list]) }, [openModal, list])
const onPressDelete = useCallback(() => { const onPressDelete = useCallback(() => {
store.shell.openModal({ openModal({
name: 'confirm', name: 'confirm',
title: 'Delete List', title: 'Delete List',
message: 'Are you sure?', message: 'Are you sure?',
@ -372,16 +374,16 @@ const Header = observer(function HeaderImpl({
} }
}, },
}) })
}, [store, list, navigation]) }, [openModal, list, navigation])
const onPressReport = useCallback(() => { const onPressReport = useCallback(() => {
if (!list.data) return if (!list.data) return
store.shell.openModal({ openModal({
name: 'report', name: 'report',
uri: list.uri, uri: list.uri,
cid: list.data.cid, cid: list.data.cid,
}) })
}, [store, list]) }, [openModal, list])
const onPressShare = useCallback(() => { const onPressShare = useCallback(() => {
const url = toShareUrl(`/profile/${list.creatorDid}/lists/${rkey}`) const url = toShareUrl(`/profile/${list.creatorDid}/lists/${rkey}`)

View File

@ -46,6 +46,7 @@ import Clipboard from '@react-native-clipboard/clipboard'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
import { import {
useSetMinimalShellMode, useSetMinimalShellMode,
useColorMode, useColorMode,
@ -82,6 +83,7 @@ export const SettingsScreen = withAuthRequired(
const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting( const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting(
store.agent, store.agent,
) )
const {openModal} = useModalControls()
const primaryBg = useCustomPalette<ViewStyle>({ const primaryBg = useCustomPalette<ViewStyle>({
light: {backgroundColor: colors.blue0}, light: {backgroundColor: colors.blue0},
@ -117,7 +119,7 @@ export const SettingsScreen = withAuthRequired(
const onPressChangeHandle = React.useCallback(() => { const onPressChangeHandle = React.useCallback(() => {
track('Settings:ChangeHandleButtonClicked') track('Settings:ChangeHandleButtonClicked')
store.shell.openModal({ openModal({
name: 'change-handle', name: 'change-handle',
onChanged() { onChanged() {
setIsSwitching(true) setIsSwitching(true)
@ -135,12 +137,12 @@ export const SettingsScreen = withAuthRequired(
) )
}, },
}) })
}, [track, store, setIsSwitching]) }, [track, store, openModal, setIsSwitching])
const onPressInviteCodes = React.useCallback(() => { const onPressInviteCodes = React.useCallback(() => {
track('Settings:InvitecodesButtonClicked') track('Settings:InvitecodesButtonClicked')
store.shell.openModal({name: 'invite-codes'}) openModal({name: 'invite-codes'})
}, [track, store]) }, [track, openModal])
const onPressLanguageSettings = React.useCallback(() => { const onPressLanguageSettings = React.useCallback(() => {
navigation.navigate('LanguageSettings') navigation.navigate('LanguageSettings')
@ -152,8 +154,8 @@ export const SettingsScreen = withAuthRequired(
}, [track, store]) }, [track, store])
const onPressDeleteAccount = React.useCallback(() => { const onPressDeleteAccount = React.useCallback(() => {
store.shell.openModal({name: 'delete-account'}) openModal({name: 'delete-account'})
}, [store]) }, [openModal])
const onPressResetPreferences = React.useCallback(async () => { const onPressResetPreferences = React.useCallback(async () => {
await store.preferences.reset() await store.preferences.reset()
@ -229,8 +231,7 @@ export const SettingsScreen = withAuthRequired(
<Text type="lg" style={pal.text}> <Text type="lg" style={pal.text}>
{store.session.currentSession?.email}{' '} {store.session.currentSession?.email}{' '}
</Text> </Text>
<Link <Link onPress={() => openModal({name: 'change-email'})}>
onPress={() => store.shell.openModal({name: 'change-email'})}>
<Text type="lg" style={pal.link}> <Text type="lg" style={pal.link}>
Change Change
</Text> </Text>
@ -240,10 +241,7 @@ export const SettingsScreen = withAuthRequired(
<Text type="lg-medium" style={pal.text}> <Text type="lg-medium" style={pal.text}>
Birthday:{' '} Birthday:{' '}
</Text> </Text>
<Link <Link onPress={() => openModal({name: 'birth-date-settings'})}>
onPress={() =>
store.shell.openModal({name: 'birth-date-settings'})
}>
<Text type="lg" style={pal.link}> <Text type="lg" style={pal.link}>
Show Show
</Text> </Text>
@ -649,6 +647,7 @@ const EmailConfirmationNotice = observer(
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const store = useStores() const store = useStores()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls()
if (!store.session.emailNeedsConfirmation) { if (!store.session.emailNeedsConfirmation) {
return null return null
@ -684,7 +683,7 @@ const EmailConfirmationNotice = observer(
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel="Verify my email" accessibilityLabel="Verify my email"
accessibilityHint="" accessibilityHint=""
onPress={() => store.shell.openModal({name: 'verify-email'})}> onPress={() => openModal({name: 'verify-email'})}>
<FontAwesomeIcon <FontAwesomeIcon
icon="envelope" icon="envelope"
color={palInverted.colors.text} color={palInverted.colors.text}

View File

@ -44,6 +44,7 @@ import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format' import {formatCount, formatCountShortOnly} from 'view/com/util/numeric/format'
import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerOpen} from '#/state/shell'
import {useModalControls} from '#/state/modals'
export const DrawerContent = observer(function DrawerContentImpl() { export const DrawerContent = observer(function DrawerContentImpl() {
const theme = useTheme() const theme = useTheme()
@ -442,11 +443,12 @@ const InviteCodes = observer(function InviteCodesImpl({
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const pal = usePalette('default') const pal = usePalette('default')
const {invitesAvailable} = store.me const {invitesAvailable} = store.me
const {openModal} = useModalControls()
const onPress = React.useCallback(() => { const onPress = React.useCallback(() => {
track('Menu:ItemClicked', {url: '#invite-codes'}) track('Menu:ItemClicked', {url: '#invite-codes'})
setDrawerOpen(false) setDrawerOpen(false)
store.shell.openModal({name: 'invite-codes'}) openModal({name: 'invite-codes'})
}, [store, track, setDrawerOpen]) }, [openModal, track, setDrawerOpen])
return ( return (
<TouchableOpacity <TouchableOpacity
testID="menuItemInviteCodes" testID="menuItemInviteCodes"

View File

@ -24,12 +24,14 @@ import {styles} from './BottomBarStyles'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useNavigationTabState} from 'lib/hooks/useNavigationTabState' import {useNavigationTabState} from 'lib/hooks/useNavigationTabState'
import {UserAvatar} from 'view/com/util/UserAvatar' import {UserAvatar} from 'view/com/util/UserAvatar'
import {useModalControls} from '#/state/modals'
type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds' type TabOptions = 'Home' | 'Search' | 'Notifications' | 'MyProfile' | 'Feeds'
export const BottomBar = observer(function BottomBarImpl({ export const BottomBar = observer(function BottomBarImpl({
navigation, navigation,
}: BottomTabBarProps) { }: BottomTabBarProps) {
const {openModal} = useModalControls()
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const safeAreaInsets = useSafeAreaInsets() const safeAreaInsets = useSafeAreaInsets()
@ -72,8 +74,8 @@ export const BottomBar = observer(function BottomBarImpl({
onPressTab('MyProfile') onPressTab('MyProfile')
}, [onPressTab]) }, [onPressTab])
const onLongPressProfile = React.useCallback(() => { const onLongPressProfile = React.useCallback(() => {
store.shell.openModal({name: 'switch-account'}) openModal({name: 'switch-account'})
}, [store]) }, [openModal])
return ( return (
<Animated.View <Animated.View

View File

@ -13,6 +13,7 @@ import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {formatCount} from 'view/com/util/numeric/format' import {formatCount} from 'view/com/util/numeric/format'
import {useModalControls} from '#/state/modals'
export const DesktopRightNav = observer(function DesktopRightNavImpl() { export const DesktopRightNav = observer(function DesktopRightNavImpl() {
const store = useStores() const store = useStores()
@ -83,12 +84,13 @@ export const DesktopRightNav = observer(function DesktopRightNavImpl() {
const InviteCodes = observer(function InviteCodesImpl() { const InviteCodes = observer(function InviteCodesImpl() {
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const {openModal} = useModalControls()
const {invitesAvailable} = store.me const {invitesAvailable} = store.me
const onPress = React.useCallback(() => { const onPress = React.useCallback(() => {
store.shell.openModal({name: 'invite-codes'}) openModal({name: 'invite-codes'})
}, [store]) }, [openModal])
return ( return (
<TouchableOpacity <TouchableOpacity
style={[styles.inviteCodes, pal.border]} style={[styles.inviteCodes, pal.border]}

View File

@ -32,12 +32,14 @@ import {
useIsDrawerSwipeDisabled, useIsDrawerSwipeDisabled,
} from '#/state/shell' } from '#/state/shell'
import {isAndroid} from 'platform/detection' import {isAndroid} from 'platform/detection'
import {useModalControls} from '#/state/modals'
const ShellInner = observer(function ShellInnerImpl() { const ShellInner = observer(function ShellInnerImpl() {
const store = useStores() const store = useStores()
const isDrawerOpen = useIsDrawerOpen() const isDrawerOpen = useIsDrawerOpen()
const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled() const isDrawerSwipeDisabled = useIsDrawerSwipeDisabled()
const setIsDrawerOpen = useSetDrawerOpen() const setIsDrawerOpen = useSetDrawerOpen()
const {closeModal} = useModalControls()
useOTAUpdate() // this hook polls for OTA updates every few seconds useOTAUpdate() // this hook polls for OTA updates every few seconds
const winDim = useWindowDimensions() const winDim = useWindowDimensions()
const safeAreaInsets = useSafeAreaInsets() const safeAreaInsets = useSafeAreaInsets()
@ -60,13 +62,14 @@ const ShellInner = observer(function ShellInnerImpl() {
if (isAndroid) { if (isAndroid) {
listener = BackHandler.addEventListener('hardwareBackPress', () => { listener = BackHandler.addEventListener('hardwareBackPress', () => {
setIsDrawerOpen(false) setIsDrawerOpen(false)
closeModal()
return store.shell.closeAnyActiveElement() return store.shell.closeAnyActiveElement()
}) })
} }
return () => { return () => {
listener.remove() listener.remove()
} }
}, [store, setIsDrawerOpen]) }, [store, setIsDrawerOpen, closeModal])
return ( return (
<> <>

View File

@ -22,11 +22,13 @@ import {
useSetDrawerOpen, useSetDrawerOpen,
useOnboardingState, useOnboardingState,
} from '#/state/shell' } from '#/state/shell'
import {useModalControls} from '#/state/modals'
const ShellInner = observer(function ShellInnerImpl() { const ShellInner = observer(function ShellInnerImpl() {
const store = useStores() const store = useStores()
const isDrawerOpen = useIsDrawerOpen() const isDrawerOpen = useIsDrawerOpen()
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const {closeModal} = useModalControls()
const onboardingState = useOnboardingState() const onboardingState = useOnboardingState()
const {isDesktop, isMobile} = useWebMediaQueries() const {isDesktop, isMobile} = useWebMediaQueries()
const navigator = useNavigation<NavigationProp>() const navigator = useNavigation<NavigationProp>()
@ -35,9 +37,10 @@ const ShellInner = observer(function ShellInnerImpl() {
useEffect(() => { useEffect(() => {
navigator.addListener('state', () => { navigator.addListener('state', () => {
setDrawerOpen(false) setDrawerOpen(false)
closeModal()
store.shell.closeAnyActiveElement() store.shell.closeAnyActiveElement()
}) })
}, [navigator, store.shell, setDrawerOpen]) }, [navigator, store.shell, setDrawerOpen, closeModal])
const showBottomBar = isMobile && !onboardingState.isActive const showBottomBar = isMobile && !onboardingState.isActive
const showSideNavs = const showSideNavs =