From 8ae6e67eea8890f639dbaa9423e99cad5e28502f Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 1 Nov 2022 14:25:41 -0500 Subject: Rework the composer to a less buggy solution --- src/state/models/shell.ts | 48 +++--- src/view/com/composer/Autocomplete.tsx | 76 +++++++++ src/view/com/composer/ComposePost.tsx | 233 ++++++++++++++++++++++++++ src/view/com/modals/ComposePost.tsx | 233 -------------------------- src/view/com/modals/Modal.tsx | 8 - src/view/com/modals/composer/Autocomplete.tsx | 76 --------- src/view/com/post-thread/PostThreadItem.tsx | 11 +- src/view/com/post/Post.tsx | 5 +- src/view/com/posts/FeedItem.tsx | 6 +- src/view/screens/Home.tsx | 3 +- src/view/shell/mobile/Composer.tsx | 83 +++++++++ src/view/shell/mobile/index.tsx | 8 + 12 files changed, 427 insertions(+), 363 deletions(-) create mode 100644 src/view/com/composer/Autocomplete.tsx create mode 100644 src/view/com/composer/ComposePost.tsx delete mode 100644 src/view/com/modals/ComposePost.tsx delete mode 100644 src/view/com/modals/composer/Autocomplete.tsx create mode 100644 src/view/shell/mobile/Composer.tsx (limited to 'src') diff --git a/src/state/models/shell.ts b/src/state/models/shell.ts index 8cb0ff9e7..33b8eef36 100644 --- a/src/state/models/shell.ts +++ b/src/state/models/shell.ts @@ -27,22 +27,6 @@ export class SharePostModel { } } -export interface ComposePostModelOpts { - replyTo?: Post.PostRef - onPost?: () => void -} -export class ComposePostModel { - name = 'compose-post' - replyTo?: Post.PostRef - onPost?: () => void - - constructor(opts?: ComposePostModelOpts) { - makeAutoObservable(this) - this.replyTo = opts?.replyTo - this.onPost = opts?.onPost - } -} - export class EditProfileModel { name = 'edit-profile' @@ -51,26 +35,22 @@ export class EditProfileModel { } } +export interface ComposerOpts { + replyTo?: Post.PostRef + onPost?: () => void +} + export class ShellModel { isModalActive = false - activeModal: - | LinkActionsModel - | SharePostModel - | ComposePostModel - | EditProfileModel - | undefined + activeModal: LinkActionsModel | SharePostModel | EditProfileModel | undefined + isComposerActive = false + composerOpts: ComposerOpts | undefined constructor() { makeAutoObservable(this) } - openModal( - modal: - | LinkActionsModel - | SharePostModel - | ComposePostModel - | EditProfileModel, - ) { + openModal(modal: LinkActionsModel | SharePostModel | EditProfileModel) { this.isModalActive = true this.activeModal = modal } @@ -79,4 +59,14 @@ export class ShellModel { this.isModalActive = false this.activeModal = undefined } + + openComposer(opts: ComposerOpts) { + this.isComposerActive = true + this.composerOpts = opts + } + + closeComposer() { + this.isComposerActive = false + this.composerOpts = undefined + } } diff --git a/src/view/com/composer/Autocomplete.tsx b/src/view/com/composer/Autocomplete.tsx new file mode 100644 index 000000000..4e4bdfc8e --- /dev/null +++ b/src/view/com/composer/Autocomplete.tsx @@ -0,0 +1,76 @@ +import React, {useEffect} from 'react' +import { + useWindowDimensions, + Text, + TouchableOpacity, + StyleSheet, +} from 'react-native' +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + interpolate, +} from 'react-native-reanimated' +import {colors} from '../../../lib/styles' + +export function Autocomplete({ + active, + items, + onSelect, +}: { + active: boolean + items: string[] + onSelect: (item: string) => void +}) { + const winDim = useWindowDimensions() + const positionInterp = useSharedValue(0) + + useEffect(() => { + if (active) { + positionInterp.value = withTiming(1, {duration: 250}) + } else { + positionInterp.value = withTiming(0, {duration: 250}) + } + }, [positionInterp, active]) + + const topAnimStyle = useAnimatedStyle(() => ({ + top: interpolate( + positionInterp.value, + [0, 1.0], + [winDim.height, winDim.height / 4], + ), + })) + return ( + + {items.map((item, i) => ( + onSelect(item)}> + @{item} + + ))} + + ) +} + +const styles = StyleSheet.create({ + outer: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + backgroundColor: colors.white, + borderTopWidth: 1, + borderTopColor: colors.gray2, + }, + item: { + borderBottomWidth: 1, + borderBottomColor: colors.gray1, + paddingVertical: 16, + paddingHorizontal: 16, + }, + itemText: { + fontSize: 16, + }, +}) diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx new file mode 100644 index 000000000..496b49a9b --- /dev/null +++ b/src/view/com/composer/ComposePost.tsx @@ -0,0 +1,233 @@ +import React, {useEffect, useMemo, useState} from 'react' +import {StyleSheet, Text, TextInput, TouchableOpacity, View} from 'react-native' +import LinearGradient from 'react-native-linear-gradient' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import * as GetUserFollows from '../../../third-party/api/src/types/app/bsky/getUserFollows' +import {Autocomplete} from './Autocomplete' +import Toast from '../util/Toast' +import ProgressCircle from '../util/ProgressCircle' +import {useStores} from '../../../state' +import * as apilib from '../../../state/lib/api' +import {ComposerOpts} from '../../../state/models/shell' +import {s, colors, gradients} from '../../lib/styles' + +const MAX_TEXT_LENGTH = 256 +const WARNING_TEXT_LENGTH = 200 +const DANGER_TEXT_LENGTH = 255 + +export function ComposePost({ + replyTo, + onPost, + onClose, +}: { + replyTo?: ComposerOpts['replyTo'] + onPost?: ComposerOpts['onPost'] + onClose: () => void +}) { + const store = useStores() + const [error, setError] = useState('') + const [text, setText] = useState('') + const [followedUsers, setFollowedUsers] = useState< + undefined | GetUserFollows.OutputSchema['follows'] + >(undefined) + const [autocompleteOptions, setAutocompleteOptions] = useState([]) + + useEffect(() => { + let aborted = false + store.api.app.bsky + .getUserFollows({ + user: store.me.did || '', + }) + .then(res => { + if (aborted) return + setFollowedUsers(res.data.follows) + }) + return () => { + aborted = true + } + }) + + const onChangeText = (newText: string) => { + if (newText.length > MAX_TEXT_LENGTH) { + newText = newText.slice(0, MAX_TEXT_LENGTH) + } + setText(newText) + + const prefix = extractTextAutocompletePrefix(newText) + if (typeof prefix === 'string' && followedUsers) { + setAutocompleteOptions( + [prefix].concat( + followedUsers + .filter(user => user.name.startsWith(prefix)) + .map(user => user.name), + ), + ) + } else if (autocompleteOptions) { + setAutocompleteOptions([]) + } + } + const onPressCancel = () => { + onClose() + } + const onPressPublish = async () => { + setError('') + if (text.trim().length === 0) { + setError('Did you want to say anything?') + return false + } + try { + await apilib.post(store, text, replyTo) + } catch (e: any) { + console.error(`Failed to create post: ${e.toString()}`) + setError( + 'Post failed to upload. Please check your Internet connection and try again.', + ) + return + } + onPost?.() + onClose() + Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`, { + duration: Toast.durations.LONG, + position: Toast.positions.TOP, + shadow: true, + animation: true, + hideOnPress: true, + }) + } + const onSelectAutocompleteItem = (item: string) => { + setText(replaceTextAutocompletePrefix(text, item)) + setAutocompleteOptions([]) + } + + const progressColor = + text.length > DANGER_TEXT_LENGTH + ? '#e60000' + : text.length > WARNING_TEXT_LENGTH + ? '#f7c600' + : undefined + + const textDecorated = useMemo(() => { + return (text || '').split(/(\s)/g).map((item, i) => { + if (/^@[a-zA-Z0-9\.-]+$/g.test(item)) { + return ( + + {item} + + ) + } + return item + }) + }, [text]) + + return ( + + + + Cancel + + + + + Post + + + + {error !== '' && ( + + + + + {error} + + )} + onChangeText(text)} + placeholder={replyTo ? 'Write your reply' : "What's new?"} + style={styles.textInput}> + {textDecorated} + + + + + + + + 0} + items={autocompleteOptions} + onSelect={onSelectAutocompleteItem} + /> + + ) +} + +const atPrefixRegex = /@([\S]*)$/i +function extractTextAutocompletePrefix(text: string) { + const match = atPrefixRegex.exec(text) + if (match) { + return match[1] + } + return undefined +} +function replaceTextAutocompletePrefix(text: string, item: string) { + return text.replace(atPrefixRegex, `@${item} `) +} + +const styles = StyleSheet.create({ + outer: { + flexDirection: 'column', + backgroundColor: '#fff', + padding: 15, + height: '100%', + }, + topbar: { + flexDirection: 'row', + alignItems: 'center', + paddingTop: 10, + paddingBottom: 5, + paddingHorizontal: 5, + }, + postBtn: { + borderRadius: 20, + paddingHorizontal: 20, + paddingVertical: 6, + }, + errorLine: { + flexDirection: 'row', + backgroundColor: colors.red1, + borderRadius: 6, + paddingHorizontal: 8, + paddingVertical: 6, + marginVertical: 6, + }, + errorIcon: { + borderWidth: 1, + borderColor: colors.red4, + color: colors.red4, + borderRadius: 30, + width: 16, + height: 16, + alignItems: 'center', + justifyContent: 'center', + marginRight: 5, + }, + textInput: { + flex: 1, + padding: 5, + fontSize: 18, + }, +}) diff --git a/src/view/com/modals/ComposePost.tsx b/src/view/com/modals/ComposePost.tsx deleted file mode 100644 index 806b5d7a0..000000000 --- a/src/view/com/modals/ComposePost.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React, {useEffect, useMemo, useState} from 'react' -import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' -import {BottomSheetTextInput} from '@gorhom/bottom-sheet' -import LinearGradient from 'react-native-linear-gradient' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import * as GetUserFollows from '../../../third-party/api/src/types/app/bsky/getUserFollows' -import * as Post from '../../../third-party/api/src/types/app/bsky/post' -import {Autocomplete} from './composer/Autocomplete' -import Toast from '../util/Toast' -import ProgressCircle from '../util/ProgressCircle' -import {useStores} from '../../../state' -import * as apilib from '../../../state/lib/api' -import {s, colors, gradients} from '../../lib/styles' - -const MAX_TEXT_LENGTH = 256 -const WARNING_TEXT_LENGTH = 200 -const DANGER_TEXT_LENGTH = 255 -export const snapPoints = ['100%'] - -export function Component({ - replyTo, - onPost, -}: { - replyTo?: Post.PostRef - onPost?: () => void -}) { - const store = useStores() - const [error, setError] = useState('') - const [text, setText] = useState('') - const [followedUsers, setFollowedUsers] = useState< - undefined | GetUserFollows.OutputSchema['follows'] - >(undefined) - const [autocompleteOptions, setAutocompleteOptions] = useState([]) - - useEffect(() => { - let aborted = false - store.api.app.bsky - .getUserFollows({ - user: store.me.did || '', - }) - .then(res => { - if (aborted) return - setFollowedUsers(res.data.follows) - }) - return () => { - aborted = true - } - }) - - const onChangeText = (newText: string) => { - if (newText.length > MAX_TEXT_LENGTH) { - newText = newText.slice(0, MAX_TEXT_LENGTH) - } - setText(newText) - - const prefix = extractTextAutocompletePrefix(newText) - if (typeof prefix === 'string' && followedUsers) { - setAutocompleteOptions( - [prefix].concat( - followedUsers - .filter(user => user.name.startsWith(prefix)) - .map(user => user.name), - ), - ) - } else if (autocompleteOptions) { - setAutocompleteOptions([]) - } - } - const onPressCancel = () => { - store.shell.closeModal() - } - const onPressPublish = async () => { - setError('') - if (text.trim().length === 0) { - setError('Did you want to say anything?') - return false - } - try { - await apilib.post(store, text, replyTo) - } catch (e: any) { - console.error(`Failed to create post: ${e.toString()}`) - setError( - 'Post failed to upload. Please check your Internet connection and try again.', - ) - return - } - onPost?.() - store.shell.closeModal() - Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`, { - duration: Toast.durations.LONG, - position: Toast.positions.TOP, - shadow: true, - animation: true, - hideOnPress: true, - }) - } - const onSelectAutocompleteItem = (item: string) => { - setText(replaceTextAutocompletePrefix(text, item)) - setAutocompleteOptions([]) - } - - const progressColor = - text.length > DANGER_TEXT_LENGTH - ? '#e60000' - : text.length > WARNING_TEXT_LENGTH - ? '#f7c600' - : undefined - - const textDecorated = useMemo(() => { - return (text || '').split(/(\s)/g).map((item, i) => { - if (/^@[a-zA-Z0-9\.-]+$/g.test(item)) { - return ( - - {item} - - ) - } - return item - }) - }, [text]) - - return ( - - - - Cancel - - - - - Post - - - - {error !== '' && ( - - - - - {error} - - )} - onChangeText(text)} - placeholder={replyTo ? 'Write your reply' : "What's new?"} - style={styles.textInput}> - {textDecorated} - - - - - - - - 0} - items={autocompleteOptions} - onSelect={onSelectAutocompleteItem} - /> - - ) -} - -const atPrefixRegex = /@([\S]*)$/i -function extractTextAutocompletePrefix(text: string) { - const match = atPrefixRegex.exec(text) - if (match) { - return match[1] - } - return undefined -} -function replaceTextAutocompletePrefix(text: string, item: string) { - return text.replace(atPrefixRegex, `@${item} `) -} - -const styles = StyleSheet.create({ - outer: { - flexDirection: 'column', - backgroundColor: '#fff', - padding: 15, - height: '100%', - }, - topbar: { - flexDirection: 'row', - alignItems: 'center', - paddingTop: 10, - paddingBottom: 5, - paddingHorizontal: 5, - }, - postBtn: { - borderRadius: 20, - paddingHorizontal: 20, - paddingVertical: 6, - }, - errorLine: { - flexDirection: 'row', - backgroundColor: colors.red1, - borderRadius: 6, - paddingHorizontal: 8, - paddingVertical: 6, - marginVertical: 6, - }, - errorIcon: { - borderWidth: 1, - borderColor: colors.red4, - color: colors.red4, - borderRadius: 30, - width: 16, - height: 16, - alignItems: 'center', - justifyContent: 'center', - marginRight: 5, - }, - textInput: { - flex: 1, - padding: 5, - fontSize: 18, - }, -}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 6282b5af1..02b65a490 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -9,7 +9,6 @@ import * as models from '../../../state/models/shell' import * as LinkActionsModal from './LinkActions' import * as SharePostModal from './SharePost.native' -import * as ComposePostModal from './ComposePost' import * as EditProfile from './EditProfile' const CLOSED_SNAPPOINTS = ['10%'] @@ -51,13 +50,6 @@ export const Modal = observer(function Modal() { {...(store.shell.activeModal as models.SharePostModel)} /> ) - } else if (store.shell.activeModal?.name === 'compose-post') { - snapPoints = ComposePostModal.snapPoints - element = ( - - ) } else if (store.shell.activeModal?.name === 'edit-profile') { snapPoints = EditProfile.snapPoints element = ( diff --git a/src/view/com/modals/composer/Autocomplete.tsx b/src/view/com/modals/composer/Autocomplete.tsx deleted file mode 100644 index 4e4bdfc8e..000000000 --- a/src/view/com/modals/composer/Autocomplete.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, {useEffect} from 'react' -import { - useWindowDimensions, - Text, - TouchableOpacity, - StyleSheet, -} from 'react-native' -import Animated, { - useSharedValue, - useAnimatedStyle, - withTiming, - interpolate, -} from 'react-native-reanimated' -import {colors} from '../../../lib/styles' - -export function Autocomplete({ - active, - items, - onSelect, -}: { - active: boolean - items: string[] - onSelect: (item: string) => void -}) { - const winDim = useWindowDimensions() - const positionInterp = useSharedValue(0) - - useEffect(() => { - if (active) { - positionInterp.value = withTiming(1, {duration: 250}) - } else { - positionInterp.value = withTiming(0, {duration: 250}) - } - }, [positionInterp, active]) - - const topAnimStyle = useAnimatedStyle(() => ({ - top: interpolate( - positionInterp.value, - [0, 1.0], - [winDim.height, winDim.height / 4], - ), - })) - return ( - - {items.map((item, i) => ( - onSelect(item)}> - @{item} - - ))} - - ) -} - -const styles = StyleSheet.create({ - outer: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - backgroundColor: colors.white, - borderTopWidth: 1, - borderTopColor: colors.gray2, - }, - item: { - borderBottomWidth: 1, - borderBottomColor: colors.gray1, - paddingVertical: 16, - paddingHorizontal: 16, - }, - itemText: { - fontSize: 16, - }, -}) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 4f0683f09..90cffc029 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -6,7 +6,6 @@ import {AtUri} from '../../../third-party/uri' import * as PostType from '../../../third-party/api/src/types/app/bsky/post' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {PostThreadViewPostModel} from '../../../state/models/post-thread-view' -import {ComposePostModel} from '../../../state/models/shell' import {Link} from '../util/Link' import {RichText} from '../util/RichText' import {PostDropdownBtn} from '../util/DropdownBtn' @@ -49,12 +48,10 @@ export const PostThreadItem = observer(function PostThreadItem({ const repostsTitle = 'Reposts of this post' const onPressReply = () => { - store.shell.openModal( - new ComposePostModel({ - replyTo: {uri: item.uri, cid: item.cid}, - onPost: onPostReply, - }), - ) + store.shell.openComposer({ + replyTo: {uri: item.uri, cid: item.cid}, + onPost: onPostReply, + }) } const onPressToggleRepost = () => { item diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index b74bbfc42..b98274c1c 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -11,7 +11,6 @@ import { } from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {PostThreadViewModel} from '../../../state/models/post-thread-view' -import {ComposePostModel} from '../../../state/models/shell' import {Link} from '../util/Link' import {UserInfoText} from '../util/UserInfoText' import {RichText} from '../util/RichText' @@ -71,9 +70,7 @@ export const Post = observer(function Post({uri}: {uri: string}) { replyHref = `/profile/${urip.hostname}/post/${urip.rkey}` } const onPressReply = () => { - store.shell.openModal( - new ComposePostModel({replyTo: {uri: item.uri, cid: item.cid}}), - ) + store.shell.openComposer({replyTo: {uri: item.uri, cid: item.cid}}) } const onPressToggleRepost = () => { item diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index cfb7d7ed7..e591113d1 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -5,7 +5,7 @@ import {AtUri} from '../../../third-party/uri' import * as PostType from '../../../third-party/api/src/types/app/bsky/post' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FeedItemModel} from '../../../state/models/feed-view' -import {ComposePostModel, SharePostModel} from '../../../state/models/shell' +import {SharePostModel} from '../../../state/models/shell' import {Link} from '../util/Link' import {PostDropdownBtn} from '../util/DropdownBtn' import {UserInfoText} from '../util/UserInfoText' @@ -40,9 +40,7 @@ export const FeedItem = observer(function FeedItem({ }, [record.reply]) const onPressReply = () => { - store.shell.openModal( - new ComposePostModel({replyTo: {uri: item.uri, cid: item.cid}}), - ) + store.shell.openComposer({replyTo: {uri: item.uri, cid: item.cid}}) } const onPressToggleRepost = () => { item diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index 355a2cec1..178b01dc2 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -6,7 +6,6 @@ import {Feed} from '../com/posts/Feed' import {FAB} from '../com/util/FloatingActionButton' import {useStores} from '../../state' import {FeedModel} from '../../state/models/feed-view' -import {ComposePostModel} from '../../state/models/shell' import {ScreenParams} from '../routes' import {s} from '../lib/styles' @@ -46,7 +45,7 @@ export const Home = observer(function Home({ }, [visible, store]) const onComposePress = () => { - store.shell.openModal(new ComposePostModel({onPost: onCreatePost})) + store.shell.openComposer({onPost: onCreatePost}) } const onCreatePost = () => { defaultFeedView.loadLatest() diff --git a/src/view/shell/mobile/Composer.tsx b/src/view/shell/mobile/Composer.tsx new file mode 100644 index 000000000..62bc7304d --- /dev/null +++ b/src/view/shell/mobile/Composer.tsx @@ -0,0 +1,83 @@ +import React, {useEffect} from 'react' +import {observer} from 'mobx-react-lite' +import { + StyleSheet, + Text, + TouchableOpacity, + TouchableWithoutFeedback, + View, +} from 'react-native' +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + interpolate, + Easing, +} from 'react-native-reanimated' +import {IconProp} from '@fortawesome/fontawesome-svg-core' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {HomeIcon, UserGroupIcon, BellIcon} from '../../lib/icons' +import {ComposePost} from '../../com/composer/ComposePost' +import {useStores} from '../../../state' +import {ComposerOpts} from '../../../state/models/shell' +import {s, colors} from '../../lib/styles' + +export const Composer = observer( + ({ + active, + winHeight, + replyTo, + onPost, + onClose, + }: { + active: boolean + winHeight: number + replyTo?: ComposerOpts['replyTo'] + onPost?: ComposerOpts['onPost'] + onClose: () => void + }) => { + const store = useStores() + const initInterp = useSharedValue(0) + + useEffect(() => { + if (active) { + initInterp.value = withTiming(1, { + duration: 300, + easing: Easing.out(Easing.exp), + }) + } else { + initInterp.value = 0 + } + }, [initInterp, active]) + const wrapperAnimStyle = useAnimatedStyle(() => ({ + top: interpolate(initInterp.value, [0, 1.0], [winHeight, 0]), + })) + + // events + // = + + // rendering + // = + + if (!active) { + return + } + + return ( + + + + ) + }, +) + +const styles = StyleSheet.create({ + wrapper: { + position: 'absolute', + top: 0, + bottom: 0, + width: '100%', + backgroundColor: '#fff', + paddingTop: 20, + }, +}) diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index 7b5dd4e91..60188f89f 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -30,6 +30,7 @@ import {Login} from '../../screens/Login' import {Modal} from '../../com/modals/Modal' import {MainMenu} from './MainMenu' import {TabsSelector} from './TabsSelector' +import {Composer} from './Composer' import {s, colors} from '../../lib/styles' import {GridIcon, HomeIcon, BellIcon} from '../../lib/icons' @@ -217,6 +218,13 @@ export const MobileShell: React.FC = observer(() => { active={isTabsSelectorActive} onClose={() => setTabsSelectorActive(false)} /> + store.shell.closeComposer()} + winHeight={winDim.height} + replyTo={store.shell.composerOpts?.replyTo} + onPost={store.shell.composerOpts?.onPost} + /> ) }) -- cgit 1.4.1