diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-07-22 16:06:51 -0500 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-07-22 16:06:51 -0500 |
commit | 7f04ac172e8ada1244de1df2064e32d32f1c2348 (patch) | |
tree | dcecdfcad0746a296b38e8d3acb5dd983589d6b0 /src | |
parent | ce83648f9da3a93018fc7845bec1d35c1519028d (diff) | |
download | voidsky-7f04ac172e8ada1244de1df2064e32d32f1c2348.tar.zst |
Add post composer
Diffstat (limited to 'src')
-rw-r--r-- | src/state/lib/api.ts | 46 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 88 | ||||
-rw-r--r-- | src/view/com/feed/FeedItem.tsx | 9 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 9 | ||||
-rw-r--r-- | src/view/index.ts | 4 | ||||
-rw-r--r-- | src/view/lib/styles.ts | 21 | ||||
-rw-r--r-- | src/view/routes/index.tsx | 27 | ||||
-rw-r--r-- | src/view/routes/types.ts | 1 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 27 | ||||
-rw-r--r-- | src/view/screens/stacks/Composer.tsx | 50 | ||||
-rw-r--r-- | src/view/screens/stacks/PostLikedBy.tsx (renamed from src/view/screens/content/PostLikedBy.tsx) | 0 | ||||
-rw-r--r-- | src/view/screens/stacks/PostRepostedBy.tsx (renamed from src/view/screens/content/PostRepostedBy.tsx) | 0 | ||||
-rw-r--r-- | src/view/screens/stacks/PostThread.tsx (renamed from src/view/screens/content/PostThread.tsx) | 0 | ||||
-rw-r--r-- | src/view/screens/stacks/Profile.tsx (renamed from src/view/screens/content/Profile.tsx) | 0 | ||||
-rw-r--r-- | src/view/screens/tabroots/Home.tsx | 59 | ||||
-rw-r--r-- | src/view/screens/tabroots/Login.tsx (renamed from src/view/screens/Login.tsx) | 2 | ||||
-rw-r--r-- | src/view/screens/tabroots/Menu.tsx (renamed from src/view/screens/Menu.tsx) | 4 | ||||
-rw-r--r-- | src/view/screens/tabroots/NotFound.tsx (renamed from src/view/screens/NotFound.tsx) | 4 | ||||
-rw-r--r-- | src/view/screens/tabroots/Notifications.tsx (renamed from src/view/screens/Notifications.tsx) | 4 | ||||
-rw-r--r-- | src/view/screens/tabroots/Search.tsx (renamed from src/view/screens/Search.tsx) | 4 | ||||
-rw-r--r-- | src/view/screens/tabroots/Signup.tsx (renamed from src/view/screens/Signup.tsx) | 2 |
21 files changed, 301 insertions, 60 deletions
diff --git a/src/state/lib/api.ts b/src/state/lib/api.ts index b3992544b..bb3ef5d1a 100644 --- a/src/state/lib/api.ts +++ b/src/state/lib/api.ts @@ -28,12 +28,46 @@ export async function setup(adx: AdxClient) { ) } +export async function post( + adx: AdxClient, + user: string, + text: string, + replyToUri?: string, +) { + let reply + if (replyToUri) { + const replyToUrip = new AdxUri(replyToUri) + const parentPost = await adx + .repo(replyToUrip.host, false) + .collection(replyToUrip.collection) + .get('Post', replyToUrip.recordKey) + if (parentPost) { + reply = { + root: parentPost.value.reply?.root || parentPost.uri, + parent: parentPost.uri, + } + } + } + return await adx + .repo(user, true) + .collection('blueskyweb.xyz:Posts') + .create('Post', { + $type: 'blueskyweb.xyz:Post', + text, + reply, + createdAt: new Date().toISOString(), + }) +} + export async function like(adx: AdxClient, user: string, uri: string) { - await adx.repo(user, true).collection('blueskyweb.xyz:Likes').create('Like', { - $type: 'blueskyweb.xyz:Like', - subject: uri, - createdAt: new Date().toISOString(), - }) + return await adx + .repo(user, true) + .collection('blueskyweb.xyz:Likes') + .create('Like', { + $type: 'blueskyweb.xyz:Like', + subject: uri, + createdAt: new Date().toISOString(), + }) } export async function unlike(adx: AdxClient, user: string, uri: string) { @@ -45,7 +79,7 @@ export async function unlike(adx: AdxClient, user: string, uri: string) { } export async function repost(adx: AdxClient, user: string, uri: string) { - await adx + return await adx .repo(user, true) .collection('blueskyweb.xyz:Posts') .create('Repost', { diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx new file mode 100644 index 000000000..5a6ad5215 --- /dev/null +++ b/src/view/com/composer/Composer.tsx @@ -0,0 +1,88 @@ +import React, {useState, forwardRef, useImperativeHandle} from 'react' +import {observer} from 'mobx-react-lite' +import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native' +// @ts-ignore no type definition -prf +import ProgressCircle from 'react-native-progress/Circle' +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 = observer( + 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) + 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 6ba1401c9..9f3ec7c56 100644 --- a/src/view/com/feed/FeedItem.tsx +++ b/src/view/com/feed/FeedItem.tsx @@ -30,6 +30,11 @@ export const FeedItem = observer(function FeedItem({ name: item.author.name, }) } + const onPressReply = () => { + onNavigateContent('Composer', { + replyTo: item.uri, + }) + } const onPressToggleRepost = () => { item .toggleRepost() @@ -78,13 +83,13 @@ export const FeedItem = observer(function FeedItem({ {record.text} </Text> <View style={styles.ctrls}> - <View style={styles.ctrl}> + <TouchableOpacity style={styles.ctrl} onPress={onPressReply}> <FontAwesomeIcon style={styles.ctrlIcon} icon={['far', 'comment']} /> <Text>{item.replyCount}</Text> - </View> + </TouchableOpacity> <TouchableOpacity style={styles.ctrl} onPress={onPressToggleRepost}> <FontAwesomeIcon style={ diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 7263c61b3..bd22ecf9a 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -54,6 +54,11 @@ export const PostThreadItem = observer(function PostThreadItem({ recordKey: urip.recordKey, }) } + const onPressReply = () => { + onNavigateContent('Composer', { + replyTo: item.uri, + }) + } const onPressToggleRepost = () => { item .toggleRepost() @@ -129,13 +134,13 @@ export const PostThreadItem = observer(function PostThreadItem({ <></> )} <View style={styles.ctrls}> - <View style={styles.ctrl}> + <TouchableOpacity style={styles.ctrl} onPress={onPressReply}> <FontAwesomeIcon style={styles.ctrlIcon} icon={['far', 'comment']} /> <Text>{item.replyCount}</Text> - </View> + </TouchableOpacity> <TouchableOpacity style={styles.ctrl} onPress={onPressToggleRepost}> <FontAwesomeIcon style={ diff --git a/src/view/index.ts b/src/view/index.ts index c80e929c1..4a469d263 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -9,8 +9,10 @@ import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart' import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart' import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse' import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass' +import {faPlus} from '@fortawesome/free-solid-svg-icons/faPlus' import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSquare' import {faRetweet} from '@fortawesome/free-solid-svg-icons/faRetweet' +import {faX} from '@fortawesome/free-solid-svg-icons/faX' export function setup() { moment.updateLocale('en', { @@ -41,8 +43,10 @@ export function setup() { faHeart, fasHeart, faHouse, + faPlus, faMagnifyingGlass, faRetweet, faShareFromSquare, + faX, ) } diff --git a/src/view/lib/styles.ts b/src/view/lib/styles.ts index f0796723c..2ae928119 100644 --- a/src/view/lib/styles.ts +++ b/src/view/lib/styles.ts @@ -34,7 +34,7 @@ export const s = StyleSheet.create({ // colors black: {color: 'black'}, gray: {color: 'gray'}, - blue: {color: 'blue'}, + blue: {color: '#006bf7'}, green: {color: 'green'}, red: {color: 'red'}, @@ -52,6 +52,25 @@ export const s = StyleSheet.create({ mb5: {marginBottom: 5}, mb10: {marginBottom: 10}, + // paddings + p2: {padding: 2}, + p5: {padding: 5}, + p10: {padding: 10}, + pr2: {paddingRight: 2}, + pr5: {paddingRight: 5}, + pr10: {paddingRight: 10}, + pl2: {paddingLeft: 2}, + pl5: {paddingLeft: 5}, + pl10: {paddingLeft: 10}, + pt2: {paddingTop: 2}, + pt5: {paddingTop: 5}, + pt10: {paddingTop: 10}, + pb2: {paddingBottom: 2}, + pb5: {paddingBottom: 5}, + pb10: {paddingBottom: 10}, + // flex flexRow: {flexDirection: 'row'}, + flexCol: {flexDirection: 'column'}, + flex1: {flex: 1}, }) diff --git a/src/view/routes/index.tsx b/src/view/routes/index.tsx index 989fda470..23340a7fe 100644 --- a/src/view/routes/index.tsx +++ b/src/view/routes/index.tsx @@ -13,17 +13,18 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import type {RootTabsParamList} from './types' import {useStores} from '../../state' import * as platform from '../../platform/detection' -import {Home} from '../screens/Home' -import {Search} from '../screens/Search' -import {Notifications} from '../screens/Notifications' -import {Menu} from '../screens/Menu' -import {Profile} from '../screens/content/Profile' -import {PostThread} from '../screens/content/PostThread' -import {PostLikedBy} from '../screens/content/PostLikedBy' -import {PostRepostedBy} from '../screens/content/PostRepostedBy' -import {Login} from '../screens/Login' -import {Signup} from '../screens/Signup' -import {NotFound} from '../screens/NotFound' +import {Home} from '../screens/tabroots/Home' +import {Search} from '../screens/tabroots/Search' +import {Notifications} from '../screens/tabroots/Notifications' +import {Menu} from '../screens/tabroots/Menu' +import {Login} from '../screens/tabroots/Login' +import {Signup} from '../screens/tabroots/Signup' +import {NotFound} from '../screens/tabroots/NotFound' +import {Composer} from '../screens/stacks/Composer' +import {PostThread} from '../screens/stacks/PostThread' +import {PostLikedBy} from '../screens/stacks/PostLikedBy' +import {PostRepostedBy} from '../screens/stacks/PostRepostedBy' +import {Profile} from '../screens/stacks/Profile' const linking: LinkingOptions<RootTabsParamList> = { prefixes: [ @@ -42,6 +43,7 @@ const linking: LinkingOptions<RootTabsParamList> = { PostThread: 'profile/:name/post/:recordKey', PostLikedBy: 'profile/:name/post/:recordKey/liked-by', PostRepostedBy: 'profile/:name/post/:recordKey/reposted-by', + Composer: 'compose', Login: 'login', Signup: 'signup', NotFound: '*', @@ -88,7 +90,8 @@ const HIDE_TAB = {tabBarButton: () => null} function HomeStackCom() { return ( <HomeTabStack.Navigator> - <HomeTabStack.Screen name="Home" component={Home} options={HIDE_HEADER} /> + <HomeTabStack.Screen name="Home" component={Home} /> + <HomeTabStack.Screen name="Composer" component={Composer} /> <HomeTabStack.Screen name="Profile" component={Profile} /> <HomeTabStack.Screen name="PostThread" component={PostThread} /> <HomeTabStack.Screen name="PostLikedBy" component={PostLikedBy} /> diff --git a/src/view/routes/types.ts b/src/view/routes/types.ts index fd58a7666..2a9e1046a 100644 --- a/src/view/routes/types.ts +++ b/src/view/routes/types.ts @@ -9,6 +9,7 @@ export type RootTabsParamList = { PostThread: {name: string; recordKey: string} PostLikedBy: {name: string; recordKey: string} PostRepostedBy: {name: string; recordKey: string} + Composer: {replyTo?: string} Login: undefined Signup: undefined NotFound: undefined diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx deleted file mode 100644 index 1b41b2d35..000000000 --- a/src/view/screens/Home.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, {useEffect} from 'react' -import {View} from 'react-native' -import {Shell} from '../shell' -import {Feed} from '../com/feed/Feed' -import type {RootTabsScreenProps} from '../routes/types' -import {useStores} from '../../state' - -export function Home({navigation}: RootTabsScreenProps<'HomeTab'>) { - const store = useStores() - useEffect(() => { - console.log('Fetching home feed') - store.homeFeed.setup() - }, [store.homeFeed]) - - const onNavigateContent = (screen: string, props: Record<string, string>) => { - // @ts-ignore it's up to the callers to supply correct params -prf - navigation.navigate(screen, props) - } - - return ( - <Shell> - <View> - <Feed feed={store.homeFeed} onNavigateContent={onNavigateContent} /> - </View> - </Shell> - ) -} diff --git a/src/view/screens/stacks/Composer.tsx b/src/view/screens/stacks/Composer.tsx new file mode 100644 index 000000000..e1b36567a --- /dev/null +++ b/src/view/screens/stacks/Composer.tsx @@ -0,0 +1,50 @@ +import React, {useLayoutEffect, useRef} from 'react' +import {Text, TouchableOpacity} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {Shell} from '../../shell' +import type {RootTabsScreenProps} from '../../routes/types' +import {Composer as ComposerComponent} from '../../com/composer/Composer' + +export const Composer = ({ + navigation, + route, +}: RootTabsScreenProps<'Composer'>) => { + const {replyTo} = route.params + const ref = useRef<{publish: () => Promise<boolean>}>() + + useLayoutEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: replyTo ? 'Reply' : 'New Post', + headerLeft: () => ( + <TouchableOpacity onPress={() => navigation.goBack()}> + <FontAwesomeIcon icon="x" /> + </TouchableOpacity> + ), + headerRight: () => ( + <TouchableOpacity + onPress={() => { + if (!ref.current) { + return + } + ref.current.publish().then( + posted => { + if (posted) { + navigation.goBack() + } + }, + err => console.error('Failed to create post', err), + ) + }}> + <Text>Post</Text> + </TouchableOpacity> + ), + }) + }, [navigation, replyTo, ref]) + + return ( + <Shell> + <ComposerComponent ref={ref} replyTo={replyTo} /> + </Shell> + ) +} diff --git a/src/view/screens/content/PostLikedBy.tsx b/src/view/screens/stacks/PostLikedBy.tsx index f12990141..f12990141 100644 --- a/src/view/screens/content/PostLikedBy.tsx +++ b/src/view/screens/stacks/PostLikedBy.tsx diff --git a/src/view/screens/content/PostRepostedBy.tsx b/src/view/screens/stacks/PostRepostedBy.tsx index 000c1a7fc..000c1a7fc 100644 --- a/src/view/screens/content/PostRepostedBy.tsx +++ b/src/view/screens/stacks/PostRepostedBy.tsx diff --git a/src/view/screens/content/PostThread.tsx b/src/view/screens/stacks/PostThread.tsx index 485a2e49a..485a2e49a 100644 --- a/src/view/screens/content/PostThread.tsx +++ b/src/view/screens/stacks/PostThread.tsx diff --git a/src/view/screens/content/Profile.tsx b/src/view/screens/stacks/Profile.tsx index ccdaed4a4..ccdaed4a4 100644 --- a/src/view/screens/content/Profile.tsx +++ b/src/view/screens/stacks/Profile.tsx diff --git a/src/view/screens/tabroots/Home.tsx b/src/view/screens/tabroots/Home.tsx new file mode 100644 index 000000000..446a5a7e9 --- /dev/null +++ b/src/view/screens/tabroots/Home.tsx @@ -0,0 +1,59 @@ +import React, {useEffect, useLayoutEffect} from 'react' +import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {Shell} from '../../shell' +import {Feed} from '../../com/feed/Feed' +import type {RootTabsScreenProps} from '../../routes/types' +import {useStores} from '../../../state' +import {AVIS} from '../../lib/assets' + +export function Home({navigation}: RootTabsScreenProps<'HomeTab'>) { + const store = useStores() + useEffect(() => { + console.log('Fetching home feed') + store.homeFeed.setup() + }, [store.homeFeed]) + + const onNavigateContent = (screen: string, props: Record<string, string>) => { + // @ts-ignore it's up to the callers to supply correct params -prf + navigation.navigate(screen, props) + } + + useLayoutEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: 'V I B E', + headerLeft: () => ( + <TouchableOpacity + onPress={() => navigation.push('Profile', {name: 'alice.com'})}> + <Image source={AVIS['alice.com']} style={styles.avi} /> + </TouchableOpacity> + ), + headerRight: () => ( + <TouchableOpacity + onPress={() => { + navigation.push('Composer', {}) + }}> + <FontAwesomeIcon icon="plus" style={{color: '#006bf7'}} /> + </TouchableOpacity> + ), + }) + }, [navigation]) + + return ( + <Shell> + <View> + <Feed feed={store.homeFeed} onNavigateContent={onNavigateContent} /> + </View> + </Shell> + ) +} + +const styles = StyleSheet.create({ + avi: { + width: 20, + height: 20, + borderRadius: 10, + resizeMode: 'cover', + }, +}) diff --git a/src/view/screens/Login.tsx b/src/view/screens/tabroots/Login.tsx index d08a5a256..a5f670bdd 100644 --- a/src/view/screens/Login.tsx +++ b/src/view/screens/tabroots/Login.tsx @@ -1,7 +1,7 @@ import React from 'react' import {Text, View} from 'react-native' import {observer} from 'mobx-react-lite' -import {Shell} from '../shell' +import {Shell} from '../../shell' // import type {RootTabsScreenProps} from '../routes/types' // import {useStores} from '../../state' diff --git a/src/view/screens/Menu.tsx b/src/view/screens/tabroots/Menu.tsx index d0cc0826f..dca5ad33b 100644 --- a/src/view/screens/Menu.tsx +++ b/src/view/screens/tabroots/Menu.tsx @@ -1,7 +1,7 @@ import React from 'react' -import {Shell} from '../shell' +import {Shell} from '../../shell' import {ScrollView, Text, View} from 'react-native' -import type {RootTabsScreenProps} from '../routes/types' +import type {RootTabsScreenProps} from '../../routes/types' export const Menu = (_props: RootTabsScreenProps<'MenuTab'>) => { return ( diff --git a/src/view/screens/NotFound.tsx b/src/view/screens/tabroots/NotFound.tsx index 5357a428a..a35808cbc 100644 --- a/src/view/screens/NotFound.tsx +++ b/src/view/screens/tabroots/NotFound.tsx @@ -1,7 +1,7 @@ import React from 'react' -import {Shell} from '../shell' +import {Shell} from '../../shell' import {Text, Button, View} from 'react-native' -import type {RootTabsScreenProps} from '../routes/types' +import type {RootTabsScreenProps} from '../../routes/types' export const NotFound = ({navigation}: RootTabsScreenProps<'NotFound'>) => { return ( diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/tabroots/Notifications.tsx index a7918f177..091410ad8 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/tabroots/Notifications.tsx @@ -1,7 +1,7 @@ import React from 'react' -import {Shell} from '../shell' +import {Shell} from '../../shell' import {Text, View} from 'react-native' -import type {RootTabsScreenProps} from '../routes/types' +import type {RootTabsScreenProps} from '../../routes/types' export const Notifications = ( _props: RootTabsScreenProps<'NotificationsTab'>, diff --git a/src/view/screens/Search.tsx b/src/view/screens/tabroots/Search.tsx index 26df2954c..044ca749c 100644 --- a/src/view/screens/Search.tsx +++ b/src/view/screens/tabroots/Search.tsx @@ -1,7 +1,7 @@ import React from 'react' -import {Shell} from '../shell' +import {Shell} from '../../shell' import {Text, View} from 'react-native' -import type {RootTabsScreenProps} from '../routes/types' +import type {RootTabsScreenProps} from '../../routes/types' export const Search = (_props: RootTabsScreenProps<'SearchTab'>) => { return ( diff --git a/src/view/screens/Signup.tsx b/src/view/screens/tabroots/Signup.tsx index 4a8c5df2d..dc2af2b1e 100644 --- a/src/view/screens/Signup.tsx +++ b/src/view/screens/tabroots/Signup.tsx @@ -1,7 +1,7 @@ import React from 'react' import {Text, View} from 'react-native' import {observer} from 'mobx-react-lite' -import {Shell} from '../shell' +import {Shell} from '../../shell' // import type {RootTabsScreenProps} from '../routes/types' // import {useStores} from '../../state' |