diff --git a/package.json b/package.json
index e6c7fed05a..703548e566 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"react-dom": "17.0.2",
"react-native": "0.68.2",
"react-native-inappbrowser-reborn": "^3.6.3",
+ "react-native-progress": "^5.0.0",
"react-native-safe-area-context": "^4.3.1",
"react-native-screens": "^3.13.1",
"react-native-svg": "^12.4.0",
diff --git a/src/state/lib/api.ts b/src/state/lib/api.ts
index b3992544ba..bb3ef5d1ad 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 0000000000..5a6ad52151
--- /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 (
+
+ 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 6ba1401c96..9f3ec7c56c 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}
-
+
{item.replyCount}
-
+
{
+ onNavigateContent('Composer', {
+ replyTo: item.uri,
+ })
+ }
const onPressToggleRepost = () => {
item
.toggleRepost()
@@ -129,13 +134,13 @@ export const PostThreadItem = observer(function PostThreadItem({
<>>
)}
-
+
{item.replyCount}
-
+
= {
prefixes: [
@@ -42,6 +43,7 @@ const linking: LinkingOptions = {
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 (
-
+
+
diff --git a/src/view/routes/types.ts b/src/view/routes/types.ts
index fd58a7666e..2a9e1046a3 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 1b41b2d35d..0000000000
--- 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) => {
- // @ts-ignore it's up to the callers to supply correct params -prf
- navigation.navigate(screen, props)
- }
-
- return (
-
-
-
-
-
- )
-}
diff --git a/src/view/screens/stacks/Composer.tsx b/src/view/screens/stacks/Composer.tsx
new file mode 100644
index 0000000000..e1b36567ac
--- /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}>()
+
+ 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/content/PostLikedBy.tsx b/src/view/screens/stacks/PostLikedBy.tsx
similarity index 100%
rename from src/view/screens/content/PostLikedBy.tsx
rename to src/view/screens/stacks/PostLikedBy.tsx
diff --git a/src/view/screens/content/PostRepostedBy.tsx b/src/view/screens/stacks/PostRepostedBy.tsx
similarity index 100%
rename from src/view/screens/content/PostRepostedBy.tsx
rename to src/view/screens/stacks/PostRepostedBy.tsx
diff --git a/src/view/screens/content/PostThread.tsx b/src/view/screens/stacks/PostThread.tsx
similarity index 100%
rename from src/view/screens/content/PostThread.tsx
rename to src/view/screens/stacks/PostThread.tsx
diff --git a/src/view/screens/content/Profile.tsx b/src/view/screens/stacks/Profile.tsx
similarity index 100%
rename from src/view/screens/content/Profile.tsx
rename to 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 0000000000..446a5a7e97
--- /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) => {
+ // @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: () => (
+ navigation.push('Profile', {name: 'alice.com'})}>
+
+
+ ),
+ headerRight: () => (
+ {
+ navigation.push('Composer', {})
+ }}>
+
+
+ ),
+ })
+ }, [navigation])
+
+ return (
+
+
+
+
+
+ )
+}
+
+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
similarity index 96%
rename from src/view/screens/Login.tsx
rename to src/view/screens/tabroots/Login.tsx
index d08a5a2565..a5f670bdd8 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
similarity index 81%
rename from src/view/screens/Menu.tsx
rename to src/view/screens/tabroots/Menu.tsx
index d0cc0826fb..dca5ad33be 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
similarity index 82%
rename from src/view/screens/NotFound.tsx
rename to src/view/screens/tabroots/NotFound.tsx
index 5357a428a7..a35808cbc6 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
similarity index 79%
rename from src/view/screens/Notifications.tsx
rename to src/view/screens/tabroots/Notifications.tsx
index a7918f1776..091410ad8c 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
similarity index 78%
rename from src/view/screens/Search.tsx
rename to src/view/screens/tabroots/Search.tsx
index 26df2954c0..044ca749c7 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
similarity index 96%
rename from src/view/screens/Signup.tsx
rename to src/view/screens/tabroots/Signup.tsx
index 4a8c5df2d0..dc2af2b1e6 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'
diff --git a/todos.txt b/todos.txt
index 7e38d5302d..e170e38e96 100644
--- a/todos.txt
+++ b/todos.txt
@@ -6,12 +6,13 @@ Paul's todo list
- Thread view
- Refresh
- Share btn
- - Reply control
- Profile view
- Refresh
- Follow / Unfollow
- Badges
-- Compose post control
+- Composer
+ - Refresh original view after reply
+ - Check on navigation stack during a bunch of replies
- Search view
- *
- Notifications view
diff --git a/yarn.lock b/yarn.lock
index 581f59f692..b15d85f35f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11146,6 +11146,13 @@ react-native-inappbrowser-reborn@^3.6.3:
invariant "^2.2.4"
opencollective-postinstall "^2.0.2"
+react-native-progress@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/react-native-progress/-/react-native-progress-5.0.0.tgz#f5ac6ceaeee27f184c660b00f29419e82a9d0ab0"
+ integrity sha512-KjnGIt3r9i5Kn2biOD9fXLJocf0bwxPRxOyAgXEnZTJQU2O+HyzgGFRCbM5h3izm9kKIkSc1txh8aGmMafCD9A==
+ dependencies:
+ prop-types "^15.7.2"
+
react-native-safe-area-context@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.3.1.tgz#5cf97b25b395e0d09bc1f828920cd7da0d792ade"