Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Optimize and refactor profile rendering a bit

+90 -76
+49
src/state/models/ui/profile.ts
··· 15 15 } 16 16 17 17 export class ProfileUiModel { 18 + static LOADING_ITEM = {_reactKey: '__loading__'} 19 + static END_ITEM = {_reactKey: '__end__'} 20 + static EMPTY_ITEM = {_reactKey: '__empty__'} 21 + 18 22 // data 19 23 profile: ProfileViewModel 20 24 feed: FeedModel ··· 74 78 75 79 get selectedView() { 76 80 return this.selectorItems[this.selectedViewIndex] 81 + } 82 + 83 + get uiItems() { 84 + let arr: any[] = [] 85 + if (this.isInitialLoading) { 86 + arr = arr.concat([ProfileUiModel.LOADING_ITEM]) 87 + } else if (this.currentView.hasError) { 88 + arr = arr.concat([ 89 + { 90 + _reactKey: '__error__', 91 + error: this.currentView.error, 92 + }, 93 + ]) 94 + } else { 95 + if ( 96 + this.selectedView === Sections.Posts || 97 + this.selectedView === Sections.PostsWithReplies 98 + ) { 99 + if (this.feed.hasContent) { 100 + if (this.selectedView === Sections.Posts) { 101 + arr = this.feed.nonReplyFeed 102 + } else { 103 + arr = this.feed.feed.slice() 104 + } 105 + if (!this.feed.hasMore) { 106 + arr = arr.concat([ProfileUiModel.END_ITEM]) 107 + } 108 + } else if (this.feed.isEmpty) { 109 + arr = arr.concat([ProfileUiModel.EMPTY_ITEM]) 110 + } 111 + } else { 112 + arr = arr.concat([ProfileUiModel.EMPTY_ITEM]) 113 + } 114 + } 115 + return arr 116 + } 117 + 118 + get showLoadingMoreFooter() { 119 + if ( 120 + this.selectedView === Sections.Posts || 121 + this.selectedView === Sections.PostsWithReplies 122 + ) { 123 + return this.feed.hasContent && this.feed.hasMore && this.feed.isLoading 124 + } 125 + return false 77 126 } 78 127 79 128 // public api
+41 -76
src/view/screens/Profile.tsx
··· 8 8 import {CenteredView} from '../com/util/Views' 9 9 import {ProfileUiModel, Sections} from 'state/models/ui/profile' 10 10 import {useStores} from 'state/index' 11 + import {FeedItemModel} from 'state/models/feed-view' 11 12 import {ProfileHeader} from '../com/profile/ProfileHeader' 12 13 import {FeedItem} from '../com/posts/FeedItem' 13 14 import {PostFeedLoadingPlaceholder} from '../com/util/LoadingPlaceholder' ··· 20 21 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' 21 22 import {useAnalytics} from 'lib/analytics' 22 23 import {ComposeIcon2} from 'lib/icons' 23 - 24 - const LOADING_ITEM = {_reactKey: '__loading__'} 25 - const END_ITEM = {_reactKey: '__end__'} 26 - const EMPTY_ITEM = {_reactKey: '__empty__'} 27 24 28 25 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'> 29 26 export const ProfileScreen = withAuthRequired( ··· 73 70 const onSelectView = (index: number) => { 74 71 uiState.setSelectedViewIndex(index) 75 72 } 76 - const onRefresh = () => { 73 + const onRefresh = React.useCallback(() => { 77 74 uiState 78 75 .refresh() 79 76 .catch((err: any) => 80 77 store.log.error('Failed to refresh user profile', err), 81 78 ) 82 - } 83 - const onEndReached = () => { 79 + }, [uiState, store]) 80 + const onEndReached = React.useCallback(() => { 84 81 uiState 85 82 .loadMore() 86 83 .catch((err: any) => 87 84 store.log.error('Failed to load more entries in user profile', err), 88 85 ) 89 - } 90 - const onPressTryAgain = () => { 86 + }, [uiState, store]) 87 + const onPressTryAgain = React.useCallback(() => { 91 88 uiState.setup() 92 - } 89 + }, [uiState]) 93 90 94 91 // rendering 95 92 // = 96 93 97 - const renderHeader = () => { 94 + const renderHeader = React.useCallback(() => { 98 95 if (!uiState) { 99 96 return <View /> 100 97 } 101 98 return <ProfileHeader view={uiState.profile} onRefreshAll={onRefresh} /> 102 - } 103 - let renderItem 104 - let Footer 105 - let items: any[] = [] 106 - if (uiState) { 107 - if (uiState.isInitialLoading) { 108 - items = items.concat([LOADING_ITEM]) 109 - renderItem = () => <PostFeedLoadingPlaceholder /> 110 - } else if (uiState.currentView.hasError) { 111 - items = items.concat([ 112 - { 113 - _reactKey: '__error__', 114 - error: uiState.currentView.error, 115 - }, 116 - ]) 117 - renderItem = (item: any) => ( 118 - <View style={s.p5}> 119 - <ErrorMessage 120 - message={item.error} 121 - onPressTryAgain={onPressTryAgain} 99 + }, [uiState, onRefresh]) 100 + const Footer = React.useMemo(() => { 101 + return uiState.showLoadingMoreFooter ? LoadingMoreFooter : undefined 102 + }, [uiState.showLoadingMoreFooter]) 103 + const renderItem = React.useCallback( 104 + (item: any) => { 105 + if (item === ProfileUiModel.END_ITEM) { 106 + return <Text style={styles.endItem}>- end of feed -</Text> 107 + } else if (item === ProfileUiModel.LOADING_ITEM) { 108 + return <PostFeedLoadingPlaceholder /> 109 + } else if (item._reactKey === '__error__') { 110 + return ( 111 + <View style={s.p5}> 112 + <ErrorMessage 113 + message={item.error} 114 + onPressTryAgain={onPressTryAgain} 115 + /> 116 + </View> 117 + ) 118 + } else if (item === ProfileUiModel.EMPTY_ITEM) { 119 + return ( 120 + <EmptyState 121 + icon={['far', 'message']} 122 + message="No posts yet!" 123 + style={styles.emptyState} 122 124 /> 123 - </View> 124 - ) 125 - } else { 126 - if ( 127 - uiState.selectedView === Sections.Posts || 128 - uiState.selectedView === Sections.PostsWithReplies 129 - ) { 130 - if (uiState.feed.hasContent) { 131 - if (uiState.selectedView === Sections.Posts) { 132 - items = uiState.feed.nonReplyFeed 133 - } else { 134 - items = uiState.feed.feed.slice() 135 - } 136 - if (!uiState.feed.hasMore) { 137 - items = items.concat([END_ITEM]) 138 - } else if (uiState.feed.isLoading) { 139 - Footer = LoadingMoreFooter 140 - } 141 - renderItem = (item: any) => { 142 - if (item === END_ITEM) { 143 - return <Text style={styles.endItem}>- end of feed -</Text> 144 - } 145 - return ( 146 - <FeedItem item={item} ignoreMuteFor={uiState.profile.did} /> 147 - ) 148 - } 149 - } else if (uiState.feed.isEmpty) { 150 - items = items.concat([EMPTY_ITEM]) 151 - renderItem = () => ( 152 - <EmptyState 153 - icon={['far', 'message']} 154 - message="No posts yet!" 155 - style={styles.emptyState} 156 - /> 157 - ) 158 - } 159 - } else { 160 - items = items.concat([EMPTY_ITEM]) 161 - renderItem = () => <Text>TODO</Text> 125 + ) 126 + } else if (item instanceof FeedItemModel) { 127 + return <FeedItem item={item} ignoreMuteFor={uiState.profile.did} /> 162 128 } 163 - } 164 - } 165 - if (!renderItem) { 166 - renderItem = () => <View /> 167 - } 129 + return <View /> 130 + }, 131 + [onPressTryAgain, uiState.profile.did], 132 + ) 168 133 169 134 return ( 170 135 <View testID="profileView" style={styles.container}> ··· 180 145 <ViewSelector 181 146 swipeEnabled={false} 182 147 sections={uiState.selectorItems} 183 - items={items} 148 + items={uiState.uiItems} 184 149 renderHeader={renderHeader} 185 150 renderItem={renderItem} 186 151 ListFooterComponent={Footer}