Rework modals to support multiple active

zio/stable
Paul Frazee 2023-02-23 17:22:03 -06:00
parent b4f2a6979a
commit 154c34e915
13 changed files with 147 additions and 187 deletions

View File

@ -1,6 +1,5 @@
/// <reference lib="dom" /> /// <reference lib="dom" />
import {CropImageModal} from 'state/models/shell-ui'
import {PickerOpts, CameraOpts, CropperOpts, PickedMedia} from './types' import {PickerOpts, CameraOpts, CropperOpts, PickedMedia} from './types'
export type {PickedMedia} from './types' export type {PickedMedia} from './types'
import {RootStoreModel} from 'state/index' import {RootStoreModel} from 'state/index'
@ -51,15 +50,17 @@ export async function openCropper(
): Promise<PickedMedia> { ): Promise<PickedMedia> {
// TODO handle more opts // TODO handle more opts
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
store.shell.openModal( store.shell.openModal({
new CropImageModal(opts.path, (img?: PickedMedia) => { name: 'crop-image',
uri: opts.path,
onSelect: (img?: PickedMedia) => {
if (img) { if (img) {
resolve(img) resolve(img)
} else { } else {
reject(new Error('Canceled')) reject(new Error('Canceled'))
} }
}), },
) })
}) })
} }

View File

@ -2,75 +2,57 @@ import {RootStoreModel} from './root-store'
import {makeAutoObservable} from 'mobx' import {makeAutoObservable} from 'mobx'
import {ProfileViewModel} from './profile-view' import {ProfileViewModel} from './profile-view'
import {isObj, hasProp} from 'lib/type-guards' import {isObj, hasProp} from 'lib/type-guards'
import {PickedMedia} from 'view/com/util/images/image-crop-picker/types' import {PickedMedia} from 'lib/media/types'
export class ConfirmModal { export interface ConfirmModal {
name = 'confirm' name: 'confirm'
title: string
constructor( message: string | (() => JSX.Element)
public title: string, onPressConfirm: () => void | Promise<void>
public message: string | (() => JSX.Element),
public onPressConfirm: () => void | Promise<void>,
) {
makeAutoObservable(this)
}
} }
export class EditProfileModal { export interface EditProfileModal {
name = 'edit-profile' name: 'edit-profile'
profileView: ProfileViewModel
constructor( onUpdate?: () => void
public profileView: ProfileViewModel,
public onUpdate?: () => void,
) {
makeAutoObservable(this)
}
} }
export class ServerInputModal { export interface ServerInputModal {
name = 'server-input' name: 'server-input'
initialService: string
constructor( onSelect: (url: string) => void
public initialService: string,
public onSelect: (url: string) => void,
) {
makeAutoObservable(this)
}
} }
export class ReportPostModal { export interface ReportPostModal {
name = 'report-post' name: 'report-post'
postUri: string
constructor(public postUri: string, public postCid: string) { postCid: string
makeAutoObservable(this)
}
} }
export class ReportAccountModal { export interface ReportAccountModal {
name = 'report-account' name: 'report-account'
did: string
constructor(public did: string) {
makeAutoObservable(this)
}
} }
export class CropImageModal { export interface CropImageModal {
name = 'crop-image' name: 'crop-image'
uri: string
constructor( onSelect: (img?: PickedMedia) => void
public uri: string,
public onSelect: (img?: PickedMedia) => void,
) {}
} }
export class DeleteAccountModal { export interface DeleteAccountModal {
name = 'delete-account' name: 'delete-account'
constructor() {
makeAutoObservable(this)
}
} }
export type Modal =
| ConfirmModal
| EditProfileModal
| ServerInputModal
| ReportPostModal
| ReportAccountModal
| CropImageModal
| DeleteAccountModal
interface LightboxModel {} interface LightboxModel {}
export class ProfileImageLightbox implements LightboxModel { export class ProfileImageLightbox implements LightboxModel {
@ -111,15 +93,7 @@ export class ShellUiModel {
minimalShellMode = false minimalShellMode = false
isMainMenuOpen = false isMainMenuOpen = false
isModalActive = false isModalActive = false
activeModal: activeModals: Modal[] = []
| ConfirmModal
| EditProfileModal
| ServerInputModal
| ReportPostModal
| ReportAccountModal
| CropImageModal
| DeleteAccountModal
| undefined
isLightboxActive = false isLightboxActive = false
activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined
isComposerActive = false isComposerActive = false
@ -159,24 +133,15 @@ export class ShellUiModel {
this.isMainMenuOpen = v this.isMainMenuOpen = v
} }
openModal( openModal(modal: Modal) {
modal:
| ConfirmModal
| EditProfileModal
| ServerInputModal
| ReportPostModal
| ReportAccountModal
| CropImageModal
| DeleteAccountModal,
) {
this.rootStore.emitNavigation() this.rootStore.emitNavigation()
this.isModalActive = true this.isModalActive = true
this.activeModal = modal this.activeModals.push(modal)
} }
closeModal() { closeModal() {
this.isModalActive = false this.activeModals.pop()
this.activeModal = undefined this.isModalActive = this.activeModals.length > 0
} }
openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) { openLightbox(lightbox: ProfileImageLightbox | ImagesLightbox) {

View File

@ -17,7 +17,7 @@ import {
VirtualizedList, VirtualizedList,
ModalProps, ModalProps,
} from 'react-native' } from 'react-native'
import {Modal} from '../../modals/Modal' import {ModalsContainer} from '../../modals/Modal'
import ImageItem from './components/ImageItem/ImageItem' import ImageItem from './components/ImageItem/ImageItem'
import ImageDefaultHeader from './components/ImageDefaultHeader' import ImageDefaultHeader from './components/ImageDefaultHeader'
@ -98,7 +98,7 @@ function ImageViewing({
return ( return (
<View style={styles.screen} onLayout={onLayout}> <View style={styles.screen} onLayout={onLayout}>
<Modal /> <ModalsContainer />
<View style={[styles.container, {opacity, backgroundColor}]}> <View style={[styles.container, {opacity, backgroundColor}]}>
<Animated.View style={[styles.header, {transform: headerTransform}]}> <Animated.View style={[styles.header, {transform: headerTransform}]}>
{typeof HeaderComponent !== 'undefined' ? ( {typeof HeaderComponent !== 'undefined' ? (

View File

@ -25,7 +25,6 @@ import {makeValidHandle, createFullHandle} from 'lib/strings/handles'
import {toNiceDomain} from 'lib/strings/url-helpers' import {toNiceDomain} from 'lib/strings/url-helpers'
import {useStores, DEFAULT_SERVICE} from 'state/index' import {useStores, DEFAULT_SERVICE} from 'state/index'
import {ServiceDescription} from 'state/models/session' import {ServiceDescription} from 'state/models/session'
import {ServerInputModal} from 'state/models/shell-ui'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
@ -84,7 +83,11 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => {
const onPressRetryConnect = () => setRetryDescribeTrigger({}) const onPressRetryConnect = () => setRetryDescribeTrigger({})
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl)) store.shell.openModal({
name: 'server-input',
initialService: serviceUrl,
onSelect: setServiceUrl,
})
Keyboard.dismiss() Keyboard.dismiss()
} }

View File

@ -279,7 +279,11 @@ const LoginForm = ({
const [password, setPassword] = useState<string>('') const [password, setPassword] = useState<string>('')
const onPressSelectService = () => { const onPressSelectService = () => {
store.shell.openModal(new ServerInputModal(serviceUrl, setServiceUrl)) store.shell.openModal({
name: 'server-input',
initialService: serviceUrl,
onSelect: setServiceUrl,
})
Keyboard.dismiss() Keyboard.dismiss()
track('Signin:PressedSelectService') track('Signin:PressedSelectService')
} }

View File

@ -5,8 +5,6 @@ import BottomSheet from '@gorhom/bottom-sheet'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop' import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import * as models from 'state/models/shell-ui'
import * as ConfirmModal from './Confirm' import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
import * as ServerInputModal from './ServerInput' import * as ServerInputModal from './ServerInput'
@ -32,52 +30,37 @@ export const Modal = observer(function Modal() {
store.shell.closeModal() store.shell.closeModal()
} }
const activeModal = React.useMemo(
() => store.shell.activeModals.at(-1),
[store.shell.activeModals],
)
useEffect(() => { useEffect(() => {
if (store.shell.isModalActive) { if (store.shell.isModalActive) {
bottomSheetRef.current?.expand() bottomSheetRef.current?.expand()
} else { } else {
bottomSheetRef.current?.close() bottomSheetRef.current?.close()
} }
}, [store.shell.isModalActive, bottomSheetRef, store.shell.activeModal?.name]) }, [store.shell.isModalActive, bottomSheetRef, activeModal?.name])
let snapPoints: (string | number)[] = CLOSED_SNAPPOINTS let snapPoints: (string | number)[] = CLOSED_SNAPPOINTS
let element let element
if (store.shell.activeModal?.name === 'confirm') { if (activeModal?.name === 'confirm') {
snapPoints = ConfirmModal.snapPoints snapPoints = ConfirmModal.snapPoints
element = ( element = <ConfirmModal.Component {...activeModal} />
<ConfirmModal.Component } else if (activeModal?.name === 'edit-profile') {
{...(store.shell.activeModal as models.ConfirmModal)}
/>
)
} else if (store.shell.activeModal?.name === 'edit-profile') {
snapPoints = EditProfileModal.snapPoints snapPoints = EditProfileModal.snapPoints
element = ( element = <EditProfileModal.Component {...activeModal} />
<EditProfileModal.Component } else if (activeModal?.name === 'server-input') {
{...(store.shell.activeModal as models.EditProfileModal)}
/>
)
} else if (store.shell.activeModal?.name === 'server-input') {
snapPoints = ServerInputModal.snapPoints snapPoints = ServerInputModal.snapPoints
element = ( element = <ServerInputModal.Component {...activeModal} />
<ServerInputModal.Component } else if (activeModal?.name === 'report-post') {
{...(store.shell.activeModal as models.ServerInputModal)}
/>
)
} else if (store.shell.activeModal?.name === 'report-post') {
snapPoints = ReportPostModal.snapPoints snapPoints = ReportPostModal.snapPoints
element = ( element = <ReportPostModal.Component {...activeModal} />
<ReportPostModal.Component } else if (activeModal?.name === 'report-account') {
{...(store.shell.activeModal as models.ReportPostModal)}
/>
)
} else if (store.shell.activeModal?.name === 'report-account') {
snapPoints = ReportAccountModal.snapPoints snapPoints = ReportAccountModal.snapPoints
element = ( element = <ReportAccountModal.Component {...activeModal} />
<ReportAccountModal.Component } else if (activeModal?.name === 'delete-account') {
{...(store.shell.activeModal as models.ReportAccountModal)}
/>
)
} else if (store.shell.activeModal?.name === 'delete-account') {
snapPoints = DeleteAccountModal.snapPoints snapPoints = DeleteAccountModal.snapPoints
element = <DeleteAccountModal.Component /> element = <DeleteAccountModal.Component />
} else { } else {

View File

@ -3,8 +3,7 @@ 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 {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import type {Modal as ModalIface} from 'state/models/shell-ui'
import * as models from 'state/models/shell-ui'
import * as ConfirmModal from './Confirm' import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile' import * as EditProfileModal from './EditProfile'
@ -13,7 +12,23 @@ import * as ReportPostModal from './ReportPost'
import * as ReportAccountModal from './ReportAccount' import * as ReportAccountModal from './ReportAccount'
import * as CropImageModal from './crop-image/CropImage.web' import * as CropImageModal from './crop-image/CropImage.web'
export const Modal = observer(function Modal() { export const ModalsContainer = observer(function ModalsContainer() {
const store = useStores()
if (!store.shell.isModalActive) {
return null
}
return (
<>
{store.shell.activeModals.map((modal, i) => (
<Modal key={`modal-${i}`} modal={modal} />
))}
</>
)
})
function Modal({modal}: {modal: ModalIface}) {
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
@ -22,7 +37,7 @@ export const Modal = observer(function Modal() {
} }
const onPressMask = () => { const onPressMask = () => {
if (store.shell.activeModal?.name === 'crop-image') { if (modal.name === 'crop-image') {
return // dont close on mask presses during crop return // dont close on mask presses during crop
} }
store.shell.closeModal() store.shell.closeModal()
@ -32,42 +47,18 @@ export const Modal = observer(function Modal() {
} }
let element let element
if (store.shell.activeModal?.name === 'confirm') { if (modal.name === 'confirm') {
element = ( element = <ConfirmModal.Component {...modal} />
<ConfirmModal.Component } else if (modal.name === 'edit-profile') {
{...(store.shell.activeModal as models.ConfirmModal)} element = <EditProfileModal.Component {...modal} />
/> } else if (modal.name === 'server-input') {
) element = <ServerInputModal.Component {...modal} />
} else if (store.shell.activeModal?.name === 'edit-profile') { } else if (modal.name === 'report-post') {
element = ( element = <ReportPostModal.Component {...modal} />
<EditProfileModal.Component } else if (modal.name === 'report-account') {
{...(store.shell.activeModal as models.EditProfileModal)} element = <ReportAccountModal.Component {...modal} />
/> } else if (modal.name === 'crop-image') {
) element = <CropImageModal.Component {...modal} />
} else if (store.shell.activeModal?.name === 'server-input') {
element = (
<ServerInputModal.Component
{...(store.shell.activeModal as models.ServerInputModal)}
/>
)
} else if (store.shell.activeModal?.name === 'report-post') {
element = (
<ReportPostModal.Component
{...(store.shell.activeModal as models.ReportPostModal)}
/>
)
} else if (store.shell.activeModal?.name === 'report-account') {
element = (
<ReportAccountModal.Component
{...(store.shell.activeModal as models.ReportAccountModal)}
/>
)
} else if (store.shell.activeModal?.name === 'crop-image') {
element = (
<CropImageModal.Component
{...(store.shell.activeModal as models.CropImageModal)}
/>
)
} else { } else {
return null return null
} }
@ -81,7 +72,7 @@ export const Modal = observer(function Modal() {
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
) )
}) }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
mask: { mask: {

View File

@ -15,11 +15,7 @@ import {
import {BlurView} from '../util/BlurView' import {BlurView} from '../util/BlurView'
import {ProfileViewModel} from 'state/models/profile-view' import {ProfileViewModel} from 'state/models/profile-view'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import { import {ProfileImageLightbox} from 'state/models/shell-ui'
EditProfileModal,
ReportAccountModal,
ProfileImageLightbox,
} from 'state/models/shell-ui'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {s, gradients} from 'lib/styles' import {s, gradients} from 'lib/styles'
@ -65,7 +61,11 @@ export const ProfileHeader = observer(function ProfileHeader({
} }
const onPressEditProfile = () => { const onPressEditProfile = () => {
track('ProfileHeader:EditProfileButtonClicked') track('ProfileHeader:EditProfileButtonClicked')
store.shell.openModal(new EditProfileModal(view, onRefreshAll)) store.shell.openModal({
name: 'edit-profile',
profileView: view,
onUpdate: onRefreshAll,
})
} }
const onPressFollowers = () => { const onPressFollowers = () => {
track('ProfileHeader:FollowersButtonClicked') track('ProfileHeader:FollowersButtonClicked')
@ -101,7 +101,10 @@ export const ProfileHeader = observer(function ProfileHeader({
} }
const onPressReportAccount = () => { const onPressReportAccount = () => {
track('ProfileHeader:ReportAccountButtonClicked') track('ProfileHeader:ReportAccountButtonClicked')
store.shell.openModal(new ReportAccountModal(view.did)) store.shell.openModal({
name: 'report-account',
did: view.did,
})
} }
// loading // loading

View File

@ -18,6 +18,7 @@ import {useStores} from 'state/index'
import {colors, gradients} from 'lib/styles' import {colors, gradients} from 'lib/styles'
import {DropdownButton} from './forms/DropdownButton' import {DropdownButton} from './forms/DropdownButton'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
export function UserAvatar({ export function UserAvatar({
size, size,
@ -58,7 +59,7 @@ export function UserAvatar({
) )
const dropdownItems = [ const dropdownItems = [
{ !isWeb && {
label: 'Camera', label: 'Camera',
icon: 'camera' as IconProp, icon: 'camera' as IconProp,
onPress: async () => { onPress: async () => {

View File

@ -18,6 +18,7 @@ import {
} from 'lib/permissions' } from 'lib/permissions'
import {DropdownButton} from './forms/DropdownButton' import {DropdownButton} from './forms/DropdownButton'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
export function UserBanner({ export function UserBanner({
banner, banner,
@ -29,7 +30,7 @@ export function UserBanner({
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const dropdownItems = [ const dropdownItems = [
{ !isWeb && {
label: 'Camera', label: 'Camera',
icon: 'camera' as IconProp, icon: 'camera' as IconProp,
onPress: async () => { onPress: async () => {

View File

@ -16,7 +16,6 @@ import {Button, ButtonType} from './Button'
import {colors} from 'lib/styles' import {colors} from 'lib/styles'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {ReportPostModal, ConfirmModal} from 'state/models/shell-ui'
import {TABS_ENABLED} from 'lib/build-flags' import {TABS_ENABLED} from 'lib/build-flags'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
@ -28,6 +27,7 @@ export interface DropdownItem {
label: string label: string
onPress: () => void onPress: () => void
} }
type MaybeDropdownItem = DropdownItem | false | undefined
export type DropdownButtonType = ButtonType | 'bare' export type DropdownButtonType = ButtonType | 'bare'
@ -44,7 +44,7 @@ export function DropdownButton({
}: { }: {
type?: DropdownButtonType type?: DropdownButtonType
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
items: DropdownItem[] items: MaybeDropdownItem[]
label?: string label?: string
menuWidth?: number menuWidth?: number
children?: React.ReactNode children?: React.ReactNode
@ -71,7 +71,12 @@ export function DropdownButton({
? pageX + width + rightOffset ? pageX + width + rightOffset
: pageX + width - menuWidth : pageX + width - menuWidth
const newY = pageY + height + bottomOffset const newY = pageY + height + bottomOffset
createDropdownMenu(newX, newY, menuWidth, items) createDropdownMenu(
newX,
newY,
menuWidth,
items.filter(v => !!v) as DropdownItem[],
)
}, },
) )
} }
@ -151,7 +156,11 @@ export function PostDropdownBtn({
icon: 'circle-exclamation', icon: 'circle-exclamation',
label: 'Report post', label: 'Report post',
onPress() { onPress() {
store.shell.openModal(new ReportPostModal(itemUri, itemCid)) store.shell.openModal({
name: 'report-post',
postUri: itemUri,
postCid: itemCid,
})
}, },
}, },
isAuthor isAuthor
@ -159,13 +168,12 @@ export function PostDropdownBtn({
icon: ['far', 'trash-can'], icon: ['far', 'trash-can'],
label: 'Delete post', label: 'Delete post',
onPress() { onPress() {
store.shell.openModal( store.shell.openModal({
new ConfirmModal( name: 'confirm',
'Delete this post?', title: 'Delete this post?',
'Are you sure? This can not be undone.', message: 'Are you sure? This can not be undone.',
onDeletePost, onPressConfirm: onDeletePost,
), })
)
}, },
} }
: undefined, : undefined,

View File

@ -28,7 +28,7 @@ import {Login} from '../../screens/Login'
import {Menu} from './Menu' import {Menu} from './Menu'
import {Onboard} from '../../screens/Onboard' import {Onboard} from '../../screens/Onboard'
import {HorzSwipe} from '../../com/util/gestures/HorzSwipe' import {HorzSwipe} from '../../com/util/gestures/HorzSwipe'
import {Modal} from '../../com/modals/Modal' import {ModalsContainer} from '../../com/modals/Modal'
import {Lightbox} from '../../com/lightbox/Lightbox' import {Lightbox} from '../../com/lightbox/Lightbox'
import {Text} from '../../com/util/text/Text' import {Text} from '../../com/util/text/Text'
import {ErrorBoundary} from '../../com/util/ErrorBoundary' import {ErrorBoundary} from '../../com/util/ErrorBoundary'
@ -366,7 +366,7 @@ export const MobileShell: React.FC = observer(() => {
return ( return (
<View style={styles.outerContainer}> <View style={styles.outerContainer}>
<Login /> <Login />
<Modal /> <ModalsContainer />
</View> </View>
) )
} }
@ -515,7 +515,7 @@ export const MobileShell: React.FC = observer(() => {
notificationCount={store.me.notifications.unreadCount} notificationCount={store.me.notifications.unreadCount}
/> />
</Animated.View> </Animated.View>
<Modal /> <ModalsContainer />
<Lightbox /> <Lightbox />
<Composer <Composer
active={store.shell.isComposerActive} active={store.shell.isComposerActive}

View File

@ -11,7 +11,7 @@ import {Onboard} from '../../screens/Onboard'
import {Login} from '../../screens/Login' import {Login} from '../../screens/Login'
import {ErrorBoundary} from '../../com/util/ErrorBoundary' import {ErrorBoundary} from '../../com/util/ErrorBoundary'
import {Lightbox} from '../../com/lightbox/Lightbox' import {Lightbox} from '../../com/lightbox/Lightbox'
import {Modal} from '../../com/modals/Modal' import {ModalsContainer} from '../../com/modals/Modal'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {Composer} from './Composer' import {Composer} from './Composer'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@ -32,7 +32,7 @@ export const WebShell: React.FC = observer(() => {
return ( return (
<View style={styles.outerContainer}> <View style={styles.outerContainer}>
<Login /> <Login />
<Modal /> <ModalsContainer />
</View> </View>
) )
} }
@ -67,7 +67,7 @@ export const WebShell: React.FC = observer(() => {
imagesOpen={store.shell.composerOpts?.imagesOpen} imagesOpen={store.shell.composerOpts?.imagesOpen}
onPost={store.shell.composerOpts?.onPost} onPost={store.shell.composerOpts?.onPost}
/> />
<Modal /> <ModalsContainer />
<Lightbox /> <Lightbox />
</View> </View>
) )