diff --git a/src/state/models/shell.ts b/src/state/models/shell.ts
index a2e83b5e3a..c67b474b7f 100644
--- a/src/state/models/shell.ts
+++ b/src/state/models/shell.ts
@@ -16,15 +16,23 @@ export class SharePostModel {
}
}
+export class ComposePostModel {
+ name = 'compose-post'
+
+ constructor(public replyTo?: string) {
+ makeAutoObservable(this)
+ }
+}
+
export class ShellModel {
isModalActive = false
- activeModal: LinkActionsModel | SharePostModel | undefined
+ activeModal: LinkActionsModel | SharePostModel | ComposePostModel | undefined
constructor() {
makeAutoObservable(this)
}
- openModal(modal: LinkActionsModel | SharePostModel) {
+ openModal(modal: LinkActionsModel | SharePostModel | ComposePostModel) {
this.isModalActive = true
this.activeModal = modal
}
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
deleted file mode 100644
index 6a15599d81..0000000000
--- a/src/view/com/composer/Composer.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, {useState, forwardRef, useImperativeHandle} from 'react'
-import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native'
-import Toast from '../util/Toast'
-import ProgressCircle from '../util/ProgressCircle'
-import {useStores} from '../../../state'
-import {s} from '../../lib/styles'
-import * as apilib from '../../../state/lib/api'
-
-const MAX_TEXT_LENGTH = 256
-const WARNING_TEXT_LENGTH = 200
-const DANGER_TEXT_LENGTH = 255
-
-export const Composer = forwardRef(function Composer(
- {
- replyTo,
- }: {
- replyTo: string | undefined
- },
- ref,
-) {
- const store = useStores()
- const [text, setText] = useState('')
-
- const onChangeText = (newText: string) => {
- if (newText.length > MAX_TEXT_LENGTH) {
- setText(newText.slice(0, MAX_TEXT_LENGTH))
- } else {
- setText(newText)
- }
- }
-
- useImperativeHandle(ref, () => ({
- async publish() {
- if (text.trim().length === 0) {
- return false
- }
- await apilib.post(store.api, 'alice.com', text, replyTo)
- Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been created`, {
- duration: Toast.durations.LONG,
- position: Toast.positions.TOP,
- shadow: true,
- animation: true,
- hideOnPress: true,
- })
- return true
- },
- }))
-
- const progressColor =
- text.length > DANGER_TEXT_LENGTH
- ? '#e60000'
- : text.length > WARNING_TEXT_LENGTH
- ? '#f7c600'
- : undefined
-
- return (
-
- onChangeText(text)}
- value={text}
- placeholder={replyTo ? 'Write your reply' : "What's new in the scene?"}
- style={styles.textInput}
- />
-
-
-
-
-
-
-
- )
-})
-
-const styles = StyleSheet.create({
- outer: {
- flexDirection: 'column',
- backgroundColor: '#fff',
- padding: 10,
- height: '100%',
- },
- textInput: {
- flex: 1,
- padding: 10,
- },
-})
diff --git a/src/view/com/feed/FeedItem.tsx b/src/view/com/feed/FeedItem.tsx
index 52d162a62e..e9cf833463 100644
--- a/src/view/com/feed/FeedItem.tsx
+++ b/src/view/com/feed/FeedItem.tsx
@@ -4,6 +4,7 @@ import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {bsky, AdxUri} from '@adxp/mock-api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {FeedViewItemModel} from '../../../state/models/feed-view'
+import {ComposePostModel} from '../../../state/models/shell'
import {Link} from '../util/Link'
import {PostDropdownBtn} from '../util/DropdownBtn'
import {s, colors} from '../../lib/styles'
@@ -28,7 +29,7 @@ export const FeedItem = observer(function FeedItem({
const authorHref = `/profile/${item.author.name}`
const onPressReply = () => {
- store.nav.navigate('/composer')
+ store.shell.openModal(new ComposePostModel(item.uri))
}
const onPressToggleRepost = () => {
item
diff --git a/src/view/com/modals/ComposePost.tsx b/src/view/com/modals/ComposePost.tsx
new file mode 100644
index 0000000000..253db37710
--- /dev/null
+++ b/src/view/com/modals/ComposePost.tsx
@@ -0,0 +1,165 @@
+import React, {useState} from 'react'
+import {
+ KeyboardAvoidingView,
+ StyleSheet,
+ Text,
+ TextInput,
+ 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 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}: {replyTo?: string}) {
+ const store = useStores()
+ const [text, setText] = useState('')
+ const [error, setError] = useState('')
+
+ const onChangeText = (newText: string) => {
+ if (newText.length > MAX_TEXT_LENGTH) {
+ setText(newText.slice(0, MAX_TEXT_LENGTH))
+ } else {
+ setText(newText)
+ }
+ }
+ 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.api, 'alice.com', 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
+ }
+ 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 progressColor =
+ text.length > DANGER_TEXT_LENGTH
+ ? '#e60000'
+ : text.length > WARNING_TEXT_LENGTH
+ ? '#f7c600'
+ : undefined
+
+ return (
+
+
+
+ Cancel
+
+
+
+
+ Post
+
+
+
+ {error !== '' && (
+
+
+
+
+ {error}
+
+ )}
+ onChangeText(text)}
+ value={text}
+ placeholder={replyTo ? 'Write your reply' : "What's new?"}
+ style={styles.textInput}
+ />
+
+
+
+
+
+
+
+ )
+}
+
+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 dc5b719bc1..6e08460003 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -5,8 +5,11 @@ import BottomSheet from '@gorhom/bottom-sheet'
import {useStores} from '../../../state'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
+import * as models from '../../../state/models/shell'
+
import * as LinkActionsModal from './LinkActions'
import * as SharePostModal from './SharePost.native'
+import * as ComposePostModal from './ComposePost'
export const Modal = observer(function Modal() {
const store = useStores()
@@ -28,10 +31,25 @@ export const Modal = observer(function Modal() {
let snapPoints, element
if (store.shell.activeModal?.name === 'link-actions') {
snapPoints = LinkActionsModal.snapPoints
- element =
+ element = (
+
+ )
} else if (store.shell.activeModal?.name === 'share-post') {
snapPoints = SharePostModal.snapPoints
- element =
+ element = (
+
+ )
+ } else if (store.shell.activeModal?.name === 'compose-post') {
+ snapPoints = ComposePostModal.snapPoints
+ element = (
+
+ )
} else {
return
}
@@ -41,6 +59,7 @@ export const Modal = observer(function Modal() {
ref={bottomSheetRef}
snapPoints={snapPoints}
enablePanDownToClose
+ keyboardBehavior="fillParent"
backdropComponent={createCustomBackdrop(onClose)}
onChange={onShareBottomSheetChange}>
{element}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 53ae8e548f..d500514efa 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -4,6 +4,7 @@ import {Image, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
import {bsky, AdxUri} from '@adxp/mock-api'
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 {PostDropdownBtn} from '../util/DropdownBtn'
import {s, colors} from '../../lib/styles'
@@ -41,7 +42,7 @@ export const PostThreadItem = observer(function PostThreadItem({
const repostsTitle = 'Reposts of this post'
const onPressReply = () => {
- store.nav.navigate(`/composer?replyTo=${item.uri}`)
+ store.shell.openModal(new ComposePostModel(item.uri))
}
const onPressToggleRepost = () => {
item
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 2de7432bdd..a6580fa5a6 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -11,6 +11,7 @@ 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 {useStores} from '../../../state'
import {s, colors} from '../../lib/styles'
@@ -63,7 +64,7 @@ export const Post = observer(function Post({uri}: {uri: string}) {
const authorHref = `/profile/${item.author.name}`
const authorTitle = item.author.name
const onPressReply = () => {
- store.nav.navigate(`/composer?replyTo=${item.uri}`)
+ store.shell.openModal(new ComposePostModel(item.uri))
}
const onPressToggleRepost = () => {
item
diff --git a/src/view/index.ts b/src/view/index.ts
index 3dcadf6768..af24030c82 100644
--- a/src/view/index.ts
+++ b/src/view/index.ts
@@ -14,6 +14,7 @@ import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'
import {faClone} from '@fortawesome/free-regular-svg-icons/faClone'
import {faComment} from '@fortawesome/free-regular-svg-icons/faComment'
import {faEllipsis} from '@fortawesome/free-solid-svg-icons/faEllipsis'
+import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation'
import {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart'
import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart'
@@ -46,6 +47,7 @@ export function setup() {
faClone,
faComment,
faEllipsis,
+ faExclamation,
faGear,
faHeart,
fasHeart,
diff --git a/src/view/routes.ts b/src/view/routes.ts
index 293d53e30e..d31dbae354 100644
--- a/src/view/routes.ts
+++ b/src/view/routes.ts
@@ -6,7 +6,6 @@ import {Notifications} from './screens/Notifications'
import {Login} from './screens/Login'
import {Signup} from './screens/Signup'
import {NotFound} from './screens/NotFound'
-import {Composer} from './screens/Composer'
import {PostThread} from './screens/PostThread'
import {PostLikedBy} from './screens/PostLikedBy'
import {PostRepostedBy} from './screens/PostRepostedBy'
@@ -48,7 +47,6 @@ export const routes: Route[] = [
'retweet',
r('/profile/(?[^/]+)/post/(?[^/]+)/reposted-by'),
],
- [Composer, 'pen-nib', r('/compose')],
[Login, ['far', 'user'], r('/login')],
[Signup, ['far', 'user'], r('/signup')],
]
diff --git a/src/view/screens/Composer.tsx b/src/view/screens/Composer.tsx
deleted file mode 100644
index 2de84583f6..0000000000
--- a/src/view/screens/Composer.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React, {useLayoutEffect, useRef} from 'react'
-// import {Text, TouchableOpacity} from 'react-native'
-// import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {Composer as ComposerComponent} from '../com/composer/Composer'
-import {ScreenParams} from '../routes'
-
-export const Composer = ({params}: ScreenParams) => {
- const {replyTo} = params
- const ref = useRef<{publish: () => Promise}>()
-
- // TODO
- // useLayoutEffect(() => {
- // navigation.setOptions({
- // headerShown: true,
- // headerTitle: replyTo ? 'Reply' : 'New Post',
- // headerLeft: () => (
- // navigation.goBack()}>
- //
- //
- // ),
- // headerRight: () => (
- // {
- // if (!ref.current) {
- // return
- // }
- // ref.current.publish().then(
- // posted => {
- // if (posted) {
- // navigation.goBack()
- // }
- // },
- // err => console.error('Failed to create post', err),
- // )
- // }}>
- // Post
- //
- // ),
- // })
- // }, [navigation, replyTo, ref])
-
- return
-}
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index b29e042bdd..27a17d0e9f 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -5,6 +5,7 @@ import {Feed} from '../com/feed/Feed'
import {FAB} from '../com/util/FloatingActionButton'
import {useStores} from '../../state'
import {FeedViewModel} from '../../state/models/feed-view'
+import {ComposePostModel} from '../../state/models/shell'
import {ScreenParams} from '../routes'
import {s} from '../lib/styles'
@@ -30,7 +31,7 @@ export const Home = observer(function Home({visible}: ScreenParams) {
}, [visible, store])
const onComposePress = () => {
- store.nav.navigate('/compose')
+ store.shell.openModal(new ComposePostModel())
}
return (
diff --git a/todos.txt b/todos.txt
index fd68f690a2..e9c96c8086 100644
--- a/todos.txt
+++ b/todos.txt
@@ -1,7 +1,7 @@
Paul's todo list
- Composer
- - Check on navigation stack during a bunch of replies
+ - Update the view after creating a post
- Search view
- *
- Linking