Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Fixes to likes list

+68 -83
+33 -49
src/state/models/votes-view.ts
··· 1 1 import {makeAutoObservable, runInAction} from 'mobx' 2 2 import {AtUri} from '../../third-party/uri' 3 - import { 4 - AppBskyFeedGetVotes as GetVotes, 5 - AppBskyActorRef as ActorRef, 6 - } from '@atproto/api' 3 + import {AppBskyFeedGetVotes as GetVotes} from '@atproto/api' 7 4 import {RootStoreModel} from './root-store' 8 5 9 - export class VotesViewItemModel implements GetVotes.Vote { 10 - // ui state 11 - _reactKey: string = '' 6 + const PAGE_SIZE = 30 12 7 13 - // data 14 - direction: 'up' | 'down' = 'up' 15 - indexedAt: string = '' 16 - createdAt: string = '' 17 - actor: ActorRef.WithInfo = { 18 - did: '', 19 - handle: '', 20 - declaration: {cid: '', actorType: ''}, 21 - } 22 - 23 - constructor(reactKey: string, v: GetVotes.Vote) { 24 - makeAutoObservable(this) 25 - this._reactKey = reactKey 26 - Object.assign(this, v) 27 - } 28 - } 8 + export type VoteItem = GetVotes.Vote 29 9 30 10 export class VotesViewModel { 31 11 // state ··· 35 15 error = '' 36 16 resolvedUri = '' 37 17 params: GetVotes.QueryParams 18 + hasMore = true 19 + loadMoreCursor?: string 20 + private _loadMorePromise: Promise<void> | undefined 38 21 39 22 // data 40 23 uri: string = '' 41 - votes: VotesViewItemModel[] = [] 24 + votes: VoteItem[] = [] 42 25 43 26 constructor(public rootStore: RootStoreModel, params: GetVotes.QueryParams) { 44 27 makeAutoObservable( ··· 67 50 // public api 68 51 // = 69 52 70 - async setup() { 71 - if (!this.resolvedUri) { 72 - await this._resolveUri() 73 - } 74 - await this._fetch() 75 - } 76 - 77 53 async refresh() { 78 - await this._fetch(true) 54 + return this.loadMore(true) 79 55 } 80 56 81 - async loadMore() { 82 - // TODO 57 + async loadMore(isRefreshing = false) { 58 + if (this._loadMorePromise) { 59 + return this._loadMorePromise 60 + } 61 + if (!this.resolvedUri) { 62 + await this._resolveUri() 63 + } 64 + this._loadMorePromise = this._loadMore(isRefreshing) 65 + await this._loadMorePromise 66 + this._loadMorePromise = undefined 83 67 } 84 68 85 69 // state transitions ··· 118 102 }) 119 103 } 120 104 121 - private async _fetch(isRefreshing = false) { 105 + private async _loadMore(isRefreshing = false) { 122 106 this._xLoading(isRefreshing) 123 107 try { 124 - const res = await this.rootStore.api.app.bsky.feed.getVotes( 125 - Object.assign({}, this.params, {uri: this.resolvedUri}), 126 - ) 127 - this._replaceAll(res) 108 + const params = Object.assign({}, this.params, { 109 + uri: this.resolvedUri, 110 + limit: PAGE_SIZE, 111 + before: this.loadMoreCursor, 112 + }) 113 + if (this.isRefreshing) { 114 + this.votes = [] 115 + } 116 + const res = await this.rootStore.api.app.bsky.feed.getVotes(params) 117 + this._appendAll(res) 128 118 this._xIdle() 129 119 } catch (e: any) { 130 120 this._xIdle(e) 131 121 } 132 122 } 133 123 134 - private _replaceAll(res: GetVotes.Response) { 135 - this.votes.length = 0 136 - let counter = 0 137 - for (const item of res.data.votes) { 138 - this._append(counter++, item) 139 - } 140 - } 141 - 142 - private _append(keyId: number, item: GetVotes.Vote) { 143 - this.votes.push(new VotesViewItemModel(`item-${keyId}`, item)) 124 + private _appendAll(res: GetVotes.Response) { 125 + this.loadMoreCursor = res.data.cursor 126 + this.hasMore = !!this.loadMoreCursor 127 + this.votes = this.votes.concat(res.data.votes) 144 128 } 145 129 }
+35 -34
src/view/com/post-thread/PostVotedBy.tsx
··· 1 1 import React, {useEffect} from 'react' 2 2 import {observer} from 'mobx-react-lite' 3 3 import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native' 4 - import { 5 - VotesViewModel, 6 - VotesViewItemModel, 7 - } from '../../../state/models/votes-view' 4 + import {VotesViewModel, VotesItem} from '../../../state/models/votes-view' 8 5 import {Link} from '../util/Link' 9 6 import {Text} from '../util/text/Text' 10 7 import {ErrorMessage} from '../util/error/ErrorMessage' ··· 20 17 direction: 'up' | 'down' 21 18 }) { 22 19 const store = useStores() 23 - const [view, setView] = React.useState<VotesViewModel | undefined>() 20 + const view = React.useMemo( 21 + () => new VotesViewModel(store, {uri, direction}), 22 + [store, uri, direction], 23 + ) 24 24 25 25 useEffect(() => { 26 - if (view?.params.uri === uri) { 27 - return // no change needed? or trigger refresh? 28 - } 29 - const newView = new VotesViewModel(store, {uri, direction}) 30 - setView(newView) 31 - newView 32 - .setup() 33 - .catch(err => store.log.error('Failed to fetch voted by', err)) 34 - }, [uri, view?.params.uri, store]) 26 + view.loadMore().catch(err => store.log.error('Failed to fetch votes', err)) 27 + }, [view, store.log]) 35 28 36 29 const onRefresh = () => { 37 - view?.refresh() 30 + view.refresh() 31 + } 32 + const onEndReached = () => { 33 + view 34 + .loadMore() 35 + .catch(err => view?.rootStore.log.error('Failed to load more votes', err)) 38 36 } 39 37 40 - // loading 41 - // = 42 - if ( 43 - !view || 44 - (view.isLoading && !view.isRefreshing) || 45 - view.params.uri !== uri 46 - ) { 38 + if (!view.hasLoaded) { 47 39 return ( 48 40 <View> 49 41 <ActivityIndicator /> ··· 67 59 68 60 // loaded 69 61 // = 70 - const renderItem = ({item}: {item: VotesViewItemModel}) => ( 71 - <LikedByItem item={item} /> 72 - ) 62 + const renderItem = ({item}: {item: VotesItem}) => <LikedByItem item={item} /> 73 63 return ( 74 - <View> 75 - <FlatList 76 - data={view.votes} 77 - keyExtractor={item => item._reactKey} 78 - renderItem={renderItem} 79 - contentContainerStyle={{paddingBottom: 200}} 80 - /> 81 - </View> 64 + <FlatList 65 + data={view.votes} 66 + keyExtractor={item => item.actor.did} 67 + refreshing={view.isRefreshing} 68 + onRefresh={onRefresh} 69 + onEndReached={onEndReached} 70 + renderItem={renderItem} 71 + initialNumToRender={15} 72 + ListFooterComponent={() => ( 73 + <View style={styles.footer}> 74 + {view.isLoading && <ActivityIndicator />} 75 + </View> 76 + )} 77 + extraData={view.isLoading} 78 + /> 82 79 ) 83 80 }) 84 81 85 - const LikedByItem = ({item}: {item: VotesViewItemModel}) => { 82 + const LikedByItem = ({item}: {item: VotesItem}) => { 86 83 return ( 87 84 <Link 88 85 style={styles.outer} ··· 134 131 paddingRight: 10, 135 132 paddingTop: 10, 136 133 paddingBottom: 10, 134 + }, 135 + footer: { 136 + height: 200, 137 + paddingTop: 20, 137 138 }, 138 139 })