Add share bottom-sheet to feed and thread
This commit is contained in:
parent
3794eca88e
commit
af55a89758
14 changed files with 574 additions and 34 deletions
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}))
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
|
114
src/view/com/sheets/SharePost.tsx
Normal file
114
src/view/com/sheets/SharePost.tsx
Normal file
|
@ -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,
|
||||
},
|
||||
})
|
|
@ -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'},
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue