* Rework feed polling to correctly detect when new content is available (close #344) * Tweak how the tuner works for consistency * Improve the feed-update behavior after posting * Load latest notifications when opening the tabzio/stable
parent
449f9243f3
commit
f6f1fe2558
|
@ -10,10 +10,12 @@ export type FeedTunerFn = (
|
|||
) => void
|
||||
|
||||
export class FeedViewPostsSlice {
|
||||
isFlattenedReply = false
|
||||
|
||||
constructor(public items: FeedViewPost[] = []) {}
|
||||
|
||||
get uri() {
|
||||
if (this.isReply) {
|
||||
if (this.isFlattenedReply) {
|
||||
return this.items[1].post.uri
|
||||
}
|
||||
return this.items[0].post.uri
|
||||
|
@ -39,12 +41,8 @@ export class FeedViewPostsSlice {
|
|||
return this.isThread && !this.items[0].reply
|
||||
}
|
||||
|
||||
get isReply() {
|
||||
return this.items.length > 1 && !this.isThread
|
||||
}
|
||||
|
||||
get rootItem() {
|
||||
if (this.isReply) {
|
||||
if (this.isFlattenedReply) {
|
||||
return this.items[1]
|
||||
}
|
||||
return this.items[0]
|
||||
|
@ -70,6 +68,7 @@ export class FeedViewPostsSlice {
|
|||
|
||||
flattenReplyParent() {
|
||||
if (this.items[0].reply?.parent) {
|
||||
this.isFlattenedReply = true
|
||||
this.items.splice(0, 0, {post: this.items[0].reply?.parent})
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +104,11 @@ export class FeedTuner {
|
|||
slices.unshift(new FeedViewPostsSlice([item]))
|
||||
}
|
||||
|
||||
// run the custom tuners
|
||||
for (const tunerFn of tunerFns) {
|
||||
tunerFn(this, slices)
|
||||
}
|
||||
|
||||
// remove any items already "seen"
|
||||
const soonToBeSeenUris: Set<string> = new Set()
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
|
@ -135,11 +139,6 @@ export class FeedTuner {
|
|||
// sort by slice roots' timestamps
|
||||
slices.sort((a, b) => b.ts.localeCompare(a.ts))
|
||||
|
||||
// run the custom tuners
|
||||
for (const tunerFn of tunerFns) {
|
||||
tunerFn(this, slices)
|
||||
}
|
||||
|
||||
for (const slice of slices) {
|
||||
for (const item of slice.items) {
|
||||
this.seenUris.add(item.post.uri)
|
||||
|
@ -170,12 +169,12 @@ export class FeedTuner {
|
|||
static likedRepliesOnly(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||
// remove any replies without at least 2 likes
|
||||
for (let i = slices.length - 1; i >= 0; i--) {
|
||||
if (slices[i].isFullThread) {
|
||||
if (slices[i].isFullThread || !slices[i].rootItem.reply) {
|
||||
continue
|
||||
}
|
||||
const item = slices[i].rootItem
|
||||
const isRepost = Boolean(item.reason)
|
||||
if (item.reply && !isRepost && item.post.upvoteCount < 2) {
|
||||
if (!isRepost && item.post.upvoteCount < 2) {
|
||||
slices.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -254,6 +254,7 @@ export class FeedModel {
|
|||
|
||||
// data
|
||||
slices: FeedSliceModel[] = []
|
||||
nextSlices: FeedSliceModel[] = []
|
||||
|
||||
constructor(
|
||||
public rootStore: RootStoreModel,
|
||||
|
@ -325,6 +326,7 @@ export class FeedModel {
|
|||
this.loadMoreCursor = undefined
|
||||
this.pollCursor = undefined
|
||||
this.slices = []
|
||||
this.nextSlices = []
|
||||
this.tuner.reset()
|
||||
}
|
||||
|
||||
|
@ -422,30 +424,6 @@ export class FeedModel {
|
|||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Load more posts to the start of the feed
|
||||
*/
|
||||
loadLatest = bundleAsync(async () => {
|
||||
await this.lock.acquireAsync()
|
||||
try {
|
||||
this.setHasNewLatest(false)
|
||||
this._xLoading()
|
||||
try {
|
||||
const res = await this._getFeed({limit: PAGE_SIZE})
|
||||
await this._prependAll(res)
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
this._xIdle() // don't bubble the error to the user
|
||||
this.rootStore.log.error('FeedView: Failed to load latest', {
|
||||
params: this.params,
|
||||
e,
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
this.lock.release()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Update content in-place
|
||||
*/
|
||||
|
@ -487,22 +465,42 @@ export class FeedModel {
|
|||
/**
|
||||
* Check if new posts are available
|
||||
*/
|
||||
async checkForLatest() {
|
||||
async checkForLatest({autoPrepend}: {autoPrepend?: boolean} = {}) {
|
||||
if (this.hasNewLatest || this.feedType === 'suggested') {
|
||||
return
|
||||
}
|
||||
const res = await this._getFeed({limit: 1})
|
||||
const currentLatestUri = this.pollCursor
|
||||
const item = res.data.feed?.[0]
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
if (AppBskyFeedFeedViewPost.isReasonRepost(item.reason)) {
|
||||
if (item.reason.by.did === this.rootStore.me.did) {
|
||||
return // ignore reposts by the user
|
||||
const res = await this._getFeed({limit: PAGE_SIZE})
|
||||
const tuner = new FeedTuner()
|
||||
const nextSlices = tuner.tune(res.data.feed, this.feedTuners)
|
||||
if (nextSlices[0]?.uri !== this.slices[0]?.uri) {
|
||||
const nextSlicesModels = nextSlices.map(
|
||||
slice =>
|
||||
new FeedSliceModel(this.rootStore, `item-${_idCounter++}`, slice),
|
||||
)
|
||||
if (autoPrepend) {
|
||||
this.slices = nextSlicesModels.concat(
|
||||
this.slices.filter(slice1 =>
|
||||
nextSlicesModels.find(slice2 => slice1.uri === slice2.uri),
|
||||
),
|
||||
)
|
||||
this.setHasNewLatest(false)
|
||||
} else {
|
||||
this.nextSlices = nextSlicesModels
|
||||
this.setHasNewLatest(true)
|
||||
}
|
||||
} else {
|
||||
this.setHasNewLatest(false)
|
||||
}
|
||||
this.setHasNewLatest(item.post.uri !== currentLatestUri)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current slices to the "next slices" loaded by checkForLatest
|
||||
*/
|
||||
resetToLatest() {
|
||||
if (this.nextSlices.length) {
|
||||
this.slices = this.nextSlices
|
||||
}
|
||||
this.setHasNewLatest(false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -574,27 +572,6 @@ export class FeedModel {
|
|||
})
|
||||
}
|
||||
|
||||
private async _prependAll(
|
||||
res: GetTimeline.Response | GetAuthorFeed.Response,
|
||||
) {
|
||||
this.pollCursor = res.data.feed[0]?.post.uri
|
||||
|
||||
const slices = this.tuner.tune(res.data.feed, this.feedTuners)
|
||||
|
||||
const toPrepend: FeedSliceModel[] = []
|
||||
for (const slice of slices) {
|
||||
const itemModel = new FeedSliceModel(
|
||||
this.rootStore,
|
||||
`item-${_idCounter++}`,
|
||||
slice,
|
||||
)
|
||||
toPrepend.push(itemModel)
|
||||
}
|
||||
runInAction(() => {
|
||||
this.slices = toPrepend.concat(this.slices)
|
||||
})
|
||||
}
|
||||
|
||||
private _updateAll(res: GetTimeline.Response | GetAuthorFeed.Response) {
|
||||
for (const item of res.data.feed) {
|
||||
const existingSlice = this.slices.find(slice =>
|
||||
|
|
|
@ -166,7 +166,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
setIsProcessing(false)
|
||||
return
|
||||
}
|
||||
store.me.mainFeed.loadLatest()
|
||||
store.me.mainFeed.checkForLatest({autoPrepend: true})
|
||||
onPost?.()
|
||||
hackfixOnClose()
|
||||
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
|
||||
|
|
|
@ -158,7 +158,7 @@ const FeedPage = observer(
|
|||
}, [feed])
|
||||
|
||||
const onPressLoadLatest = React.useCallback(() => {
|
||||
feed.refresh()
|
||||
feed.resetToLatest()
|
||||
scrollToTop()
|
||||
}, [feed, scrollToTop])
|
||||
|
||||
|
|
|
@ -74,7 +74,8 @@ export const NotificationsScreen = withAuthRequired(
|
|||
React.useCallback(() => {
|
||||
store.log.debug('NotificationsScreen: Updating feed')
|
||||
const softResetSub = store.onScreenSoftReset(scrollToTop)
|
||||
store.me.notifications.update()
|
||||
store.me.notifications.loadUnreadCount()
|
||||
store.me.notifications.loadLatest()
|
||||
screen('Notifications')
|
||||
|
||||
return () => {
|
||||
|
|
Loading…
Reference in New Issue