Add custom feeds selector, rework search, simplify onboarding (#325)
* Get home screen's swipable pager working with the drawer * Add tab bar to pager * Implement popular & following views on home screen * Visual tune-up * Move the feed selector to the footer * Fix to 'new posts' poll * Add the view header as a feed item * Use the native driver on the tabbar indicator to improve perf * Reduce home polling to the currently active page; also reuse some code * Add soft reset on tap selected in tab bar * Remove explicit 'onboarding' flow * Choose good stuff based on service * Add foaf-based follow discovery * Fall back to who to follow * Fix backgrounds * Switch to the off-spec goodstuff route * 1.8 * Fix for dev & staging * Swap the tab bar items and rename suggested to what's hot * Go to whats-hot by default if you have no follows * Implement pager and tabbar for desktop web * Pin deps to make expo happy * Add language filtering to goodstuff
This commit is contained in:
parent
c31ffdac1b
commit
1de724b24b
33 changed files with 1634 additions and 692 deletions
|
@ -257,7 +257,7 @@ export class FeedModel {
|
|||
|
||||
constructor(
|
||||
public rootStore: RootStoreModel,
|
||||
public feedType: 'home' | 'author' | 'suggested',
|
||||
public feedType: 'home' | 'author' | 'suggested' | 'goodstuff',
|
||||
params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams,
|
||||
) {
|
||||
makeAutoObservable(
|
||||
|
@ -336,6 +336,20 @@ export class FeedModel {
|
|||
return this.setup()
|
||||
}
|
||||
|
||||
private get feedTuners() {
|
||||
if (this.feedType === 'goodstuff') {
|
||||
return [
|
||||
FeedTuner.dedupReposts,
|
||||
FeedTuner.likedRepliesOnly,
|
||||
FeedTuner.englishOnly,
|
||||
]
|
||||
}
|
||||
if (this.feedType === 'home') {
|
||||
return [FeedTuner.dedupReposts, FeedTuner.likedRepliesOnly]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Load for first render
|
||||
*/
|
||||
|
@ -399,6 +413,7 @@ export class FeedModel {
|
|||
params: this.params,
|
||||
e,
|
||||
})
|
||||
this.hasMore = false
|
||||
}
|
||||
} finally {
|
||||
this.lock.release()
|
||||
|
@ -476,7 +491,8 @@ export class FeedModel {
|
|||
}
|
||||
const res = await this._getFeed({limit: 1})
|
||||
const currentLatestUri = this.pollCursor
|
||||
const item = res.data.feed[0]
|
||||
const slices = this.tuner.tune(res.data.feed, this.feedTuners)
|
||||
const item = slices[0]?.rootItem
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
|
@ -541,12 +557,7 @@ export class FeedModel {
|
|||
this.loadMoreCursor = res.data.cursor
|
||||
this.hasMore = !!this.loadMoreCursor
|
||||
|
||||
const slices = this.tuner.tune(
|
||||
res.data.feed,
|
||||
this.feedType === 'home'
|
||||
? [FeedTuner.dedupReposts, FeedTuner.likedRepliesOnly]
|
||||
: [],
|
||||
)
|
||||
const slices = this.tuner.tune(res.data.feed, this.feedTuners)
|
||||
|
||||
const toAppend: FeedSliceModel[] = []
|
||||
for (const slice of slices) {
|
||||
|
@ -571,12 +582,7 @@ export class FeedModel {
|
|||
) {
|
||||
this.pollCursor = res.data.feed[0]?.post.uri
|
||||
|
||||
const slices = this.tuner.tune(
|
||||
res.data.feed,
|
||||
this.feedType === 'home'
|
||||
? [FeedTuner.dedupReposts, FeedTuner.likedRepliesOnly]
|
||||
: [],
|
||||
)
|
||||
const slices = this.tuner.tune(res.data.feed, this.feedTuners)
|
||||
|
||||
const toPrepend: FeedSliceModel[] = []
|
||||
for (const slice of slices) {
|
||||
|
@ -634,6 +640,15 @@ export class FeedModel {
|
|||
return this.rootStore.api.app.bsky.feed.getTimeline(
|
||||
params as GetTimeline.QueryParams,
|
||||
)
|
||||
} else if (this.feedType === 'goodstuff') {
|
||||
const res = await getGoodStuff(
|
||||
this.rootStore.session.currentSession?.accessJwt || '',
|
||||
params as GetTimeline.QueryParams,
|
||||
)
|
||||
res.data.feed = (res.data.feed || []).filter(
|
||||
item => !item.post.author.viewer?.muted,
|
||||
)
|
||||
return res
|
||||
} else {
|
||||
return this.rootStore.api.app.bsky.feed.getAuthorFeed(
|
||||
params as GetAuthorFeed.QueryParams,
|
||||
|
@ -641,3 +656,45 @@ export class FeedModel {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK
|
||||
// temporary off-spec route to get the good stuff
|
||||
// -prf
|
||||
async function getGoodStuff(
|
||||
accessJwt: string,
|
||||
params: GetTimeline.QueryParams,
|
||||
): Promise<GetTimeline.Response> {
|
||||
const controller = new AbortController()
|
||||
const to = setTimeout(() => controller.abort(), 15e3)
|
||||
|
||||
const uri = new URL('https://bsky.social/xrpc/app.bsky.unspecced.getPopular')
|
||||
let k: keyof GetTimeline.QueryParams
|
||||
for (k in params) {
|
||||
if (typeof params[k] !== 'undefined') {
|
||||
uri.searchParams.set(k, String(params[k]))
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(String(uri), {
|
||||
method: 'get',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
authorization: `Bearer ${accessJwt}`,
|
||||
},
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
const resHeaders: Record<string, string> = {}
|
||||
res.headers.forEach((value: string, key: string) => {
|
||||
resHeaders[key] = value
|
||||
})
|
||||
let resBody = await res.json()
|
||||
|
||||
clearTimeout(to)
|
||||
|
||||
return {
|
||||
success: res.status === 200,
|
||||
headers: resHeaders,
|
||||
data: resBody,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue