[APP-643] Account preferences server sync (#615)

* Bump deps

* Bump deps

* Add server sync of content preferences and an adult content toggle
zio/stable
Paul Frazee 2023-05-11 17:52:38 -05:00 committed by GitHub
parent c2a8713ff4
commit 75007d8fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 19 deletions

View File

@ -22,7 +22,7 @@
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
},
"dependencies": {
"@atproto/api": "0.3.1",
"@atproto/api": "0.3.3",
"@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2",
"@expo/webpack-config": "^18.0.1",
@ -140,7 +140,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
"@atproto/pds": "^0.1.6",
"@atproto/pds": "^0.1.8",
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",

View File

@ -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)
}

View File

@ -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): {

View File

@ -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,
},
})

View File

@ -40,10 +40,10 @@
tlds "^1.234.0"
typed-emitter "^2.1.0"
"@atproto/api@0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.3.1.tgz#98699479f8385c7494a853144657895be392c3f3"
integrity sha512-/AAZntrLUPCxw7q8FMtDsSYOjsAs5aAmllmArXyye5ITvbSw4pzWfJcBiKnQdmXpdwSrVWVEX7uwIp+GYWopqg==
"@atproto/api@0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.3.3.tgz#8c8d41567beb7b37217f76d2aacf2c280e9fd07e"
integrity sha512-BlgpYbdPO0KSBypg2KgqHM0kS2Pk82P3X0w2rJs/vrdcMl72d2WeI9kQ5PPFiz80p6C6XcLcpnzzKKtQeFvh4A==
dependencies:
"@atproto/common-web" "*"
"@atproto/uri" "*"
@ -148,10 +148,10 @@
resolved "https://registry.yarnpkg.com/@atproto/nsid/-/nsid-0.0.1.tgz#0cdc00cefe8f0b1385f352b9f57b3ad37fff09a4"
integrity sha512-t5M6/CzWBVYoBbIvfKDpqPj/+ZmyoK9ydZSStcTXosJ27XXwOPhz0VDUGKK2SM9G5Y7TPes8S5KTAU0UdVYFCw==
"@atproto/pds@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.1.6.tgz#2858355887eac06f5e2da8701231e5cb46004c18"
integrity sha512-bddIWH+OrEIxJ5HYst1mBS+95bNWC08FLaa3DVtJRHRCdfYaGDndZUVpOLLgzBRklDLicJyvva2JYEgp2mdgLA==
"@atproto/pds@^0.1.8":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.1.8.tgz#cf1a9bab2301c3fe1120c63576153ac5a20bf70d"
integrity sha512-I493U+/NNU9D8L8tVbM/OpD6gQ6/Mv7uE+/i4a1vfBGO6NqYJ6jKw3qeCy4jq3NVbTxcs+lSSpK27hgApx4PtA==
dependencies:
"@atproto/api" "*"
"@atproto/common" "*"