Revise custom feed header style

This commit is contained in:
Eric Bailey
2023-10-31 13:48:16 -05:00
parent 6c11c0b81d
commit 2d7a562239
5 changed files with 402 additions and 151 deletions
+2 -1
View File
@@ -22,6 +22,7 @@ export function isNetworkError(e: unknown) {
return ( return (
str.includes('Abort') || str.includes('Abort') ||
str.includes('Network request failed') || str.includes('Network request failed') ||
str.includes('Failed to fetch') str.includes('Failed to fetch') ||
str.includes('NetworkError when attempting to fetch resource')
) )
} }
+27 -1
View File
@@ -23,6 +23,11 @@ import {MergeFeedAPI} from 'lib/api/feed/merge'
const PAGE_SIZE = 30 const PAGE_SIZE = 30
export type PostsFeedModelError = {
type: 'network' | 'upstream'
message: string
}
type Options = { type Options = {
/** /**
* Formats the feed in a flat array with no threading of replies, just * Formats the feed in a flat array with no threading of replies, just
@@ -47,6 +52,7 @@ export class PostsFeedModel {
isBlockedBy = false isBlockedBy = false
error = '' error = ''
loadMoreError = '' loadMoreError = ''
cleanError?: PostsFeedModelError = undefined
params: QueryParams params: QueryParams
hasMore = true hasMore = true
pollCursor: string | undefined pollCursor: string | undefined
@@ -137,6 +143,7 @@ export class PostsFeedModel {
this.hasNewLatest = false this.hasNewLatest = false
this.hasLoaded = false this.hasLoaded = false
this.error = '' this.error = ''
this.cleanError = undefined
this.hasMore = true this.hasMore = true
this.pollCursor = undefined this.pollCursor = undefined
this.slices = [] this.slices = []
@@ -281,6 +288,7 @@ export class PostsFeedModel {
this.isLoading = true this.isLoading = true
this.isRefreshing = isRefreshing this.isRefreshing = isRefreshing
this.error = '' this.error = ''
this.cleanError = undefined
} }
_xIdle(error?: any, loadMoreError?: any) { _xIdle(error?: any, loadMoreError?: any) {
@@ -290,7 +298,8 @@ export class PostsFeedModel {
this.isBlocking = error instanceof GetAuthorFeed.BlockedActorError this.isBlocking = error instanceof GetAuthorFeed.BlockedActorError
this.isBlockedBy = error instanceof GetAuthorFeed.BlockedByActorError this.isBlockedBy = error instanceof GetAuthorFeed.BlockedByActorError
this.error = cleanError(error) this.error = cleanError(error)
this.loadMoreError = cleanError(loadMoreError) this.loadMoreError = cleanError(loadMoreError) // TODO
this.cleanError = this.error ? this._cleanError(this.error) : undefined
if (error) { if (error) {
this.rootStore.log.error('Posts feed request failed', error) this.rootStore.log.error('Posts feed request failed', error)
} }
@@ -358,4 +367,21 @@ export class PostsFeedModel {
} }
}) })
} }
_cleanError(error: string) {
const e: PostsFeedModelError = {
type: 'network',
message: `Hmmm, the feed appears to be offline. Please check your internet connection and try again.`,
}
if (
error.includes('could not find feed') ||
error.includes('server appears to be experiencing issues')
) {
e.type = 'upstream'
e.message = `Hmmm, we're having trouble finding this feed. It may have been deleted.`
}
return e
}
} }
@@ -0,0 +1,168 @@
import React from 'react'
import {useNavigation} from '@react-navigation/native'
import {AtUri} from '@atproto/api'
import {Haptics} from 'lib/haptics'
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {CustomFeedModel} from 'state/models/feeds/custom-feed'
import {useStores} from 'state/index'
import {NavigationProp} from 'lib/routes/types'
import {toShareUrl} from 'lib/strings/url-helpers'
import {shareUrl} from 'lib/sharing'
import {useAnalytics} from 'lib/analytics/analytics'
import * as Toast from 'view/com/util/Toast'
export function CustomFeedContextMenu({
children,
feed,
...rest
}: React.PropsWithChildren<
{feed: CustomFeedModel} & Omit<
React.ComponentProps<typeof NativeDropdown>,
'items'
>
>) {
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const handleOrDid = feed?.data?.creator?.handle || feed?.data?.creator?.did
const {track} = useAnalytics()
const onPressAbout = React.useCallback(() => {
store.shell.openModal({
name: 'confirm',
title: feed?.displayName || '',
message: feed?.data.description || 'This feed has no description.',
confirmBtnText: 'Close',
onPressConfirm() {},
})
}, [store, feed])
const onPressViewAuthor = React.useCallback(() => {
navigation.navigate('Profile', {name: handleOrDid})
}, [handleOrDid, navigation])
const onPressShare = React.useCallback(() => {
const {rkey} = new AtUri(feed.uri)
const url = toShareUrl(`/profile/${handleOrDid}/feed/${rkey}`)
shareUrl(url)
track('CustomFeed:Share')
}, [feed.uri, handleOrDid, track])
const onPressReport = React.useCallback(() => {
if (!feed) return
store.shell.openModal({
name: 'report',
uri: feed.uri,
cid: feed.data.cid,
})
}, [store, feed])
const onToggleSaved = React.useCallback(async () => {
try {
Haptics.default()
if (feed?.isSaved) {
await feed?.unsave()
} else {
await feed?.save()
}
} catch (err) {
Toast.show(
'There was an an issue updating your feeds, please check your internet connection and try again.',
)
store.log.error('Failed up update feeds', {err})
}
}, [store, feed])
const dropdownItems: DropdownItem[] = React.useMemo(() => {
return [
feed
? {
testID: 'feedHeaderDropdownAboutBtn',
label: 'About this feed',
onPress: onPressAbout,
icon: {
ios: {
name: 'info.circle',
},
android: '',
web: 'info',
},
}
: undefined,
{
testID: 'feedHeaderDropdownViewAuthorBtn',
label: 'View author',
onPress: onPressViewAuthor,
icon: {
ios: {
name: 'person',
},
android: '',
web: ['far', 'user'],
},
},
{
testID: 'feedHeaderDropdownToggleSavedBtn',
label: feed?.isSaved ? 'Remove from my feeds' : 'Add to my feeds',
onPress: onToggleSaved,
icon: feed?.isSaved
? {
ios: {
name: 'trash',
},
android: 'ic_delete',
web: 'trash',
}
: {
ios: {
name: 'plus',
},
android: '',
web: 'plus',
},
},
{
testID: 'feedHeaderDropdownReportBtn',
label: 'Report feed',
onPress: onPressReport,
icon: {
ios: {
name: 'exclamationmark.triangle',
},
android: 'ic_menu_report_image',
web: 'circle-exclamation',
},
},
{
testID: 'feedHeaderDropdownShareBtn',
label: 'Share link',
onPress: onPressShare,
icon: {
ios: {
name: 'square.and.arrow.up',
},
android: 'ic_menu_share',
web: 'share',
},
},
].filter(Boolean) as DropdownItem[]
}, [
feed,
onPressAbout,
onToggleSaved,
onPressReport,
onPressShare,
onPressViewAuthor,
])
return (
<NativeDropdown
testID="feedHeaderDropdownBtn"
items={dropdownItems}
accessibilityLabel="More options"
accessibilityHint=""
{...rest}>
{children}
</NativeDropdown>
)
}
+186 -11
View File
@@ -8,10 +8,15 @@ import {
View, View,
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {AtUri, AppBskyFeedGetFeed} from '@atproto/api'
import {toShareUrl} from 'lib/strings/url-helpers'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useCustomFeed} from 'lib/hooks/useCustomFeed'
import {FlatList} from '../util/Views' import {FlatList} from '../util/Views'
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage' import {PostsFeedModel, PostsFeedModelError} from 'state/models/feeds/posts'
import {PostsFeedModel} from 'state/models/feeds/posts'
import {FeedSlice} from './FeedSlice' import {FeedSlice} from './FeedSlice'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll' import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
@@ -19,11 +24,18 @@ import {s} from 'lib/styles'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {Text} from 'view/com/util/text/Text'
import {TextLink} from '../util/Link'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {Button} from 'view/com/util/forms/Button'
import {CustomFeedContextMenu} from 'view/com/feeds/CustomFeedContextMenu'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const ERROR_ITEM = {_reactKey: '__error__'} const ERROR_ITEM = {_reactKey: '__error__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
const FEED_INNER_HEADER = {_reactKey: '__feedInnerHeader__'}
const FEED_INNER_HEADER_LOADING = {_reactKey: '__feedInnerHeaderLoading__'}
export const Feed = observer(function Feed({ export const Feed = observer(function Feed({
feed, feed,
@@ -38,6 +50,7 @@ export const Feed = observer(function Feed({
headerOffset = 0, headerOffset = 0,
ListHeaderComponent, ListHeaderComponent,
extraData, extraData,
showFeedHeaderContextMenu,
}: { }: {
feed: PostsFeedModel feed: PostsFeedModel
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
@@ -51,16 +64,23 @@ export const Feed = observer(function Feed({
headerOffset?: number headerOffset?: number
ListHeaderComponent?: () => JSX.Element ListHeaderComponent?: () => JSX.Element
extraData?: any extraData?: any
showFeedHeaderContextMenu?: boolean
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const {track} = useAnalytics() const {track} = useAnalytics()
const [isRefreshing, setIsRefreshing] = React.useState(false) const [isRefreshing, setIsRefreshing] = React.useState(false)
const params = feed.params as AppBskyFeedGetFeed.QueryParams
const isCustomFeed = Boolean(params.feed)
const data = React.useMemo(() => { const data = React.useMemo(() => {
let feedItems: any[] = [] let feedItems: any[] = []
if (feed.hasLoaded) { if (feed.hasLoaded) {
if (feed.hasError) { if (isCustomFeed) {
feedItems.push(FEED_INNER_HEADER)
}
if (feed.hasError && !isCustomFeed) {
// applies to our internal algo feeds only
feedItems = feedItems.concat([ERROR_ITEM]) feedItems = feedItems.concat([ERROR_ITEM])
} }
if (feed.isEmpty) { if (feed.isEmpty) {
@@ -79,6 +99,7 @@ export const Feed = observer(function Feed({
feed.isEmpty, feed.isEmpty,
feed.slices, feed.slices,
feed.loadMoreError, feed.loadMoreError,
isCustomFeed,
]) ])
// events // events
@@ -115,15 +136,20 @@ export const Feed = observer(function Feed({
const renderItem = React.useCallback( const renderItem = React.useCallback(
({item}: {item: any}) => { ({item}: {item: any}) => {
if (item === EMPTY_FEED_ITEM) { if (item === FEED_INNER_HEADER_LOADING) {
return renderEmptyState() return <FeedHeaderLoading />
} else if (item === ERROR_ITEM) { } else if (item === FEED_INNER_HEADER) {
return ( return (
<ErrorMessage <FeedInnerHeader
message={feed.error} showContextMenu={showFeedHeaderContextMenu}
onPressTryAgain={onPressTryAgain} error={feed.cleanError}
params={params}
/> />
) )
} else if (item === EMPTY_FEED_ITEM) {
return renderEmptyState()
} else if (item === ERROR_ITEM) {
return <ErrorMessage feed={feed} onPress={onPressTryAgain} />
} else if (item === LOAD_MORE_ERROR_ITEM) { } else if (item === LOAD_MORE_ERROR_ITEM) {
return ( return (
<LoadMoreRetryBtn <LoadMoreRetryBtn
@@ -136,7 +162,14 @@ export const Feed = observer(function Feed({
} }
return <FeedSlice slice={item} /> return <FeedSlice slice={item} />
}, },
[feed, onPressTryAgain, onPressRetryLoadMore, renderEmptyState], [
feed,
params,
showFeedHeaderContextMenu,
onPressTryAgain,
onPressRetryLoadMore,
renderEmptyState,
],
) )
const FeedFooter = React.useCallback( const FeedFooter = React.useCallback(
@@ -158,7 +191,13 @@ export const Feed = observer(function Feed({
<FlatList <FlatList
testID={testID ? `${testID}-flatlist` : undefined} testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef} ref={scrollElRef}
data={!feed.hasLoaded ? [LOADING_ITEM] : data} data={
!feed.hasLoaded
? isCustomFeed
? [FEED_INNER_HEADER_LOADING, LOADING_ITEM]
: [LOADING_ITEM]
: data
}
keyExtractor={item => item._reactKey} keyExtractor={item => item._reactKey}
renderItem={renderItem} renderItem={renderItem}
ListFooterComponent={FeedFooter} ListFooterComponent={FeedFooter}
@@ -189,6 +228,142 @@ export const Feed = observer(function Feed({
) )
}) })
function ErrorMessage({
feed,
onPress,
}: {
feed: PostsFeedModel
onPress?: () => void
}) {
const pal = usePalette('default')
return (
<View
style={[
pal.viewLight,
{
flexDirection: 'row',
alignItems: 'flex-start',
paddingHorizontal: 18,
paddingVertical: 12,
},
]}>
<Text style={{paddingRight: 18}}>
{feed.cleanError?.message || feed.error}
</Text>
<Button type="default-light" onPress={onPress}>
Try again
</Button>
</View>
)
}
function FeedHeaderLoading() {
const pal = usePalette('default')
return (
<View
style={[
pal.viewLight,
{
width: '100%',
paddingHorizontal: 18,
paddingVertical: 18,
},
]}
/>
)
}
function FeedInnerHeader({
error,
params,
showContextMenu = true,
}: {
error?: PostsFeedModelError
params: AppBskyFeedGetFeed.QueryParams
showContextMenu?: boolean
}) {
const pal = usePalette('default')
const {host, rkey} = new AtUri(params.feed)
const uri = makeRecordUri(host, 'app.bsky.feed.generator', rkey)
const feed = useCustomFeed(uri)
const author = feed?.data?.creator?.handle
const {isDesktop} = useWebMediaQueries()
const shareUrl = toShareUrl(`/profile/${host}`)
return (
<View
style={[
pal.viewLight,
{
width: '100%',
paddingHorizontal: 18,
},
]}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 8,
}}>
<Text>
<Text style={[pal.textLight]}>By&nbsp;</Text>
{author ? (
<TextLink href={shareUrl} text={'@' + author} />
) : (
<Text style={[pal.textLight]}>...</Text>
)}
</Text>
{author && showContextMenu && !isDesktop ? (
<CustomFeedContextMenu feed={feed}>
<View
style={{
height: 24,
width: 24,
paddingTop: 1,
borderRadius: 24,
backgroundColor: pal.view.backgroundColor,
justifyContent: 'center',
alignItems: 'center',
}}>
<FontAwesomeIcon
icon="ellipsis"
size={18}
color={pal.colors.textLight}
/>
</View>
</CustomFeedContextMenu>
) : (
<View style={{height: 24}} />
)}
</View>
{error?.message && (
<>
<View style={[pal.view, {height: 1}]} />
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-end',
paddingVertical: 12,
}}>
<Text style={{width: '100%'}}>{error.message}</Text>
{error.type === 'upstream' ? (
<Button type="default-light" style={{marginTop: 12}}>
Remove from my feeds
</Button>
) : null}
</View>
</>
)}
</View>
)
}
const styles = StyleSheet.create({ const styles = StyleSheet.create({
feedFooter: {paddingTop: 20}, feedFooter: {paddingTop: 20},
}) })
+19 -138
View File
@@ -21,19 +21,16 @@ import {Text} from 'view/com/util/text/Text'
import * as Toast from 'view/com/util/Toast' import * as Toast from 'view/com/util/Toast'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetTitle} from 'lib/hooks/useSetTitle' import {useSetTitle} from 'lib/hooks/useSetTitle'
import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers'
import {Haptics} from 'lib/haptics' import {Haptics} from 'lib/haptics'
import {ComposeIcon2} from 'lib/icons' import {ComposeIcon2} from 'lib/icons'
import {FAB} from '../com/util/fab/FAB' import {FAB} from '../com/util/fab/FAB'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {EmptyState} from 'view/com/util/EmptyState' import {EmptyState} from 'view/com/util/EmptyState'
import {useAnalytics} from 'lib/analytics/analytics'
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {resolveName} from 'lib/api' import {resolveName} from 'lib/api'
import {CenteredView} from 'view/com/util/Views' import {CenteredView} from 'view/com/util/Views'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {CustomFeedContextMenu} from 'view/com/feeds/CustomFeedContextMenu'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'>
@@ -122,11 +119,9 @@ export const CustomFeedScreenInner = observer(
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const navigation = useNavigation<NavigationProp>()
const isScreenFocused = useIsFocused() const isScreenFocused = useIsFocused()
const {isMobile, isTabletOrDesktop} = useWebMediaQueries() const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
const {track} = useAnalytics() const {rkey} = route.params
const {rkey, name: handleOrDid} = route.params
const uri = useMemo( const uri = useMemo(
() => makeRecordUri(feedOwnerDid, 'app.bsky.feed.generator', rkey), () => makeRecordUri(feedOwnerDid, 'app.bsky.feed.generator', rkey),
[rkey, feedOwnerDid], [rkey, feedOwnerDid],
@@ -185,36 +180,6 @@ export const CustomFeedScreenInner = observer(
}) })
}, [store, currentFeed]) }, [store, currentFeed])
const onPressAbout = React.useCallback(() => {
store.shell.openModal({
name: 'confirm',
title: currentFeed?.displayName || '',
message:
currentFeed?.data.description || 'This feed has no description.',
confirmBtnText: 'Close',
onPressConfirm() {},
})
}, [store, currentFeed])
const onPressViewAuthor = React.useCallback(() => {
navigation.navigate('Profile', {name: handleOrDid})
}, [handleOrDid, navigation])
const onPressShare = React.useCallback(() => {
const url = toShareUrl(`/profile/${handleOrDid}/feed/${rkey}`)
shareUrl(url)
track('CustomFeed:Share')
}, [handleOrDid, rkey, track])
const onPressReport = React.useCallback(() => {
if (!currentFeed) return
store.shell.openModal({
name: 'report',
uri: currentFeed.uri,
cid: currentFeed.data.cid,
})
}, [store, currentFeed])
const onScrollToTop = React.useCallback(() => { const onScrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({offset: 0, animated: true}) scrollElRef.current?.scrollToOffset({offset: 0, animated: true})
resetMainScroll() resetMainScroll()
@@ -243,90 +208,6 @@ export const CustomFeedScreenInner = observer(
} }
}, [store, onSoftReset, isScreenFocused]) }, [store, onSoftReset, isScreenFocused])
const dropdownItems: DropdownItem[] = React.useMemo(() => {
return [
currentFeed
? {
testID: 'feedHeaderDropdownAboutBtn',
label: 'About this feed',
onPress: onPressAbout,
icon: {
ios: {
name: 'info.circle',
},
android: '',
web: 'info',
},
}
: undefined,
{
testID: 'feedHeaderDropdownViewAuthorBtn',
label: 'View author',
onPress: onPressViewAuthor,
icon: {
ios: {
name: 'person',
},
android: '',
web: ['far', 'user'],
},
},
{
testID: 'feedHeaderDropdownToggleSavedBtn',
label: currentFeed?.isSaved
? 'Remove from my feeds'
: 'Add to my feeds',
onPress: onToggleSaved,
icon: currentFeed?.isSaved
? {
ios: {
name: 'trash',
},
android: 'ic_delete',
web: 'trash',
}
: {
ios: {
name: 'plus',
},
android: '',
web: 'plus',
},
},
{
testID: 'feedHeaderDropdownReportBtn',
label: 'Report feed',
onPress: onPressReport,
icon: {
ios: {
name: 'exclamationmark.triangle',
},
android: 'ic_menu_report_image',
web: 'circle-exclamation',
},
},
{
testID: 'feedHeaderDropdownShareBtn',
label: 'Share link',
onPress: onPressShare,
icon: {
ios: {
name: 'square.and.arrow.up',
},
android: 'ic_menu_share',
web: 'share',
},
},
].filter(Boolean) as DropdownItem[]
}, [
currentFeed,
onPressAbout,
onToggleSaved,
onPressReport,
onPressShare,
onPressViewAuthor,
])
const renderEmptyState = React.useCallback(() => { const renderEmptyState = React.useCallback(() => {
return ( return (
<View style={[pal.border, {borderTopWidth: 1, paddingTop: 20}]}> <View style={[pal.border, {borderTopWidth: 1, paddingTop: 20}]}>
@@ -403,25 +284,24 @@ export const CustomFeedScreenInner = observer(
</Text> </Text>
</Button> </Button>
)} )}
<CustomFeedContextMenu
feed={currentFeed}
testID="feedHeaderDropdownBtn">
<View
style={{
paddingLeft: 12,
paddingRight: isMobile ? 12 : 0,
}}>
<FontAwesomeIcon
icon="ellipsis"
size={20}
color={pal.colors.textLight}
/>
</View>
</CustomFeedContextMenu>
</> </>
) : null} ) : null}
<NativeDropdown
testID="feedHeaderDropdownBtn"
items={dropdownItems}
accessibilityLabel="More options"
accessibilityHint="">
<View
style={{
paddingLeft: 12,
paddingRight: isMobile ? 12 : 0,
}}>
<FontAwesomeIcon
icon="ellipsis"
size={20}
color={pal.colors.textLight}
/>
</View>
</NativeDropdown>
</SimpleViewHeader> </SimpleViewHeader>
<Feed <Feed
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
@@ -431,6 +311,7 @@ export const CustomFeedScreenInner = observer(
renderEmptyState={renderEmptyState} renderEmptyState={renderEmptyState}
extraData={[uri, isPinned]} extraData={[uri, isPinned]}
style={!isTabletOrDesktop ? {flex: 1} : undefined} style={!isTabletOrDesktop ? {flex: 1} : undefined}
showFeedHeaderContextMenu={false}
/> />
{isScrolledDown ? ( {isScrolledDown ? (
<LoadLatestBtn <LoadLatestBtn