about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-09-21 21:00:32 -0700
committerGitHub <noreply@github.com>2023-09-21 21:00:32 -0700
commit8584009baebb6119bf6c531360ceb52c480486a0 (patch)
treed08d871b1ac90c5dfa1698db5bc19db65441a2c0 /src
parent28b692a11868be6a128cc0e1b6c4a38b436fe163 (diff)
downloadvoidsky-8584009baebb6119bf6c531360ceb52c480486a0.tar.zst
Move home feed and thread preferences to server (#1507)
* Move home feed and thread preferences to server

* Fix thread usage of prefs

* Remove log

* Bump @atproto/api@0.6.16

* Improve type usage
Diffstat (limited to 'src')
-rw-r--r--src/lib/api/feed/merge.ts2
-rw-r--r--src/state/models/content/post-thread.ts20
-rw-r--r--src/state/models/ui/preferences.ts258
-rw-r--r--src/view/screens/PostThread.tsx2
-rw-r--r--src/view/screens/PreferencesHomeFeed.tsx52
-rw-r--r--src/view/screens/PreferencesThreads.tsx18
6 files changed, 191 insertions, 161 deletions
diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts
index f93278263..31e27fece 100644
--- a/src/lib/api/feed/merge.ts
+++ b/src/lib/api/feed/merge.ts
@@ -109,7 +109,7 @@ export class MergeFeedAPI implements FeedAPI {
   }
 
   _captureFeedsIfNeeded() {
-    if (!this.rootStore.preferences.homeFeedMergeFeedEnabled) {
+    if (!this.rootStore.preferences.homeFeed.lab_mergeFeedEnabled) {
       return
     }
     if (this.customFeeds.length === 0) {
diff --git a/src/state/models/content/post-thread.ts b/src/state/models/content/post-thread.ts
index 2d3a3d64a..981fb1f1d 100644
--- a/src/state/models/content/post-thread.ts
+++ b/src/state/models/content/post-thread.ts
@@ -8,6 +8,7 @@ import {AtUri} from '@atproto/api'
 import {RootStoreModel} from '../root-store'
 import * as apilib from 'lib/api/index'
 import {cleanError} from 'lib/strings/errors'
+import {ThreadViewPreference} from '../ui/preferences'
 import {PostThreadItemModel} from './post-thread-item'
 
 export class PostThreadModel {
@@ -241,7 +242,7 @@ export class PostThreadModel {
       res.data.thread as AppBskyFeedDefs.ThreadViewPost,
       thread.uri,
     )
-    sortThread(thread, this.rootStore.preferences)
+    sortThread(thread, this.rootStore.preferences.thread)
     this.thread = thread
   }
 }
@@ -263,16 +264,11 @@ function pruneReplies(post: MaybePost) {
   }
 }
 
-interface SortSettings {
-  threadDefaultSort: string
-  threadFollowedUsersFirst: boolean
-}
-
 type MaybeThreadItem =
   | PostThreadItemModel
   | AppBskyFeedDefs.NotFoundPost
   | AppBskyFeedDefs.BlockedPost
-function sortThread(item: MaybeThreadItem, opts: SortSettings) {
+function sortThread(item: MaybeThreadItem, opts: ThreadViewPreference) {
   if ('notFound' in item) {
     return
   }
@@ -301,7 +297,7 @@ function sortThread(item: MaybeThreadItem, opts: SortSettings) {
       if (modScore(a.moderation) !== modScore(b.moderation)) {
         return modScore(a.moderation) - modScore(b.moderation)
       }
-      if (opts.threadFollowedUsersFirst) {
+      if (opts.prioritizeFollowedUsers) {
         const af = a.post.author.viewer?.following
         const bf = b.post.author.viewer?.following
         if (af && !bf) {
@@ -310,17 +306,17 @@ function sortThread(item: MaybeThreadItem, opts: SortSettings) {
           return 1
         }
       }
-      if (opts.threadDefaultSort === 'oldest') {
+      if (opts.sort === 'oldest') {
         return a.post.indexedAt.localeCompare(b.post.indexedAt)
-      } else if (opts.threadDefaultSort === 'newest') {
+      } else if (opts.sort === 'newest') {
         return b.post.indexedAt.localeCompare(a.post.indexedAt)
-      } else if (opts.threadDefaultSort === 'most-likes') {
+      } else if (opts.sort === 'most-likes') {
         if (a.post.likeCount === b.post.likeCount) {
           return b.post.indexedAt.localeCompare(a.post.indexedAt) // newest
         } else {
           return (b.post.likeCount || 0) - (a.post.likeCount || 0) // most likes
         }
-      } else if (opts.threadDefaultSort === 'random') {
+      } else if (opts.sort === 'random') {
         return 0.5 - Math.random() // this is vaguely criminal but we can get away with it
       }
       return b.post.indexedAt.localeCompare(a.post.indexedAt)
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index 5ae391670..b3365bd7c 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -1,5 +1,9 @@
 import {makeAutoObservable, runInAction} from 'mobx'
-import {LabelPreference as APILabelPreference} from '@atproto/api'
+import {
+  LabelPreference as APILabelPreference,
+  BskyFeedViewPreference,
+  BskyThreadViewPreference,
+} from '@atproto/api'
 import AwaitLock from 'await-lock'
 import isEqual from 'lodash.isequal'
 import {isObj, hasProp} from 'lib/type-guards'
@@ -13,6 +17,12 @@ import {LANGUAGES} from '../../../locale/languages'
 
 // TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf
 export type LabelPreference = APILabelPreference | 'show'
+export type FeedViewPreference = BskyFeedViewPreference & {
+  lab_mergeFeedEnabled?: boolean | undefined
+}
+export type ThreadViewPreference = BskyThreadViewPreference & {
+  lab_treeViewEnabled?: boolean | undefined
+}
 const LABEL_GROUPS = [
   'nsfw',
   'nudity',
@@ -28,6 +38,13 @@ const DEFAULT_LANG_CODES = (deviceLocales || [])
   .slice(0, 6)
 const THREAD_SORT_VALUES = ['oldest', 'newest', 'most-likes', 'random']
 
+interface LegacyPreferences {
+  hideReplies?: boolean
+  hideRepliesByLikeCount?: number
+  hideReposts?: boolean
+  hideQuotePosts?: boolean
+}
+
 export class LabelPreferencesModel {
   nsfw: LabelPreference = 'hide'
   nudity: LabelPreference = 'warn'
@@ -52,17 +69,24 @@ export class PreferencesModel {
   savedFeeds: string[] = []
   pinnedFeeds: string[] = []
   birthDate: Date | undefined = undefined
-  homeFeedRepliesEnabled: boolean = true
-  homeFeedRepliesByFollowedOnlyEnabled: boolean = true
-  homeFeedRepliesThreshold: number = 0
-  homeFeedRepostsEnabled: boolean = true
-  homeFeedQuotePostsEnabled: boolean = true
-  homeFeedMergeFeedEnabled: boolean = false
-  threadDefaultSort: string = 'oldest'
-  threadFollowedUsersFirst: boolean = true
-  threadTreeViewEnabled: boolean = false
+  homeFeed: FeedViewPreference = {
+    hideReplies: false,
+    hideRepliesByUnfollowed: false,
+    hideRepliesByLikeCount: 0,
+    hideReposts: false,
+    hideQuotePosts: false,
+    lab_mergeFeedEnabled: false, // experimental
+  }
+  thread: ThreadViewPreference = {
+    sort: 'oldest',
+    prioritizeFollowedUsers: true,
+    lab_treeViewEnabled: false, // experimental
+  }
   requireAltTextEnabled: boolean = false
 
+  // used to help with transitions from device-stored to server-stored preferences
+  legacyPreferences: LegacyPreferences | undefined
+
   // used to linearize async modifications to state
   lock = new AwaitLock()
 
@@ -86,16 +110,6 @@ export class PreferencesModel {
       contentLabels: this.contentLabels,
       savedFeeds: this.savedFeeds,
       pinnedFeeds: this.pinnedFeeds,
-      homeFeedRepliesEnabled: this.homeFeedRepliesEnabled,
-      homeFeedRepliesByFollowedOnlyEnabled:
-        this.homeFeedRepliesByFollowedOnlyEnabled,
-      homeFeedRepliesThreshold: this.homeFeedRepliesThreshold,
-      homeFeedRepostsEnabled: this.homeFeedRepostsEnabled,
-      homeFeedQuotePostsEnabled: this.homeFeedQuotePostsEnabled,
-      homeFeedMergeFeedEnabled: this.homeFeedMergeFeedEnabled,
-      threadDefaultSort: this.threadDefaultSort,
-      threadFollowedUsersFirst: this.threadFollowedUsersFirst,
-      threadTreeViewEnabled: this.threadTreeViewEnabled,
       requireAltTextEnabled: this.requireAltTextEnabled,
     }
   }
@@ -165,71 +179,6 @@ export class PreferencesModel {
       ) {
         this.pinnedFeeds = v.pinnedFeeds
       }
-      // check if home feed replies are enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'homeFeedRepliesEnabled') &&
-        typeof v.homeFeedRepliesEnabled === 'boolean'
-      ) {
-        this.homeFeedRepliesEnabled = v.homeFeedRepliesEnabled
-      }
-      // check if home feed replies "followed only" are enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'homeFeedRepliesByFollowedOnlyEnabled') &&
-        typeof v.homeFeedRepliesByFollowedOnlyEnabled === 'boolean'
-      ) {
-        this.homeFeedRepliesByFollowedOnlyEnabled =
-          v.homeFeedRepliesByFollowedOnlyEnabled
-      }
-      // check if home feed replies threshold is enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'homeFeedRepliesThreshold') &&
-        typeof v.homeFeedRepliesThreshold === 'number'
-      ) {
-        this.homeFeedRepliesThreshold = v.homeFeedRepliesThreshold
-      }
-      // check if home feed reposts are enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'homeFeedRepostsEnabled') &&
-        typeof v.homeFeedRepostsEnabled === 'boolean'
-      ) {
-        this.homeFeedRepostsEnabled = v.homeFeedRepostsEnabled
-      }
-      // check if home feed quote posts are enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'homeFeedQuotePostsEnabled') &&
-        typeof v.homeFeedQuotePostsEnabled === 'boolean'
-      ) {
-        this.homeFeedQuotePostsEnabled = v.homeFeedQuotePostsEnabled
-      }
-      // check if home feed mergefeed is enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'homeFeedMergeFeedEnabled') &&
-        typeof v.homeFeedMergeFeedEnabled === 'boolean'
-      ) {
-        this.homeFeedMergeFeedEnabled = v.homeFeedMergeFeedEnabled
-      }
-      // check if thread sort order is set in preferences, then hydrate
-      if (
-        hasProp(v, 'threadDefaultSort') &&
-        typeof v.threadDefaultSort === 'string' &&
-        THREAD_SORT_VALUES.includes(v.threadDefaultSort)
-      ) {
-        this.threadDefaultSort = v.threadDefaultSort
-      }
-      // check if thread followed-users-first is enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'threadFollowedUsersFirst') &&
-        typeof v.threadFollowedUsersFirst === 'boolean'
-      ) {
-        this.threadFollowedUsersFirst = v.threadFollowedUsersFirst
-      }
-      // check if thread treeview is enabled in preferences, then hydrate
-      if (
-        hasProp(v, 'threadTreeViewEnabled') &&
-        typeof v.threadTreeViewEnabled === 'boolean'
-      ) {
-        this.threadTreeViewEnabled = v.threadTreeViewEnabled
-      }
       // check if requiring alt text is enabled in preferences, then hydrate
       if (
         hasProp(v, 'requireAltTextEnabled') &&
@@ -237,6 +186,8 @@ export class PreferencesModel {
       ) {
         this.requireAltTextEnabled = v.requireAltTextEnabled
       }
+      // grab legacy values
+      this.legacyPreferences = getLegacyPreferences(v)
     }
   }
 
@@ -250,6 +201,10 @@ export class PreferencesModel {
       const prefs = await this.rootStore.agent.getPreferences()
 
       runInAction(() => {
+        if (prefs.feedViewPrefs.home) {
+          this.homeFeed = prefs.feedViewPrefs.home
+        }
+        this.thread = prefs.threadViewPrefs
         this.adultContentEnabled = prefs.adultContentEnabled
         for (const label in prefs.contentLabels) {
           if (
@@ -272,6 +227,9 @@ export class PreferencesModel {
         this.birthDate = prefs.birthDate
       })
 
+      // sync legacy values if needed
+      await this.syncLegacyPreferences()
+
       // set defaults on missing items
       if (typeof prefs.feeds.saved === 'undefined') {
         try {
@@ -298,6 +256,14 @@ export class PreferencesModel {
     await this.rootStore.me.savedFeeds.updateCache(clearCache)
   }
 
+  async syncLegacyPreferences() {
+    if (this.legacyPreferences) {
+      this.homeFeed = {...this.homeFeed, ...this.legacyPreferences}
+      this.legacyPreferences = undefined
+      await this.rootStore.agent.setFeedViewPrefs('home', this.homeFeed)
+    }
+  }
+
   /**
    * This function resets the preferences to an empty array of no preferences.
    */
@@ -510,43 +476,68 @@ export class PreferencesModel {
     await this.rootStore.agent.setPersonalDetails({birthDate})
   }
 
-  toggleHomeFeedRepliesEnabled() {
-    this.homeFeedRepliesEnabled = !this.homeFeedRepliesEnabled
+  async toggleHomeFeedHideReplies() {
+    this.homeFeed.hideReplies = !this.homeFeed.hideReplies
+    await this.rootStore.agent.setFeedViewPrefs('home', {
+      hideReplies: this.homeFeed.hideReplies,
+    })
   }
 
-  toggleHomeFeedRepliesByFollowedOnlyEnabled() {
-    this.homeFeedRepliesByFollowedOnlyEnabled =
-      !this.homeFeedRepliesByFollowedOnlyEnabled
+  async toggleHomeFeedHideRepliesByUnfollowed() {
+    this.homeFeed.hideRepliesByUnfollowed =
+      !this.homeFeed.hideRepliesByUnfollowed
+    await this.rootStore.agent.setFeedViewPrefs('home', {
+      hideRepliesByUnfollowed: this.homeFeed.hideRepliesByUnfollowed,
+    })
   }
 
-  setHomeFeedRepliesThreshold(threshold: number) {
-    this.homeFeedRepliesThreshold = threshold
+  async setHomeFeedHideRepliesByLikeCount(threshold: number) {
+    this.homeFeed.hideRepliesByLikeCount = threshold
+    await this.rootStore.agent.setFeedViewPrefs('home', {
+      hideRepliesByLikeCount: this.homeFeed.hideRepliesByLikeCount,
+    })
   }
 
-  toggleHomeFeedRepostsEnabled() {
-    this.homeFeedRepostsEnabled = !this.homeFeedRepostsEnabled
+  async toggleHomeFeedHideReposts() {
+    this.homeFeed.hideReposts = !this.homeFeed.hideReposts
+    await this.rootStore.agent.setFeedViewPrefs('home', {
+      hideReposts: this.homeFeed.hideReposts,
+    })
   }
 
-  toggleHomeFeedQuotePostsEnabled() {
-    this.homeFeedQuotePostsEnabled = !this.homeFeedQuotePostsEnabled
+  async toggleHomeFeedHideQuotePosts() {
+    this.homeFeed.hideQuotePosts = !this.homeFeed.hideQuotePosts
+    await this.rootStore.agent.setFeedViewPrefs('home', {
+      hideQuotePosts: this.homeFeed.hideQuotePosts,
+    })
   }
 
-  toggleHomeFeedMergeFeedEnabled() {
-    this.homeFeedMergeFeedEnabled = !this.homeFeedMergeFeedEnabled
+  async toggleHomeFeedMergeFeedEnabled() {
+    this.homeFeed.lab_mergeFeedEnabled = !this.homeFeed.lab_mergeFeedEnabled
+    await this.rootStore.agent.setFeedViewPrefs('home', {
+      lab_mergeFeedEnabled: this.homeFeed.lab_mergeFeedEnabled,
+    })
   }
 
-  setThreadDefaultSort(v: string) {
+  async setThreadSort(v: string) {
     if (THREAD_SORT_VALUES.includes(v)) {
-      this.threadDefaultSort = v
+      this.thread.sort = v
+      await this.rootStore.agent.setThreadViewPrefs({sort: v})
     }
   }
 
-  toggleThreadFollowedUsersFirst() {
-    this.threadFollowedUsersFirst = !this.threadFollowedUsersFirst
+  async togglePrioritizedFollowedUsers() {
+    this.thread.prioritizeFollowedUsers = !this.thread.prioritizeFollowedUsers
+    await this.rootStore.agent.setThreadViewPrefs({
+      prioritizeFollowedUsers: this.thread.prioritizeFollowedUsers,
+    })
   }
 
-  toggleThreadTreeViewEnabled() {
-    this.threadTreeViewEnabled = !this.threadTreeViewEnabled
+  async toggleThreadTreeViewEnabled() {
+    this.thread.lab_treeViewEnabled = !this.thread.lab_treeViewEnabled
+    await this.rootStore.agent.setThreadViewPrefs({
+      lab_treeViewEnabled: this.thread.lab_treeViewEnabled,
+    })
   }
 
   toggleRequireAltTextEnabled() {
@@ -560,13 +551,6 @@ export class PreferencesModel {
   getFeedTuners(
     feedType: 'home' | 'following' | 'author' | 'custom' | 'likes',
   ) {
-    const areRepliesEnabled = this.homeFeedRepliesEnabled
-    const areRepliesByFollowedOnlyEnabled =
-      this.homeFeedRepliesByFollowedOnlyEnabled
-    const repliesThreshold = this.homeFeedRepliesThreshold
-    const areRepostsEnabled = this.homeFeedRepostsEnabled
-    const areQuotePostsEnabled = this.homeFeedQuotePostsEnabled
-
     if (feedType === 'custom') {
       return [
         FeedTuner.dedupReposts,
@@ -576,25 +560,25 @@ export class PreferencesModel {
     if (feedType === 'home' || feedType === 'following') {
       const feedTuners = []
 
-      if (areRepostsEnabled) {
-        feedTuners.push(FeedTuner.dedupReposts)
-      } else {
+      if (this.homeFeed.hideReposts) {
         feedTuners.push(FeedTuner.removeReposts)
+      } else {
+        feedTuners.push(FeedTuner.dedupReposts)
       }
 
-      if (areRepliesEnabled) {
+      if (this.homeFeed.hideReplies) {
+        feedTuners.push(FeedTuner.removeReplies)
+      } else {
         feedTuners.push(
           FeedTuner.thresholdRepliesOnly({
             userDid: this.rootStore.session.data?.did || '',
-            minLikes: repliesThreshold,
-            followedOnly: areRepliesByFollowedOnlyEnabled,
+            minLikes: this.homeFeed.hideRepliesByLikeCount,
+            followedOnly: !!this.homeFeed.hideRepliesByUnfollowed,
           }),
         )
-      } else {
-        feedTuners.push(FeedTuner.removeReplies)
       }
 
-      if (!areQuotePostsEnabled) {
+      if (this.homeFeed.hideQuotePosts) {
         feedTuners.push(FeedTuner.removeQuotePosts)
       }
 
@@ -611,3 +595,37 @@ function tempfixLabelPref(pref: LabelPreference): APILabelPreference {
   }
   return pref
 }
+
+function getLegacyPreferences(
+  v: Record<string, unknown>,
+): LegacyPreferences | undefined {
+  const legacyPreferences: LegacyPreferences = {}
+  if (
+    hasProp(v, 'homeFeedRepliesEnabled') &&
+    typeof v.homeFeedRepliesEnabled === 'boolean'
+  ) {
+    legacyPreferences.hideReplies = !v.homeFeedRepliesEnabled
+  }
+  if (
+    hasProp(v, 'homeFeedRepliesThreshold') &&
+    typeof v.homeFeedRepliesThreshold === 'number'
+  ) {
+    legacyPreferences.hideRepliesByLikeCount = v.homeFeedRepliesThreshold
+  }
+  if (
+    hasProp(v, 'homeFeedRepostsEnabled') &&
+    typeof v.homeFeedRepostsEnabled === 'boolean'
+  ) {
+    legacyPreferences.hideReposts = !v.homeFeedRepostsEnabled
+  }
+  if (
+    hasProp(v, 'homeFeedQuotePostsEnabled') &&
+    typeof v.homeFeedQuotePostsEnabled === 'boolean'
+  ) {
+    legacyPreferences.hideQuotePosts = !v.homeFeedQuotePostsEnabled
+  }
+  if (Object.keys(legacyPreferences).length) {
+    return legacyPreferences
+  }
+  return undefined
+}
diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx
index 630a4b6b9..d4447f139 100644
--- a/src/view/screens/PostThread.tsx
+++ b/src/view/screens/PostThread.tsx
@@ -76,7 +76,7 @@ export const PostThreadScreen = withAuthRequired(
             uri={uri}
             view={view}
             onPressReply={onPressReply}
-            treeView={store.preferences.threadTreeViewEnabled}
+            treeView={!!store.preferences.thread.lab_treeViewEnabled}
           />
         </View>
         {isMobile && !store.shell.minimalShellMode && (
diff --git a/src/view/screens/PreferencesHomeFeed.tsx b/src/view/screens/PreferencesHomeFeed.tsx
index 8f6e0761a..21c15931f 100644
--- a/src/view/screens/PreferencesHomeFeed.tsx
+++ b/src/view/screens/PreferencesHomeFeed.tsx
@@ -13,11 +13,23 @@ import {ToggleButton} from 'view/com/util/forms/ToggleButton'
 import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
 import {ViewHeader} from 'view/com/util/ViewHeader'
 import {CenteredView} from 'view/com/util/Views'
+import debounce from 'lodash.debounce'
 
 function RepliesThresholdInput({enabled}: {enabled: boolean}) {
   const store = useStores()
   const pal = usePalette('default')
-  const [value, setValue] = useState(store.preferences.homeFeedRepliesThreshold)
+  const [value, setValue] = useState(
+    store.preferences.homeFeed.hideRepliesByLikeCount,
+  )
+  const save = React.useMemo(
+    () =>
+      debounce(
+        threshold =>
+          store.preferences.setHomeFeedHideRepliesByLikeCount(threshold),
+        500,
+      ), // debouce for 500ms
+    [store],
+  )
 
   return (
     <View style={[!enabled && styles.dimmed]}>
@@ -26,7 +38,7 @@ function RepliesThresholdInput({enabled}: {enabled: boolean}) {
         onValueChange={(v: number | number[]) => {
           const threshold = Math.floor(Array.isArray(v) ? v[0] : v)
           setValue(threshold)
-          store.preferences.setHomeFeedRepliesThreshold(threshold)
+          save(threshold)
         }}
         minimumValue={0}
         maximumValue={25}
@@ -88,16 +100,16 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
             <ToggleButton
               testID="toggleRepliesBtn"
               type="default-light"
-              label={store.preferences.homeFeedRepliesEnabled ? 'Yes' : 'No'}
-              isSelected={store.preferences.homeFeedRepliesEnabled}
-              onPress={store.preferences.toggleHomeFeedRepliesEnabled}
+              label={store.preferences.homeFeed.hideReplies ? 'No' : 'Yes'}
+              isSelected={!store.preferences.homeFeed.hideReplies}
+              onPress={store.preferences.toggleHomeFeedHideReplies}
             />
           </View>
           <View
             style={[
               pal.viewLight,
               styles.card,
-              !store.preferences.homeFeedRepliesEnabled && styles.dimmed,
+              store.preferences.homeFeed.hideReplies && styles.dimmed,
             ]}>
             <Text type="title-sm" style={[pal.text, s.pb5]}>
               Reply Filters
@@ -108,12 +120,10 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
             <ToggleButton
               type="default-light"
               label="Followed users only"
-              isSelected={
-                store.preferences.homeFeedRepliesByFollowedOnlyEnabled
-              }
+              isSelected={store.preferences.homeFeed.hideRepliesByUnfollowed}
               onPress={
-                store.preferences.homeFeedRepliesEnabled
-                  ? store.preferences.toggleHomeFeedRepliesByFollowedOnlyEnabled
+                !store.preferences.homeFeed.hideReplies
+                  ? store.preferences.toggleHomeFeedHideRepliesByUnfollowed
                   : undefined
               }
               style={[s.mb10]}
@@ -123,7 +133,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
               feed.
             </Text>
             <RepliesThresholdInput
-              enabled={store.preferences.homeFeedRepliesEnabled}
+              enabled={!store.preferences.homeFeed.hideReplies}
             />
           </View>
 
@@ -136,9 +146,9 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
             </Text>
             <ToggleButton
               type="default-light"
-              label={store.preferences.homeFeedRepostsEnabled ? 'Yes' : 'No'}
-              isSelected={store.preferences.homeFeedRepostsEnabled}
-              onPress={store.preferences.toggleHomeFeedRepostsEnabled}
+              label={store.preferences.homeFeed.hideReposts ? 'No' : 'Yes'}
+              isSelected={!store.preferences.homeFeed.hideReposts}
+              onPress={store.preferences.toggleHomeFeedHideReposts}
             />
           </View>
 
@@ -152,9 +162,9 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
             </Text>
             <ToggleButton
               type="default-light"
-              label={store.preferences.homeFeedQuotePostsEnabled ? 'Yes' : 'No'}
-              isSelected={store.preferences.homeFeedQuotePostsEnabled}
-              onPress={store.preferences.toggleHomeFeedQuotePostsEnabled}
+              label={store.preferences.homeFeed.hideQuotePosts ? 'No' : 'Yes'}
+              isSelected={!store.preferences.homeFeed.hideQuotePosts}
+              onPress={store.preferences.toggleHomeFeedHideQuotePosts}
             />
           </View>
 
@@ -169,8 +179,10 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
             </Text>
             <ToggleButton
               type="default-light"
-              label={store.preferences.homeFeedMergeFeedEnabled ? 'Yes' : 'No'}
-              isSelected={store.preferences.homeFeedMergeFeedEnabled}
+              label={
+                store.preferences.homeFeed.lab_mergeFeedEnabled ? 'Yes' : 'No'
+              }
+              isSelected={!!store.preferences.homeFeed.lab_mergeFeedEnabled}
               onPress={store.preferences.toggleHomeFeedMergeFeedEnabled}
             />
           </View>
diff --git a/src/view/screens/PreferencesThreads.tsx b/src/view/screens/PreferencesThreads.tsx
index 74b28267d..af98a1833 100644
--- a/src/view/screens/PreferencesThreads.tsx
+++ b/src/view/screens/PreferencesThreads.tsx
@@ -59,8 +59,8 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
                   {key: 'most-likes', label: 'Most-liked replies first'},
                   {key: 'random', label: 'Random (aka "Poster\'s Roulette")'},
                 ]}
-                onSelect={store.preferences.setThreadDefaultSort}
-                initialSelection={store.preferences.threadDefaultSort}
+                onSelect={store.preferences.setThreadSort}
+                initialSelection={store.preferences.thread.sort}
               />
             </View>
           </View>
@@ -74,9 +74,11 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
             </Text>
             <ToggleButton
               type="default-light"
-              label={store.preferences.threadFollowedUsersFirst ? 'Yes' : 'No'}
-              isSelected={store.preferences.threadFollowedUsersFirst}
-              onPress={store.preferences.toggleThreadFollowedUsersFirst}
+              label={
+                store.preferences.thread.prioritizeFollowedUsers ? 'Yes' : 'No'
+              }
+              isSelected={store.preferences.thread.prioritizeFollowedUsers}
+              onPress={store.preferences.togglePrioritizedFollowedUsers}
             />
           </View>
 
@@ -91,8 +93,10 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
             </Text>
             <ToggleButton
               type="default-light"
-              label={store.preferences.threadTreeViewEnabled ? 'Yes' : 'No'}
-              isSelected={store.preferences.threadTreeViewEnabled}
+              label={
+                store.preferences.thread.lab_treeViewEnabled ? 'Yes' : 'No'
+              }
+              isSelected={!!store.preferences.thread.lab_treeViewEnabled}
               onPress={store.preferences.toggleThreadTreeViewEnabled}
             />
           </View>