[APP-643] Account preferences server sync (#615)
* Bump deps * Bump deps * Add server sync of content preferences and an adult content togglezio/stable
parent
c2a8713ff4
commit
75007d8fae
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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): {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -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" "*"
|
||||
|
|
Loading…
Reference in New Issue