diff options
author | Paul Frazee <pfrazee@gmail.com> | 2022-07-20 11:07:39 -0500 |
---|---|---|
committer | Paul Frazee <pfrazee@gmail.com> | 2022-07-20 11:07:39 -0500 |
commit | 19c694bc601c2b5d494d635134ffe9ca3fdc7774 (patch) | |
tree | 4d9a42e58c2f94dd59bcdd36177ff733fd63d9dc /src | |
parent | 8131158c0e2f45765e5ee953487161302c731792 (diff) | |
download | voidsky-19c694bc601c2b5d494d635134ffe9ca3fdc7774.tar.zst |
Update feed component to use flatlist for lazy-loading scrolling; update feed-view model to match loading needs
Diffstat (limited to 'src')
-rw-r--r-- | src/state/models/feed-view.ts | 121 | ||||
-rw-r--r-- | src/view/com/Feed.tsx | 28 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 2 |
3 files changed, 124 insertions, 27 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index 1fc507a70..4b915a766 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -25,15 +25,22 @@ export class FeedViewItemModel implements bsky.FeedView.FeedItem { } export class FeedViewModel implements bsky.FeedView.Response { - state = 'idle' + isLoading = false + isRefreshing = false + hasLoaded = false error = '' params: bsky.FeedView.Params feed: FeedViewItemModel[] = [] + _loadMorePromise: Promise<void> | undefined constructor(public rootStore: RootStoreModel, params: bsky.FeedView.Params) { makeAutoObservable( this, - {rootStore: false, params: false}, + { + rootStore: false, + params: false, + _loadMorePromise: false, + }, {autoBind: true}, ) this.params = params @@ -47,52 +54,126 @@ export class FeedViewModel implements bsky.FeedView.Response { return this.error !== '' } - get isLoading() { - return this.state === 'loading' + get isEmpty() { + return this.hasLoaded && !this.hasContent } - get isEmpty() { - return !this.hasContent && !this.hasError && !this.isLoading + get loadMoreCursor() { + if (this.hasContent) { + return this.feed[this.feed.length - 1].indexedAt + } + return undefined } - async fetch() { + // public api + // = + + async setup() { + if (this._loadMorePromise) { + return this._loadMorePromise + } if (this.hasContent) { - await this.updateContent() + await this._refresh() } else { - await this.initialLoad() + await this._initialLoad() + } + } + + async refresh() { + if (this._loadMorePromise) { + return this._loadMorePromise } + await this._refresh() } - async initialLoad() { - this.state = 'loading' + async loadMore() { + if (this._loadMorePromise) { + return this._loadMorePromise + } + this._loadMorePromise = this._loadMore() + await this._loadMorePromise + this._loadMorePromise = undefined + } + + // state transitions + // = + + private _xLoading(isRefreshing = false) { + this.isLoading = true + this.isRefreshing = isRefreshing this.error = '' + } + + private _xIdle(err: string = '') { + this.isLoading = false + this.isRefreshing = false + this.hasLoaded = true + this.error = err + } + + // loader functions + // = + + private async _initialLoad() { + console.log('_initialLoad()') + this._xLoading() + await new Promise(r => setTimeout(r, 1e3)) // DEBUG try { const res = (await this.rootStore.api.mainPds.view( 'blueskyweb.xyz:FeedView', this.params, )) as bsky.FeedView.Response this._replaceAll(res) - runInAction(() => { - this.state = 'idle' - }) + this._xIdle() } catch (e: any) { - runInAction(() => { - this.state = 'error' - this.error = `Failed to load feed: ${e.toString()}` + this._xIdle(`Failed to load feed: ${e.toString()}`) + } + } + + private async _loadMore() { + console.log('_loadMore()') + this._xLoading() + await new Promise(r => setTimeout(r, 1e3)) // DEBUG + try { + const params = Object.assign({}, this.params, { + before: this.loadMoreCursor, }) + const res = (await this.rootStore.api.mainPds.view( + 'blueskyweb.xyz:FeedView', + params, + )) as bsky.FeedView.Response + this._appendAll(res) + this._xIdle() + } catch (e: any) { + this._xIdle(`Failed to load feed: ${e.toString()}`) } } - async updateContent() { + private async _refresh() { + console.log('_refresh()') + this._xLoading(true) // TODO: refetch and update items + await new Promise(r => setTimeout(r, 1e3)) // DEBUG + this._xIdle() } private _replaceAll(res: bsky.FeedView.Response) { this.feed.length = 0 let counter = 0 for (const item of res.feed) { - // TODO: validate .record - this.feed.push(new FeedViewItemModel(`item-${counter++}`, item)) + this._append(counter++, item) + } + } + + private _appendAll(res: bsky.FeedView.Response) { + let counter = this.feed.length + for (const item of res.feed) { + this._append(counter++, item) } } + + private _append(keyId: number, item: bsky.FeedView.FeedItem) { + // TODO: validate .record + this.feed.push(new FeedViewItemModel(`item-${keyId}`, item)) + } } diff --git a/src/view/com/Feed.tsx b/src/view/com/Feed.tsx index 2cba0610a..507f0edde 100644 --- a/src/view/com/Feed.tsx +++ b/src/view/com/Feed.tsx @@ -1,16 +1,32 @@ import React from 'react' -import {observer} from 'mobx-react-lite' -import {Text, View} from 'react-native' -import {FeedViewModel} from '../../state/models/feed-view' +import {observer, Observer} from 'mobx-react-lite' +import {Text, View, FlatList} from 'react-native' +import {FeedViewModel, FeedViewItemModel} from '../../state/models/feed-view' import {FeedItem} from './FeedItem' export const Feed = observer(function Feed({feed}: {feed: FeedViewModel}) { + const renderItem = ({item}: {item: FeedViewItemModel}) => ( + <Observer>{() => <FeedItem item={item} />}</Observer> + ) + const onRefresh = () => { + feed.refresh().catch(err => console.error('Failed to refresh', err)) + } + const onEndReached = () => { + feed.loadMore().catch(err => console.error('Failed to load more', err)) + } return ( <View> - {feed.isLoading && <Text>Loading...</Text>} + {feed.isLoading && !feed.isRefreshing && <Text>Loading...</Text>} {feed.hasError && <Text>{feed.error}</Text>} - {feed.hasContent && - feed.feed.map(item => <FeedItem key={item.key} item={item} />)} + {feed.hasContent && ( + <FlatList + data={feed.feed} + renderItem={renderItem} + refreshing={feed.isRefreshing} + onRefresh={onRefresh} + onEndReached={onEndReached} + /> + )} {feed.isEmpty && <Text>This feed is empty!</Text>} </View> ) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index affd042d1..6685eb794 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -9,7 +9,7 @@ export function Home(/*{navigation}: RootTabsScreenProps<'Home'>*/) { const store = useStores() useEffect(() => { console.log('Fetching home feed') - store.homeFeed.fetch() + store.homeFeed.setup() }, [store.homeFeed]) return ( <Shell> |