diff options
Diffstat (limited to 'src/view/com/discover')
-rw-r--r-- | src/view/com/discover/LiteSuggestedFollows.tsx | 11 | ||||
-rw-r--r-- | src/view/com/discover/SuggestedFollows.tsx | 208 | ||||
-rw-r--r-- | src/view/com/discover/SuggestedPosts.tsx | 66 | ||||
-rw-r--r-- | src/view/com/discover/WhoToFollow.tsx | 89 |
4 files changed, 181 insertions, 193 deletions
diff --git a/src/view/com/discover/LiteSuggestedFollows.tsx b/src/view/com/discover/LiteSuggestedFollows.tsx index ce01af7c7..5314e691c 100644 --- a/src/view/com/discover/LiteSuggestedFollows.tsx +++ b/src/view/com/discover/LiteSuggestedFollows.tsx @@ -8,19 +8,18 @@ import { import LinearGradient from 'react-native-linear-gradient' import {observer} from 'mobx-react-lite' import _omit from 'lodash.omit' -import {ErrorMessage} from '../util/error/ErrorMessage' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' import * as Toast from '../util/Toast' -import {useStores} from '../../../state' -import * as apilib from '../../../state/lib/api' +import {useStores} from 'state/index' +import * as apilib from 'lib/api/index' import { SuggestedActorsViewModel, SuggestedActor, -} from '../../../state/models/suggested-actors-view' -import {s, gradients} from '../../lib/styles' -import {usePalette} from '../../lib/hooks/usePalette' +} from 'state/models/suggested-actors-view' +import {s, gradients} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' export const LiteSuggestedFollows = observer(() => { const store = useStores() diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx index 9ed893148..1e40956ce 100644 --- a/src/view/com/discover/SuggestedFollows.tsx +++ b/src/view/com/discover/SuggestedFollows.tsx @@ -1,51 +1,28 @@ -import React, {useEffect, useState} from 'react' -import { - ActivityIndicator, - StyleSheet, - TouchableOpacity, - View, -} from 'react-native' -import LinearGradient from 'react-native-linear-gradient' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {observer} from 'mobx-react-lite' -import _omit from 'lodash.omit' +import React from 'react' +import {ActivityIndicator, StyleSheet, View} from 'react-native' import {CenteredView, FlatList} from '../util/Views' +import {observer} from 'mobx-react-lite' import {ErrorScreen} from '../util/error/ErrorScreen' -import {Link} from '../util/Link' -import {Text} from '../util/text/Text' -import {UserAvatar} from '../util/UserAvatar' -import * as Toast from '../util/Toast' -import {useStores} from '../../../state' -import * as apilib from '../../../state/lib/api' +import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' +import {useStores} from 'state/index' import { SuggestedActorsViewModel, SuggestedActor, -} from '../../../state/models/suggested-actors-view' -import {s, gradients} from '../../lib/styles' -import {usePalette} from '../../lib/hooks/usePalette' +} from 'state/models/suggested-actors-view' +import {s} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' export const SuggestedFollows = observer( - ({ - onNoSuggestions, - asLinks, - }: { - onNoSuggestions?: () => void - asLinks?: boolean - }) => { + ({onNoSuggestions}: {onNoSuggestions?: () => void}) => { const pal = usePalette('default') const store = useStores() - const [follows, setFollows] = useState<Record<string, string>>({}) - // Using default import (React.use...) instead of named import (use...) to be able to mock store's data in jest environment const view = React.useMemo<SuggestedActorsViewModel>( () => new SuggestedActorsViewModel(store), [store], ) - useEffect(() => { + React.useEffect(() => { view .loadMore() .catch((err: any) => @@ -53,7 +30,7 @@ export const SuggestedFollows = observer( ) }, [view, store.log]) - useEffect(() => { + React.useEffect(() => { if (!view.isLoading && !view.hasError && !view.hasContent) { onNoSuggestions?.() } @@ -74,46 +51,16 @@ export const SuggestedFollows = observer( ) } - const onPressFollow = async (item: SuggestedActor) => { - try { - const res = await apilib.follow(store, item.did, item.declaration.cid) - setFollows({[item.did]: res.uri, ...follows}) - } catch (e: any) { - store.log.error('Failed fo create follow', e) - Toast.show('An issue occurred, please try again.') - } - } - const onPressUnfollow = async (item: SuggestedActor) => { - try { - await apilib.unfollow(store, follows[item.did]) - setFollows(_omit(follows, [item.did])) - } catch (e: any) { - store.log.error('Failed fo delete follow', e) - Toast.show('An issue occurred, please try again.') - } - } - const renderItem = ({item}: {item: SuggestedActor}) => { - if (asLinks) { - return ( - <Link - href={`/profile/${item.handle}`} - title={item.displayName || item.handle}> - <User - item={item} - follow={follows[item.did]} - onPressFollow={onPressFollow} - onPressUnfollow={onPressUnfollow} - /> - </Link> - ) - } return ( - <User - item={item} - follow={follows[item.did]} - onPressFollow={onPressFollow} - onPressUnfollow={onPressUnfollow} + <ProfileCardWithFollowBtn + key={item.did} + did={item.did} + declarationCid={item.declaration.cid} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + description={item.description} /> ) } @@ -146,7 +93,6 @@ export const SuggestedFollows = observer( </View> )} contentContainerStyle={s.contentContainer} - style={s.flex1} /> </View> )} @@ -155,128 +101,16 @@ export const SuggestedFollows = observer( }, ) -const User = ({ - item, - follow, - onPressFollow, - onPressUnfollow, -}: { - item: SuggestedActor - follow: string | undefined - onPressFollow: (item: SuggestedActor) => void - onPressUnfollow: (item: SuggestedActor) => void -}) => { - const pal = usePalette('default') - return ( - <View style={[styles.actor, pal.view, pal.border]}> - <View style={styles.actorMeta}> - <View style={styles.actorAvi}> - <UserAvatar - size={40} - displayName={item.displayName} - handle={item.handle} - avatar={item.avatar} - /> - </View> - <View style={styles.actorContent}> - <Text type="title-sm" style={pal.text} numberOfLines={1}> - {item.displayName || item.handle} - </Text> - <Text style={pal.textLight} numberOfLines={1}> - @{item.handle} - </Text> - </View> - <View style={styles.actorBtn}> - {follow ? ( - <TouchableOpacity onPress={() => onPressUnfollow(item)}> - <View style={[styles.btn, styles.secondaryBtn, pal.btn]}> - <Text type="button" style={pal.text}> - Unfollow - </Text> - </View> - </TouchableOpacity> - ) : ( - <TouchableOpacity onPress={() => onPressFollow(item)}> - <LinearGradient - colors={[gradients.blueLight.start, gradients.blueLight.end]} - start={{x: 0, y: 0}} - end={{x: 1, y: 1}} - style={[styles.btn, styles.gradientBtn]}> - <FontAwesomeIcon - icon="plus" - style={[s.white as FontAwesomeIconStyle, s.mr5]} - size={15} - /> - <Text style={[s.white, s.fw600, s.f15]}>Follow</Text> - </LinearGradient> - </TouchableOpacity> - )} - </View> - </View> - {item.description ? ( - <View style={styles.actorDetails}> - <Text style={pal.text} numberOfLines={4}> - {item.description} - </Text> - </View> - ) : undefined} - </View> - ) -} - const styles = StyleSheet.create({ container: { - flex: 1, + height: '100%', }, suggestionsContainer: { - flex: 1, + height: '100%', }, footer: { height: 200, paddingTop: 20, }, - - actor: { - borderTopWidth: 1, - }, - actorMeta: { - flexDirection: 'row', - }, - actorAvi: { - width: 60, - paddingLeft: 10, - paddingTop: 10, - paddingBottom: 10, - }, - actorContent: { - flex: 1, - paddingRight: 10, - paddingTop: 10, - }, - actorBtn: { - paddingRight: 10, - paddingTop: 10, - }, - actorDetails: { - paddingLeft: 60, - paddingRight: 10, - paddingBottom: 10, - }, - - gradientBtn: { - paddingHorizontal: 24, - paddingVertical: 6, - }, - secondaryBtn: { - paddingHorizontal: 14, - }, - btn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 7, - borderRadius: 50, - marginLeft: 6, - }, }) diff --git a/src/view/com/discover/SuggestedPosts.tsx b/src/view/com/discover/SuggestedPosts.tsx new file mode 100644 index 000000000..86a6bd394 --- /dev/null +++ b/src/view/com/discover/SuggestedPosts.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import {ActivityIndicator, StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {useStores} from 'state/index' +import {SuggestedPostsView} from 'state/models/suggested-posts-view' +import {s} from 'lib/styles' +import {FeedItem as Post} from '../posts/FeedItem' +import {Text} from '../util/text/Text' +import {usePalette} from 'lib/hooks/usePalette' + +export const SuggestedPosts = observer(() => { + const pal = usePalette('default') + const store = useStores() + const suggestedPostsView = React.useMemo<SuggestedPostsView>( + () => new SuggestedPostsView(store), + [store], + ) + + React.useEffect(() => { + if (!suggestedPostsView.hasLoaded) { + suggestedPostsView.setup() + } + }, [store, suggestedPostsView]) + + return ( + <> + {(suggestedPostsView.hasContent || suggestedPostsView.isLoading) && ( + <Text type="title" style={[styles.heading, pal.text]}> + Recently, on Bluesky... + </Text> + )} + {suggestedPostsView.hasContent && ( + <> + <View style={[pal.border, styles.bottomBorder]}> + {suggestedPostsView.posts.map(item => ( + <Post item={item} key={item._reactKey} /> + ))} + </View> + </> + )} + {suggestedPostsView.isLoading && ( + <View style={s.mt10}> + <ActivityIndicator /> + </View> + )} + </> + ) +}) + +const styles = StyleSheet.create({ + heading: { + fontWeight: 'bold', + paddingHorizontal: 12, + paddingTop: 16, + paddingBottom: 8, + }, + + bottomBorder: { + borderBottomWidth: 1, + }, + + loadMore: { + paddingLeft: 12, + paddingVertical: 10, + }, +}) diff --git a/src/view/com/discover/WhoToFollow.tsx b/src/view/com/discover/WhoToFollow.tsx new file mode 100644 index 000000000..17c10ca7e --- /dev/null +++ b/src/view/com/discover/WhoToFollow.tsx @@ -0,0 +1,89 @@ +import React from 'react' +import { + ActivityIndicator, + StyleSheet, + TouchableOpacity, + View, +} from 'react-native' +import {observer} from 'mobx-react-lite' +import {useStores} from 'state/index' +import {SuggestedActorsViewModel} from 'state/models/suggested-actors-view' +import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' +import {Text} from '../util/text/Text' +import {s} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' + +export const WhoToFollow = observer(() => { + const pal = usePalette('default') + const store = useStores() + const suggestedActorsView = React.useMemo<SuggestedActorsViewModel>( + () => new SuggestedActorsViewModel(store, {pageSize: 5}), + [store], + ) + + React.useEffect(() => { + suggestedActorsView.loadMore(true) + }, [store, suggestedActorsView]) + + const onPressLoadMoreSuggestedActors = () => { + suggestedActorsView.loadMore() + } + return ( + <> + {(suggestedActorsView.hasContent || suggestedActorsView.isLoading) && ( + <Text type="title" style={[styles.heading, pal.text]}> + Who to follow + </Text> + )} + {suggestedActorsView.hasContent && ( + <> + <View style={[pal.border, styles.bottomBorder]}> + {suggestedActorsView.suggestions.map(item => ( + <ProfileCardWithFollowBtn + key={item.did} + did={item.did} + declarationCid={item.declaration.cid} + handle={item.handle} + displayName={item.displayName} + avatar={item.avatar} + description={item.description} + /> + ))} + </View> + {!suggestedActorsView.isLoading && suggestedActorsView.hasMore && ( + <TouchableOpacity + onPress={onPressLoadMoreSuggestedActors} + style={styles.loadMore}> + <Text type="lg" style={pal.link}> + Show more + </Text> + </TouchableOpacity> + )} + </> + )} + {suggestedActorsView.isLoading && ( + <View style={s.mt10}> + <ActivityIndicator /> + </View> + )} + </> + ) +}) + +const styles = StyleSheet.create({ + heading: { + fontWeight: 'bold', + paddingHorizontal: 12, + paddingTop: 16, + paddingBottom: 8, + }, + + bottomBorder: { + borderBottomWidth: 1, + }, + + loadMore: { + paddingLeft: 16, + paddingVertical: 12, + }, +}) |