Implement content filtering preferences
This commit is contained in:
+20
-85
@@ -1,87 +1,27 @@
|
||||
export const FILTER_SETTINGS = {
|
||||
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,
|
||||
),
|
||||
},
|
||||
}
|
||||
import {LabelPreferencesModel} from 'state/models/ui/preferences'
|
||||
|
||||
export interface LabelValGroup {
|
||||
id: string
|
||||
id: keyof LabelPreferencesModel | 'illegal' | 'unknown'
|
||||
title: string
|
||||
values: string[]
|
||||
}
|
||||
|
||||
export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
|
||||
illegal: {
|
||||
id: 'illegal',
|
||||
title: 'Illegal Content',
|
||||
values: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
|
||||
},
|
||||
export const ILLEGAL_LABEL_GROUP: LabelValGroup = {
|
||||
id: 'illegal',
|
||||
title: 'Illegal Content',
|
||||
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: {
|
||||
id: 'nsfw',
|
||||
title: 'Sexual Content',
|
||||
@@ -89,12 +29,12 @@ export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
|
||||
},
|
||||
gore: {
|
||||
id: 'gore',
|
||||
title: 'Violent / Bloody Content',
|
||||
title: 'Violent / Bloody',
|
||||
values: ['gore', 'self-harm', 'torture'],
|
||||
},
|
||||
hate: {
|
||||
id: 'hate',
|
||||
title: 'Political Hate-Group Content',
|
||||
title: 'Political Hate-Groups',
|
||||
values: ['icon-kkk', 'icon-nazi', 'icon-confederate'],
|
||||
},
|
||||
spam: {
|
||||
@@ -107,9 +47,4 @@ export const LABEL_VAL_GROUPS: Record<string, LabelValGroup> = {
|
||||
title: '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 {
|
||||
for (const id in LABEL_VAL_GROUPS) {
|
||||
if (LABEL_VAL_GROUPS[id].values.includes(labelVal)) {
|
||||
return LABEL_VAL_GROUPS[id]
|
||||
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 (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 {getLocales} from 'expo-localization'
|
||||
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()
|
||||
|
||||
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 {
|
||||
_contentLanguages: string[] | undefined
|
||||
contentLabels = new LabelPreferencesModel()
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {}, {autoBind: true})
|
||||
@@ -22,6 +44,7 @@ export class PreferencesModel {
|
||||
serialize() {
|
||||
return {
|
||||
contentLanguages: this._contentLanguages,
|
||||
contentLabels: this.contentLabels,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +57,46 @@ export class PreferencesModel {
|
||||
) {
|
||||
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'
|
||||
}
|
||||
|
||||
export interface ContentFilteringSettingsModal {
|
||||
name: 'content-filtering-settings'
|
||||
}
|
||||
|
||||
export type Modal =
|
||||
| ConfirmModal
|
||||
| EditProfileModal
|
||||
@@ -77,6 +81,7 @@ export type Modal =
|
||||
| ChangeHandleModal
|
||||
| WaitlistModal
|
||||
| InviteCodesModal
|
||||
| ContentFilteringSettingsModal
|
||||
|
||||
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 {View} from 'react-native'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import BottomSheet from '@gorhom/bottom-sheet'
|
||||
import {useStores} from 'state/index'
|
||||
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
|
||||
import * as ConfirmModal from './Confirm'
|
||||
import * as EditProfileModal from './EditProfile'
|
||||
@@ -15,8 +16,7 @@ import * as DeleteAccountModal from './DeleteAccount'
|
||||
import * as ChangeHandleModal from './ChangeHandle'
|
||||
import * as WaitlistModal from './Waitlist'
|
||||
import * as InviteCodesModal from './InviteCodes'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {StyleSheet} from 'react-native'
|
||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||
|
||||
const DEFAULT_SNAPPOINTS = ['90%']
|
||||
|
||||
@@ -77,6 +77,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||
} else if (activeModal?.name === 'invite-codes') {
|
||||
snapPoints = InviteCodesModal.snapPoints
|
||||
element = <InviteCodesModal.Component />
|
||||
} else if (activeModal?.name === 'content-filtering-settings') {
|
||||
snapPoints = ContentFilteringSettingsModal.snapPoints
|
||||
element = <ContentFilteringSettingsModal.Component />
|
||||
} else {
|
||||
return <View />
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import * as CropImageModal from './crop-image/CropImage.web'
|
||||
import * as ChangeHandleModal from './ChangeHandle'
|
||||
import * as WaitlistModal from './Waitlist'
|
||||
import * as InviteCodesModal from './InviteCodes'
|
||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||
|
||||
export const ModalsContainer = observer(function ModalsContainer() {
|
||||
const store = useStores()
|
||||
@@ -75,6 +76,8 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||
element = <WaitlistModal.Component />
|
||||
} else if (modal.name === 'invite-codes') {
|
||||
element = <InviteCodesModal.Component />
|
||||
} else if (modal.name === 'content-filtering-settings') {
|
||||
element = <ContentFilteringSettingsModal.Component />
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import {
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {ComAtprotoLabelDefs} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useStores} from 'state/index'
|
||||
import {Text} from '../text/Text'
|
||||
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
||||
import {addStyle} from 'lib/styles'
|
||||
|
||||
export function ContentHider({
|
||||
@@ -29,8 +28,10 @@ export function ContentHider({
|
||||
}>) {
|
||||
const pal = usePalette('default')
|
||||
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 (
|
||||
<View testID={testID} style={style}>
|
||||
{children}
|
||||
@@ -38,9 +39,7 @@ export function ContentHider({
|
||||
)
|
||||
}
|
||||
|
||||
const label = labels?.[0] // TODO use config to settle on most relevant item
|
||||
const labelGroup = getLabelValueGroup(label?.val || '')
|
||||
if (labelGroup.id === 'illegal') {
|
||||
if (labelPref.pref === 'hide') {
|
||||
return <></>
|
||||
}
|
||||
|
||||
@@ -55,10 +54,8 @@ export function ContentHider({
|
||||
<Text type="md" style={pal.textLight}>
|
||||
{isMuted ? (
|
||||
<>Post from an account you muted.</>
|
||||
) : label ? (
|
||||
<>Warning: {labelGroup.title}</>
|
||||
) : (
|
||||
''
|
||||
<>Warning: {labelPref.desc.title}</>
|
||||
)}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
|
||||
@@ -11,8 +11,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {Link} from '../Link'
|
||||
import {Text} from '../text/Text'
|
||||
import {getLabelValueGroup} from 'lib/labeling/helpers'
|
||||
import {addStyle} from 'lib/styles'
|
||||
import {useStores} from 'state/index'
|
||||
|
||||
export function PostHider({
|
||||
testID,
|
||||
@@ -28,13 +28,13 @@ export function PostHider({
|
||||
labels: ComAtprotoLabelDefs.Label[] | undefined
|
||||
style: StyleProp<ViewStyle>
|
||||
}>) {
|
||||
const store = useStores()
|
||||
const pal = usePalette('default')
|
||||
const [override, setOverride] = React.useState(false)
|
||||
const bg = override ? pal.viewLight : pal.view
|
||||
|
||||
const label = labels?.[0] // TODO use config to settle on most relevant item
|
||||
const labelGroup = getLabelValueGroup(label?.val || '')
|
||||
if (labelGroup.id === 'illegal') {
|
||||
const labelPref = store.preferences.getLabelPreference(labels)
|
||||
if (labelPref.pref === 'hide') {
|
||||
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 {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope'
|
||||
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 {faGear} from '@fortawesome/free-solid-svg-icons/faGear'
|
||||
import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe'
|
||||
@@ -106,8 +107,8 @@ export function setup() {
|
||||
faCompass,
|
||||
faEllipsis,
|
||||
faEnvelope,
|
||||
faEye,
|
||||
faExclamation,
|
||||
faQuoteLeft,
|
||||
farEyeSlash,
|
||||
faGear,
|
||||
faGlobe,
|
||||
@@ -128,6 +129,7 @@ export function setup() {
|
||||
faPenNib,
|
||||
faPenToSquare,
|
||||
faPlus,
|
||||
faQuoteLeft,
|
||||
faReply,
|
||||
faRetweet,
|
||||
faRss,
|
||||
|
||||
@@ -124,6 +124,11 @@ export const SettingsScreen = withAuthRequired(
|
||||
store.shell.openModal({name: 'invite-codes'})
|
||||
}, [track, store])
|
||||
|
||||
const onPressContentFiltering = React.useCallback(() => {
|
||||
track('Settings:ContentfilteringButtonClicked')
|
||||
store.shell.openModal({name: 'content-filtering-settings'})
|
||||
}, [track, store])
|
||||
|
||||
const onPressSignout = React.useCallback(() => {
|
||||
track('Settings:SignOutButtonClicked')
|
||||
store.session.logout()
|
||||
@@ -248,6 +253,20 @@ export const SettingsScreen = withAuthRequired(
|
||||
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
||||
Advanced
|
||||
</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
|
||||
testID="changeHandleBtn"
|
||||
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
|
||||
|
||||
Reference in New Issue
Block a user