diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-05-11 17:52:38 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-11 17:52:38 -0500 |
commit | 75007d8fae9507fbd7f8b6f1922b52eb667abd68 (patch) | |
tree | a4961a429d01587fdcff437406194a1b16e0e72a /src | |
parent | c2a8713ff479beceb24b7b24464b8328cb8f159e (diff) | |
download | voidsky-75007d8fae9507fbd7f8b6f1922b52eb667abd68.tar.zst |
[APP-643] Account preferences server sync (#615)
* Bump deps * Bump deps * Add server sync of content preferences and an adult content toggle
Diffstat (limited to 'src')
-rw-r--r-- | src/state/models/root-store.ts | 4 | ||||
-rw-r--r-- | src/state/models/ui/preferences.ts | 84 | ||||
-rw-r--r-- | src/view/com/modals/ContentFilteringSettings.tsx | 69 |
3 files changed, 148 insertions, 9 deletions
diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 8cd23efcd..f2a352a79 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -37,7 +37,7 @@ export class RootStoreModel { log = new LogModel() session = new SessionModel(this) shell = new ShellUiModel(this) - preferences = new PreferencesModel() + preferences = new PreferencesModel(this) me = new MeModel(this) invitedUsers = new InvitedUsers(this) profiles = new ProfilesCache(this) @@ -126,6 +126,7 @@ export class RootStoreModel { this.log.debug('RootStoreModel:handleSessionChange') this.agent = agent this.me.clear() + /* dont await */ this.preferences.sync() await this.me.load() if (!hadSession) { resetNavigation() @@ -161,6 +162,7 @@ export class RootStoreModel { } try { await this.me.updateIfNeeded() + await this.preferences.sync() } catch (e: any) { this.log.error('Failed to fetch latest state', e) } diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts index fcd33af8e..1471420fc 100644 --- a/src/state/models/ui/preferences.ts +++ b/src/state/models/ui/preferences.ts @@ -1,7 +1,8 @@ -import {makeAutoObservable} from 'mobx' +import {makeAutoObservable, runInAction} from 'mobx' import {getLocales} from 'expo-localization' import {isObj, hasProp} from 'lib/type-guards' -import {ComAtprotoLabelDefs} from '@atproto/api' +import {RootStoreModel} from '../root-store' +import {ComAtprotoLabelDefs, AppBskyActorDefs} from '@atproto/api' import {LabelValGroup} from 'lib/labeling/types' import {getLabelValueGroup} from 'lib/labeling/helpers' import { @@ -15,6 +16,15 @@ import {isIOS} from 'platform/detection' const deviceLocales = getLocales() export type LabelPreference = 'show' | 'warn' | 'hide' +const LABEL_GROUPS = [ + 'nsfw', + 'nudity', + 'suggestive', + 'gore', + 'hate', + 'spam', + 'impersonation', +] export class LabelPreferencesModel { nsfw: LabelPreference = 'hide' @@ -36,7 +46,7 @@ export class PreferencesModel { deviceLocales?.map?.(locale => locale.languageCode) || [] contentLabels = new LabelPreferencesModel() - constructor() { + constructor(public rootStore: RootStoreModel) { makeAutoObservable(this, {}, {autoBind: true}) } @@ -65,6 +75,35 @@ export class PreferencesModel { } } + async sync() { + const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) + runInAction(() => { + for (const pref of res.data.preferences) { + if ( + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success + ) { + this.adultContentEnabled = pref.enabled + } else if ( + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success + ) { + if (LABEL_GROUPS.includes(pref.label)) { + this.contentLabels[pref.label] = pref.visibility + } + } + } + }) + } + + async update(cb: (prefs: AppBskyActorDefs.Preferences) => void) { + const res = await this.rootStore.agent.app.bsky.actor.getPreferences({}) + cb(res.data.preferences) + await this.rootStore.agent.app.bsky.actor.putPreferences({ + preferences: res.data.preferences, + }) + } + hasContentLanguage(code2: string) { return this.contentLanguages.includes(code2) } @@ -79,11 +118,48 @@ export class PreferencesModel { } } - setContentLabelPref( + async setContentLabelPref( key: keyof LabelPreferencesModel, value: LabelPreference, ) { this.contentLabels[key] = value + + await this.update((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.find( + pref => + AppBskyActorDefs.isContentLabelPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success && + pref.label === key, + ) + if (existing) { + existing.visibility = value + } else { + prefs.push({ + $type: 'app.bsky.actor.defs#contentLabelPref', + label: key, + visibility: value, + }) + } + }) + } + + async setAdultContentEnabled(v: boolean) { + this.adultContentEnabled = v + await this.update((prefs: AppBskyActorDefs.Preferences) => { + const existing = prefs.find( + pref => + AppBskyActorDefs.isAdultContentPref(pref) && + AppBskyActorDefs.validateAdultContentPref(pref).success, + ) + if (existing) { + existing.enabled = v + } else { + prefs.push({ + $type: 'app.bsky.actor.defs#adultContentPref', + enabled: v, + }) + } + }) } getLabelPreference(labels: ComAtprotoLabelDefs.Label[] | undefined): { diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx index 91c968684..1256bd420 100644 --- a/src/view/com/modals/ContentFilteringSettings.tsx +++ b/src/view/com/modals/ContentFilteringSettings.tsx @@ -7,15 +7,37 @@ 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 {usePalette} from 'lib/hooks/usePalette' import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const' -import {isDesktopWeb} from 'platform/detection' +import {isDesktopWeb, isIOS} from 'platform/detection' +import * as Toast from '../util/Toast' export const snapPoints = ['90%'] -export function Component({}: {}) { +export const Component = observer(({}: {}) => { const store = useStores() const pal = usePalette('default') + + React.useEffect(() => { + store.preferences.sync() + }, [store]) + + const onToggleAdultContent = React.useCallback(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') + store.log.error('Failed to update preferences with server', {e}) + } + }, [store]) + const onPressDone = React.useCallback(() => { store.shell.closeModal() }, [store]) @@ -24,6 +46,27 @@ export function Component({}: {}) { <View testID="contentFilteringModal" style={[pal.view, styles.container]}> <Text style={[pal.text, styles.title]}>Content Filtering</Text> <ScrollView style={styles.scrollContainer}> + <View style={s.mb10}> + {isIOS ? ( + <Text type="md" style={pal.textLight}> + Adult content can only be enabled via the Web at{' '} + <TextLink + style={pal.link} + href="https://staging.bsky.app" + text="staging.bsky.app" + /> + . + </Text> + ) : ( + <ToggleButton + type="default-light" + label="Enable Adult Content" + isSelected={store.preferences.adultContentEnabled} + onPress={onToggleAdultContent} + style={styles.toggleBtn} + /> + )} + </View> <ContentLabelPref group="nsfw" disabled={!store.preferences.adultContentEnabled} @@ -63,7 +106,7 @@ export function Component({}: {}) { </View> </View> ) -} +}) // TODO: Refactor this component to pass labels down to each tab const ContentLabelPref = observer( @@ -76,6 +119,21 @@ const ContentLabelPref = observer( }) => { const store = useStores() const pal = usePalette('default') + + 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', + ) + store.log.error('Failed to update preferences with server', {e}) + } + }, + [store, group], + ) + return ( <View style={[styles.contentLabelPref, pal.border]}> <View style={s.flex1}> @@ -95,7 +153,7 @@ const ContentLabelPref = observer( ) : ( <SelectGroup current={store.preferences.contentLabels[group]} - onChange={v => store.preferences.setContentLabelPref(group, v)} + onChange={onChange} group={group} /> )} @@ -250,4 +308,7 @@ const styles = StyleSheet.create({ padding: 14, backgroundColor: colors.gray1, }, + toggleBtn: { + paddingHorizontal: 0, + }, }) |