Add 'my feeds' tab
parent
c55ce6de02
commit
f0003d1931
|
@ -139,8 +139,8 @@ export class SavedFeedsModel {
|
|||
// public api
|
||||
// =
|
||||
|
||||
async refresh() {
|
||||
return this.loadMore(true)
|
||||
async refresh(quietRefresh = false) {
|
||||
return this.loadMore(true, quietRefresh)
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
@ -153,11 +153,12 @@ export class SavedFeedsModel {
|
|||
this.feeds = []
|
||||
}
|
||||
|
||||
loadMore = bundleAsync(async (replace: boolean = false) => {
|
||||
loadMore = bundleAsync(
|
||||
async (replace: boolean = false, quietRefresh = false) => {
|
||||
if (!replace && !this.hasMore) {
|
||||
return
|
||||
}
|
||||
this._xLoading(replace)
|
||||
this._xLoading(replace && !quietRefresh)
|
||||
try {
|
||||
const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
|
||||
limit: PAGE_SIZE,
|
||||
|
@ -172,7 +173,8 @@ export class SavedFeedsModel {
|
|||
} catch (e: any) {
|
||||
this._xIdle(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
removeFeed(uri: string) {
|
||||
this.feeds = this.feeds.filter(f => f.data.uri !== uri)
|
||||
|
|
|
@ -24,11 +24,13 @@ export const CustomFeed = observer(
|
|||
item,
|
||||
style,
|
||||
showSaveBtn = false,
|
||||
showDescription = false,
|
||||
showLikes = false,
|
||||
}: {
|
||||
item: CustomFeedModel
|
||||
style?: StyleProp<ViewStyle>
|
||||
showSaveBtn?: boolean
|
||||
showDescription?: boolean
|
||||
showLikes?: boolean
|
||||
}) => {
|
||||
const store = useStores()
|
||||
|
@ -75,7 +77,7 @@ export const CustomFeed = observer(
|
|||
)}
|
||||
</View>
|
||||
|
||||
{item.data.description ? (
|
||||
{showDescription && item.data.description ? (
|
||||
<Text style={[pal.textLight, styles.description]} numberOfLines={3}>
|
||||
{item.data.description}
|
||||
</Text>
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
import React, {useEffect, useCallback} from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
RefreshControl,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {useStores} from 'state/index'
|
||||
import {CustomFeedModel} from 'state/models/feeds/custom-feed'
|
||||
import {SavedFeedsModel} from 'state/models/ui/saved-feeds'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {isDesktopWeb} from 'platform/detection'
|
||||
import {s, colors} from 'lib/styles'
|
||||
import {Link} from 'view/com/util/Link'
|
||||
import {CustomFeed} from 'view/com/feeds/CustomFeed'
|
||||
|
||||
export const SavedFeeds = observer(
|
||||
({
|
||||
headerOffset = 0,
|
||||
isPageFocused,
|
||||
}: {
|
||||
headerOffset?: number
|
||||
isPageFocused: boolean
|
||||
}) => {
|
||||
const pal = usePalette('default')
|
||||
const store = useStores()
|
||||
|
||||
useEffect(() => {
|
||||
if (isPageFocused) {
|
||||
store.shell.setMinimalShellMode(false)
|
||||
store.me.savedFeeds.refresh(true)
|
||||
}
|
||||
}, [store, isPageFocused])
|
||||
|
||||
const renderListEmptyComponent = useCallback(() => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
pal.border,
|
||||
!isDesktopWeb && s.flex1,
|
||||
pal.viewLight,
|
||||
styles.empty,
|
||||
]}>
|
||||
<Text type="lg" style={[pal.text]}>
|
||||
You don't have any saved feeds. You can find feeds by searching on
|
||||
Bluesky.
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}, [pal])
|
||||
|
||||
const renderListFooterComponent = useCallback(() => {
|
||||
return (
|
||||
<Link
|
||||
style={[styles.footerLink, pal.border]}
|
||||
href="/settings/pinned-feeds">
|
||||
<FontAwesomeIcon icon="cog" size={18} color={pal.colors.icon} />
|
||||
<Text type="lg-medium" style={pal.textLight}>
|
||||
Settings
|
||||
</Text>
|
||||
</Link>
|
||||
)
|
||||
}, [pal])
|
||||
|
||||
const renderItem = useCallback(
|
||||
({item}) => (
|
||||
<SavedFeedItem
|
||||
key={item.data.uri}
|
||||
item={item}
|
||||
savedFeeds={store.me.savedFeeds}
|
||||
/>
|
||||
),
|
||||
[store.me.savedFeeds],
|
||||
)
|
||||
|
||||
return (
|
||||
<CenteredView style={[s.flex1]}>
|
||||
<FlatList
|
||||
style={[!isDesktopWeb && s.flex1, {paddingTop: headerOffset}]}
|
||||
data={store.me.savedFeeds.feeds}
|
||||
keyExtractor={item => item.data.uri}
|
||||
refreshing={store.me.savedFeeds.isRefreshing}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={store.me.savedFeeds.isRefreshing}
|
||||
onRefresh={() => store.me.savedFeeds.refresh()}
|
||||
tintColor={pal.colors.text}
|
||||
titleColor={pal.colors.text}
|
||||
progressViewOffset={headerOffset}
|
||||
/>
|
||||
}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={10}
|
||||
ListFooterComponent={renderListFooterComponent}
|
||||
ListEmptyComponent={renderListEmptyComponent}
|
||||
extraData={store.me.savedFeeds.isLoading}
|
||||
contentOffset={{x: 0, y: headerOffset * -1}}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
</CenteredView>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const SavedFeedItem = observer(
|
||||
({
|
||||
item,
|
||||
savedFeeds,
|
||||
}: {
|
||||
item: CustomFeedModel
|
||||
savedFeeds: SavedFeedsModel
|
||||
}) => {
|
||||
const isPinned = savedFeeds.isPinned(item)
|
||||
const onTogglePinned = useCallback(
|
||||
() => savedFeeds.togglePinnedFeed(item),
|
||||
[savedFeeds, item],
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.itemContainer}>
|
||||
<CustomFeed key={item.data.uri} item={item} />
|
||||
<TouchableOpacity accessibilityRole="button" onPress={onTogglePinned}>
|
||||
<FontAwesomeIcon
|
||||
icon="thumb-tack"
|
||||
size={20}
|
||||
color={isPinned ? colors.blue3 : colors.gray3}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
footerLink: {
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
paddingHorizontal: 26,
|
||||
paddingVertical: 18,
|
||||
gap: 18,
|
||||
},
|
||||
empty: {
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 16,
|
||||
borderRadius: 8,
|
||||
marginHorizontal: 18,
|
||||
marginTop: 10,
|
||||
},
|
||||
itemContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginRight: 18,
|
||||
},
|
||||
})
|
|
@ -37,6 +37,7 @@ export const FeedsTabBar = observer(
|
|||
'Following',
|
||||
"What's hot",
|
||||
...store.me.savedFeeds.listOfPinnedFeedNames,
|
||||
'My feeds',
|
||||
],
|
||||
[store.me.savedFeeds.listOfPinnedFeedNames],
|
||||
)
|
||||
|
|
|
@ -14,6 +14,7 @@ import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
|
|||
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
|
||||
import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||
import {FAB} from '../com/util/fab/FAB'
|
||||
import {SavedFeeds} from 'view/com/feeds/SavedFeeds'
|
||||
import {useStores} from 'state/index'
|
||||
import {s} from 'lib/styles'
|
||||
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
||||
|
@ -115,14 +116,19 @@ export const HomeScreen = withAuthRequired(
|
|||
{store.me.savedFeeds.pinned.map((f, index) => {
|
||||
return (
|
||||
<FeedPage
|
||||
key={String(2 + index + 1)}
|
||||
testID="customFeed"
|
||||
key={String(3 + index)}
|
||||
testID="customFeedPage"
|
||||
isPageFocused={selectedPage === 2 + index}
|
||||
feed={new PostsFeedModel(store, 'custom', {feed: f.uri})}
|
||||
renderEmptyState={renderFollowingEmptyState}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<SavedFeeds
|
||||
key={String(3 + store.me.savedFeeds.pinned.length)}
|
||||
headerOffset={HEADER_OFFSET}
|
||||
isPageFocused={selectedPage === 2 + store.me.savedFeeds.pinned.length}
|
||||
/>
|
||||
</Pager>
|
||||
)
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue