diff options
Diffstat (limited to 'src/view/com/modals')
-rw-r--r-- | src/view/com/modals/BirthDateSettings.tsx | 52 | ||||
-rw-r--r-- | src/view/com/modals/ContentFilteringSettings.tsx | 220 |
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> |