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
110
src/state/models/discovery/foafs.ts
Normal file
110
src/state/models/discovery/foafs.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
import {AppBskyActorProfile, AppBskyActorRef} from '@atproto/api'
|
||||
import {makeAutoObservable, runInAction} from 'mobx'
|
||||
import sampleSize from 'lodash.samplesize'
|
||||
import {bundleAsync} from 'lib/async/bundle'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
|
||||
export type RefWithInfoAndFollowers = AppBskyActorRef.WithInfo & {
|
||||
followers: AppBskyActorProfile.View[]
|
||||
}
|
||||
|
||||
export type ProfileViewFollows = AppBskyActorProfile.View & {
|
||||
follows: AppBskyActorRef.WithInfo[]
|
||||
}
|
||||
|
||||
export class FoafsModel {
|
||||
isLoading = false
|
||||
hasData = false
|
||||
sources: string[] = []
|
||||
foafs: Map<string, ProfileViewFollows> = new Map()
|
||||
popular: RefWithInfoAndFollowers[] = []
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
get hasContent() {
|
||||
if (this.popular.length > 0) {
|
||||
return true
|
||||
}
|
||||
for (const foaf of this.foafs.values()) {
|
||||
if (foaf.follows.length) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fetch = bundleAsync(async () => {
|
||||
try {
|
||||
this.isLoading = true
|
||||
await this.rootStore.me.follows.fetchIfNeeded()
|
||||
// grab 10 of the users followed by the user
|
||||
this.sources = sampleSize(
|
||||
Object.keys(this.rootStore.me.follows.followDidToRecordMap),
|
||||
10,
|
||||
)
|
||||
if (this.sources.length === 0) {
|
||||
return
|
||||
}
|
||||
this.foafs.clear()
|
||||
this.popular.length = 0
|
||||
|
||||
// fetch their profiles
|
||||
const profiles = await this.rootStore.api.app.bsky.actor.getProfiles({
|
||||
actors: this.sources,
|
||||
})
|
||||
|
||||
// fetch their follows
|
||||
const results = await Promise.allSettled(
|
||||
this.sources.map(source =>
|
||||
this.rootStore.api.app.bsky.graph.getFollows({user: source}),
|
||||
),
|
||||
)
|
||||
|
||||
// store the follows and construct a "most followed" set
|
||||
const popular: RefWithInfoAndFollowers[] = []
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const res = results[i]
|
||||
const profile = profiles.data.profiles[i]
|
||||
const source = this.sources[i]
|
||||
if (res.status === 'fulfilled' && profile) {
|
||||
// filter out users already followed by the user or that *is* the user
|
||||
res.value.data.follows = res.value.data.follows.filter(follow => {
|
||||
return (
|
||||
follow.did !== this.rootStore.me.did &&
|
||||
!this.rootStore.me.follows.isFollowing(follow.did)
|
||||
)
|
||||
})
|
||||
|
||||
runInAction(() => {
|
||||
this.foafs.set(source, {
|
||||
...profile,
|
||||
follows: res.value.data.follows,
|
||||
})
|
||||
})
|
||||
for (const follow of res.value.data.follows) {
|
||||
let item = popular.find(p => p.did === follow.did)
|
||||
if (!item) {
|
||||
item = {...follow, followers: []}
|
||||
popular.push(item)
|
||||
}
|
||||
item.followers.push(profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popular.sort((a, b) => b.followers.length - a.followers.length)
|
||||
runInAction(() => {
|
||||
this.popular = popular.filter(p => p.followers.length > 1).slice(0, 20)
|
||||
})
|
||||
this.hasData = true
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch FOAFs', e)
|
||||
} finally {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue