about summary refs log tree commit diff
path: root/src/screens/Settings/SettingsInterests.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/Settings/SettingsInterests.tsx')
-rw-r--r--src/screens/Settings/SettingsInterests.tsx226
1 files changed, 226 insertions, 0 deletions
diff --git a/src/screens/Settings/SettingsInterests.tsx b/src/screens/Settings/SettingsInterests.tsx
new file mode 100644
index 000000000..266545560
--- /dev/null
+++ b/src/screens/Settings/SettingsInterests.tsx
@@ -0,0 +1,226 @@
+import {useMemo, useState} from 'react'
+import {type TextStyle, View, type ViewStyle} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useQueryClient} from '@tanstack/react-query'
+import debounce from 'lodash.debounce'
+
+import {
+  preferencesQueryKey,
+  usePreferencesQuery,
+} from '#/state/queries/preferences'
+import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
+import {useAgent} from '#/state/session'
+import * as Toast from '#/view/com/util/Toast'
+import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
+import {atoms as a, useGutters, useTheme} from '#/alf'
+import {Divider} from '#/components/Divider'
+import * as Toggle from '#/components/forms/Toggle'
+import * as Layout from '#/components/Layout'
+import {Loader} from '#/components/Loader'
+import {Text} from '#/components/Typography'
+
+export function SettingsInterests() {
+  const t = useTheme()
+  const gutters = useGutters(['base'])
+  const {data: preferences} = usePreferencesQuery()
+  const [isSaving, setIsSaving] = useState(false)
+
+  return (
+    <Layout.Screen>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content>
+          <Layout.Header.TitleText>
+            <Trans>Your interests</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Layout.Header.Slot>{isSaving && <Loader />}</Layout.Header.Slot>
+      </Layout.Header.Outer>
+      <Layout.Content>
+        <View style={[gutters, a.gap_lg]}>
+          <Text
+            style={[
+              a.flex_1,
+              a.text_sm,
+              a.leading_snug,
+              t.atoms.text_contrast_medium,
+            ]}>
+            <Trans>
+              Selecting interests from the list below helps us deliver you
+              higher quality content.
+            </Trans>
+          </Text>
+
+          <Divider />
+
+          {preferences ? (
+            <Inner preferences={preferences} setIsSaving={setIsSaving} />
+          ) : (
+            <View style={[a.flex_row, a.justify_center, a.p_lg]}>
+              <Loader size="xl" />
+            </View>
+          )}
+        </View>
+      </Layout.Content>
+    </Layout.Screen>
+  )
+}
+
+function Inner({
+  preferences,
+  setIsSaving,
+}: {
+  preferences: UsePreferencesQueryResponse
+  setIsSaving: (isSaving: boolean) => void
+}) {
+  const {_} = useLingui()
+  const agent = useAgent()
+  const qc = useQueryClient()
+  const interestsDisplayNames = useInterestsDisplayNames()
+  const preselectedInterests = useMemo(
+    () => preferences.interests.tags || [],
+    [preferences.interests.tags],
+  )
+  const [interests, setInterests] = useState<string[]>(preselectedInterests)
+
+  const saveInterests = useMemo(() => {
+    return debounce(async (interests: string[]) => {
+      const noEdits =
+        interests.length === preselectedInterests.length &&
+        preselectedInterests.every(pre => {
+          return interests.find(int => int === pre)
+        })
+
+      if (noEdits) return
+
+      setIsSaving(true)
+
+      try {
+        await agent.setInterestsPref({tags: interests})
+        await qc.invalidateQueries({queryKey: preferencesQueryKey})
+        Toast.show(
+          _(msg({message: 'Content preferences updated!', context: 'toast'})),
+        )
+      } catch (error) {
+        Toast.show(
+          _(
+            msg({
+              message: 'Failed to save content prefefences.',
+              context: 'toast',
+            }),
+          ),
+          'xmark',
+        )
+      } finally {
+        setIsSaving(false)
+      }
+    }, 1500)
+  }, [_, agent, setIsSaving, qc, preselectedInterests])
+
+  const onChangeInterests = async (interests: string[]) => {
+    setInterests(interests)
+    saveInterests(interests)
+  }
+
+  return (
+    <Toggle.Group
+      values={interests}
+      onChange={onChangeInterests}
+      label={_(msg`Select your interests from the options below`)}>
+      <View style={[a.flex_row, a.flex_wrap, a.gap_sm]}>
+        {INTERESTS.map(interest => {
+          const name = interestsDisplayNames[interest]
+          if (!name) return null
+          return (
+            <Toggle.Item
+              key={interest}
+              name={interest}
+              label={interestsDisplayNames[interest]}>
+              <InterestButton interest={interest} />
+            </Toggle.Item>
+          )
+        })}
+      </View>
+    </Toggle.Group>
+  )
+}
+
+export function InterestButton({interest}: {interest: string}) {
+  const t = useTheme()
+  const interestsDisplayNames = useInterestsDisplayNames()
+  const ctx = Toggle.useItemContext()
+
+  const styles = useMemo(() => {
+    const hovered: ViewStyle[] = [t.atoms.bg_contrast_100]
+    const focused: ViewStyle[] = []
+    const pressed: ViewStyle[] = []
+    const selected: ViewStyle[] = [t.atoms.bg_contrast_900]
+    const selectedHover: ViewStyle[] = [t.atoms.bg_contrast_975]
+    const textSelected: TextStyle[] = [t.atoms.text_inverted]
+
+    return {
+      hovered,
+      focused,
+      pressed,
+      selected,
+      selectedHover,
+      textSelected,
+    }
+  }, [t])
+
+  return (
+    <View
+      style={[
+        a.rounded_full,
+        a.py_md,
+        a.px_xl,
+        t.atoms.bg_contrast_50,
+        ctx.hovered ? styles.hovered : {},
+        ctx.focused ? styles.hovered : {},
+        ctx.pressed ? styles.hovered : {},
+        ctx.selected ? styles.selected : {},
+        ctx.selected && (ctx.hovered || ctx.focused || ctx.pressed)
+          ? styles.selectedHover
+          : {},
+      ]}>
+      <Text
+        selectable={false}
+        style={[
+          {
+            color: t.palette.contrast_900,
+          },
+          a.font_bold,
+          ctx.selected ? styles.textSelected : {},
+        ]}>
+        {interestsDisplayNames[interest]}
+      </Text>
+    </View>
+  )
+}
+
+const INTERESTS = [
+  'animals',
+  'art',
+  'books',
+  'comedy',
+  'comics',
+  'culture',
+  'dev',
+  'education',
+  'food',
+  'gaming',
+  'journalism',
+  'movies',
+  'music',
+  'nature',
+  'news',
+  'pets',
+  'photography',
+  'politics',
+  'science',
+  'sports',
+  'tech',
+  'tv',
+  'writers',
+]