Implement content filtering preferences
This commit is contained in:
+17
-82
@@ -1,87 +1,27 @@
|
|||||||
export const FILTER_SETTINGS = {
|
import {LabelPreferencesModel} from 'state/models/ui/preferences'
|
||||||
base: {
|
|
||||||
sourceDids: [
|
|
||||||
'did:plc:ar7c4by46qjdydhdevvrndac',
|
|
||||||
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
|
|
||||||
],
|
|
||||||
filterLabelVals: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
|
|
||||||
},
|
|
||||||
'bsky-unfiltered': {
|
|
||||||
sourceDids: [
|
|
||||||
'did:plc:ar7c4by46qjdydhdevvrndac',
|
|
||||||
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
|
|
||||||
],
|
|
||||||
filterLabelVals: [],
|
|
||||||
},
|
|
||||||
'bsky-default': {
|
|
||||||
sourceDids: [
|
|
||||||
'did:plc:ar7c4by46qjdydhdevvrndac',
|
|
||||||
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
|
|
||||||
],
|
|
||||||
filterLabelVals: ['porn', 'nudity', 'gore', 'self-harm', 'torture', 'spam'],
|
|
||||||
},
|
|
||||||
'bsky-calm': {
|
|
||||||
sourceDids: [
|
|
||||||
'did:plc:ar7c4by46qjdydhdevvrndac',
|
|
||||||
'did:plc:jonz5u6ulzdkwpnhqlwbsbrh',
|
|
||||||
],
|
|
||||||
filterLabelVals: [
|
|
||||||
'porn',
|
|
||||||
'nudity',
|
|
||||||
'sexual',
|
|
||||||
'gore',
|
|
||||||
'self-harm',
|
|
||||||
'torture',
|
|
||||||
'icon-kkk',
|
|
||||||
'icon-nazi',
|
|
||||||
'icon-confederate',
|
|
||||||
'spam',
|
|
||||||
'impersonation',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FILTERS = {
|
|
||||||
'bsky-unfiltered': {
|
|
||||||
title: 'Unfiltered',
|
|
||||||
author: 'Bluesky Team',
|
|
||||||
description: 'Show me everything.',
|
|
||||||
filterLabelVals: FILTER_SETTINGS.base.filterLabelVals.concat(
|
|
||||||
FILTER_SETTINGS['bsky-unfiltered'].filterLabelVals,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'bsky-default': {
|
|
||||||
title: 'Default',
|
|
||||||
author: 'Bluesky Team',
|
|
||||||
description:
|
|
||||||
'Filters out NSFW and nudity, gore, self-harm, torture, and spam.',
|
|
||||||
filterLabelVals: FILTER_SETTINGS.base.filterLabelVals.concat(
|
|
||||||
FILTER_SETTINGS['bsky-default'].filterLabelVals,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'bsky-calm': {
|
|
||||||
title: 'Calm',
|
|
||||||
author: 'Bluesky Team',
|
|
||||||
description:
|
|
||||||
'Like default, but also filters out hate-group iconography and impersonations.',
|
|
||||||
filterLabelVals: FILTER_SETTINGS.base.filterLabelVals.concat(
|
|
||||||
FILTER_SETTINGS['bsky-calm'].filterLabelVals,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LabelValGroup {
|
export interface LabelValGroup {
|
||||||
id: string
|
id: keyof LabelPreferencesModel | 'illegal' | 'unknown'
|
||||||
title: string
|
title: string
|
||||||
values: string[]
|
values: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
|
export const ILLEGAL_LABEL_GROUP: LabelValGroup = {
|
||||||
illegal: {
|
|
||||||
id: 'illegal',
|
id: 'illegal',
|
||||||
title: 'Illegal Content',
|
title: 'Illegal Content',
|
||||||
values: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
|
values: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
|
||||||
},
|
}
|
||||||
|
|
||||||
|
export const UNKNOWN_LABEL_GROUP: LabelValGroup = {
|
||||||
|
id: 'unknown',
|
||||||
|
title: 'Unknown Label',
|
||||||
|
values: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CONFIGURABLE_LABEL_GROUPS: Record<
|
||||||
|
keyof LabelPreferencesModel,
|
||||||
|
LabelValGroup
|
||||||
|
> = {
|
||||||
nsfw: {
|
nsfw: {
|
||||||
id: 'nsfw',
|
id: 'nsfw',
|
||||||
title: 'Sexual Content',
|
title: 'Sexual Content',
|
||||||
@@ -89,12 +29,12 @@ export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
|
|||||||
},
|
},
|
||||||
gore: {
|
gore: {
|
||||||
id: 'gore',
|
id: 'gore',
|
||||||
title: 'Violent / Bloody Content',
|
title: 'Violent / Bloody',
|
||||||
values: ['gore', 'self-harm', 'torture'],
|
values: ['gore', 'self-harm', 'torture'],
|
||||||
},
|
},
|
||||||
hate: {
|
hate: {
|
||||||
id: 'hate',
|
id: 'hate',
|
||||||
title: 'Political Hate-Group Content',
|
title: 'Political Hate-Groups',
|
||||||
values: ['icon-kkk', 'icon-nazi', 'icon-confederate'],
|
values: ['icon-kkk', 'icon-nazi', 'icon-confederate'],
|
||||||
},
|
},
|
||||||
spam: {
|
spam: {
|
||||||
@@ -107,9 +47,4 @@ export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
|
|||||||
title: 'Impersonation',
|
title: 'Impersonation',
|
||||||
values: ['impersonation'],
|
values: ['impersonation'],
|
||||||
},
|
},
|
||||||
unknown: {
|
|
||||||
id: 'unknown',
|
|
||||||
title: 'Unknown Label',
|
|
||||||
values: [],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import {LabelValGroup, LABEL_VAL_GROUPS} from './const'
|
import {
|
||||||
|
LabelValGroup,
|
||||||
|
CONFIGURABLE_LABEL_GROUPS,
|
||||||
|
ILLEGAL_LABEL_GROUP,
|
||||||
|
UNKNOWN_LABEL_GROUP,
|
||||||
|
} from './const'
|
||||||
|
|
||||||
export function getLabelValueGroup(labelVal: string): LabelValGroup {
|
export function getLabelValueGroup(labelVal: string): LabelValGroup {
|
||||||
for (const id in LABEL_VAL_GROUPS) {
|
let id: keyof typeof CONFIGURABLE_LABEL_GROUPS
|
||||||
if (LABEL_VAL_GROUPS[id].values.includes(labelVal)) {
|
for (id in CONFIGURABLE_LABEL_GROUPS) {
|
||||||
return LABEL_VAL_GROUPS[id]
|
if (ILLEGAL_LABEL_GROUP.values.includes(labelVal)) {
|
||||||
|
return ILLEGAL_LABEL_GROUP
|
||||||
|
}
|
||||||
|
if (CONFIGURABLE_LABEL_GROUPS[id].values.includes(labelVal)) {
|
||||||
|
return CONFIGURABLE_LABEL_GROUPS[id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return LABEL_VAL_GROUPS.unknown
|
return UNKNOWN_LABEL_GROUP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,33 @@
|
|||||||
import {makeAutoObservable} from 'mobx'
|
import {makeAutoObservable} 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 {getLabelValueGroup} from 'lib/labeling/helpers'
|
||||||
|
import {
|
||||||
|
LabelValGroup,
|
||||||
|
UNKNOWN_LABEL_GROUP,
|
||||||
|
ILLEGAL_LABEL_GROUP,
|
||||||
|
} from 'lib/labeling/const'
|
||||||
|
|
||||||
const deviceLocales = getLocales()
|
const deviceLocales = getLocales()
|
||||||
|
|
||||||
|
export type LabelPreference = 'show' | 'warn' | 'hide'
|
||||||
|
|
||||||
|
export class LabelPreferencesModel {
|
||||||
|
nsfw: LabelPreference = 'warn'
|
||||||
|
gore: LabelPreference = 'hide'
|
||||||
|
hate: LabelPreference = 'hide'
|
||||||
|
spam: LabelPreference = 'hide'
|
||||||
|
impersonation: LabelPreference = 'warn'
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this, {}, {autoBind: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PreferencesModel {
|
export class PreferencesModel {
|
||||||
_contentLanguages: string[] | undefined
|
_contentLanguages: string[] | undefined
|
||||||
|
contentLabels = new LabelPreferencesModel()
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this, {}, {autoBind: true})
|
makeAutoObservable(this, {}, {autoBind: true})
|
||||||
@@ -22,6 +44,7 @@ export class PreferencesModel {
|
|||||||
serialize() {
|
serialize() {
|
||||||
return {
|
return {
|
||||||
contentLanguages: this._contentLanguages,
|
contentLanguages: this._contentLanguages,
|
||||||
|
contentLabels: this.contentLabels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +57,46 @@ export class PreferencesModel {
|
|||||||
) {
|
) {
|
||||||
this._contentLanguages = v.contentLanguages
|
this._contentLanguages = v.contentLanguages
|
||||||
}
|
}
|
||||||
|
if (hasProp(v, 'contentLabels') && typeof v.contentLabels === 'object') {
|
||||||
|
Object.assign(this.contentLabels, v.contentLabels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setContentLabelPref(
|
||||||
|
key: keyof LabelPreferencesModel,
|
||||||
|
value: LabelPreference,
|
||||||
|
) {
|
||||||
|
this.contentLabels[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
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 === '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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ export interface InviteCodesModal {
|
|||||||
name: 'invite-codes'
|
name: 'invite-codes'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContentFilteringSettingsModal {
|
||||||
|
name: 'content-filtering-settings'
|
||||||
|
}
|
||||||
|
|
||||||
export type Modal =
|
export type Modal =
|
||||||
| ConfirmModal
|
| ConfirmModal
|
||||||
| EditProfileModal
|
| EditProfileModal
|
||||||
@@ -77,6 +81,7 @@ export type Modal =
|
|||||||
| ChangeHandleModal
|
| ChangeHandleModal
|
||||||
| WaitlistModal
|
| WaitlistModal
|
||||||
| InviteCodesModal
|
| InviteCodesModal
|
||||||
|
| ContentFilteringSettingsModal
|
||||||
|
|
||||||
interface LightboxModel {}
|
interface LightboxModel {}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||||
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
|
import {observer} from 'mobx-react-lite'
|
||||||
|
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 {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const'
|
||||||
|
|
||||||
|
export const snapPoints = [500]
|
||||||
|
|
||||||
|
export function Component({}: {}) {
|
||||||
|
const store = useStores()
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const onPressDone = React.useCallback(() => {
|
||||||
|
store.shell.closeModal()
|
||||||
|
}, [store])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View testID="reportPostModal" style={[pal.view, styles.container]}>
|
||||||
|
<Text style={[pal.text, styles.title]}>Content Filtering</Text>
|
||||||
|
<ContentLabelPref group="nsfw" />
|
||||||
|
<ContentLabelPref group="gore" />
|
||||||
|
<ContentLabelPref group="hate" />
|
||||||
|
<ContentLabelPref group="spam" />
|
||||||
|
<ContentLabelPref group="impersonation" />
|
||||||
|
<View style={s.flex1} />
|
||||||
|
<TouchableOpacity testID="sendReportBtn" onPress={onPressDone}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
style={[styles.btn]}>
|
||||||
|
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContentLabelPref = observer(
|
||||||
|
({group}: {group: keyof typeof CONFIGURABLE_LABEL_GROUPS}) => {
|
||||||
|
const store = useStores()
|
||||||
|
const pal = usePalette('default')
|
||||||
|
return (
|
||||||
|
<View style={[styles.contentLabelPref, pal.border]}>
|
||||||
|
<Text type="md-medium" style={[pal.text]}>
|
||||||
|
{CONFIGURABLE_LABEL_GROUPS[group].title}
|
||||||
|
</Text>
|
||||||
|
<SelectGroup
|
||||||
|
current={store.preferences.contentLabels[group]}
|
||||||
|
onChange={v => store.preferences.setContentLabelPref(group, v)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function SelectGroup({
|
||||||
|
current,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
current: LabelPreference
|
||||||
|
onChange: (v: LabelPreference) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<View style={styles.selectableBtns}>
|
||||||
|
<SelectableBtn
|
||||||
|
current={current}
|
||||||
|
value="hide"
|
||||||
|
label="Hide"
|
||||||
|
left
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<SelectableBtn
|
||||||
|
current={current}
|
||||||
|
value="warn"
|
||||||
|
label="Warn"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<SelectableBtn
|
||||||
|
current={current}
|
||||||
|
value="show"
|
||||||
|
label="Show"
|
||||||
|
right
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectableBtn({
|
||||||
|
current,
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
current: string
|
||||||
|
value: LabelPreference
|
||||||
|
label: string
|
||||||
|
left?: boolean
|
||||||
|
right?: boolean
|
||||||
|
onChange: (v: LabelPreference) => void
|
||||||
|
}) {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const palPrimary = usePalette('inverted')
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.selectableBtn,
|
||||||
|
left && styles.selectableBtnLeft,
|
||||||
|
right && styles.selectableBtnRight,
|
||||||
|
pal.border,
|
||||||
|
current === value ? palPrimary.view : pal.view,
|
||||||
|
]}
|
||||||
|
onPress={() => onChange(value)}>
|
||||||
|
<Text style={current === value ? palPrimary.text : pal.text}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingBottom: 40,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 24,
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
paddingHorizontal: 2,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
contentLabelPref: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingTop: 10,
|
||||||
|
paddingLeft: 4,
|
||||||
|
marginBottom: 10,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
selectableBtns: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
selectableBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderLeftWidth: 0,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 10,
|
||||||
|
},
|
||||||
|
selectableBtnLeft: {
|
||||||
|
borderTopLeftRadius: 8,
|
||||||
|
borderBottomLeftRadius: 8,
|
||||||
|
borderLeftWidth: 1,
|
||||||
|
},
|
||||||
|
selectableBtnRight: {
|
||||||
|
borderTopRightRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
|
},
|
||||||
|
|
||||||
|
btn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 32,
|
||||||
|
padding: 14,
|
||||||
|
backgroundColor: colors.gray1,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, {useRef, useEffect} from 'react'
|
import React, {useRef, useEffect} from 'react'
|
||||||
import {View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import BottomSheet from '@gorhom/bottom-sheet'
|
import BottomSheet from '@gorhom/bottom-sheet'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
|
||||||
import * as ConfirmModal from './Confirm'
|
import * as ConfirmModal from './Confirm'
|
||||||
import * as EditProfileModal from './EditProfile'
|
import * as EditProfileModal from './EditProfile'
|
||||||
@@ -15,8 +16,7 @@ import * as DeleteAccountModal from './DeleteAccount'
|
|||||||
import * as ChangeHandleModal from './ChangeHandle'
|
import * as ChangeHandleModal from './ChangeHandle'
|
||||||
import * as WaitlistModal from './Waitlist'
|
import * as WaitlistModal from './Waitlist'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
import * as InviteCodesModal from './InviteCodes'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||||
import {StyleSheet} from 'react-native'
|
|
||||||
|
|
||||||
const DEFAULT_SNAPPOINTS = ['90%']
|
const DEFAULT_SNAPPOINTS = ['90%']
|
||||||
|
|
||||||
@@ -77,6 +77,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
|||||||
} else if (activeModal?.name === 'invite-codes') {
|
} else if (activeModal?.name === 'invite-codes') {
|
||||||
snapPoints = InviteCodesModal.snapPoints
|
snapPoints = InviteCodesModal.snapPoints
|
||||||
element = <InviteCodesModal.Component />
|
element = <InviteCodesModal.Component />
|
||||||
|
} else if (activeModal?.name === 'content-filtering-settings') {
|
||||||
|
snapPoints = ContentFilteringSettingsModal.snapPoints
|
||||||
|
element = <ContentFilteringSettingsModal.Component />
|
||||||
} else {
|
} else {
|
||||||
return <View />
|
return <View />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import * as CropImageModal from './crop-image/CropImage.web'
|
|||||||
import * as ChangeHandleModal from './ChangeHandle'
|
import * as ChangeHandleModal from './ChangeHandle'
|
||||||
import * as WaitlistModal from './Waitlist'
|
import * as WaitlistModal from './Waitlist'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
import * as InviteCodesModal from './InviteCodes'
|
||||||
|
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||||
|
|
||||||
export const ModalsContainer = observer(function ModalsContainer() {
|
export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
@@ -75,6 +76,8 @@ function Modal({modal}: {modal: ModalIface}) {
|
|||||||
element = <WaitlistModal.Component />
|
element = <WaitlistModal.Component />
|
||||||
} else if (modal.name === 'invite-codes') {
|
} else if (modal.name === 'invite-codes') {
|
||||||
element = <InviteCodesModal.Component />
|
element = <InviteCodesModal.Component />
|
||||||
|
} else if (modal.name === 'content-filtering-settings') {
|
||||||
|
element = <ContentFilteringSettingsModal.Component />
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import {
|
|||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {ComAtprotoLabelDefs} from '@atproto/api'
|
import {ComAtprotoLabelDefs} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {useStores} from 'state/index'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
|
||||||
import {addStyle} from 'lib/styles'
|
import {addStyle} from 'lib/styles'
|
||||||
|
|
||||||
export function ContentHider({
|
export function ContentHider({
|
||||||
@@ -29,8 +28,10 @@ export function ContentHider({
|
|||||||
}>) {
|
}>) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const [override, setOverride] = React.useState(false)
|
const [override, setOverride] = React.useState(false)
|
||||||
|
const store = useStores()
|
||||||
|
const labelPref = store.preferences.getLabelPreference(labels)
|
||||||
|
|
||||||
if (!isMuted && !labels?.length) {
|
if (!isMuted && labelPref.pref === 'show') {
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={style}>
|
<View testID={testID} style={style}>
|
||||||
{children}
|
{children}
|
||||||
@@ -38,9 +39,7 @@ export function ContentHider({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = labels?.[0] // TODO use config to settle on most relevant item
|
if (labelPref.pref === 'hide') {
|
||||||
const labelGroup = getLabelValueGroup(label?.val || '')
|
|
||||||
if (labelGroup.id === 'illegal') {
|
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,10 +54,8 @@ export function ContentHider({
|
|||||||
<Text type="md" style={pal.textLight}>
|
<Text type="md" style={pal.textLight}>
|
||||||
{isMuted ? (
|
{isMuted ? (
|
||||||
<>Post from an account you muted.</>
|
<>Post from an account you muted.</>
|
||||||
) : label ? (
|
|
||||||
<>Warning: {labelGroup.title}</>
|
|
||||||
) : (
|
) : (
|
||||||
''
|
<>Warning: {labelPref.desc.title}</>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {Link} from '../Link'
|
import {Link} from '../Link'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
|
||||||
import {addStyle} from 'lib/styles'
|
import {addStyle} from 'lib/styles'
|
||||||
|
import {useStores} from 'state/index'
|
||||||
|
|
||||||
export function PostHider({
|
export function PostHider({
|
||||||
testID,
|
testID,
|
||||||
@@ -28,13 +28,13 @@ export function PostHider({
|
|||||||
labels: ComAtprotoLabelDefs.Label[] | undefined
|
labels: ComAtprotoLabelDefs.Label[] | undefined
|
||||||
style: StyleProp<ViewStyle>
|
style: StyleProp<ViewStyle>
|
||||||
}>) {
|
}>) {
|
||||||
|
const store = useStores()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const [override, setOverride] = React.useState(false)
|
const [override, setOverride] = React.useState(false)
|
||||||
const bg = override ? pal.viewLight : pal.view
|
const bg = override ? pal.viewLight : pal.view
|
||||||
|
|
||||||
const label = labels?.[0] // TODO use config to settle on most relevant item
|
const labelPref = store.preferences.getLabelPreference(labels)
|
||||||
const labelGroup = getLabelValueGroup(label?.val || '')
|
if (labelPref.pref === 'hide') {
|
||||||
if (labelGroup.id === 'illegal') {
|
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -34,6 +34,7 @@ import {faCompass} from '@fortawesome/free-regular-svg-icons/faCompass'
|
|||||||
import {faEllipsis} from '@fortawesome/free-solid-svg-icons/faEllipsis'
|
import {faEllipsis} from '@fortawesome/free-solid-svg-icons/faEllipsis'
|
||||||
import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope'
|
import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope'
|
||||||
import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation'
|
import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation'
|
||||||
|
import {faEye} from '@fortawesome/free-solid-svg-icons/faEye'
|
||||||
import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash'
|
import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash'
|
||||||
import {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
|
import {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
|
||||||
import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
|
import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
|
||||||
@@ -106,8 +107,8 @@ export function setup() {
|
|||||||
faCompass,
|
faCompass,
|
||||||
faEllipsis,
|
faEllipsis,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
|
faEye,
|
||||||
faExclamation,
|
faExclamation,
|
||||||
faQuoteLeft,
|
|
||||||
farEyeSlash,
|
farEyeSlash,
|
||||||
faGear,
|
faGear,
|
||||||
faGlobe,
|
faGlobe,
|
||||||
@@ -128,6 +129,7 @@ export function setup() {
|
|||||||
faPenNib,
|
faPenNib,
|
||||||
faPenToSquare,
|
faPenToSquare,
|
||||||
faPlus,
|
faPlus,
|
||||||
|
faQuoteLeft,
|
||||||
faReply,
|
faReply,
|
||||||
faRetweet,
|
faRetweet,
|
||||||
faRss,
|
faRss,
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ export const SettingsScreen = withAuthRequired(
|
|||||||
store.shell.openModal({name: 'invite-codes'})
|
store.shell.openModal({name: 'invite-codes'})
|
||||||
}, [track, store])
|
}, [track, store])
|
||||||
|
|
||||||
|
const onPressContentFiltering = React.useCallback(() => {
|
||||||
|
track('Settings:ContentfilteringButtonClicked')
|
||||||
|
store.shell.openModal({name: 'content-filtering-settings'})
|
||||||
|
}, [track, store])
|
||||||
|
|
||||||
const onPressSignout = React.useCallback(() => {
|
const onPressSignout = React.useCallback(() => {
|
||||||
track('Settings:SignOutButtonClicked')
|
track('Settings:SignOutButtonClicked')
|
||||||
store.session.logout()
|
store.session.logout()
|
||||||
@@ -248,6 +253,20 @@ export const SettingsScreen = withAuthRequired(
|
|||||||
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
||||||
Advanced
|
Advanced
|
||||||
</Text>
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
testID="contentFilteringBtn"
|
||||||
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
||||||
|
onPress={isSwitching ? undefined : onPressContentFiltering}>
|
||||||
|
<View style={[styles.iconContainer, pal.btn]}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="eye"
|
||||||
|
style={pal.text as FontAwesomeIconStyle}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text type="lg" style={pal.text}>
|
||||||
|
Content moderation
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
testID="changeHandleBtn"
|
testID="changeHandleBtn"
|
||||||
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
||||||
|
|||||||
Reference in New Issue
Block a user