diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/App.native.tsx | 12 | ||||
-rw-r--r-- | src/App.web.tsx | 12 | ||||
-rw-r--r-- | src/view/com/composer/Composer.tsx | 8 | ||||
-rw-r--r-- | src/view/com/feed/Feed.tsx | 15 | ||||
-rw-r--r-- | src/view/com/feed/FeedItem.tsx | 8 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 40 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThreadItem.tsx | 8 | ||||
-rw-r--r-- | src/view/com/sheets/SharePost.tsx | 114 | ||||
-rw-r--r-- | src/view/lib/styles.ts | 9 |
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'}, }) |