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 {CenteredView, FlatList} from '../util/Views' 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 { SuggestedActorsViewModel, SuggestedActor, } from '../../../state/models/suggested-actors-view' import {s, gradients} from '../../lib/styles' import {usePalette} from '../../lib/hooks/usePalette' export const SuggestedFollows = observer( ({ onNoSuggestions, asLinks, }: { onNoSuggestions?: () => void asLinks?: boolean }) => { const pal = usePalette('default') const store = useStores() const [follows, setFollows] = useState>({}) // 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( () => new SuggestedActorsViewModel(store), [store], ) useEffect(() => { view .loadMore() .catch((err: any) => store.log.error('Failed to fetch suggestions', err), ) }, [view, store.log]) useEffect(() => { if (!view.isLoading && !view.hasError && !view.hasContent) { onNoSuggestions?.() } }, [view, view.isLoading, view.hasError, view.hasContent, onNoSuggestions]) const onRefresh = () => { view .refresh() .catch((err: any) => store.log.error('Failed to fetch suggestions', err), ) } const onEndReached = () => { view .loadMore() .catch(err => view?.rootStore.log.error('Failed to load more suggestions', err), ) } 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 ( ) } return ( ) } return ( {view.hasError ? ( ) : view.isEmpty ? ( ) : ( item.did} refreshing={view.isRefreshing} onRefresh={onRefresh} onEndReached={onEndReached} renderItem={renderItem} initialNumToRender={15} ListFooterComponent={() => ( {view.isLoading && } )} contentContainerStyle={s.contentContainer} style={s.flex1} /> )} ) }, ) const User = ({ item, follow, onPressFollow, onPressUnfollow, }: { item: SuggestedActor follow: string | undefined onPressFollow: (item: SuggestedActor) => void onPressUnfollow: (item: SuggestedActor) => void }) => { const pal = usePalette('default') return ( {item.displayName || item.handle} @{item.handle} {follow ? ( onPressUnfollow(item)}> Unfollow ) : ( onPressFollow(item)}> Follow )} {item.description ? ( {item.description} ) : undefined} ) } const styles = StyleSheet.create({ container: { flex: 1, }, suggestionsContainer: { flex: 1, }, 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, }, })