From bb6e2ae3dc1a87fdca435af0049f30f6684d2041 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 3 Apr 2025 14:49:44 -0500 Subject: [PATCH] Content prefs screen --- src/Navigation.tsx | 9 + src/lib/routes/types.ts | 7 +- src/routes.ts | 1 + .../Settings/ContentAndMediaSettings.tsx | 9 + src/screens/Settings/ContentPreferences.tsx | 249 ++++++++++++++++++ 5 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 src/screens/Settings/ContentPreferences.tsx diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 420a49d4c6..ca46ac2ca1 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -82,6 +82,7 @@ import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLiked import {SearchScreen} from '#/screens/Search' import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings' import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings' +import {ContentPreferences} from '#/screens/Settings/ContentPreferences' import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings' import { StarterPackScreen, @@ -375,6 +376,14 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { requireAuth: true, }} /> + ContentPreferences} + options={{ + title: title(msg`Content Preferences`), + requireAuth: true, + }} + /> AboutSettingsScreen} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 0e38c92627..342331f5cc 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -1,7 +1,7 @@ -import {NavigationState, PartialState} from '@react-navigation/native' -import type {NativeStackNavigationProp} from '@react-navigation/native-stack' +import {type NavigationState, type PartialState} from '@react-navigation/native' +import {type NativeStackNavigationProp} from '@react-navigation/native-stack' -import {VideoFeedSourceContext} from '#/screens/VideoFeed/types' +import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' export type {NativeStackScreenProps} from '@react-navigation/native-stack' @@ -51,6 +51,7 @@ export type CommonNavigatorParams = { AccountSettings: undefined PrivacyAndSecuritySettings: undefined ContentAndMediaSettings: undefined + ContentPreferences: undefined AboutSettings: undefined AppIconSettings: undefined Search: {q?: string} diff --git a/src/routes.ts b/src/routes.ts index b6a11acbf7..9a85a35f46 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -45,6 +45,7 @@ export const router = new Router({ AccountSettings: '/settings/account', PrivacyAndSecuritySettings: '/settings/privacy-and-security', ContentAndMediaSettings: '/settings/content-and-media', + ContentPreferences: '/settings/content-preferences', AboutSettings: '/settings/about', AppIconSettings: '/settings/app-icon', // support diff --git a/src/screens/Settings/ContentAndMediaSettings.tsx b/src/screens/Settings/ContentAndMediaSettings.tsx index 57b86fb2bf..30994e7957 100644 --- a/src/screens/Settings/ContentAndMediaSettings.tsx +++ b/src/screens/Settings/ContentAndMediaSettings.tsx @@ -18,6 +18,7 @@ import {useTrendingConfig} from '#/state/trending-config' import * as SettingsList from '#/screens/Settings/components/SettingsList' import * as Toggle from '#/components/forms/Toggle' import {Bubbles_Stroke2_Corner2_Rounded as BubblesIcon} from '#/components/icons/Bubble' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {Hashtag_Stroke2_Corner0_Rounded as HashtagIcon} from '#/components/icons/Hashtag' import {Home_Stroke2_Corner2_Rounded as HomeIcon} from '#/components/icons/Home' import {Macintosh_Stroke2_Corner2_Rounded as MacintoshIcon} from '#/components/icons/Macintosh' @@ -86,6 +87,14 @@ export function ContentAndMediaSettingsScreen({}: Props) { External media + + + + Content preferences + + {isNative && ( { + return debounce(async (interests: string[]) => { + 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) + } + }, 1000) + }, [_, agent, setIsSaving, qc]) + + return ( + + + + + + Content preferences + + + {isSaving && } + + + + + + Selecting interests from the list below helps us deliver you + higher quality content. + + + + + + {preferences ? ( + + ) : ( + + + + )} + + + + ) +} + +function Inner({ + preferences, + saveInterests, +}: { + preferences: UsePreferencesQueryResponse + saveInterests: (interests: string[]) => Promise +}) { + const {_} = useLingui() + const interestsDisplayNames = useInterestsDisplayNames() + const preselectedInterests = preferences.interests.tags || [] + const [interests, setInterests] = useState(preselectedInterests) + + const onChangeInterests = async (interests: string[]) => { + setInterests(interests) + const isEdited = !deepEqual(interests, preselectedInterests) + console.log(isEdited, {interests, preselectedInterests}) + if (isEdited) saveInterests(interests) + } + + return ( + + + {INTERESTS.map(interest => { + const name = interestsDisplayNames[interest] + if (!name) return null + return ( + + + + ) + })} + + + ) +} + +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 ( + + + {interestsDisplayNames[interest]} + + + ) +} + +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', +] + +function useInterestsDisplayNames() { + const {_} = useLingui() + + return useMemo>(() => { + return { + // Keep this alphabetized + animals: _(msg`Animals`), + art: _(msg`Art`), + books: _(msg`Books`), + comedy: _(msg`Comedy`), + comics: _(msg`Comics`), + culture: _(msg`Culture`), + dev: _(msg`Software Dev`), + education: _(msg`Education`), + food: _(msg`Food`), + gaming: _(msg`Video Games`), + journalism: _(msg`Journalism`), + movies: _(msg`Movies`), + music: _(msg`Music`), + nature: _(msg`Nature`), + news: _(msg`News`), + pets: _(msg`Pets`), + photography: _(msg`Photography`), + politics: _(msg`Politics`), + science: _(msg`Science`), + sports: _(msg`Sports`), + tech: _(msg`Tech`), + tv: _(msg`TV`), + writers: _(msg`Writers`), + } + }, [_]) +}