about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/auth/create/CreateAccount.tsx23
-rw-r--r--src/view/com/feeds/FeedSourceCard.tsx145
-rw-r--r--src/view/com/modals/BirthDateSettings.tsx52
-rw-r--r--src/view/com/modals/ContentFilteringSettings.tsx220
-rw-r--r--src/view/com/testing/TestCtrls.e2e.tsx6
5 files changed, 315 insertions, 131 deletions
diff --git a/src/view/com/auth/create/CreateAccount.tsx b/src/view/com/auth/create/CreateAccount.tsx
index 65f9ba26d..0f3ff41af 100644
--- a/src/view/com/auth/create/CreateAccount.tsx
+++ b/src/view/com/auth/create/CreateAccount.tsx
@@ -19,6 +19,12 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useOnboardingDispatch} from '#/state/shell'
 import {useSessionApi} from '#/state/session'
+import {
+  usePreferencesSetBirthDateMutation,
+  useSetSaveFeedsMutation,
+  DEFAULT_PROD_FEEDS,
+} from '#/state/queries/preferences'
+import {IS_PROD} from '#/lib/constants'
 
 import {Step1} from './Step1'
 import {Step2} from './Step2'
@@ -36,6 +42,8 @@ export const CreateAccount = observer(function CreateAccountImpl({
   const {_} = useLingui()
   const onboardingDispatch = useOnboardingDispatch()
   const {createAccount} = useSessionApi()
+  const {mutate: setBirthDate} = usePreferencesSetBirthDateMutation()
+  const {mutate: setSavedFeeds} = useSetSaveFeedsMutation()
 
   React.useEffect(() => {
     screen('CreateAccount')
@@ -70,13 +78,26 @@ export const CreateAccount = observer(function CreateAccountImpl({
           onboardingDispatch,
           createAccount,
         })
+
+        setBirthDate({birthDate: model.birthDate})
+
+        if (IS_PROD(model.serviceUrl)) {
+          setSavedFeeds(DEFAULT_PROD_FEEDS)
+        }
       } catch {
         // dont need to handle here
       } finally {
         track('Try Create Account')
       }
     }
-  }, [model, track, onboardingDispatch, createAccount])
+  }, [
+    model,
+    track,
+    onboardingDispatch,
+    createAccount,
+    setBirthDate,
+    setSavedFeeds,
+  ])
 
   return (
     <LoggedOutLayout
diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx
index 63af52619..6f9687be5 100644
--- a/src/view/com/feeds/FeedSourceCard.tsx
+++ b/src/view/com/feeds/FeedSourceCard.tsx
@@ -16,6 +16,151 @@ import * as Toast from 'view/com/util/Toast'
 import {sanitizeHandle} from 'lib/strings/handles'
 import {logger} from '#/logger'
 import {useModalControls} from '#/state/modals'
+import {
+  usePreferencesQuery,
+  useSaveFeedMutation,
+  useRemoveFeedMutation,
+} from '#/state/queries/preferences'
+import {useFeedSourceInfoQuery} from '#/state/queries/feed'
+
+export const NewFeedSourceCard = observer(function FeedSourceCardImpl({
+  feedUri,
+  style,
+  showSaveBtn = false,
+  showDescription = false,
+  showLikes = false,
+}: {
+  feedUri: string
+  style?: StyleProp<ViewStyle>
+  showSaveBtn?: boolean
+  showDescription?: boolean
+  showLikes?: boolean
+}) {
+  const pal = usePalette('default')
+  const navigation = useNavigation<NavigationProp>()
+  const {openModal} = useModalControls()
+  const {data: preferences} = usePreferencesQuery()
+  const {data: info} = useFeedSourceInfoQuery({uri: feedUri})
+  const {isPending: isSavePending, mutateAsync: saveFeed} =
+    useSaveFeedMutation()
+  const {isPending: isRemovePending, mutateAsync: removeFeed} =
+    useRemoveFeedMutation()
+
+  const isSaved = Boolean(preferences?.feeds?.saved?.includes(feedUri))
+
+  const onToggleSaved = React.useCallback(async () => {
+    // Only feeds can be un/saved, lists are handled elsewhere
+    if (info?.type !== 'feed') return
+
+    if (isSaved) {
+      openModal({
+        name: 'confirm',
+        title: 'Remove from my feeds',
+        message: `Remove ${info?.displayName} from my feeds?`,
+        onPressConfirm: async () => {
+          try {
+            await removeFeed({uri: feedUri})
+            // await item.unsave()
+            Toast.show('Removed from my feeds')
+          } catch (e) {
+            Toast.show('There was an issue contacting your server')
+            logger.error('Failed to unsave feed', {error: e})
+          }
+        },
+      })
+    } else {
+      try {
+        await saveFeed({uri: feedUri})
+        Toast.show('Added to my feeds')
+      } catch (e) {
+        Toast.show('There was an issue contacting your server')
+        logger.error('Failed to save feed', {error: e})
+      }
+    }
+  }, [isSaved, openModal, info, feedUri, removeFeed, saveFeed])
+
+  if (!info || !preferences) return null
+
+  return (
+    <Pressable
+      testID={`feed-${info.displayName}`}
+      accessibilityRole="button"
+      style={[styles.container, pal.border, style]}
+      onPress={() => {
+        if (info.type === 'feed') {
+          navigation.push('ProfileFeed', {
+            name: info.creatorDid,
+            rkey: new AtUri(info.uri).rkey,
+          })
+        } else if (info.type === 'list') {
+          navigation.push('ProfileList', {
+            name: info.creatorDid,
+            rkey: new AtUri(info.uri).rkey,
+          })
+        }
+      }}
+      key={info.uri}>
+      <View style={[styles.headerContainer]}>
+        <View style={[s.mr10]}>
+          <UserAvatar type="algo" size={36} avatar={info.avatar} />
+        </View>
+        <View style={[styles.headerTextContainer]}>
+          <Text style={[pal.text, s.bold]} numberOfLines={3}>
+            {info.displayName}
+          </Text>
+          <Text style={[pal.textLight]} numberOfLines={3}>
+            {info.type === 'feed' ? 'Feed' : 'List'} by{' '}
+            {sanitizeHandle(info.creatorHandle, '@')}
+          </Text>
+        </View>
+
+        {showSaveBtn && info.type === 'feed' && (
+          <View>
+            <Pressable
+              disabled={isSavePending || isRemovePending}
+              accessibilityRole="button"
+              accessibilityLabel={
+                isSaved ? 'Remove from my feeds' : 'Add to my feeds'
+              }
+              accessibilityHint=""
+              onPress={onToggleSaved}
+              hitSlop={15}
+              style={styles.btn}>
+              {isSaved ? (
+                <FontAwesomeIcon
+                  icon={['far', 'trash-can']}
+                  size={19}
+                  color={pal.colors.icon}
+                />
+              ) : (
+                <FontAwesomeIcon
+                  icon="plus"
+                  size={18}
+                  color={pal.colors.link}
+                />
+              )}
+            </Pressable>
+          </View>
+        )}
+      </View>
+
+      {showDescription && info.description ? (
+        <RichText
+          style={[pal.textLight, styles.description]}
+          richText={info.description}
+          numberOfLines={3}
+        />
+      ) : null}
+
+      {showLikes && info.type === 'feed' ? (
+        <Text type="sm-medium" style={[pal.text, pal.textLight]}>
+          Liked by {info.likeCount || 0}{' '}
+          {pluralize(info.likeCount || 0, 'user')}
+        </Text>
+      ) : null}
+    </Pressable>
+  )
+})
 
 export const FeedSourceCard = observer(function FeedSourceCardImpl({
   item,
diff --git a/src/view/com/modals/BirthDateSettings.tsx b/src/view/com/modals/BirthDateSettings.tsx
index 6655b7a6b..9996c5641 100644
--- a/src/view/com/modals/BirthDateSettings.tsx
+++ b/src/view/com/modals/BirthDateSettings.tsx
@@ -9,7 +9,6 @@ import {observer} from 'mobx-react-lite'
 import {Text} from '../util/text/Text'
 import {DateInput} from '../util/forms/DateInput'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {useStores} from 'state/index'
 import {s, colors} from 'lib/styles'
 import {usePalette} from 'lib/hooks/usePalette'
 import {isWeb} from 'platform/detection'
@@ -18,33 +17,36 @@ import {cleanError} from 'lib/strings/errors'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useModalControls} from '#/state/modals'
+import {
+  usePreferencesQuery,
+  usePreferencesSetBirthDateMutation,
+  UsePreferencesQueryResponse,
+} from '#/state/queries/preferences'
+import {logger} from '#/logger'
 
 export const snapPoints = ['50%']
 
-export const Component = observer(function Component({}: {}) {
+function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) {
   const pal = usePalette('default')
-  const store = useStores()
+  const {isMobile} = useWebMediaQueries()
   const {_} = useLingui()
+  const {
+    isPending,
+    isError,
+    error,
+    mutateAsync: setBirthDate,
+  } = usePreferencesSetBirthDateMutation()
+  const [date, setDate] = useState(preferences.birthDate || new Date())
   const {closeModal} = useModalControls()
-  const [date, setDate] = useState<Date>(
-    store.preferences.birthDate || new Date(),
-  )
-  const [isProcessing, setIsProcessing] = useState<boolean>(false)
-  const [error, setError] = useState<string>('')
-  const {isMobile} = useWebMediaQueries()
 
-  const onSave = async () => {
-    setError('')
-    setIsProcessing(true)
+  const onSave = React.useCallback(async () => {
     try {
-      await store.preferences.setBirthDate(date)
+      await setBirthDate({birthDate: date})
       closeModal()
     } catch (e) {
-      setError(cleanError(String(e)))
-    } finally {
-      setIsProcessing(false)
+      logger.error(`setBirthDate failed`, {error: e})
     }
-  }
+  }, [date, setBirthDate, closeModal])
 
   return (
     <View
@@ -74,12 +76,12 @@ export const Component = observer(function Component({}: {}) {
         />
       </View>
 
-      {error ? (
-        <ErrorMessage message={error} style={styles.error} />
+      {isError ? (
+        <ErrorMessage message={cleanError(error)} style={styles.error} />
       ) : undefined}
 
       <View style={[styles.btnContainer, pal.borderDark]}>
-        {isProcessing ? (
+        {isPending ? (
           <View style={styles.btn}>
             <ActivityIndicator color="#fff" />
           </View>
@@ -99,6 +101,16 @@ export const Component = observer(function Component({}: {}) {
       </View>
     </View>
   )
+}
+
+export const Component = observer(function Component({}: {}) {
+  const {data: preferences} = usePreferencesQuery()
+
+  return !preferences ? (
+    <ActivityIndicator />
+  ) : (
+    <Inner preferences={preferences} />
+  )
 })
 
 const styles = StyleSheet.create({
diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx
index ad4a0fa52..cd539406c 100644
--- a/src/view/com/modals/ContentFilteringSettings.tsx
+++ b/src/view/com/modals/ContentFilteringSettings.tsx
@@ -1,17 +1,15 @@
 import React from 'react'
+import {BskyPreferences, LabelPreference} from '@atproto/api'
 import {StyleSheet, Pressable, View} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {observer} from 'mobx-react-lite'
 import {ScrollView} from './util'
-import {useStores} from 'state/index'
-import {LabelPreference} from 'state/models/ui/preferences'
 import {s, colors, gradients} from 'lib/styles'
 import {Text} from '../util/text/Text'
 import {TextLink} from '../util/Link'
 import {ToggleButton} from '../util/forms/ToggleButton'
 import {Button} from '../util/forms/Button'
 import {usePalette} from 'lib/hooks/usePalette'
-import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const'
 import {isIOS} from 'platform/detection'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import * as Toast from '../util/Toast'
@@ -19,20 +17,23 @@ import {logger} from '#/logger'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useModalControls} from '#/state/modals'
+import {
+  usePreferencesQuery,
+  usePreferencesSetContentLabelMutation,
+  usePreferencesSetAdultContentMutation,
+  ConfigurableLabelGroup,
+  CONFIGURABLE_LABEL_GROUPS,
+} from '#/state/queries/preferences'
 
 export const snapPoints = ['90%']
 
 export const Component = observer(
   function ContentFilteringSettingsImpl({}: {}) {
-    const store = useStores()
     const {isMobile} = useWebMediaQueries()
     const pal = usePalette('default')
     const {_} = useLingui()
     const {closeModal} = useModalControls()
-
-    React.useEffect(() => {
-      store.preferences.sync()
-    }, [store])
+    const {data: preferences} = usePreferencesQuery()
 
     const onPressDone = React.useCallback(() => {
       closeModal()
@@ -43,29 +44,38 @@ export const Component = observer(
         <Text style={[pal.text, styles.title]}>
           <Trans>Content Filtering</Trans>
         </Text>
+
         <ScrollView style={styles.scrollContainer}>
           <AdultContentEnabledPref />
           <ContentLabelPref
-            group="nsfw"
-            disabled={!store.preferences.adultContentEnabled}
+            preferences={preferences}
+            labelGroup="nsfw"
+            disabled={!preferences?.adultContentEnabled}
           />
           <ContentLabelPref
-            group="nudity"
-            disabled={!store.preferences.adultContentEnabled}
+            preferences={preferences}
+            labelGroup="nudity"
+            disabled={!preferences?.adultContentEnabled}
           />
           <ContentLabelPref
-            group="suggestive"
-            disabled={!store.preferences.adultContentEnabled}
+            preferences={preferences}
+            labelGroup="suggestive"
+            disabled={!preferences?.adultContentEnabled}
           />
           <ContentLabelPref
-            group="gore"
-            disabled={!store.preferences.adultContentEnabled}
+            preferences={preferences}
+            labelGroup="gore"
+            disabled={!preferences?.adultContentEnabled}
+          />
+          <ContentLabelPref preferences={preferences} labelGroup="hate" />
+          <ContentLabelPref preferences={preferences} labelGroup="spam" />
+          <ContentLabelPref
+            preferences={preferences}
+            labelGroup="impersonation"
           />
-          <ContentLabelPref group="hate" />
-          <ContentLabelPref group="spam" />
-          <ContentLabelPref group="impersonation" />
           <View style={{height: isMobile ? 60 : 0}} />
         </ScrollView>
+
         <View
           style={[
             styles.btnContainer,
@@ -94,118 +104,114 @@ export const Component = observer(
   },
 )
 
-const AdultContentEnabledPref = observer(
-  function AdultContentEnabledPrefImpl() {
-    const store = useStores()
-    const pal = usePalette('default')
-    const {openModal} = useModalControls()
+function AdultContentEnabledPref() {
+  const pal = usePalette('default')
+  const {data: preferences} = usePreferencesQuery()
+  const {mutate, variables} = usePreferencesSetAdultContentMutation()
+  const {openModal} = useModalControls()
 
-    const onSetAge = () => openModal({name: 'birth-date-settings'})
+  const onSetAge = React.useCallback(
+    () => openModal({name: 'birth-date-settings'}),
+    [openModal],
+  )
 
-    const onToggleAdultContent = async () => {
-      if (isIOS) {
-        return
-      }
-      try {
-        await store.preferences.setAdultContentEnabled(
-          !store.preferences.adultContentEnabled,
-        )
-      } catch (e) {
-        Toast.show(
-          'There was an issue syncing your preferences with the server',
-        )
-        logger.error('Failed to update preferences with server', {error: e})
-      }
+  const onToggleAdultContent = React.useCallback(async () => {
+    if (isIOS) return
+
+    try {
+      mutate({
+        enabled: !(variables?.enabled ?? preferences?.adultContentEnabled),
+      })
+    } catch (e) {
+      Toast.show('There was an issue syncing your preferences with the server')
+      logger.error('Failed to update preferences with server', {error: e})
     }
+  }, [variables, preferences, mutate])
 
-    return (
-      <View style={s.mb10}>
-        {isIOS ? (
-          store.preferences.adultContentEnabled ? null : (
-            <Text type="md" style={pal.textLight}>
-              Adult content can only be enabled via the Web at{' '}
-              <TextLink
-                style={pal.link}
-                href="https://bsky.app"
-                text="bsky.app"
-              />
-              .
-            </Text>
-          )
-        ) : typeof store.preferences.birthDate === 'undefined' ? (
-          <View style={[pal.viewLight, styles.agePrompt]}>
-            <Text type="md" style={[pal.text, {flex: 1}]}>
-              Confirm your age to enable adult content.
-            </Text>
-            <Button type="primary" label="Set Age" onPress={onSetAge} />
-          </View>
-        ) : (store.preferences.userAge || 0) >= 18 ? (
-          <ToggleButton
-            type="default-light"
-            label="Enable Adult Content"
-            isSelected={store.preferences.adultContentEnabled}
-            onPress={onToggleAdultContent}
-            style={styles.toggleBtn}
-          />
-        ) : (
-          <View style={[pal.viewLight, styles.agePrompt]}>
-            <Text type="md" style={[pal.text, {flex: 1}]}>
-              You must be 18 or older to enable adult content.
-            </Text>
-            <Button type="primary" label="Set Age" onPress={onSetAge} />
-          </View>
-        )}
-      </View>
-    )
-  },
-)
+  return (
+    <View style={s.mb10}>
+      {isIOS ? (
+        preferences?.adultContentEnabled ? null : (
+          <Text type="md" style={pal.textLight}>
+            Adult content can only be enabled via the Web at{' '}
+            <TextLink
+              style={pal.link}
+              href="https://bsky.app"
+              text="bsky.app"
+            />
+            .
+          </Text>
+        )
+      ) : typeof preferences?.birthDate === 'undefined' ? (
+        <View style={[pal.viewLight, styles.agePrompt]}>
+          <Text type="md" style={[pal.text, {flex: 1}]}>
+            Confirm your age to enable adult content.
+          </Text>
+          <Button type="primary" label="Set Age" onPress={onSetAge} />
+        </View>
+      ) : (preferences.userAge || 0) >= 18 ? (
+        <ToggleButton
+          type="default-light"
+          label="Enable Adult Content"
+          isSelected={variables?.enabled ?? preferences?.adultContentEnabled}
+          onPress={onToggleAdultContent}
+          style={styles.toggleBtn}
+        />
+      ) : (
+        <View style={[pal.viewLight, styles.agePrompt]}>
+          <Text type="md" style={[pal.text, {flex: 1}]}>
+            You must be 18 or older to enable adult content.
+          </Text>
+          <Button type="primary" label="Set Age" onPress={onSetAge} />
+        </View>
+      )}
+    </View>
+  )
+}
 
 // TODO: Refactor this component to pass labels down to each tab
 const ContentLabelPref = observer(function ContentLabelPrefImpl({
-  group,
+  preferences,
+  labelGroup,
   disabled,
 }: {
-  group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+  preferences?: BskyPreferences
+  labelGroup: ConfigurableLabelGroup
   disabled?: boolean
 }) {
-  const store = useStores()
   const pal = usePalette('default')
+  const visibility = preferences?.contentLabels?.[labelGroup]
+  const {mutate, variables} = usePreferencesSetContentLabelMutation()
 
   const onChange = React.useCallback(
-    async (v: LabelPreference) => {
-      try {
-        await store.preferences.setContentLabelPref(group, v)
-      } catch (e) {
-        Toast.show(
-          'There was an issue syncing your preferences with the server',
-        )
-        logger.error('Failed to update preferences with server', {error: e})
-      }
+    (vis: LabelPreference) => {
+      mutate({labelGroup, visibility: vis})
     },
-    [store, group],
+    [mutate, labelGroup],
   )
 
   return (
     <View style={[styles.contentLabelPref, pal.border]}>
       <View style={s.flex1}>
         <Text type="md-medium" style={[pal.text]}>
-          {CONFIGURABLE_LABEL_GROUPS[group].title}
+          {CONFIGURABLE_LABEL_GROUPS[labelGroup].title}
         </Text>
-        {typeof CONFIGURABLE_LABEL_GROUPS[group].subtitle === 'string' && (
+        {typeof CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle === 'string' && (
           <Text type="sm" style={[pal.textLight]}>
-            {CONFIGURABLE_LABEL_GROUPS[group].subtitle}
+            {CONFIGURABLE_LABEL_GROUPS[labelGroup].subtitle}
           </Text>
         )}
       </View>
-      {disabled ? (
+
+      {disabled || !visibility ? (
         <Text type="sm-bold" style={pal.textLight}>
           Hide
         </Text>
       ) : (
         <SelectGroup
-          current={store.preferences.contentLabels[group]}
+          current={variables?.visibility || visibility}
           onChange={onChange}
-          group={group}
+          labelGroup={labelGroup}
         />
       )}
     </View>
@@ -215,10 +221,10 @@ const ContentLabelPref = observer(function ContentLabelPrefImpl({
 interface SelectGroupProps {
   current: LabelPreference
   onChange: (v: LabelPreference) => void
-  group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+  labelGroup: ConfigurableLabelGroup
 }
 
-function SelectGroup({current, onChange, group}: SelectGroupProps) {
+function SelectGroup({current, onChange, labelGroup}: SelectGroupProps) {
   return (
     <View style={styles.selectableBtns}>
       <SelectableBtn
@@ -227,14 +233,14 @@ function SelectGroup({current, onChange, group}: SelectGroupProps) {
         label="Hide"
         left
         onChange={onChange}
-        group={group}
+        labelGroup={labelGroup}
       />
       <SelectableBtn
         current={current}
         value="warn"
         label="Warn"
         onChange={onChange}
-        group={group}
+        labelGroup={labelGroup}
       />
       <SelectableBtn
         current={current}
@@ -242,7 +248,7 @@ function SelectGroup({current, onChange, group}: SelectGroupProps) {
         label="Show"
         right
         onChange={onChange}
-        group={group}
+        labelGroup={labelGroup}
       />
     </View>
   )
@@ -255,7 +261,7 @@ interface SelectableBtnProps {
   left?: boolean
   right?: boolean
   onChange: (v: LabelPreference) => void
-  group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+  labelGroup: ConfigurableLabelGroup
 }
 
 function SelectableBtn({
@@ -265,7 +271,7 @@ function SelectableBtn({
   left,
   right,
   onChange,
-  group,
+  labelGroup,
 }: SelectableBtnProps) {
   const pal = usePalette('default')
   const palPrimary = usePalette('inverted')
@@ -281,7 +287,7 @@ function SelectableBtn({
       onPress={() => onChange(value)}
       accessibilityRole="button"
       accessibilityLabel={value}
-      accessibilityHint={`Set ${value} for ${group} content moderation policy`}>
+      accessibilityHint={`Set ${value} for ${labelGroup} content moderation policy`}>
       <Text style={current === value ? palPrimary.text : pal.text}>
         {label}
       </Text>
diff --git a/src/view/com/testing/TestCtrls.e2e.tsx b/src/view/com/testing/TestCtrls.e2e.tsx
index 5e9d816ac..41abc25d3 100644
--- a/src/view/com/testing/TestCtrls.e2e.tsx
+++ b/src/view/com/testing/TestCtrls.e2e.tsx
@@ -1,10 +1,10 @@
 import React from 'react'
 import {Pressable, View} from 'react-native'
-import {useStores} from 'state/index'
 import {navigate} from '../../../Navigation'
 import {useModalControls} from '#/state/modals'
 import {useQueryClient} from '@tanstack/react-query'
 import {useSessionApi} from '#/state/session'
+import {useSetFeedViewPreferencesMutation} from '#/state/queries/preferences'
 
 /**
  * This utility component is only included in the test simulator
@@ -15,10 +15,10 @@ import {useSessionApi} from '#/state/session'
 const BTN = {height: 1, width: 1, backgroundColor: 'red'}
 
 export function TestCtrls() {
-  const store = useStores()
   const queryClient = useQueryClient()
   const {logout, login} = useSessionApi()
   const {openModal} = useModalControls()
+  const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
   const onPressSignInAlice = async () => {
     await login({
       service: 'http://localhost:3000',
@@ -79,7 +79,7 @@ export function TestCtrls() {
       />
       <Pressable
         testID="e2eToggleMergefeed"
-        onPress={() => store.preferences.toggleHomeFeedMergeFeedEnabled()}
+        onPress={() => setFeedViewPref({lab_mergeFeedEnabled: true})}
         accessibilityRole="button"
         style={BTN}
       />