[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"
|
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "0.3.1",
|
"@atproto/api": "0.3.3",
|
||||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@expo/webpack-config": "^18.0.1",
|
"@expo/webpack-config": "^18.0.1",
|
||||||
|
@ -140,7 +140,7 @@
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@atproto/pds": "^0.1.6",
|
"@atproto/pds": "^0.1.8",
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
"@babel/preset-env": "^7.20.0",
|
"@babel/preset-env": "^7.20.0",
|
||||||
"@babel/runtime": "^7.20.0",
|
"@babel/runtime": "^7.20.0",
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class RootStoreModel {
|
||||||
log = new LogModel()
|
log = new LogModel()
|
||||||
session = new SessionModel(this)
|
session = new SessionModel(this)
|
||||||
shell = new ShellUiModel(this)
|
shell = new ShellUiModel(this)
|
||||||
preferences = new PreferencesModel()
|
preferences = new PreferencesModel(this)
|
||||||
me = new MeModel(this)
|
me = new MeModel(this)
|
||||||
invitedUsers = new InvitedUsers(this)
|
invitedUsers = new InvitedUsers(this)
|
||||||
profiles = new ProfilesCache(this)
|
profiles = new ProfilesCache(this)
|
||||||
|
@ -126,6 +126,7 @@ export class RootStoreModel {
|
||||||
this.log.debug('RootStoreModel:handleSessionChange')
|
this.log.debug('RootStoreModel:handleSessionChange')
|
||||||
this.agent = agent
|
this.agent = agent
|
||||||
this.me.clear()
|
this.me.clear()
|
||||||
|
/* dont await */ this.preferences.sync()
|
||||||
await this.me.load()
|
await this.me.load()
|
||||||
if (!hadSession) {
|
if (!hadSession) {
|
||||||
resetNavigation()
|
resetNavigation()
|
||||||
|
@ -161,6 +162,7 @@ export class RootStoreModel {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.me.updateIfNeeded()
|
await this.me.updateIfNeeded()
|
||||||
|
await this.preferences.sync()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.log.error('Failed to fetch latest state', e)
|
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 {getLocales} from 'expo-localization'
|
||||||
import {isObj, hasProp} from 'lib/type-guards'
|
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 {LabelValGroup} from 'lib/labeling/types'
|
||||||
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
||||||
import {
|
import {
|
||||||
|
@ -15,6 +16,15 @@ import {isIOS} from 'platform/detection'
|
||||||
const deviceLocales = getLocales()
|
const deviceLocales = getLocales()
|
||||||
|
|
||||||
export type LabelPreference = 'show' | 'warn' | 'hide'
|
export type LabelPreference = 'show' | 'warn' | 'hide'
|
||||||
|
const LABEL_GROUPS = [
|
||||||
|
'nsfw',
|
||||||
|
'nudity',
|
||||||
|
'suggestive',
|
||||||
|
'gore',
|
||||||
|
'hate',
|
||||||
|
'spam',
|
||||||
|
'impersonation',
|
||||||
|
]
|
||||||
|
|
||||||
export class LabelPreferencesModel {
|
export class LabelPreferencesModel {
|
||||||
nsfw: LabelPreference = 'hide'
|
nsfw: LabelPreference = 'hide'
|
||||||
|
@ -36,7 +46,7 @@ export class PreferencesModel {
|
||||||
deviceLocales?.map?.(locale => locale.languageCode) || []
|
deviceLocales?.map?.(locale => locale.languageCode) || []
|
||||||
contentLabels = new LabelPreferencesModel()
|
contentLabels = new LabelPreferencesModel()
|
||||||
|
|
||||||
constructor() {
|
constructor(public rootStore: RootStoreModel) {
|
||||||
makeAutoObservable(this, {}, {autoBind: true})
|
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) {
|
hasContentLanguage(code2: string) {
|
||||||
return this.contentLanguages.includes(code2)
|
return this.contentLanguages.includes(code2)
|
||||||
}
|
}
|
||||||
|
@ -79,11 +118,48 @@ export class PreferencesModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentLabelPref(
|
async setContentLabelPref(
|
||||||
key: keyof LabelPreferencesModel,
|
key: keyof LabelPreferencesModel,
|
||||||
value: LabelPreference,
|
value: LabelPreference,
|
||||||
) {
|
) {
|
||||||
this.contentLabels[key] = value
|
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): {
|
getLabelPreference(labels: ComAtprotoLabelDefs.Label[] | undefined): {
|
||||||
|
|
|
@ -7,15 +7,37 @@ import {useStores} from 'state/index'
|
||||||
import {LabelPreference} from 'state/models/ui/preferences'
|
import {LabelPreference} from 'state/models/ui/preferences'
|
||||||
import {s, colors, gradients} from 'lib/styles'
|
import {s, colors, gradients} from 'lib/styles'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
|
import {TextLink} from '../util/Link'
|
||||||
|
import {ToggleButton} from '../util/forms/ToggleButton'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const'
|
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 const snapPoints = ['90%']
|
||||||
|
|
||||||
export function Component({}: {}) {
|
export const Component = observer(({}: {}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const pal = usePalette('default')
|
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(() => {
|
const onPressDone = React.useCallback(() => {
|
||||||
store.shell.closeModal()
|
store.shell.closeModal()
|
||||||
}, [store])
|
}, [store])
|
||||||
|
@ -24,6 +46,27 @@ export function Component({}: {}) {
|
||||||
<View testID="contentFilteringModal" style={[pal.view, styles.container]}>
|
<View testID="contentFilteringModal" style={[pal.view, styles.container]}>
|
||||||
<Text style={[pal.text, styles.title]}>Content Filtering</Text>
|
<Text style={[pal.text, styles.title]}>Content Filtering</Text>
|
||||||
<ScrollView style={styles.scrollContainer}>
|
<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
|
<ContentLabelPref
|
||||||
group="nsfw"
|
group="nsfw"
|
||||||
disabled={!store.preferences.adultContentEnabled}
|
disabled={!store.preferences.adultContentEnabled}
|
||||||
|
@ -63,7 +106,7 @@ export function Component({}: {}) {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
// TODO: Refactor this component to pass labels down to each tab
|
// TODO: Refactor this component to pass labels down to each tab
|
||||||
const ContentLabelPref = observer(
|
const ContentLabelPref = observer(
|
||||||
|
@ -76,6 +119,21 @@ const ContentLabelPref = observer(
|
||||||
}) => {
|
}) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const pal = usePalette('default')
|
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 (
|
return (
|
||||||
<View style={[styles.contentLabelPref, pal.border]}>
|
<View style={[styles.contentLabelPref, pal.border]}>
|
||||||
<View style={s.flex1}>
|
<View style={s.flex1}>
|
||||||
|
@ -95,7 +153,7 @@ const ContentLabelPref = observer(
|
||||||
) : (
|
) : (
|
||||||
<SelectGroup
|
<SelectGroup
|
||||||
current={store.preferences.contentLabels[group]}
|
current={store.preferences.contentLabels[group]}
|
||||||
onChange={v => store.preferences.setContentLabelPref(group, v)}
|
onChange={onChange}
|
||||||
group={group}
|
group={group}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -250,4 +308,7 @@ const styles = StyleSheet.create({
|
||||||
padding: 14,
|
padding: 14,
|
||||||
backgroundColor: colors.gray1,
|
backgroundColor: colors.gray1,
|
||||||
},
|
},
|
||||||
|
toggleBtn: {
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -40,10 +40,10 @@
|
||||||
tlds "^1.234.0"
|
tlds "^1.234.0"
|
||||||
typed-emitter "^2.1.0"
|
typed-emitter "^2.1.0"
|
||||||
|
|
||||||
"@atproto/api@0.3.1":
|
"@atproto/api@0.3.3":
|
||||||
version "0.3.1"
|
version "0.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.3.1.tgz#98699479f8385c7494a853144657895be392c3f3"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.3.3.tgz#8c8d41567beb7b37217f76d2aacf2c280e9fd07e"
|
||||||
integrity sha512-/AAZntrLUPCxw7q8FMtDsSYOjsAs5aAmllmArXyye5ITvbSw4pzWfJcBiKnQdmXpdwSrVWVEX7uwIp+GYWopqg==
|
integrity sha512-BlgpYbdPO0KSBypg2KgqHM0kS2Pk82P3X0w2rJs/vrdcMl72d2WeI9kQ5PPFiz80p6C6XcLcpnzzKKtQeFvh4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "*"
|
"@atproto/common-web" "*"
|
||||||
"@atproto/uri" "*"
|
"@atproto/uri" "*"
|
||||||
|
@ -148,10 +148,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/nsid/-/nsid-0.0.1.tgz#0cdc00cefe8f0b1385f352b9f57b3ad37fff09a4"
|
resolved "https://registry.yarnpkg.com/@atproto/nsid/-/nsid-0.0.1.tgz#0cdc00cefe8f0b1385f352b9f57b3ad37fff09a4"
|
||||||
integrity sha512-t5M6/CzWBVYoBbIvfKDpqPj/+ZmyoK9ydZSStcTXosJ27XXwOPhz0VDUGKK2SM9G5Y7TPes8S5KTAU0UdVYFCw==
|
integrity sha512-t5M6/CzWBVYoBbIvfKDpqPj/+ZmyoK9ydZSStcTXosJ27XXwOPhz0VDUGKK2SM9G5Y7TPes8S5KTAU0UdVYFCw==
|
||||||
|
|
||||||
"@atproto/pds@^0.1.6":
|
"@atproto/pds@^0.1.8":
|
||||||
version "0.1.6"
|
version "0.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.1.6.tgz#2858355887eac06f5e2da8701231e5cb46004c18"
|
resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.1.8.tgz#cf1a9bab2301c3fe1120c63576153ac5a20bf70d"
|
||||||
integrity sha512-bddIWH+OrEIxJ5HYst1mBS+95bNWC08FLaa3DVtJRHRCdfYaGDndZUVpOLLgzBRklDLicJyvva2JYEgp2mdgLA==
|
integrity sha512-I493U+/NNU9D8L8tVbM/OpD6gQ6/Mv7uE+/i4a1vfBGO6NqYJ6jKw3qeCy4jq3NVbTxcs+lSSpK27hgApx4PtA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/api" "*"
|
"@atproto/api" "*"
|
||||||
"@atproto/common" "*"
|
"@atproto/common" "*"
|
||||||
|
|
Loading…
Reference in New Issue