From 8ae6e67eea8890f639dbaa9423e99cad5e28502f Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 1 Nov 2022 14:25:41 -0500 Subject: [PATCH] Rework the composer to a less buggy solution --- src/state/models/shell.ts | 48 +++++------ .../{modals => }/composer/Autocomplete.tsx | 0 .../com/{modals => composer}/ComposePost.tsx | 24 +++--- src/view/com/modals/Modal.tsx | 8 -- 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 ++ 10 files changed, 130 insertions(+), 66 deletions(-) rename src/view/com/{modals => }/composer/Autocomplete.tsx (100%) rename src/view/com/{modals => composer}/ComposePost.tsx (92%) create mode 100644 src/view/shell/mobile/Composer.tsx diff --git a/src/state/models/shell.ts b/src/state/models/shell.ts index 8cb0ff9e..33b8eef3 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/modals/composer/Autocomplete.tsx b/src/view/com/composer/Autocomplete.tsx similarity index 100% rename from src/view/com/modals/composer/Autocomplete.tsx rename to src/view/com/composer/Autocomplete.tsx diff --git a/src/view/com/modals/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx similarity index 92% rename from src/view/com/modals/ComposePost.tsx rename to src/view/com/composer/ComposePost.tsx index 806b5d7a..496b49a9 100644 --- a/src/view/com/modals/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -1,28 +1,28 @@ import React, {useEffect, useMemo, useState} from 'react' -import {StyleSheet, Text, TouchableOpacity, View} from 'react-native' -import {BottomSheetTextInput} from '@gorhom/bottom-sheet' +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 * as Post from '../../../third-party/api/src/types/app/bsky/post' -import {Autocomplete} from './composer/Autocomplete' +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 const snapPoints = ['100%'] -export function Component({ +export function ComposePost({ replyTo, onPost, + onClose, }: { - replyTo?: Post.PostRef - onPost?: () => void + replyTo?: ComposerOpts['replyTo'] + onPost?: ComposerOpts['onPost'] + onClose: () => void }) { const store = useStores() const [error, setError] = useState('') @@ -67,7 +67,7 @@ export function Component({ } } const onPressCancel = () => { - store.shell.closeModal() + onClose() } const onPressPublish = async () => { setError('') @@ -85,7 +85,7 @@ export function Component({ return } onPost?.() - store.shell.closeModal() + onClose() Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`, { duration: Toast.durations.LONG, position: Toast.positions.TOP, @@ -148,7 +148,7 @@ export function Component({ {error} )} - {textDecorated} - + diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index 6282b5af..02b65a49 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/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 4f0683f0..90cffc02 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 b74bbfc4..b98274c1 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 cfb7d7ed..e591113d 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 355a2cec..178b01dc 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 00000000..62bc7304 --- /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 7b5dd4e9..60188f89 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} + /> ) })