Add 'my feeds' tab
parent
c55ce6de02
commit
f0003d1931
|
@ -139,8 +139,8 @@ export class SavedFeedsModel {
|
||||||
// public api
|
// public api
|
||||||
// =
|
// =
|
||||||
|
|
||||||
async refresh() {
|
async refresh(quietRefresh = false) {
|
||||||
return this.loadMore(true)
|
return this.loadMore(true, quietRefresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
|
@ -153,26 +153,28 @@ export class SavedFeedsModel {
|
||||||
this.feeds = []
|
this.feeds = []
|
||||||
}
|
}
|
||||||
|
|
||||||
loadMore = bundleAsync(async (replace: boolean = false) => {
|
loadMore = bundleAsync(
|
||||||
if (!replace && !this.hasMore) {
|
async (replace: boolean = false, quietRefresh = false) => {
|
||||||
return
|
if (!replace && !this.hasMore) {
|
||||||
}
|
return
|
||||||
this._xLoading(replace)
|
|
||||||
try {
|
|
||||||
const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
|
|
||||||
limit: PAGE_SIZE,
|
|
||||||
cursor: replace ? undefined : this.loadMoreCursor,
|
|
||||||
})
|
|
||||||
if (replace) {
|
|
||||||
this._replaceAll(res)
|
|
||||||
} else {
|
|
||||||
this._appendAll(res)
|
|
||||||
}
|
}
|
||||||
this._xIdle()
|
this._xLoading(replace && !quietRefresh)
|
||||||
} catch (e: any) {
|
try {
|
||||||
this._xIdle(e)
|
const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
|
||||||
}
|
limit: PAGE_SIZE,
|
||||||
})
|
cursor: replace ? undefined : this.loadMoreCursor,
|
||||||
|
})
|
||||||
|
if (replace) {
|
||||||
|
this._replaceAll(res)
|
||||||
|
} else {
|
||||||
|
this._appendAll(res)
|
||||||
|
}
|
||||||
|
this._xIdle()
|
||||||
|
} catch (e: any) {
|
||||||
|
this._xIdle(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
removeFeed(uri: string) {
|
removeFeed(uri: string) {
|
||||||
this.feeds = this.feeds.filter(f => f.data.uri !== uri)
|
this.feeds = this.feeds.filter(f => f.data.uri !== uri)
|
||||||
|
|
|
@ -24,11 +24,13 @@ export const CustomFeed = observer(
|
||||||
item,
|
item,
|
||||||
style,
|
style,
|
||||||
showSaveBtn = false,
|
showSaveBtn = false,
|
||||||
|
showDescription = false,
|
||||||
showLikes = false,
|
showLikes = false,
|
||||||
}: {
|
}: {
|
||||||
item: CustomFeedModel
|
item: CustomFeedModel
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
showSaveBtn?: boolean
|
showSaveBtn?: boolean
|
||||||
|
showDescription?: boolean
|
||||||
showLikes?: boolean
|
showLikes?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
@ -75,7 +77,7 @@ export const CustomFeed = observer(
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{item.data.description ? (
|
{showDescription && item.data.description ? (
|
||||||
<Text style={[pal.textLight, styles.description]} numberOfLines={3}>
|
<Text style={[pal.textLight, styles.description]} numberOfLines={3}>
|
||||||
{item.data.description}
|
{item.data.description}
|
||||||
</Text>
|
</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',
|
'Following',
|
||||||
"What's hot",
|
"What's hot",
|
||||||
...store.me.savedFeeds.listOfPinnedFeedNames,
|
...store.me.savedFeeds.listOfPinnedFeedNames,
|
||||||
|
'My feeds',
|
||||||
],
|
],
|
||||||
[store.me.savedFeeds.listOfPinnedFeedNames],
|
[store.me.savedFeeds.listOfPinnedFeedNames],
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {LoadLatestBtn} from '../com/util/load-latest/LoadLatestBtn'
|
||||||
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
|
import {FeedsTabBar} from '../com/pager/FeedsTabBar'
|
||||||
import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
import {Pager, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||||
import {FAB} from '../com/util/fab/FAB'
|
import {FAB} from '../com/util/fab/FAB'
|
||||||
|
import {SavedFeeds} from 'view/com/feeds/SavedFeeds'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
|
||||||
|
@ -115,14 +116,19 @@ export const HomeScreen = withAuthRequired(
|
||||||
{store.me.savedFeeds.pinned.map((f, index) => {
|
{store.me.savedFeeds.pinned.map((f, index) => {
|
||||||
return (
|
return (
|
||||||
<FeedPage
|
<FeedPage
|
||||||
key={String(2 + index + 1)}
|
key={String(3 + index)}
|
||||||
testID="customFeed"
|
testID="customFeedPage"
|
||||||
isPageFocused={selectedPage === 2 + index}
|
isPageFocused={selectedPage === 2 + index}
|
||||||
feed={new PostsFeedModel(store, 'custom', {feed: f.uri})}
|
feed={new PostsFeedModel(store, 'custom', {feed: f.uri})}
|
||||||
renderEmptyState={renderFollowingEmptyState}
|
renderEmptyState={renderFollowingEmptyState}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
<SavedFeeds
|
||||||
|
key={String(3 + store.me.savedFeeds.pinned.length)}
|
||||||
|
headerOffset={HEADER_OFFSET}
|
||||||
|
isPageFocused={selectedPage === 2 + store.me.savedFeeds.pinned.length}
|
||||||
|
/>
|
||||||
</Pager>
|
</Pager>
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in New Issue