about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-07-25 18:31:42 -0500
committerPaul Frazee <pfrazee@gmail.com>2022-07-25 18:31:42 -0500
commitaf55a89758fc6d44896051b9ddd015a73b92e0f6 (patch)
tree5b9e4e226c6fc9210029325daf879cf627b0a1c2 /src
parent3794eca88e13c3c292b0f64b1acb1169ecbeb83d (diff)
downloadvoidsky-af55a89758fc6d44896051b9ddd015a73b92e0f6.tar.zst
Add share bottom-sheet to feed and thread
Diffstat (limited to 'src')
-rw-r--r--src/App.native.tsx12
-rw-r--r--src/App.web.tsx12
-rw-r--r--src/view/com/composer/Composer.tsx8
-rw-r--r--src/view/com/feed/Feed.tsx15
-rw-r--r--src/view/com/feed/FeedItem.tsx8
-rw-r--r--src/view/com/post-thread/PostThread.tsx40
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx8
-rw-r--r--src/view/com/sheets/SharePost.tsx114
-rw-r--r--src/view/lib/styles.ts9
9 files changed, 199 insertions, 27 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx
index ffeb7d5fc..4309fa3c3 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -1,5 +1,7 @@
 import 'react-native-url-polyfill/auto'
 import React, {useState, useEffect} from 'react'
+import {RootSiblingParent} from 'react-native-root-siblings'
+import {GestureHandlerRootView} from 'react-native-gesture-handler'
 import {whenWebCrypto} from './platform/polyfills.native'
 import * as view from './view/index'
 import {RootStoreModel, setupState, RootStoreProvider} from './state'
@@ -26,9 +28,13 @@ function App() {
   }
 
   return (
-    <RootStoreProvider value={rootStore}>
-      <Routes.Root />
-    </RootStoreProvider>
+    <GestureHandlerRootView style={{flex: 1}}>
+      <RootSiblingParent>
+        <RootStoreProvider value={rootStore}>
+          <Routes.Root />
+        </RootStoreProvider>
+      </RootSiblingParent>
+    </GestureHandlerRootView>
   )
 }
 
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 018ac4003..838b81ee2 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -1,4 +1,6 @@
 import React, {useState, useEffect} from 'react'
+import {RootSiblingParent} from 'react-native-root-siblings'
+import {GestureHandlerRootView} from 'react-native-gesture-handler'
 import * as view from './view/index'
 import {RootStoreModel, setupState, RootStoreProvider} from './state'
 import * as Routes from './view/routes'
@@ -20,9 +22,13 @@ function App() {
   }
 
   return (
-    <RootStoreProvider value={rootStore}>
-      <Routes.Root />
-    </RootStoreProvider>
+    <GestureHandlerRootView style={{flex: 1}}>
+      <RootSiblingParent>
+        <RootStoreProvider value={rootStore}>
+          <Routes.Root />
+        </RootStoreProvider>
+      </RootSiblingParent>
+    </GestureHandlerRootView>
   )
 }
 
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 5a6ad5215..c7ce3f4c7 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -1,6 +1,7 @@
 import React, {useState, forwardRef, useImperativeHandle} from 'react'
 import {observer} from 'mobx-react-lite'
 import {KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native'
+import Toast from 'react-native-root-toast'
 // @ts-ignore no type definition -prf
 import ProgressCircle from 'react-native-progress/Circle'
 import {useStores} from '../../../state'
@@ -37,6 +38,13 @@ export const Composer = observer(
           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
       },
     }))
diff --git a/src/view/com/feed/Feed.tsx b/src/view/com/feed/Feed.tsx
index c666fc05e..8283e275e 100644
--- a/src/view/com/feed/Feed.tsx
+++ b/src/view/com/feed/Feed.tsx
@@ -1,9 +1,10 @@
-import React from 'react'
+import React, {useRef} from 'react'
 import {observer} from 'mobx-react-lite'
 import {Text, View, FlatList} from 'react-native'
 import {OnNavigateContent} from '../../routes/types'
 import {FeedViewModel, FeedViewItemModel} from '../../../state/models/feed-view'
 import {FeedItem} from './FeedItem'
+import {ShareBottomSheet} from '../sheets/SharePost'
 
 export const Feed = observer(function Feed({
   feed,
@@ -12,12 +13,21 @@ export const Feed = observer(function Feed({
   feed: FeedViewModel
   onNavigateContent: OnNavigateContent
 }) {
+  const shareSheetRef = useRef<{open: (uri: string) => void}>()
+
+  const onPressShare = (uri: string) => {
+    shareSheetRef.current?.open(uri)
+  }
   // TODO optimize renderItem or FeedItem, we're getting this notice from RN: -prf
   //   VirtualizedList: You have a large list that is slow to update - make sure your
   //   renderItem function renders components that follow React performance best practices
   //   like PureComponent, shouldComponentUpdate, etc
   const renderItem = ({item}: {item: FeedViewItemModel}) => (
-    <FeedItem item={item} onNavigateContent={onNavigateContent} />
+    <FeedItem
+      item={item}
+      onNavigateContent={onNavigateContent}
+      onPressShare={onPressShare}
+    />
   )
   const onRefresh = () => {
     feed.refresh().catch(err => console.error('Failed to refresh', err))
@@ -42,6 +52,7 @@ export const Feed = observer(function Feed({
         />
       )}
       {feed.isEmpty && <Text>This feed is empty!</Text>}
+      <ShareBottomSheet ref={shareSheetRef} />
     </View>
   )
 })
diff --git a/src/view/com/feed/FeedItem.tsx b/src/view/com/feed/FeedItem.tsx
index 9f3ec7c56..018b58179 100644
--- a/src/view/com/feed/FeedItem.tsx
+++ b/src/view/com/feed/FeedItem.tsx
@@ -12,9 +12,11 @@ import {AVIS} from '../../lib/assets'
 export const FeedItem = observer(function FeedItem({
   item,
   onNavigateContent,
+  onPressShare,
 }: {
   item: FeedViewItemModel
   onNavigateContent: OnNavigateContent
+  onPressShare: (uri: string) => void
 }) {
   const record = item.record as unknown as bsky.Post.Record
 
@@ -118,12 +120,14 @@ export const FeedItem = observer(function FeedItem({
                 {item.likeCount}
               </Text>
             </TouchableOpacity>
-            <View style={styles.ctrl}>
+            <TouchableOpacity
+              style={styles.ctrl}
+              onPress={() => onPressShare(item.uri)}>
               <FontAwesomeIcon
                 style={styles.ctrlIcon}
                 icon="share-from-square"
               />
-            </View>
+            </TouchableOpacity>
           </View>
         </View>
       </View>
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index bc9562ea1..784cc39d2 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -1,4 +1,4 @@
-import React, {useState, useEffect} from 'react'
+import React, {useState, useEffect, useRef} from 'react'
 import {observer} from 'mobx-react-lite'
 import {ActivityIndicator, FlatList, Text, View} from 'react-native'
 import {useFocusEffect} from '@react-navigation/native'
@@ -9,6 +9,8 @@ import {
 } from '../../../state/models/post-thread-view'
 import {useStores} from '../../../state'
 import {PostThreadItem} from './PostThreadItem'
+import {ShareBottomSheet} from '../sheets/SharePost'
+import {s} from '../../lib/styles'
 
 const UPDATE_DELAY = 2e3 // wait 2s before refetching the thread for updates
 
@@ -22,6 +24,7 @@ export const PostThread = observer(function PostThread({
   const store = useStores()
   const [view, setView] = useState<PostThreadViewModel | undefined>()
   const [lastUpdate, setLastUpdate] = useState<number>(Date.now())
+  const shareSheetRef = useRef<{open: (uri: string) => void}>()
 
   useEffect(() => {
     if (view?.params.uri === uri) {
@@ -41,6 +44,13 @@ export const PostThread = observer(function PostThread({
     }
   })
 
+  const onPressShare = (uri: string) => {
+    shareSheetRef.current?.open(uri)
+  }
+  const onRefresh = () => {
+    view?.refresh().catch(err => console.error('Failed to refresh', err))
+  }
+
   // loading
   // =
   if (
@@ -69,22 +79,22 @@ export const PostThread = observer(function PostThread({
   // =
   const posts = view.thread ? Array.from(flattenThread(view.thread)) : []
   const renderItem = ({item}: {item: PostThreadViewPostModel}) => (
-    <PostThreadItem item={item} onNavigateContent={onNavigateContent} />
+    <PostThreadItem
+      item={item}
+      onNavigateContent={onNavigateContent}
+      onPressShare={onPressShare}
+    />
   )
-  const onRefresh = () => {
-    view.refresh().catch(err => console.error('Failed to refresh', err))
-  }
   return (
-    <View>
-      {view.hasContent && (
-        <FlatList
-          data={posts}
-          keyExtractor={item => item._reactKey}
-          renderItem={renderItem}
-          refreshing={view.isRefreshing}
-          onRefresh={onRefresh}
-        />
-      )}
+    <View style={s.h100pct}>
+      <FlatList
+        data={posts}
+        keyExtractor={item => item._reactKey}
+        renderItem={renderItem}
+        refreshing={view.isRefreshing}
+        onRefresh={onRefresh}
+      />
+      <ShareBottomSheet ref={shareSheetRef} />
     </View>
   )
 })
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index bd22ecf9a..30a64bc0e 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -21,9 +21,11 @@ function iter<T>(n: number, fn: (_i: number) => T): Array<T> {
 export const PostThreadItem = observer(function PostThreadItem({
   item,
   onNavigateContent,
+  onPressShare,
 }: {
   item: PostThreadViewPostModel
   onNavigateContent: OnNavigateContent
+  onPressShare: (uri: string) => void
 }) {
   const record = item.record as unknown as bsky.Post.Record
   const hasEngagement = item.likeCount || item.repostCount
@@ -169,12 +171,14 @@ export const PostThreadItem = observer(function PostThreadItem({
                 {item.likeCount}
               </Text>
             </TouchableOpacity>
-            <View style={styles.ctrl}>
+            <TouchableOpacity
+              style={styles.ctrl}
+              onPress={() => onPressShare(item.uri)}>
               <FontAwesomeIcon
                 style={styles.ctrlIcon}
                 icon="share-from-square"
               />
-            </View>
+            </TouchableOpacity>
           </View>
         </View>
       </View>
diff --git a/src/view/com/sheets/SharePost.tsx b/src/view/com/sheets/SharePost.tsx
new file mode 100644
index 000000000..b0f22c54e
--- /dev/null
+++ b/src/view/com/sheets/SharePost.tsx
@@ -0,0 +1,114 @@
+import React, {
+  forwardRef,
+  useState,
+  useMemo,
+  useImperativeHandle,
+  useRef,
+} from 'react'
+import {
+  Button,
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  TouchableWithoutFeedback,
+  View,
+} from 'react-native'
+import BottomSheet, {BottomSheetBackdropProps} from '@gorhom/bottom-sheet'
+import Animated, {
+  Extrapolate,
+  interpolate,
+  useAnimatedStyle,
+} from 'react-native-reanimated'
+import Toast from 'react-native-root-toast'
+import Clipboard from '@react-native-clipboard/clipboard'
+import {s} from '../../lib/styles'
+
+export const ShareBottomSheet = forwardRef(function ShareBottomSheet(
+  {}: {},
+  ref,
+) {
+  const [isOpen, setIsOpen] = useState<boolean>(false)
+  const [uri, setUri] = useState<string>('')
+  const bottomSheetRef = useRef<BottomSheet>(null)
+
+  useImperativeHandle(ref, () => ({
+    open(uri: string) {
+      console.log('sharing', uri)
+      setUri(uri)
+      setIsOpen(true)
+    },
+  }))
+
+  const onPressCopy = () => {
+    Clipboard.setString(uri)
+    Toast.show('Link copied', {
+      position: Toast.positions.TOP,
+    })
+  }
+  const onShareBottomSheetChange = (snapPoint: number) => {
+    if (snapPoint === -1) {
+      console.log('unsharing')
+      setIsOpen(false)
+    }
+  }
+  const onClose = () => {
+    bottomSheetRef.current?.close()
+  }
+
+  const CustomBackdrop = ({animatedIndex, style}: BottomSheetBackdropProps) => {
+    console.log('hit!', animatedIndex.value)
+    // animated variables
+    const opacity = useAnimatedStyle(() => ({
+      opacity: interpolate(
+        animatedIndex.value, // current snap index
+        [-1, 0], // input range
+        [0, 0.5], // output range
+        Extrapolate.CLAMP,
+      ),
+    }))
+
+    const containerStyle = useMemo(
+      () => [style, {backgroundColor: '#000'}, opacity],
+      [style, opacity],
+    )
+
+    return (
+      <TouchableWithoutFeedback onPress={onClose}>
+        <Animated.View style={containerStyle} />
+      </TouchableWithoutFeedback>
+    )
+  }
+  return (
+    <>
+      {isOpen && (
+        <BottomSheet
+          ref={bottomSheetRef}
+          snapPoints={['50%']}
+          enablePanDownToClose
+          backdropComponent={CustomBackdrop}
+          onChange={onShareBottomSheetChange}>
+          <View>
+            <Text style={[s.textCenter, s.bold, s.mb10]}>Share this post</Text>
+            <Text style={[s.textCenter, s.mb10]}>{uri}</Text>
+            <Button title="Copy to clipboard" onPress={onPressCopy} />
+            <View style={s.p10}>
+              <TouchableOpacity onPress={onClose} style={styles.closeBtn}>
+                <Text style={s.textCenter}>Close</Text>
+              </TouchableOpacity>
+            </View>
+          </View>
+        </BottomSheet>
+      )}
+    </>
+  )
+})
+
+const styles = StyleSheet.create({
+  closeBtn: {
+    width: '100%',
+    borderColor: '#000',
+    borderWidth: 1,
+    borderRadius: 4,
+    padding: 10,
+  },
+})
diff --git a/src/view/lib/styles.ts b/src/view/lib/styles.ts
index 2ae928119..01e02a296 100644
--- a/src/view/lib/styles.ts
+++ b/src/view/lib/styles.ts
@@ -73,4 +73,13 @@ export const s = StyleSheet.create({
   flexRow: {flexDirection: 'row'},
   flexCol: {flexDirection: 'column'},
   flex1: {flex: 1},
+
+  // dimensions
+  w100pct: {width: '100%'},
+  h100pct: {height: '100%'},
+
+  // text align
+  textLeft: {textAlign: 'left'},
+  textCenter: {textAlign: 'center'},
+  textRight: {textAlign: 'right'},
 })