about summary refs log tree commit diff
path: root/src/state/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models')
-rw-r--r--src/state/models/feed-view.ts28
-rw-r--r--src/state/models/me.ts1
-rw-r--r--src/state/models/my-follows.ts7
-rw-r--r--src/state/models/onboard.ts65
-rw-r--r--src/state/models/root-store.ts6
-rw-r--r--src/state/models/session.ts1
-rw-r--r--src/state/models/suggested-actors-view.ts22
-rw-r--r--src/state/models/suggested-posts-view.ts92
8 files changed, 54 insertions, 168 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts
index f80c5f2c0..645b1f2eb 100644
--- a/src/state/models/feed-view.ts
+++ b/src/state/models/feed-view.ts
@@ -15,6 +15,12 @@ import {RootStoreModel} from './root-store'
 import * as apilib from 'lib/api/index'
 import {cleanError} from 'lib/strings/errors'
 import {RichText} from 'lib/strings/rich-text'
+import {SUGGESTED_FOLLOWS} from 'lib/constants'
+import {
+  getCombinedCursors,
+  getMultipleAuthorsPosts,
+  mergePosts,
+} from 'lib/api/build-suggested-posts'
 
 const PAGE_SIZE = 30
 
@@ -535,11 +541,31 @@ export class FeedModel {
     }
   }
 
-  protected _getFeed(
+  protected async _getFeed(
     params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams = {},
   ): Promise<GetTimeline.Response | GetAuthorFeed.Response> {
     params = Object.assign({}, this.params, params)
     if (this.feedType === 'home') {
+      await this.rootStore.me.follows.fetchIfNeeded()
+      if (this.rootStore.me.follows.isEmpty) {
+        const responses = await getMultipleAuthorsPosts(
+          this.rootStore,
+          SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)),
+          params.before,
+          20,
+        )
+        const combinedCursor = getCombinedCursors(responses)
+        const finalData = mergePosts(responses, {bestOfOnly: true})
+        const lastHeaders = responses[responses.length - 1].headers
+        return {
+          success: true,
+          data: {
+            feed: finalData,
+            cursor: combinedCursor,
+          },
+          headers: lastHeaders,
+        }
+      }
       return this.rootStore.api.app.bsky.feed.getTimeline(
         params as GetTimeline.QueryParams,
       )
diff --git a/src/state/models/me.ts b/src/state/models/me.ts
index 0cb84c9fc..451d562a4 100644
--- a/src/state/models/me.ts
+++ b/src/state/models/me.ts
@@ -96,6 +96,7 @@ export class MeModel {
           this.avatar = ''
         }
       })
+      this.mainFeed.clear()
       await Promise.all([
         this.mainFeed.setup().catch(e => {
           this.rootStore.log.error('Failed to setup main feed model', e)
diff --git a/src/state/models/my-follows.ts b/src/state/models/my-follows.ts
index 252e8a3d3..c1fba1352 100644
--- a/src/state/models/my-follows.ts
+++ b/src/state/models/my-follows.ts
@@ -20,6 +20,7 @@ export class MyFollowsModel {
   // data
   followDidToRecordMap: Record<string, string> = {}
   lastSync = 0
+  myDid?: string
 
   constructor(public rootStore: RootStoreModel) {
     makeAutoObservable(
@@ -36,6 +37,7 @@ export class MyFollowsModel {
 
   fetchIfNeeded = bundleAsync(async () => {
     if (
+      this.myDid !== this.rootStore.me.did ||
       Object.keys(this.followDidToRecordMap).length === 0 ||
       Date.now() - this.lastSync > CACHE_TTL
     ) {
@@ -62,6 +64,7 @@ export class MyFollowsModel {
         this.followDidToRecordMap[record.value.subject.did] = record.uri
       }
       this.lastSync = Date.now()
+      this.myDid = this.rootStore.me.did
     })
   })
 
@@ -69,6 +72,10 @@ export class MyFollowsModel {
     return !!this.followDidToRecordMap[did]
   }
 
+  get isEmpty() {
+    return Object.keys(this.followDidToRecordMap).length === 0
+  }
+
   getFollowUri(did: string): string {
     const v = this.followDidToRecordMap[did]
     if (!v) {
diff --git a/src/state/models/onboard.ts b/src/state/models/onboard.ts
deleted file mode 100644
index aa275c6b7..000000000
--- a/src/state/models/onboard.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import {makeAutoObservable} from 'mobx'
-import {isObj, hasProp} from 'lib/type-guards'
-
-export const OnboardStage = {
-  Explainers: 'explainers',
-  Follows: 'follows',
-}
-
-export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows]
-
-export class OnboardModel {
-  isOnboarding: boolean = false
-  stage: string = OnboardStageOrder[0]
-
-  constructor() {
-    makeAutoObservable(this, {
-      serialize: false,
-      hydrate: false,
-    })
-  }
-
-  serialize(): unknown {
-    return {
-      isOnboarding: this.isOnboarding,
-      stage: this.stage,
-    }
-  }
-
-  hydrate(v: unknown) {
-    if (isObj(v)) {
-      if (hasProp(v, 'isOnboarding') && typeof v.isOnboarding === 'boolean') {
-        this.isOnboarding = v.isOnboarding
-      }
-      if (
-        hasProp(v, 'stage') &&
-        typeof v.stage === 'string' &&
-        OnboardStageOrder.includes(v.stage)
-      ) {
-        this.stage = v.stage
-      }
-    }
-  }
-
-  start() {
-    this.isOnboarding = true
-  }
-
-  stop() {
-    this.isOnboarding = false
-  }
-
-  next() {
-    if (!this.isOnboarding) {
-      return
-    }
-    let i = OnboardStageOrder.indexOf(this.stage)
-    i++
-    if (i >= OnboardStageOrder.length) {
-      this.isOnboarding = false
-      this.stage = OnboardStageOrder[0] // in case they make a new account
-    } else {
-      this.stage = OnboardStageOrder[i]
-    }
-  }
-}
diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts
index 43523b759..4b62f501e 100644
--- a/src/state/models/root-store.ts
+++ b/src/state/models/root-store.ts
@@ -17,7 +17,6 @@ import {ProfilesViewModel} from './profiles-view'
 import {LinkMetasViewModel} from './link-metas-view'
 import {NotificationsViewItemModel} from './notifications-view'
 import {MeModel} from './me'
-import {OnboardModel} from './onboard'
 
 export const appInfo = z.object({
   build: z.string(),
@@ -35,7 +34,6 @@ export class RootStoreModel {
   nav = new NavigationModel(this)
   shell = new ShellUiModel(this)
   me = new MeModel(this)
-  onboard = new OnboardModel()
   profiles = new ProfilesViewModel(this)
   linkMetas = new LinkMetasViewModel(this)
 
@@ -85,7 +83,6 @@ export class RootStoreModel {
       session: this.session.serialize(),
       me: this.me.serialize(),
       nav: this.nav.serialize(),
-      onboard: this.onboard.serialize(),
       shell: this.shell.serialize(),
     }
   }
@@ -107,9 +104,6 @@ export class RootStoreModel {
       if (hasProp(v, 'nav')) {
         this.nav.hydrate(v.nav)
       }
-      if (hasProp(v, 'onboard')) {
-        this.onboard.hydrate(v.onboard)
-      }
       if (hasProp(v, 'session')) {
         this.session.hydrate(v.session)
       }
diff --git a/src/state/models/session.ts b/src/state/models/session.ts
index 6e816120d..75a60f353 100644
--- a/src/state/models/session.ts
+++ b/src/state/models/session.ts
@@ -345,7 +345,6 @@ export class SessionModel {
     )
 
     this.setActiveSession(agent, did)
-    this.rootStore.onboard.start()
     this.rootStore.log.debug('SessionModel:createAccount succeeded')
   }
 
diff --git a/src/state/models/suggested-actors-view.ts b/src/state/models/suggested-actors-view.ts
index 4764f581e..33c73b4e1 100644
--- a/src/state/models/suggested-actors-view.ts
+++ b/src/state/models/suggested-actors-view.ts
@@ -4,26 +4,12 @@ import shuffle from 'lodash.shuffle'
 import {RootStoreModel} from './root-store'
 import {cleanError} from 'lib/strings/errors'
 import {bundleAsync} from 'lib/async/bundle'
-import {
-  DEV_SUGGESTED_FOLLOWS,
-  PROD_SUGGESTED_FOLLOWS,
-  STAGING_SUGGESTED_FOLLOWS,
-} from 'lib/constants'
+import {SUGGESTED_FOLLOWS} from 'lib/constants'
 
 const PAGE_SIZE = 30
 
 export type SuggestedActor = Profile.ViewBasic | Profile.View
 
-const getSuggestionList = ({serviceUrl}: {serviceUrl: string}) => {
-  if (serviceUrl.includes('localhost')) {
-    return DEV_SUGGESTED_FOLLOWS
-  } else if (serviceUrl.includes('staging')) {
-    return STAGING_SUGGESTED_FOLLOWS
-  } else {
-    return PROD_SUGGESTED_FOLLOWS
-  }
-}
-
 export class SuggestedActorsViewModel {
   // state
   pageSize = PAGE_SIZE
@@ -126,9 +112,9 @@ export class SuggestedActorsViewModel {
     try {
       // clone the array so we can mutate it
       const actors = [
-        ...getSuggestionList({
-          serviceUrl: this.rootStore.session.currentSession?.service || '',
-        }),
+        ...SUGGESTED_FOLLOWS(
+          this.rootStore.session.currentSession?.service || '',
+        ),
       ]
 
       // fetch the profiles in chunks of 25 (the limit allowed by `getProfiles`)
diff --git a/src/state/models/suggested-posts-view.ts b/src/state/models/suggested-posts-view.ts
index 7b44370de..c6710c44e 100644
--- a/src/state/models/suggested-posts-view.ts
+++ b/src/state/models/suggested-posts-view.ts
@@ -1,21 +1,12 @@
 import {makeAutoObservable, runInAction} from 'mobx'
-import {
-  AppBskyFeedFeedViewPost,
-  AppBskyFeedGetAuthorFeed as GetAuthorFeed,
-} from '@atproto/api'
-type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost
 import {RootStoreModel} from './root-store'
 import {FeedItemModel} from './feed-view'
 import {cleanError} from 'lib/strings/errors'
-
-const TEAM_HANDLES = [
-  'jay.bsky.social',
-  'paul.bsky.social',
-  'dan.bsky.social',
-  'divy.bsky.social',
-  'why.bsky.social',
-  'iamrosewang.bsky.social',
-]
+import {TEAM_HANDLES} from 'lib/constants'
+import {
+  getMultipleAuthorsPosts,
+  mergePosts,
+} from 'lib/api/build-suggested-posts'
 
 export class SuggestedPostsView {
   // state
@@ -54,15 +45,18 @@ export class SuggestedPostsView {
   async setup() {
     this._xLoading()
     try {
-      const responses = await Promise.all(
-        TEAM_HANDLES.map(handle =>
-          this.rootStore.api.app.bsky.feed
-            .getAuthorFeed({author: handle, limit: 10})
-            .catch(_err => ({success: false, headers: {}, data: {feed: []}})),
-        ),
+      const responses = await getMultipleAuthorsPosts(
+        this.rootStore,
+        TEAM_HANDLES(String(this.rootStore.agent.service)),
       )
       runInAction(() => {
-        this.posts = mergeAndFilterResponses(this.rootStore, responses)
+        const finalPosts = mergePosts(responses, {repostsOnly: true})
+        // hydrate into models
+        this.posts = finalPosts.map((post, i) => {
+          // strip the reasons to hide that these are reposts
+          delete post.reason
+          return new FeedItemModel(this.rootStore, `post-${i}`, post)
+        })
       })
       this._xIdle()
     } catch (e: any) {
@@ -90,59 +84,3 @@ export class SuggestedPostsView {
     }
   }
 }
-
-function mergeAndFilterResponses(
-  store: RootStoreModel,
-  responses: GetAuthorFeed.Response[],
-): FeedItemModel[] {
-  let posts: AppBskyFeedFeedViewPost.Main[] = []
-
-  // merge into one array
-  for (const res of responses) {
-    if (res.success) {
-      posts = posts.concat(res.data.feed)
-    }
-  }
-
-  // filter down to reposts of other users
-  const now = Date.now()
-  const uris = new Set()
-  posts = posts.filter(p => {
-    if (isARepostOfSomeoneElse(p) && isRecentEnough(now, p)) {
-      if (uris.has(p.post.uri)) {
-        return false
-      }
-      uris.add(p.post.uri)
-      return true
-    }
-    return false
-  })
-
-  // sort by index time
-  posts.sort((a, b) => {
-    return (
-      Number(new Date(b.post.indexedAt)) - Number(new Date(a.post.indexedAt))
-    )
-  })
-
-  // hydrate into models and strip the reasons to hide that these are reposts
-  return posts.map((post, i) => {
-    delete post.reason
-    return new FeedItemModel(store, `post-${i}`, post)
-  })
-}
-
-function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean {
-  return (
-    post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' &&
-    post.post.author.did !== (post.reason as ReasonRepost).by.did
-  )
-}
-
-const THREE_DAYS = 3 * 24 * 60 * 60 * 1000
-function isRecentEnough(
-  now: number,
-  post: AppBskyFeedFeedViewPost.Main,
-): boolean {
-  return now - Number(new Date(post.post.indexedAt)) < THREE_DAYS
-}