Moderation settings fixes (#1336)
* Default isAdultContentEnabled to false on all devices. The original intent of setting the default based on the device was to make the maximally-permissive choice. It turns out this was a mistake as it created sync issues between devices; users would be confused by the lack of congruity between them. We have to go with false by default to ensure sync is retained. * Update preferences model to use new sdk api * Delete dead code * Dont show the iOS adult content warning in content filtering settings if adult content is enabled * Bump @atproto/api@0.6.8 * Codebase style consistencyzio/stable
parent
3a90b479fd
commit
a29f10aefe
|
@ -24,7 +24,7 @@
|
|||
"e2e:run": "detox test --configuration ios.sim.debug --take-screenshots all"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.6.6",
|
||||
"@atproto/api": "^0.6.8",
|
||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
|
|
|
@ -1,436 +0,0 @@
|
|||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyGraphDefs,
|
||||
AppBskyEmbedRecordWithMedia,
|
||||
AppBskyEmbedRecord,
|
||||
AppBskyEmbedImages,
|
||||
AppBskyEmbedExternal,
|
||||
} from '@atproto/api'
|
||||
import {
|
||||
CONFIGURABLE_LABEL_GROUPS,
|
||||
ILLEGAL_LABEL_GROUP,
|
||||
ALWAYS_FILTER_LABEL_GROUP,
|
||||
ALWAYS_WARN_LABEL_GROUP,
|
||||
UNKNOWN_LABEL_GROUP,
|
||||
} from './const'
|
||||
import {
|
||||
Label,
|
||||
LabelValGroup,
|
||||
ModerationBehaviorCode,
|
||||
ModerationBehavior,
|
||||
PostModeration,
|
||||
ProfileModeration,
|
||||
PostLabelInfo,
|
||||
ProfileLabelInfo,
|
||||
} from './types'
|
||||
import {RootStoreModel} from 'state/index'
|
||||
|
||||
type Embed =
|
||||
| AppBskyEmbedRecord.View
|
||||
| AppBskyEmbedImages.View
|
||||
| AppBskyEmbedExternal.View
|
||||
| AppBskyEmbedRecordWithMedia.View
|
||||
| {$type: string; [k: string]: unknown}
|
||||
|
||||
export function getLabelValueGroup(labelVal: string): LabelValGroup {
|
||||
let id: keyof typeof CONFIGURABLE_LABEL_GROUPS
|
||||
for (id in CONFIGURABLE_LABEL_GROUPS) {
|
||||
if (ILLEGAL_LABEL_GROUP.values.includes(labelVal)) {
|
||||
return ILLEGAL_LABEL_GROUP
|
||||
}
|
||||
if (ALWAYS_FILTER_LABEL_GROUP.values.includes(labelVal)) {
|
||||
return ALWAYS_FILTER_LABEL_GROUP
|
||||
}
|
||||
if (ALWAYS_WARN_LABEL_GROUP.values.includes(labelVal)) {
|
||||
return ALWAYS_WARN_LABEL_GROUP
|
||||
}
|
||||
if (CONFIGURABLE_LABEL_GROUPS[id].values.includes(labelVal)) {
|
||||
return CONFIGURABLE_LABEL_GROUPS[id]
|
||||
}
|
||||
}
|
||||
return UNKNOWN_LABEL_GROUP
|
||||
}
|
||||
|
||||
export function getPostModeration(
|
||||
store: RootStoreModel,
|
||||
postInfo: PostLabelInfo,
|
||||
): PostModeration {
|
||||
const accountPref = store.preferences.getLabelPreference(
|
||||
postInfo.accountLabels,
|
||||
)
|
||||
const profilePref = store.preferences.getLabelPreference(
|
||||
postInfo.profileLabels,
|
||||
)
|
||||
const postPref = store.preferences.getLabelPreference(postInfo.postLabels)
|
||||
|
||||
// avatar
|
||||
let avatar = {
|
||||
warn: accountPref.pref === 'hide' || accountPref.pref === 'warn',
|
||||
blur:
|
||||
postInfo.isBlocking ||
|
||||
accountPref.pref === 'hide' ||
|
||||
accountPref.pref === 'warn' ||
|
||||
profilePref.pref === 'hide' ||
|
||||
profilePref.pref === 'warn',
|
||||
}
|
||||
|
||||
// hide no-override cases
|
||||
if (accountPref.pref === 'hide' && accountPref.desc.id === 'illegal') {
|
||||
return hidePostNoOverride(accountPref.desc.warning)
|
||||
}
|
||||
if (profilePref.pref === 'hide' && profilePref.desc.id === 'illegal') {
|
||||
return hidePostNoOverride(profilePref.desc.warning)
|
||||
}
|
||||
if (postPref.pref === 'hide' && postPref.desc.id === 'illegal') {
|
||||
return hidePostNoOverride(postPref.desc.warning)
|
||||
}
|
||||
|
||||
// hide cases
|
||||
if (postInfo.isBlocking) {
|
||||
return {
|
||||
avatar,
|
||||
list: hide('Post from an account you blocked.'),
|
||||
thread: hide('Post from an account you blocked.'),
|
||||
view: warn('Post from an account you blocked.'),
|
||||
}
|
||||
}
|
||||
if (postInfo.isBlockedBy) {
|
||||
return {
|
||||
avatar,
|
||||
list: hide('Post from an account that has blocked you.'),
|
||||
thread: hide('Post from an account that has blocked you.'),
|
||||
view: warn('Post from an account that has blocked you.'),
|
||||
}
|
||||
}
|
||||
if (accountPref.pref === 'hide') {
|
||||
return {
|
||||
avatar,
|
||||
list: hide(accountPref.desc.warning),
|
||||
thread: hide(accountPref.desc.warning),
|
||||
view: warn(accountPref.desc.warning),
|
||||
}
|
||||
}
|
||||
if (profilePref.pref === 'hide') {
|
||||
return {
|
||||
avatar,
|
||||
list: hide(profilePref.desc.warning),
|
||||
thread: hide(profilePref.desc.warning),
|
||||
view: warn(profilePref.desc.warning),
|
||||
}
|
||||
}
|
||||
if (postPref.pref === 'hide') {
|
||||
return {
|
||||
avatar,
|
||||
list: hide(postPref.desc.warning),
|
||||
thread: hide(postPref.desc.warning),
|
||||
view: warn(postPref.desc.warning),
|
||||
}
|
||||
}
|
||||
|
||||
// muting
|
||||
if (postInfo.isMuted) {
|
||||
let msg = 'Post from an account you muted.'
|
||||
if (postInfo.mutedByList) {
|
||||
msg = `Muted by ${postInfo.mutedByList.name}`
|
||||
}
|
||||
return {
|
||||
avatar,
|
||||
list: isMute(hide(msg)),
|
||||
thread: isMute(warn(msg)),
|
||||
view: isMute(warn(msg)),
|
||||
}
|
||||
}
|
||||
|
||||
// warning cases
|
||||
if (postPref.pref === 'warn') {
|
||||
if (postPref.desc.isAdultImagery) {
|
||||
return {
|
||||
avatar,
|
||||
list: warnImages(postPref.desc.warning),
|
||||
thread: warnImages(postPref.desc.warning),
|
||||
view: warnImages(postPref.desc.warning),
|
||||
}
|
||||
}
|
||||
return {
|
||||
avatar,
|
||||
list: warnContent(postPref.desc.warning),
|
||||
thread: warnContent(postPref.desc.warning),
|
||||
view: warnContent(postPref.desc.warning),
|
||||
}
|
||||
}
|
||||
if (accountPref.pref === 'warn') {
|
||||
return {
|
||||
avatar,
|
||||
list: warnContent(accountPref.desc.warning),
|
||||
thread: warnContent(accountPref.desc.warning),
|
||||
view: warnContent(accountPref.desc.warning),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
avatar,
|
||||
list: show(),
|
||||
thread: show(),
|
||||
view: show(),
|
||||
}
|
||||
}
|
||||
|
||||
export function mergePostModerations(
|
||||
moderations: PostModeration[],
|
||||
): PostModeration {
|
||||
const merged: PostModeration = {
|
||||
avatar: {warn: false, blur: false},
|
||||
list: show(),
|
||||
thread: show(),
|
||||
view: show(),
|
||||
}
|
||||
for (const mod of moderations) {
|
||||
if (mod.list.behavior === ModerationBehaviorCode.Hide) {
|
||||
merged.list = mod.list
|
||||
}
|
||||
if (mod.thread.behavior === ModerationBehaviorCode.Hide) {
|
||||
merged.thread = mod.thread
|
||||
}
|
||||
if (mod.view.behavior === ModerationBehaviorCode.Hide) {
|
||||
merged.view = mod.view
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
export function getProfileModeration(
|
||||
store: RootStoreModel,
|
||||
profileInfo: ProfileLabelInfo,
|
||||
): ProfileModeration {
|
||||
const accountPref = store.preferences.getLabelPreference(
|
||||
profileInfo.accountLabels,
|
||||
)
|
||||
const profilePref = store.preferences.getLabelPreference(
|
||||
profileInfo.profileLabels,
|
||||
)
|
||||
|
||||
// avatar
|
||||
let avatar = {
|
||||
warn: accountPref.pref === 'hide' || accountPref.pref === 'warn',
|
||||
blur:
|
||||
profileInfo.isBlocking ||
|
||||
accountPref.pref === 'hide' ||
|
||||
accountPref.pref === 'warn' ||
|
||||
profilePref.pref === 'hide' ||
|
||||
profilePref.pref === 'warn',
|
||||
}
|
||||
|
||||
// hide no-override cases
|
||||
if (accountPref.pref === 'hide' && accountPref.desc.id === 'illegal') {
|
||||
return hideProfileNoOverride(accountPref.desc.warning)
|
||||
}
|
||||
if (profilePref.pref === 'hide' && profilePref.desc.id === 'illegal') {
|
||||
return hideProfileNoOverride(profilePref.desc.warning)
|
||||
}
|
||||
|
||||
// hide cases
|
||||
if (accountPref.pref === 'hide') {
|
||||
return {
|
||||
avatar,
|
||||
list: hide(accountPref.desc.warning),
|
||||
view: hide(accountPref.desc.warning),
|
||||
}
|
||||
}
|
||||
if (profilePref.pref === 'hide') {
|
||||
return {
|
||||
avatar,
|
||||
list: hide(profilePref.desc.warning),
|
||||
view: hide(profilePref.desc.warning),
|
||||
}
|
||||
}
|
||||
|
||||
// warn cases
|
||||
if (accountPref.pref === 'warn') {
|
||||
return {
|
||||
avatar,
|
||||
list:
|
||||
profileInfo.isBlocking || profileInfo.isBlockedBy
|
||||
? hide('Blocked account')
|
||||
: warn(accountPref.desc.warning),
|
||||
view: warn(accountPref.desc.warning),
|
||||
}
|
||||
}
|
||||
// we don't warn for this
|
||||
// if (profilePref.pref === 'warn') {
|
||||
// return {
|
||||
// avatar,
|
||||
// list: warn(profilePref.desc.warning),
|
||||
// view: warn(profilePref.desc.warning),
|
||||
// }
|
||||
// }
|
||||
|
||||
return {
|
||||
avatar,
|
||||
list: profileInfo.isBlocking ? hide('Blocked account') : show(),
|
||||
view: show(),
|
||||
}
|
||||
}
|
||||
|
||||
export function getProfileViewBasicLabelInfo(
|
||||
profile: AppBskyActorDefs.ProfileViewBasic,
|
||||
): ProfileLabelInfo {
|
||||
return {
|
||||
accountLabels: filterAccountLabels(profile.labels),
|
||||
profileLabels: filterProfileLabels(profile.labels),
|
||||
isMuted: profile.viewer?.muted || false,
|
||||
isBlocking: !!profile.viewer?.blocking || false,
|
||||
isBlockedBy: !!profile.viewer?.blockedBy || false,
|
||||
}
|
||||
}
|
||||
|
||||
export function getEmbedLabels(embed?: Embed): Label[] {
|
||||
if (!embed) {
|
||||
return []
|
||||
}
|
||||
if (
|
||||
AppBskyEmbedRecord.isView(embed) &&
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record)
|
||||
) {
|
||||
return embed.record.labels || []
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function getEmbedMuted(embed?: Embed): boolean {
|
||||
if (!embed) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
AppBskyEmbedRecord.isView(embed) &&
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record)
|
||||
) {
|
||||
return !!embed.record.author.viewer?.muted
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function getEmbedMutedByList(
|
||||
embed?: Embed,
|
||||
): AppBskyGraphDefs.ListViewBasic | undefined {
|
||||
if (!embed) {
|
||||
return undefined
|
||||
}
|
||||
if (
|
||||
AppBskyEmbedRecord.isView(embed) &&
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record)
|
||||
) {
|
||||
return embed.record.author.viewer?.mutedByList
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getEmbedBlocking(embed?: Embed): boolean {
|
||||
if (!embed) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
AppBskyEmbedRecord.isView(embed) &&
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record)
|
||||
) {
|
||||
return !!embed.record.author.viewer?.blocking
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function getEmbedBlockedBy(embed?: Embed): boolean {
|
||||
if (!embed) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
AppBskyEmbedRecord.isView(embed) &&
|
||||
AppBskyEmbedRecord.isViewRecord(embed.record)
|
||||
) {
|
||||
return !!embed.record.author.viewer?.blockedBy
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function filterAccountLabels(labels?: Label[]): Label[] {
|
||||
if (!labels) {
|
||||
return []
|
||||
}
|
||||
return labels.filter(
|
||||
label => !label.uri.endsWith('/app.bsky.actor.profile/self'),
|
||||
)
|
||||
}
|
||||
|
||||
export function filterProfileLabels(labels?: Label[]): Label[] {
|
||||
if (!labels) {
|
||||
return []
|
||||
}
|
||||
return labels.filter(label =>
|
||||
label.uri.endsWith('/app.bsky.actor.profile/self'),
|
||||
)
|
||||
}
|
||||
|
||||
// internal methods
|
||||
// =
|
||||
|
||||
function show() {
|
||||
return {
|
||||
behavior: ModerationBehaviorCode.Show,
|
||||
}
|
||||
}
|
||||
|
||||
function hidePostNoOverride(reason: string) {
|
||||
return {
|
||||
avatar: {warn: true, blur: true},
|
||||
list: hideNoOverride(reason),
|
||||
thread: hideNoOverride(reason),
|
||||
view: hideNoOverride(reason),
|
||||
}
|
||||
}
|
||||
|
||||
function hideProfileNoOverride(reason: string) {
|
||||
return {
|
||||
avatar: {warn: true, blur: true},
|
||||
list: hideNoOverride(reason),
|
||||
view: hideNoOverride(reason),
|
||||
}
|
||||
}
|
||||
|
||||
function hideNoOverride(reason: string) {
|
||||
return {
|
||||
behavior: ModerationBehaviorCode.Hide,
|
||||
reason,
|
||||
noOverride: true,
|
||||
}
|
||||
}
|
||||
|
||||
function hide(reason: string) {
|
||||
return {
|
||||
behavior: ModerationBehaviorCode.Hide,
|
||||
reason,
|
||||
}
|
||||
}
|
||||
|
||||
function warn(reason: string) {
|
||||
return {
|
||||
behavior: ModerationBehaviorCode.Warn,
|
||||
reason,
|
||||
}
|
||||
}
|
||||
|
||||
function warnContent(reason: string) {
|
||||
return {
|
||||
behavior: ModerationBehaviorCode.WarnContent,
|
||||
reason,
|
||||
}
|
||||
}
|
||||
|
||||
function isMute(behavior: ModerationBehavior): ModerationBehavior {
|
||||
behavior.isMute = true
|
||||
return behavior
|
||||
}
|
||||
|
||||
function warnImages(reason: string) {
|
||||
return {
|
||||
behavior: ModerationBehaviorCode.WarnImages,
|
||||
reason,
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import {ComAtprotoLabelDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||
import {ComAtprotoLabelDefs} from '@atproto/api'
|
||||
import {LabelPreferencesModel} from 'state/models/ui/preferences'
|
||||
|
||||
export type Label = ComAtprotoLabelDefs.Label
|
||||
|
@ -16,54 +16,3 @@ export interface LabelValGroup {
|
|||
warning: string
|
||||
values: string[]
|
||||
}
|
||||
|
||||
export interface PostLabelInfo {
|
||||
postLabels: Label[]
|
||||
accountLabels: Label[]
|
||||
profileLabels: Label[]
|
||||
isMuted: boolean
|
||||
mutedByList?: AppBskyGraphDefs.ListViewBasic
|
||||
isBlocking: boolean
|
||||
isBlockedBy: boolean
|
||||
}
|
||||
|
||||
export interface ProfileLabelInfo {
|
||||
accountLabels: Label[]
|
||||
profileLabels: Label[]
|
||||
isMuted: boolean
|
||||
isBlocking: boolean
|
||||
isBlockedBy: boolean
|
||||
}
|
||||
|
||||
export enum ModerationBehaviorCode {
|
||||
Show,
|
||||
Hide,
|
||||
Warn,
|
||||
WarnContent,
|
||||
WarnImages,
|
||||
}
|
||||
|
||||
export interface ModerationBehavior {
|
||||
behavior: ModerationBehaviorCode
|
||||
isMute?: boolean
|
||||
noOverride?: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface AvatarModeration {
|
||||
warn: boolean
|
||||
blur: boolean
|
||||
}
|
||||
|
||||
export interface PostModeration {
|
||||
avatar: AvatarModeration
|
||||
list: ModerationBehavior
|
||||
thread: ModerationBehavior
|
||||
view: ModerationBehavior
|
||||
}
|
||||
|
||||
export interface ProfileModeration {
|
||||
avatar: AvatarModeration
|
||||
list: ModerationBehavior
|
||||
view: ModerationBehavior
|
||||
}
|
||||
|
|
|
@ -4,21 +4,9 @@ import AwaitLock from 'await-lock'
|
|||
import isEqual from 'lodash.isequal'
|
||||
import {isObj, hasProp} from 'lib/type-guards'
|
||||
import {RootStoreModel} from '../root-store'
|
||||
import {
|
||||
ComAtprotoLabelDefs,
|
||||
AppBskyActorDefs,
|
||||
ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import {LabelValGroup} from 'lib/labeling/types'
|
||||
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
||||
import {
|
||||
UNKNOWN_LABEL_GROUP,
|
||||
ILLEGAL_LABEL_GROUP,
|
||||
ALWAYS_FILTER_LABEL_GROUP,
|
||||
ALWAYS_WARN_LABEL_GROUP,
|
||||
} from 'lib/labeling/const'
|
||||
import {ModerationOpts} from '@atproto/api'
|
||||
import {DEFAULT_FEEDS} from 'lib/constants'
|
||||
import {isIOS, deviceLocales} from 'platform/detection'
|
||||
import {deviceLocales} from 'platform/detection'
|
||||
import {LANGUAGES} from '../../../locale/languages'
|
||||
|
||||
// TEMP we need to permanently convert 'show' to 'ignore', for now we manually convert -prf
|
||||
|
@ -32,7 +20,7 @@ const LABEL_GROUPS = [
|
|||
'spam',
|
||||
'impersonation',
|
||||
]
|
||||
const VISIBILITY_VALUES = ['show', 'warn', 'hide']
|
||||
const VISIBILITY_VALUES = ['ignore', 'warn', 'hide']
|
||||
const DEFAULT_LANG_CODES = (deviceLocales || [])
|
||||
.concat(['en', 'ja', 'pt', 'de'])
|
||||
.slice(0, 6)
|
||||
|
@ -52,7 +40,7 @@ export class LabelPreferencesModel {
|
|||
}
|
||||
|
||||
export class PreferencesModel {
|
||||
adultContentEnabled = !isIOS
|
||||
adultContentEnabled = false
|
||||
contentLanguages: string[] = deviceLocales || []
|
||||
postLanguage: string = deviceLocales[0] || 'en'
|
||||
postLanguageHistory: string[] = DEFAULT_LANG_CODES
|
||||
|
@ -189,43 +177,32 @@ export class PreferencesModel {
|
|||
await this.lock.acquireAsync()
|
||||
try {
|
||||
// fetch preferences
|
||||
let hasSavedFeedsPref = false
|
||||
const res = await this.rootStore.agent.app.bsky.actor.getPreferences({})
|
||||
const prefs = await this.rootStore.agent.getPreferences()
|
||||
|
||||
runInAction(() => {
|
||||
for (const pref of res.data.preferences) {
|
||||
this.adultContentEnabled = prefs.adultContentEnabled
|
||||
for (const label in prefs.contentLabels) {
|
||||
if (
|
||||
AppBskyActorDefs.isAdultContentPref(pref) &&
|
||||
AppBskyActorDefs.validateAdultContentPref(pref).success
|
||||
LABEL_GROUPS.includes(label) &&
|
||||
VISIBILITY_VALUES.includes(prefs.contentLabels[label])
|
||||
) {
|
||||
this.adultContentEnabled = pref.enabled
|
||||
} else if (
|
||||
AppBskyActorDefs.isContentLabelPref(pref) &&
|
||||
AppBskyActorDefs.validateAdultContentPref(pref).success
|
||||
) {
|
||||
if (
|
||||
LABEL_GROUPS.includes(pref.label) &&
|
||||
VISIBILITY_VALUES.includes(pref.visibility)
|
||||
) {
|
||||
this.contentLabels[pref.label as keyof LabelPreferencesModel] =
|
||||
pref.visibility as LabelPreference
|
||||
}
|
||||
} else if (
|
||||
AppBskyActorDefs.isSavedFeedsPref(pref) &&
|
||||
AppBskyActorDefs.validateSavedFeedsPref(pref).success
|
||||
) {
|
||||
if (!isEqual(this.savedFeeds, pref.saved)) {
|
||||
this.savedFeeds = pref.saved
|
||||
}
|
||||
if (!isEqual(this.pinnedFeeds, pref.pinned)) {
|
||||
this.pinnedFeeds = pref.pinned
|
||||
}
|
||||
hasSavedFeedsPref = true
|
||||
this.contentLabels[label as keyof LabelPreferencesModel] =
|
||||
prefs.contentLabels[label]
|
||||
}
|
||||
}
|
||||
if (prefs.feeds.saved && !isEqual(this.savedFeeds, prefs.feeds.saved)) {
|
||||
this.savedFeeds = prefs.feeds.saved
|
||||
}
|
||||
if (
|
||||
prefs.feeds.pinned &&
|
||||
!isEqual(this.pinnedFeeds, prefs.feeds.pinned)
|
||||
) {
|
||||
this.pinnedFeeds = prefs.feeds.pinned
|
||||
}
|
||||
})
|
||||
|
||||
// set defaults on missing items
|
||||
if (!hasSavedFeedsPref) {
|
||||
if (typeof prefs.feeds.saved === 'undefined') {
|
||||
const {saved, pinned} = await DEFAULT_FEEDS(
|
||||
this.rootStore.agent.service.toString(),
|
||||
(handle: string) =>
|
||||
|
@ -237,14 +214,7 @@ export class PreferencesModel {
|
|||
this.savedFeeds = saved
|
||||
this.pinnedFeeds = pinned
|
||||
})
|
||||
res.data.preferences.push({
|
||||
$type: 'app.bsky.actor.defs#savedFeedsPref',
|
||||
saved,
|
||||
pinned,
|
||||
})
|
||||
await this.rootStore.agent.app.bsky.actor.putPreferences({
|
||||
preferences: res.data.preferences,
|
||||
})
|
||||
await this.rootStore.agent.setSavedFeeds(saved, pinned)
|
||||
}
|
||||
} finally {
|
||||
this.lock.release()
|
||||
|
@ -253,35 +223,6 @@ export class PreferencesModel {
|
|||
await this.rootStore.me.savedFeeds.updateCache(clearCache)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function updates the preferences of a user and allows for a callback function to be executed
|
||||
* before the update.
|
||||
* @param cb - cb is a callback function that takes in a single parameter of type
|
||||
* AppBskyActorDefs.Preferences and returns either a boolean or void. This callback function is used to
|
||||
* update the preferences of the user. The function is called with the current preferences as an
|
||||
* argument and if the callback returns false, the preferences are not updated.
|
||||
* @returns void
|
||||
*/
|
||||
async update(
|
||||
cb: (
|
||||
prefs: AppBskyActorDefs.Preferences,
|
||||
) => AppBskyActorDefs.Preferences | false,
|
||||
) {
|
||||
await this.lock.acquireAsync()
|
||||
try {
|
||||
const res = await this.rootStore.agent.app.bsky.actor.getPreferences({})
|
||||
const newPrefs = cb(res.data.preferences)
|
||||
if (newPrefs === false) {
|
||||
return
|
||||
}
|
||||
await this.rootStore.agent.app.bsky.actor.putPreferences({
|
||||
preferences: newPrefs,
|
||||
})
|
||||
} finally {
|
||||
this.lock.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function resets the preferences to an empty array of no preferences.
|
||||
*/
|
||||
|
@ -381,84 +322,12 @@ export class PreferencesModel {
|
|||
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,
|
||||
})
|
||||
}
|
||||
return prefs
|
||||
})
|
||||
await this.rootStore.agent.setContentLabelPref(key, 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,
|
||||
})
|
||||
}
|
||||
return prefs
|
||||
})
|
||||
}
|
||||
|
||||
getLabelPreference(labels: ComAtprotoLabelDefs.Label[] | undefined): {
|
||||
pref: LabelPreference
|
||||
desc: LabelValGroup
|
||||
} {
|
||||
let res: {pref: LabelPreference; desc: LabelValGroup} = {
|
||||
pref: 'show',
|
||||
desc: UNKNOWN_LABEL_GROUP,
|
||||
}
|
||||
if (!labels?.length) {
|
||||
return res
|
||||
}
|
||||
for (const label of labels) {
|
||||
const group = getLabelValueGroup(label.val)
|
||||
if (group.id === 'illegal') {
|
||||
return {pref: 'hide', desc: ILLEGAL_LABEL_GROUP}
|
||||
} else if (group.id === 'always-filter') {
|
||||
return {pref: 'hide', desc: ALWAYS_FILTER_LABEL_GROUP}
|
||||
} else if (group.id === 'always-warn') {
|
||||
res.pref = 'warn'
|
||||
res.desc = ALWAYS_WARN_LABEL_GROUP
|
||||
continue
|
||||
} else if (group.id === 'unknown') {
|
||||
continue
|
||||
}
|
||||
let pref = this.contentLabels[group.id]
|
||||
if (pref === 'hide') {
|
||||
res.pref = 'hide'
|
||||
res.desc = group
|
||||
} else if (pref === 'warn' && res.pref === 'show') {
|
||||
res.pref = 'warn'
|
||||
res.desc = group
|
||||
}
|
||||
}
|
||||
if (res.desc.isAdultImagery && !this.adultContentEnabled) {
|
||||
res.pref = 'hide'
|
||||
}
|
||||
return res
|
||||
await this.rootStore.agent.setAdultContentEnabled(v)
|
||||
}
|
||||
|
||||
get moderationOpts(): ModerationOpts {
|
||||
|
@ -499,31 +368,20 @@ export class PreferencesModel {
|
|||
}
|
||||
}
|
||||
|
||||
async setSavedFeeds(saved: string[], pinned: string[]) {
|
||||
async _optimisticUpdateSavedFeeds(
|
||||
saved: string[],
|
||||
pinned: string[],
|
||||
cb: () => Promise<{saved: string[]; pinned: string[]}>,
|
||||
) {
|
||||
const oldSaved = this.savedFeeds
|
||||
const oldPinned = this.pinnedFeeds
|
||||
this.savedFeeds = saved
|
||||
this.pinnedFeeds = pinned
|
||||
try {
|
||||
await this.update((prefs: AppBskyActorDefs.Preferences) => {
|
||||
let feedsPref = prefs.find(
|
||||
pref =>
|
||||
AppBskyActorDefs.isSavedFeedsPref(pref) &&
|
||||
AppBskyActorDefs.validateSavedFeedsPref(pref).success,
|
||||
)
|
||||
if (feedsPref) {
|
||||
feedsPref.saved = saved
|
||||
feedsPref.pinned = pinned
|
||||
} else {
|
||||
feedsPref = {
|
||||
$type: 'app.bsky.actor.defs#savedFeedsPref',
|
||||
saved,
|
||||
pinned,
|
||||
}
|
||||
}
|
||||
return prefs
|
||||
.filter(pref => !AppBskyActorDefs.isSavedFeedsPref(pref))
|
||||
.concat([feedsPref])
|
||||
const res = await cb()
|
||||
runInAction(() => {
|
||||
this.savedFeeds = res.saved
|
||||
this.pinnedFeeds = res.pinned
|
||||
})
|
||||
} catch (e) {
|
||||
runInAction(() => {
|
||||
|
@ -534,25 +392,41 @@ export class PreferencesModel {
|
|||
}
|
||||
}
|
||||
|
||||
async setSavedFeeds(saved: string[], pinned: string[]) {
|
||||
return this._optimisticUpdateSavedFeeds(saved, pinned, () =>
|
||||
this.rootStore.agent.setSavedFeeds(saved, pinned),
|
||||
)
|
||||
}
|
||||
|
||||
async addSavedFeed(v: string) {
|
||||
return this.setSavedFeeds([...this.savedFeeds, v], this.pinnedFeeds)
|
||||
return this._optimisticUpdateSavedFeeds(
|
||||
[...this.savedFeeds, v],
|
||||
this.pinnedFeeds,
|
||||
() => this.rootStore.agent.addSavedFeed(v),
|
||||
)
|
||||
}
|
||||
|
||||
async removeSavedFeed(v: string) {
|
||||
return this.setSavedFeeds(
|
||||
return this._optimisticUpdateSavedFeeds(
|
||||
this.savedFeeds.filter(uri => uri !== v),
|
||||
this.pinnedFeeds.filter(uri => uri !== v),
|
||||
() => this.rootStore.agent.removeSavedFeed(v),
|
||||
)
|
||||
}
|
||||
|
||||
async addPinnedFeed(v: string) {
|
||||
return this.setSavedFeeds(this.savedFeeds, [...this.pinnedFeeds, v])
|
||||
return this._optimisticUpdateSavedFeeds(
|
||||
this.savedFeeds,
|
||||
[...this.pinnedFeeds, v],
|
||||
() => this.rootStore.agent.addPinnedFeed(v),
|
||||
)
|
||||
}
|
||||
|
||||
async removePinnedFeed(v: string) {
|
||||
return this.setSavedFeeds(
|
||||
return this._optimisticUpdateSavedFeeds(
|
||||
this.savedFeeds,
|
||||
this.pinnedFeeds.filter(uri => uri !== v),
|
||||
() => this.rootStore.agent.removePinnedFeed(v),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -48,15 +48,17 @@ export const Component = observer(({}: {}) => {
|
|||
<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://bsky.app"
|
||||
text="bsky.app"
|
||||
/>
|
||||
.
|
||||
</Text>
|
||||
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>
|
||||
)
|
||||
) : (
|
||||
<ToggleButton
|
||||
type="default-light"
|
||||
|
@ -188,7 +190,7 @@ function SelectGroup({current, onChange, group}: SelectGroupProps) {
|
|||
/>
|
||||
<SelectableBtn
|
||||
current={current}
|
||||
value="show"
|
||||
value="ignore"
|
||||
label="Show"
|
||||
right
|
||||
onChange={onChange}
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -45,13 +45,13 @@
|
|||
tlds "^1.234.0"
|
||||
typed-emitter "^2.1.0"
|
||||
|
||||
"@atproto/api@^0.6.6":
|
||||
version "0.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.6.tgz#c1bfdb6bc7dee9cdba1901cde0081c2d422d7c29"
|
||||
integrity sha512-j+yNTjllVxuTc4bAegghTopju7MdhczLXWvWIli40uXwCzQ3JjS1mFr/47eETtysib2phWYQvfhtCrqQq6AAig==
|
||||
"@atproto/api@^0.6.8":
|
||||
version "0.6.8"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.6.8.tgz#fe77f3ab2e7a815edca1357b0a89a7496be8f764"
|
||||
integrity sha512-WmXpIbO79f85UA8AzvvSqKibojBXN1HT3KEHhUOqXJRW8X1trYijgWIXwhnxhoBQXgiQfzKG7HdORvRjmRSLoQ==
|
||||
dependencies:
|
||||
"@atproto/common-web" "*"
|
||||
"@atproto/uri" "*"
|
||||
"@atproto/syntax" "*"
|
||||
"@atproto/xrpc" "*"
|
||||
tlds "^1.234.0"
|
||||
typed-emitter "^2.1.0"
|
||||
|
@ -317,6 +317,13 @@
|
|||
uint8arrays "3.0.0"
|
||||
zod "^3.21.4"
|
||||
|
||||
"@atproto/syntax@*":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.1.0.tgz#f13b9dad8d13342cc54295ecd702ea13c82c9bf0"
|
||||
integrity sha512-Ktui0qvIXt1o1Px1KKC0eqn69MfRHQ9ok5EwjcxIEPbJ8OY5XqkeyJneFDIWRJZiR6vqPHfjFYRUpTB+jNPfRQ==
|
||||
dependencies:
|
||||
"@atproto/common-web" "*"
|
||||
|
||||
"@atproto/uri@*":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/uri/-/uri-0.1.0.tgz#1cb4695d2f1766ec8d542af6a495a416f6f6c214"
|
||||
|
|
Loading…
Reference in New Issue