diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/composer/Composer.tsx | 2 | ||||
-rw-r--r-- | src/view/com/composer/photos/Gallery.tsx | 123 | ||||
-rw-r--r-- | src/view/com/modals/AltImage.tsx | 106 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 8 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/notifications/FeedItem.tsx | 5 | ||||
-rw-r--r-- | src/view/com/util/images/AutoSizedImage.tsx | 41 | ||||
-rw-r--r-- | src/view/com/util/images/ImageHorzList.tsx | 22 | ||||
-rw-r--r-- | src/view/com/util/images/ImageLayoutGrid.tsx | 122 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 4 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 2 |
11 files changed, 331 insertions, 107 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 08f977f79..275001309 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -142,7 +142,7 @@ export const ComposePost = observer(function ComposePost({ await apilib.post(store, { rawText: rt.text, replyTo: replyTo?.uri, - images: gallery.paths, + images: gallery.images, quote: quote, extLink: extLink, onStateChange: setProcessingState, diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index f4dfc88fa..98f0824fd 100644 --- a/src/view/com/composer/photos/Gallery.tsx +++ b/src/view/com/composer/photos/Gallery.tsx @@ -1,4 +1,5 @@ import React, {useCallback} from 'react' +import {ImageStyle, Keyboard} from 'react-native' import {GalleryModel} from 'state/models/media/gallery' import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' @@ -6,6 +7,8 @@ import {colors} from 'lib/styles' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {ImageModel} from 'state/models/media/image' import {Image} from 'expo-image' +import {Text} from 'view/com/util/text/Text' +import {isDesktopWeb} from 'platform/detection' interface Props { gallery: GalleryModel @@ -13,17 +16,28 @@ interface Props { export const Gallery = observer(function ({gallery}: Props) { const getImageStyle = useCallback(() => { - switch (gallery.size) { - case 1: - return styles.image250 - case 2: - return styles.image175 - default: - return styles.image85 + let side: number + + if (gallery.size === 1) { + side = 250 + } else { + side = (isDesktopWeb ? 560 : 350) / gallery.size + } + + return { + height: side, + width: side, } }, [gallery]) const imageStyle = getImageStyle() + const handleAddImageAltText = useCallback( + (image: ImageModel) => { + Keyboard.dismiss() + gallery.setAltText(image) + }, + [gallery], + ) const handleRemovePhoto = useCallback( (image: ImageModel) => { gallery.remove(image) @@ -38,14 +52,68 @@ export const Gallery = observer(function ({gallery}: Props) { [gallery], ) + const isOverflow = !isDesktopWeb && gallery.size > 2 + + const imageControlLabelStyle = { + borderRadius: 5, + paddingHorizontal: 10, + position: 'absolute' as const, + width: 46, + zIndex: 1, + ...(isOverflow + ? { + left: 4, + bottom: 4, + } + : isDesktopWeb && gallery.size < 3 + ? { + left: 8, + top: 8, + } + : { + left: 4, + top: 4, + }), + } + + const imageControlsSubgroupStyle = { + display: 'flex' as const, + flexDirection: 'row' as const, + position: 'absolute' as const, + ...(isOverflow + ? { + top: 4, + right: 4, + gap: 4, + } + : isDesktopWeb && gallery.size < 3 + ? { + top: 8, + right: 8, + gap: 8, + } + : { + top: 4, + right: 4, + gap: 4, + }), + zIndex: 1, + } + return !gallery.isEmpty ? ( <View testID="selectedPhotosView" style={styles.gallery}> {gallery.images.map(image => image.compressed !== undefined ? ( - <View - key={`selected-image-${image.path}`} - style={[styles.imageContainer, imageStyle]}> - <View style={styles.imageControls}> + <View key={`selected-image-${image.path}`} style={[imageStyle]}> + <TouchableOpacity + testID="altTextButton" + onPress={() => { + handleAddImageAltText(image) + }} + style={[styles.imageControl, imageControlLabelStyle]}> + <Text style={styles.imageControlTextContent}>ALT</Text> + </TouchableOpacity> + <View style={imageControlsSubgroupStyle}> <TouchableOpacity testID="cropPhotoButton" onPress={() => { @@ -72,7 +140,7 @@ export const Gallery = observer(function ({gallery}: Props) { <Image testID="selectedPhotoImage" - style={[styles.image, imageStyle]} + style={[styles.image, imageStyle] as ImageStyle} source={{ uri: image.compressed.path, }} @@ -88,36 +156,13 @@ const styles = StyleSheet.create({ gallery: { flex: 1, flexDirection: 'row', + gap: 8, marginTop: 16, }, - imageContainer: { - margin: 2, - }, image: { resizeMode: 'cover', borderRadius: 8, }, - image250: { - width: 250, - height: 250, - }, - image175: { - width: 175, - height: 175, - }, - image85: { - width: 85, - height: 85, - }, - imageControls: { - position: 'absolute', - display: 'flex', - flexDirection: 'row', - gap: 4, - top: 8, - right: 8, - zIndex: 1, - }, imageControl: { width: 24, height: 24, @@ -127,4 +172,10 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, + imageControlTextContent: { + color: 'white', + fontSize: 12, + fontWeight: 'bold', + letterSpacing: 1, + }, }) diff --git a/src/view/com/modals/AltImage.tsx b/src/view/com/modals/AltImage.tsx new file mode 100644 index 000000000..987df1462 --- /dev/null +++ b/src/view/com/modals/AltImage.tsx @@ -0,0 +1,106 @@ +import React, {useCallback, useState} from 'react' +import {StyleSheet, View} from 'react-native' +import {usePalette} from 'lib/hooks/usePalette' +import {TextInput} from './util' +import {gradients, s} from 'lib/styles' +import {enforceLen} from 'lib/strings/helpers' +import {MAX_ALT_TEXT} from 'lib/constants' +import {useTheme} from 'lib/ThemeContext' +import {Text} from '../util/text/Text' +import {TouchableOpacity} from 'react-native-gesture-handler' +import LinearGradient from 'react-native-linear-gradient' +import {useStores} from 'state/index' +import {isDesktopWeb} from 'platform/detection' + +export const snapPoints = [330] + +interface Props { + onAltTextSet: (altText?: string | undefined) => void +} + +export function Component({onAltTextSet}: Props) { + const pal = usePalette('default') + const store = useStores() + const theme = useTheme() + const [altText, setAltText] = useState('') + + const onPressSave = useCallback(() => { + onAltTextSet(altText) + store.shell.closeModal() + }, [store, altText, onAltTextSet]) + + const onPressCancel = () => { + store.shell.closeModal() + } + + return ( + <View testID="altTextImageModal" style={[pal.view, styles.container]}> + <Text style={[styles.title, pal.text]}>Add alt text</Text> + <TextInput + testID="altTextImageInput" + style={[styles.textArea, pal.border, pal.text]} + keyboardAppearance={theme.colorScheme} + multiline + value={altText} + onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))} + /> + <View style={styles.buttonControls}> + <TouchableOpacity testID="altTextImageSaveBtn" onPress={onPressSave}> + <LinearGradient + colors={[gradients.blueLight.start, gradients.blueLight.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={[styles.button]}> + <Text type="button-lg" style={[s.white, s.bold]}> + Save + </Text> + </LinearGradient> + </TouchableOpacity> + <TouchableOpacity + testID="altTextImageCancelBtn" + onPress={onPressCancel}> + <View style={[styles.button]}> + <Text type="button-lg" style={[pal.textLight]}> + Cancel + </Text> + </View> + </TouchableOpacity> + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + gap: 18, + bottom: 0, + paddingVertical: 18, + paddingHorizontal: isDesktopWeb ? 0 : 12, + width: '100%', + }, + title: { + textAlign: 'center', + fontWeight: 'bold', + fontSize: 24, + }, + textArea: { + borderWidth: 1, + borderRadius: 6, + paddingTop: 10, + paddingHorizontal: 12, + fontSize: 16, + height: 100, + textAlignVertical: 'top', + }, + button: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + borderRadius: 32, + padding: 10, + }, + buttonControls: { + gap: 8, + }, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 3f10ec836..a83cdfdae 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -1,5 +1,5 @@ import React, {useRef, useEffect} from 'react' -import {StyleSheet, View} from 'react-native' +import {StyleSheet} from 'react-native' import {observer} from 'mobx-react-lite' import BottomSheet from '@gorhom/bottom-sheet' import {useStores} from 'state/index' @@ -11,6 +11,7 @@ import * as EditProfileModal from './EditProfile' import * as ServerInputModal from './ServerInput' import * as ReportPostModal from './ReportPost' import * as RepostModal from './Repost' +import * as AltImageModal from './AltImage' import * as ReportAccountModal from './ReportAccount' import * as DeleteAccountModal from './DeleteAccount' import * as ChangeHandleModal from './ChangeHandle' @@ -68,6 +69,9 @@ export const ModalsContainer = observer(function ModalsContainer() { } else if (activeModal?.name === 'repost') { snapPoints = RepostModal.snapPoints element = <RepostModal.Component {...activeModal} /> + } else if (activeModal?.name === 'alt-text-image') { + snapPoints = AltImageModal.snapPoints + element = <AltImageModal.Component {...activeModal} /> } else if (activeModal?.name === 'change-handle') { snapPoints = ChangeHandleModal.snapPoints element = <ChangeHandleModal.Component {...activeModal} /> @@ -81,7 +85,7 @@ export const ModalsContainer = observer(function ModalsContainer() { snapPoints = ContentFilteringSettingsModal.snapPoints element = <ContentFilteringSettingsModal.Component /> } else { - return <View /> + return null } return ( diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 25fed69a4..1effee69b 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -14,6 +14,7 @@ import * as ReportAccountModal from './ReportAccount' import * as DeleteAccountModal from './DeleteAccount' import * as RepostModal from './Repost' import * as CropImageModal from './crop-image/CropImage.web' +import * as AltTextImageModal from './AltImage' import * as ChangeHandleModal from './ChangeHandle' import * as WaitlistModal from './Waitlist' import * as InviteCodesModal from './InviteCodes' @@ -78,6 +79,8 @@ function Modal({modal}: {modal: ModalIface}) { element = <InviteCodesModal.Component /> } else if (modal.name === 'content-filtering-settings') { element = <ContentFilteringSettingsModal.Component /> + } else if (modal.name === 'alt-text-image') { + element = <AltTextImageModal.Component {...modal} /> } else { return null } diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index b05111ffc..02dea4204 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -369,10 +369,7 @@ function AdditionalPostText({ <> {text?.length > 0 && <Text style={pal.textLight}>{text}</Text>} {images && images?.length > 0 && ( - <ImageHorzList - uris={images?.map(img => img.thumb)} - style={styles.additionalPostImages} - /> + <ImageHorzList images={images} style={styles.additionalPostImages} /> )} </> ) diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 17e3e809b..8c31f5614 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -9,29 +9,33 @@ import { import {Image} from 'expo-image' import {clamp} from 'lib/numbers' import {useStores} from 'state/index' -import {Dim} from 'lib/media/manip' +import {Dimensions} from 'lib/media/types' export const DELAY_PRESS_IN = 500 const MIN_ASPECT_RATIO = 0.33 // 1/3 const MAX_ASPECT_RATIO = 5 // 5/1 +interface Props { + alt?: string + uri: string + onPress?: () => void + onLongPress?: () => void + onPressIn?: () => void + style?: StyleProp<ViewStyle> + children?: React.ReactNode +} + export function AutoSizedImage({ + alt, uri, onPress, onLongPress, onPressIn, style, children = null, -}: { - uri: string - onPress?: () => void - onLongPress?: () => void - onPressIn?: () => void - style?: StyleProp<ViewStyle> - children?: React.ReactNode -}) { +}: Props) { const store = useStores() - const [dim, setDim] = React.useState<Dim | undefined>( + const [dim, setDim] = React.useState<Dimensions | undefined>( store.imageSizes.get(uri), ) const [aspectRatio, setAspectRatio] = React.useState<number>( @@ -59,20 +63,31 @@ export function AutoSizedImage({ onPressIn={onPressIn} delayPressIn={DELAY_PRESS_IN} style={[styles.container, style]}> - <Image style={[styles.image, {aspectRatio}]} source={uri} /> + <Image + style={[styles.image, {aspectRatio}]} + source={uri} + accessible={true} // Must set for `accessibilityLabel` to work + accessibilityLabel={alt} + /> {children} </TouchableOpacity> ) } + return ( <View style={[styles.container, style]}> - <Image style={[styles.image, {aspectRatio}]} source={{uri}} /> + <Image + style={[styles.image, {aspectRatio}]} + source={{uri}} + accessible={true} // Must set for `accessibilityLabel` to work + accessibilityLabel={alt} + /> {children} </View> ) } -function calc(dim: Dim) { +function calc(dim: Dimensions) { if (dim.width === 0 || dim.height === 0) { return 1 } diff --git a/src/view/com/util/images/ImageHorzList.tsx b/src/view/com/util/images/ImageHorzList.tsx index 40f1948d6..5c232e0b4 100644 --- a/src/view/com/util/images/ImageHorzList.tsx +++ b/src/view/com/util/images/ImageHorzList.tsx @@ -7,21 +7,25 @@ import { ViewStyle, } from 'react-native' import {Image} from 'expo-image' +import {AppBskyEmbedImages} from '@atproto/api' -export function ImageHorzList({ - uris, - onPress, - style, -}: { - uris: string[] +interface Props { + images: AppBskyEmbedImages.ViewImage[] onPress?: (index: number) => void style?: StyleProp<ViewStyle> -}) { +} + +export function ImageHorzList({images, onPress, style}: Props) { return ( <View style={[styles.flexRow, style]}> - {uris.map((uri, i) => ( + {images.map(({thumb, alt}, i) => ( <TouchableWithoutFeedback key={i} onPress={() => onPress?.(i)}> - <Image source={{uri}} style={styles.image} /> + <Image + source={{uri: thumb}} + style={styles.image} + accessible={true} + accessibilityLabel={alt} + /> </TouchableWithoutFeedback> ))} </View> diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx index f4fe59522..51bb04fe9 100644 --- a/src/view/com/util/images/ImageLayoutGrid.tsx +++ b/src/view/com/util/images/ImageLayoutGrid.tsx @@ -9,26 +9,25 @@ import { } from 'react-native' import {Image, ImageStyle} from 'expo-image' import {Dimensions} from 'lib/media/types' +import {AppBskyEmbedImages} from '@atproto/api' export const DELAY_PRESS_IN = 500 -export type ImageLayoutGridType = number +interface ImageLayoutGridProps { + images: AppBskyEmbedImages.ViewImage[] + onPress?: (index: number) => void + onLongPress?: (index: number) => void + onPressIn?: (index: number) => void + style?: StyleProp<ViewStyle> +} export function ImageLayoutGrid({ - type, - uris, + images, onPress, onLongPress, onPressIn, style, -}: { - type: ImageLayoutGridType - uris: string[] - onPress?: (index: number) => void - onLongPress?: (index: number) => void - onPressIn?: (index: number) => void - style?: StyleProp<ViewStyle> -}) { +}: ImageLayoutGridProps) { const [containerInfo, setContainerInfo] = useState<Dimensions | undefined>() const onLayout = (evt: LayoutChangeEvent) => { @@ -42,8 +41,7 @@ export function ImageLayoutGrid({ <View style={style} onLayout={onLayout}> {containerInfo ? ( <ImageLayoutGridInner - type={type} - uris={uris} + images={images} onPress={onPress} onPressIn={onPressIn} onLongPress={onLongPress} @@ -54,41 +52,42 @@ export function ImageLayoutGrid({ ) } +interface ImageLayoutGridInnerProps { + images: AppBskyEmbedImages.ViewImage[] + onPress?: (index: number) => void + onLongPress?: (index: number) => void + onPressIn?: (index: number) => void + containerInfo: Dimensions +} + function ImageLayoutGridInner({ - type, - uris, + images, onPress, onLongPress, onPressIn, containerInfo, -}: { - type: ImageLayoutGridType - uris: string[] - onPress?: (index: number) => void - onLongPress?: (index: number) => void - onPressIn?: (index: number) => void - containerInfo: Dimensions -}) { +}: ImageLayoutGridInnerProps) { + const count = images.length const size1 = useMemo<ImageStyle>(() => { - if (type === 3) { + if (count === 3) { const size = (containerInfo.width - 10) / 3 return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} } else { const size = (containerInfo.width - 5) / 2 return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} } - }, [type, containerInfo]) + }, [count, containerInfo]) const size2 = React.useMemo<ImageStyle>(() => { - if (type === 3) { + if (count === 3) { const size = ((containerInfo.width - 10) / 3) * 2 + 5 return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} } else { const size = (containerInfo.width - 5) / 2 return {width: size, height: size, resizeMode: 'cover', borderRadius: 4} } - }, [type, containerInfo]) + }, [count, containerInfo]) - if (type === 2) { + if (count === 2) { return ( <View style={styles.flexRow}> <TouchableOpacity @@ -96,7 +95,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(0)} onPressIn={() => onPressIn?.(0)} onLongPress={() => onLongPress?.(0)}> - <Image source={{uri: uris[0]}} style={size1} /> + <Image + source={{uri: images[0].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[0].alt} + /> </TouchableOpacity> <View style={styles.wSpace} /> <TouchableOpacity @@ -104,12 +108,17 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(1)} onPressIn={() => onPressIn?.(1)} onLongPress={() => onLongPress?.(1)}> - <Image source={{uri: uris[1]}} style={size1} /> + <Image + source={{uri: images[1].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[1].alt} + /> </TouchableOpacity> </View> ) } - if (type === 3) { + if (count === 3) { return ( <View style={styles.flexRow}> <TouchableOpacity @@ -117,7 +126,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(0)} onPressIn={() => onPressIn?.(0)} onLongPress={() => onLongPress?.(0)}> - <Image source={{uri: uris[0]}} style={size2} /> + <Image + source={{uri: images[0].thumb}} + style={size2} + accessible={true} + accessibilityLabel={images[0].alt} + /> </TouchableOpacity> <View style={styles.wSpace} /> <View> @@ -126,7 +140,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(1)} onPressIn={() => onPressIn?.(1)} onLongPress={() => onLongPress?.(1)}> - <Image source={{uri: uris[1]}} style={size1} /> + <Image + source={{uri: images[1].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[1].alt} + /> </TouchableOpacity> <View style={styles.hSpace} /> <TouchableOpacity @@ -134,13 +153,18 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(2)} onPressIn={() => onPressIn?.(2)} onLongPress={() => onLongPress?.(2)}> - <Image source={{uri: uris[2]}} style={size1} /> + <Image + source={{uri: images[2].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[2].alt} + /> </TouchableOpacity> </View> </View> ) } - if (type === 4) { + if (count === 4) { return ( <View style={styles.flexRow}> <View> @@ -149,7 +173,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(0)} onPressIn={() => onPressIn?.(0)} onLongPress={() => onLongPress?.(0)}> - <Image source={{uri: uris[0]}} style={size1} /> + <Image + source={{uri: images[0].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[0].alt} + /> </TouchableOpacity> <View style={styles.hSpace} /> <TouchableOpacity @@ -157,7 +186,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(2)} onPressIn={() => onPressIn?.(2)} onLongPress={() => onLongPress?.(2)}> - <Image source={{uri: uris[2]}} style={size1} /> + <Image + source={{uri: images[2].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[2].alt} + /> </TouchableOpacity> </View> <View style={styles.wSpace} /> @@ -167,7 +201,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(1)} onPressIn={() => onPressIn?.(1)} onLongPress={() => onLongPress?.(1)}> - <Image source={{uri: uris[1]}} style={size1} /> + <Image + source={{uri: images[1].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[1].alt} + /> </TouchableOpacity> <View style={styles.hSpace} /> <TouchableOpacity @@ -175,7 +214,12 @@ function ImageLayoutGridInner({ onPress={() => onPress?.(3)} onPressIn={() => onPressIn?.(3)} onLongPress={() => onLongPress?.(3)}> - <Image source={{uri: uris[3]}} style={size1} /> + <Image + source={{uri: images[3].thumb}} + style={size1} + accessible={true} + accessibilityLabel={images[3].alt} + /> </TouchableOpacity> </View> </View> diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index c15986b76..f37fba342 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -112,6 +112,7 @@ export function PostEmbeds({ return ( <View style={[styles.imagesContainer, style]}> <AutoSizedImage + alt={embed.images[0].alt} uri={embed.images[0].thumb} onPress={() => openLightbox(0)} onLongPress={() => onLongPress(0)} @@ -124,8 +125,7 @@ export function PostEmbeds({ return ( <View style={[styles.imagesContainer, style]}> <ImageLayoutGrid - type={embed.images.length} - uris={embed.images.map(img => img.thumb)} + images={embed.images} onPress={openLightbox} onLongPress={onLongPress} onPressIn={onPressIn} diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index eab050fd0..e0abec777 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -54,7 +54,6 @@ const ShellInner = observer(() => { </Drawer> </ErrorBoundary> </View> - <ModalsContainer /> <Lightbox /> <Composer active={store.shell.isComposerActive} @@ -64,6 +63,7 @@ const ShellInner = observer(() => { onPost={store.shell.composerOpts?.onPost} quote={store.shell.composerOpts?.quote} /> + <ModalsContainer /> </> ) }) |