diff --git a/src/lib/labeling/const.ts b/src/lib/labeling/const.ts
index 54cc732b..2a9b921d 100644
--- a/src/lib/labeling/const.ts
+++ b/src/lib/labeling/const.ts
@@ -6,7 +6,6 @@ export const ILLEGAL_LABEL_GROUP: LabelValGroup = {
title: 'Illegal Content',
warning: 'Illegal Content',
values: ['csam', 'dmca-violation', 'nudity-nonconsentual'],
- imagesOnly: false,
}
export const ALWAYS_FILTER_LABEL_GROUP: LabelValGroup = {
@@ -14,7 +13,6 @@ export const ALWAYS_FILTER_LABEL_GROUP: LabelValGroup = {
title: 'Content Warning',
warning: 'Content Warning',
values: ['!filter'],
- imagesOnly: false,
}
export const ALWAYS_WARN_LABEL_GROUP: LabelValGroup = {
@@ -22,7 +20,6 @@ export const ALWAYS_WARN_LABEL_GROUP: LabelValGroup = {
title: 'Content Warning',
warning: 'Content Warning',
values: ['!warn'],
- imagesOnly: false,
}
export const UNKNOWN_LABEL_GROUP: LabelValGroup = {
@@ -30,7 +27,6 @@ export const UNKNOWN_LABEL_GROUP: LabelValGroup = {
title: 'Unknown Label',
warning: 'Content Warning',
values: [],
- imagesOnly: false,
}
export const CONFIGURABLE_LABEL_GROUPS: Record<
@@ -43,7 +39,7 @@ export const CONFIGURABLE_LABEL_GROUPS: Record<
subtitle: 'i.e. Pornography',
warning: 'Sexually Explicit',
values: ['porn'],
- imagesOnly: false, // apply to whole thing
+ isAdultImagery: true,
},
nudity: {
id: 'nudity',
@@ -51,7 +47,7 @@ export const CONFIGURABLE_LABEL_GROUPS: Record<
subtitle: 'Including non-sexual and artistic',
warning: 'Nudity',
values: ['nudity'],
- imagesOnly: true,
+ isAdultImagery: true,
},
suggestive: {
id: 'suggestive',
@@ -59,7 +55,7 @@ export const CONFIGURABLE_LABEL_GROUPS: Record<
subtitle: 'Does not include nudity',
warning: 'Sexually Suggestive',
values: ['sexual'],
- imagesOnly: true,
+ isAdultImagery: true,
},
gore: {
id: 'gore',
@@ -67,14 +63,13 @@ export const CONFIGURABLE_LABEL_GROUPS: Record<
subtitle: 'Gore, self-harm, torture',
warning: 'Violence',
values: ['gore', 'self-harm', 'torture'],
- imagesOnly: true,
+ isAdultImagery: true,
},
hate: {
id: 'hate',
title: 'Political Hate-Groups',
warning: 'Hate',
values: ['icon-kkk', 'icon-nazi', 'icon-intolerant', 'behavior-intolerant'],
- imagesOnly: false,
},
spam: {
id: 'spam',
@@ -82,7 +77,6 @@ export const CONFIGURABLE_LABEL_GROUPS: Record<
subtitle: 'Excessive low-quality posts',
warning: 'Spam',
values: ['spam'],
- imagesOnly: false,
},
impersonation: {
id: 'impersonation',
@@ -90,6 +84,5 @@ export const CONFIGURABLE_LABEL_GROUPS: Record<
subtitle: 'Accounts falsely claiming to be people or orgs',
warning: 'Impersonation',
values: ['impersonation'],
- imagesOnly: false,
},
}
diff --git a/src/lib/labeling/helpers.ts b/src/lib/labeling/helpers.ts
index 71ea43c0..baac0ed5 100644
--- a/src/lib/labeling/helpers.ts
+++ b/src/lib/labeling/helpers.ts
@@ -137,12 +137,12 @@ export function getPostModeration(
// warning cases
if (postPref.pref === 'warn') {
- if (postPref.desc.imagesOnly) {
+ if (postPref.desc.isAdultImagery) {
return {
avatar,
- list: warnContent(postPref.desc.warning), // TODO make warnImages when there's time
- thread: warnContent(postPref.desc.warning), // TODO make warnImages when there's time
- view: warnContent(postPref.desc.warning), // TODO make warnImages when there's time
+ list: warnImages(postPref.desc.warning),
+ thread: warnImages(postPref.desc.warning),
+ view: warnImages(postPref.desc.warning),
}
}
return {
@@ -401,10 +401,9 @@ function warnContent(reason: string) {
}
}
-// TODO
-// function warnImages(reason: string) {
-// return {
-// behavior: ModerationBehaviorCode.WarnImages,
-// reason,
-// }
-// }
+function warnImages(reason: string) {
+ return {
+ behavior: ModerationBehaviorCode.WarnImages,
+ reason,
+ }
+}
diff --git a/src/lib/labeling/types.ts b/src/lib/labeling/types.ts
index 123c5d1f..07804307 100644
--- a/src/lib/labeling/types.ts
+++ b/src/lib/labeling/types.ts
@@ -11,7 +11,7 @@ export interface LabelValGroup {
| 'always-warn'
| 'unknown'
title: string
- imagesOnly: boolean
+ isAdultImagery?: boolean
subtitle?: string
warning: string
values: string[]
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index 7b41fa74..fcd33af8 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -10,15 +10,16 @@ import {
ALWAYS_FILTER_LABEL_GROUP,
ALWAYS_WARN_LABEL_GROUP,
} from 'lib/labeling/const'
+import {isIOS} from 'platform/detection'
const deviceLocales = getLocales()
export type LabelPreference = 'show' | 'warn' | 'hide'
export class LabelPreferencesModel {
- nsfw: LabelPreference = 'warn'
- nudity: LabelPreference = 'show'
- suggestive: LabelPreference = 'show'
+ nsfw: LabelPreference = 'hide'
+ nudity: LabelPreference = 'warn'
+ suggestive: LabelPreference = 'warn'
gore: LabelPreference = 'warn'
hate: LabelPreference = 'hide'
spam: LabelPreference = 'hide'
@@ -30,6 +31,7 @@ export class LabelPreferencesModel {
}
export class PreferencesModel {
+ adultContentEnabled = !isIOS
contentLanguages: string[] =
deviceLocales?.map?.(locale => locale.languageCode) || []
contentLabels = new LabelPreferencesModel()
@@ -102,7 +104,9 @@ export class PreferencesModel {
} else if (group.id === 'always-filter') {
return {pref: 'hide', desc: ALWAYS_FILTER_LABEL_GROUP}
} else if (group.id === 'always-warn') {
- return {pref: 'warn', desc: ALWAYS_WARN_LABEL_GROUP}
+ res.pref = 'warn'
+ res.desc = ALWAYS_WARN_LABEL_GROUP
+ continue
} else if (group.id === 'unknown') {
continue
}
@@ -115,6 +119,9 @@ export class PreferencesModel {
res.desc = group
}
}
+ if (res.desc.isAdultImagery && !this.adultContentEnabled) {
+ res.pref = 'hide'
+ }
return res
}
}
diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx
index cfba2575..30b46556 100644
--- a/src/view/com/modals/ContentFilteringSettings.tsx
+++ b/src/view/com/modals/ContentFilteringSettings.tsx
@@ -24,10 +24,22 @@ export function Component({}: {}) {
Content Moderation
-
-
-
-
+
+
+
+
@@ -55,7 +67,13 @@ export function Component({}: {}) {
// TODO: Refactor this component to pass labels down to each tab
const ContentLabelPref = observer(
- ({group}: {group: keyof typeof CONFIGURABLE_LABEL_GROUPS}) => {
+ ({
+ group,
+ disabled,
+ }: {
+ group: keyof typeof CONFIGURABLE_LABEL_GROUPS
+ disabled?: boolean
+ }) => {
const store = useStores()
const pal = usePalette('default')
return (
@@ -70,11 +88,17 @@ const ContentLabelPref = observer(
)}
- store.preferences.setContentLabelPref(group, v)}
- group={group}
- />
+ {disabled ? (
+
+ Hide
+
+ ) : (
+ store.preferences.setContentLabelPref(group, v)}
+ group={group}
+ />
+ )}
)
},
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index d657c92c..563a3ead 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -24,6 +24,7 @@ import {PostEmbeds} from '../util/post-embeds'
import {PostCtrls} from '../util/PostCtrls'
import {PostHider} from '../util/moderation/PostHider'
import {ContentHider} from '../util/moderation/ContentHider'
+import {ImageHider} from '../util/moderation/ImageHider'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette'
import {formatCount} from '../util/numeric/format'
@@ -234,7 +235,9 @@ export const PostThreadItem = observer(function PostThreadItem({
/>
) : undefined}
-
+
+
+
{niceDate(item.post.indexedAt)}
@@ -366,7 +369,9 @@ export const PostThreadItem = observer(function PostThreadItem({
/>
) : undefined}
-
+
+
+
) : undefined}
-
+
+
+
) : undefined}
-
+
+
+
) {
const pal = usePalette('default')
const [override, setOverride] = React.useState(false)
+ const onPressShow = React.useCallback(() => {
+ setOverride(true)
+ }, [setOverride])
+ const onPressHide = React.useCallback(() => {
+ setOverride(false)
+ }, [setOverride])
if (
moderation.behavior === ModerationBehaviorCode.Show ||
@@ -44,7 +44,15 @@ export function ContentHider({
return (
-
{moderation.reason || 'Content warning'}
- setOverride(v => !v)}
- accessibilityLabel={override ? 'Hide post' : 'Show post'}
- // TODO: The text labelling should be split up so controls have unique roles
- accessibilityHint={
- override
- ? 'Re-hide post'
- : 'Shows post hidden based on your moderation settings'
- }>
-
+
+
{override ? 'Hide' : 'Show'}
-
-
+
+
{override && (
diff --git a/src/view/com/util/moderation/ImageHider.tsx b/src/view/com/util/moderation/ImageHider.tsx
new file mode 100644
index 00000000..b42c6397
--- /dev/null
+++ b/src/view/com/util/moderation/ImageHider.tsx
@@ -0,0 +1,128 @@
+import React from 'react'
+import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
+import {usePalette} from 'lib/hooks/usePalette'
+import {Text} from '../text/Text'
+import {BlurView} from '../BlurView'
+import {ModerationBehavior, ModerationBehaviorCode} from 'lib/labeling/types'
+import {isAndroid} from 'platform/detection'
+
+export function ImageHider({
+ testID,
+ moderation,
+ style,
+ containerStyle,
+ children,
+}: React.PropsWithChildren<{
+ testID?: string
+ moderation: ModerationBehavior
+ style?: StyleProp
+ containerStyle?: StyleProp
+}>) {
+ const pal = usePalette('default')
+ const [override, setOverride] = React.useState(false)
+ const onPressShow = React.useCallback(() => {
+ setOverride(true)
+ }, [setOverride])
+ const onPressHide = React.useCallback(() => {
+ setOverride(false)
+ }, [setOverride])
+
+ if (moderation.behavior !== ModerationBehaviorCode.WarnImages) {
+ return (
+
+ {children}
+
+ )
+ }
+
+ if (moderation.behavior === ModerationBehaviorCode.Hide) {
+ return null
+ }
+
+ return (
+
+
+ {children}
+
+ {override ? (
+
+
+ Hide
+
+
+ ) : (
+ <>
+ {isAndroid ? (
+ /* android has an issue that breaks the blurview */
+ /* see https://github.com/Kureev/react-native-blur/issues/486 */
+
+ ) : (
+
+ )}
+
+
+
+ {moderation.reason || 'Content warning'}
+
+
+ Show
+
+
+
+ >
+ )}
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ position: 'relative',
+ marginBottom: 10,
+ },
+ overlay: {
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ },
+ blurView: {
+ borderRadius: 8,
+ },
+ coverView: {
+ borderRadius: 8,
+ },
+ info: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ showBtn: {
+ flexDirection: 'row',
+ gap: 8,
+ paddingHorizontal: 18,
+ paddingVertical: 14,
+ borderRadius: 24,
+ },
+ hideBtn: {
+ position: 'absolute',
+ left: 8,
+ bottom: 20,
+ paddingHorizontal: 8,
+ paddingVertical: 6,
+ borderRadius: 8,
+ },
+})