about summary refs log tree commit diff
path: root/src/view/com/modals
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals')
-rw-r--r--src/view/com/modals/BirthDateSettings.tsx52
-rw-r--r--src/view/com/modals/ContentFilteringSettings.tsx220
2 files changed, 145 insertions, 127 deletions
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>