diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/composer/Composer.tsx | 90 | ||||
-rw-r--r-- | src/view/com/feed/FeedItem.tsx | 3 | ||||
-rw-r--r-- | src/view/com/modals/ComposePost.tsx | 165 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 23 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 3 | ||||
-rw-r--r-- | src/view/com/post/Post.tsx | 3 |
6 files changed, 192 insertions, 95 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx deleted file mode 100644 index 6a15599d8..000000000 --- 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 ( - <KeyboardAvoidingView style={styles.outer} behavior="padding"> - <TextInput - multiline - scrollEnabled - onChangeText={text => onChangeText(text)} - value={text} - placeholder={replyTo ? 'Write your reply' : "What's new in the scene?"} - style={styles.textInput} - /> - <View style={[s.flexRow, s.pt10, s.pb10, s.pr5]}> - <View style={s.flex1} /> - <View> - <ProgressCircle - color={progressColor} - progress={text.length / MAX_TEXT_LENGTH} - /> - </View> - </View> - </KeyboardAvoidingView> - ) -}) - -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 52d162a62..e9cf83346 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 000000000..253db3771 --- /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 ( + <View style={styles.outer}> + <View style={styles.topbar}> + <TouchableOpacity onPress={onPressCancel}> + <Text style={[s.blue3, s.f16]}>Cancel</Text> + </TouchableOpacity> + <View style={s.flex1} /> + <TouchableOpacity onPress={onPressPublish}> + <LinearGradient + colors={[gradients.primary.start, gradients.primary.end]} + start={{x: 0, y: 0}} + end={{x: 1, y: 1}} + style={styles.postBtn}> + <Text style={[s.white, s.f16, s.semiBold]}>Post</Text> + </LinearGradient> + </TouchableOpacity> + </View> + {error !== '' && ( + <View style={styles.errorLine}> + <View style={styles.errorIcon}> + <FontAwesomeIcon + icon="exclamation" + style={{color: colors.red4}} + size={10} + /> + </View> + <Text style={s.red4}>{error}</Text> + </View> + )} + <BottomSheetTextInput + multiline + scrollEnabled + autoFocus + onChangeText={(text: string) => onChangeText(text)} + value={text} + placeholder={replyTo ? 'Write your reply' : "What's new?"} + style={styles.textInput} + /> + <View style={[s.flexRow, s.pt10, s.pb10, s.pr5]}> + <View style={s.flex1} /> + <View> + <ProgressCircle + color={progressColor} + progress={text.length / MAX_TEXT_LENGTH} + /> + </View> + </View> + </View> + ) +} + +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 dc5b719bc..6e0846000 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 = <LinkActionsModal.Component {...store.shell.activeModal} /> + element = ( + <LinkActionsModal.Component + {...(store.shell.activeModal as models.LinkActionsModel)} + /> + ) } else if (store.shell.activeModal?.name === 'share-post') { snapPoints = SharePostModal.snapPoints - element = <SharePostModal.Component {...store.shell.activeModal} /> + element = ( + <SharePostModal.Component + {...(store.shell.activeModal as models.SharePostModel)} + /> + ) + } else if (store.shell.activeModal?.name === 'compose-post') { + snapPoints = ComposePostModal.snapPoints + element = ( + <ComposePostModal.Component + {...(store.shell.activeModal as models.ComposePostModel)} + /> + ) } else { return <View /> } @@ -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 53ae8e548..d500514ef 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 2de7432bd..a6580fa5a 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 |