about summary refs log tree commit diff
path: root/src/view/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com')
-rw-r--r--src/view/com/composer/labels/LabelsBtn.tsx288
-rw-r--r--src/view/com/composer/state/composer.ts5
2 files changed, 174 insertions, 119 deletions
diff --git a/src/view/com/composer/labels/LabelsBtn.tsx b/src/view/com/composer/labels/LabelsBtn.tsx
index d72366aea..a176426dc 100644
--- a/src/view/com/composer/labels/LabelsBtn.tsx
+++ b/src/view/com/composer/labels/LabelsBtn.tsx
@@ -1,44 +1,52 @@
 import React from 'react'
-import {Keyboard, LayoutAnimation, View} from 'react-native'
+import {Keyboard, View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {ShieldExclamation} from '#/lib/icons'
+import {
+  ADULT_CONTENT_LABELS,
+  AdultSelfLabel,
+  OTHER_SELF_LABELS,
+  OtherSelfLabel,
+  SelfLabel,
+} from '#/lib/moderation'
+import {isWeb} from '#/platform/detection'
 import {atoms as a, useTheme} from '#/alf'
 import {Button, ButtonText} from '#/components/Button'
 import * as Dialog from '#/components/Dialog'
-import * as ToggleButton from '#/components/forms/ToggleButton'
+import * as Toggle from '#/components/forms/Toggle'
 import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
 import {Text} from '#/components/Typography'
-
-const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
-
 export function LabelsBtn({
   labels,
   hasMedia,
   onChange,
 }: {
-  labels: string[]
+  labels: SelfLabel[]
   hasMedia: boolean
-  onChange: (v: string[]) => void
+  onChange: (v: SelfLabel[]) => void
 }) {
   const control = Dialog.useDialogControl()
   const t = useTheme()
   const {_} = useLingui()
 
-  const removeAdultLabel = () => {
-    const final = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l))
-    onChange(final)
-    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
+  const hasLabel = labels.length > 0
+
+  const updateAdultLabels = (newLabels: AdultSelfLabel[]) => {
+    const newLabel = newLabels[newLabels.length - 1]
+    const filtered = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l))
+    onChange([...filtered, newLabel].filter(Boolean) as SelfLabel[])
   }
 
-  const hasAdultSelection =
-    labels.includes('sexual') ||
-    labels.includes('nudity') ||
-    labels.includes('porn')
+  const updateOtherLabels = (newLabels: OtherSelfLabel[]) => {
+    const newLabel = newLabels[newLabels.length - 1]
+    const filtered = labels.filter(l => !OTHER_SELF_LABELS.includes(l))
+    onChange([...filtered, newLabel].filter(Boolean) as SelfLabel[])
+  }
 
-  if (!hasMedia && hasAdultSelection) {
-    removeAdultLabel()
+  if (!hasMedia && hasLabel) {
+    onChange([])
   }
 
   return (
@@ -64,10 +72,9 @@ export function LabelsBtn({
         <Dialog.Handle />
         <DialogInner
           labels={labels}
-          onChange={onChange}
-          hasAdultSelection={hasAdultSelection}
           hasMedia={hasMedia}
-          removeAdultLabel={removeAdultLabel}
+          updateAdultLabels={updateAdultLabels}
+          updateOtherLabels={updateOtherLabels}
         />
       </Dialog.Outer>
     </>
@@ -76,16 +83,14 @@ export function LabelsBtn({
 
 function DialogInner({
   labels,
-  onChange,
-  hasAdultSelection,
   hasMedia,
-  removeAdultLabel,
+  updateAdultLabels,
+  updateOtherLabels,
 }: {
   labels: string[]
-  onChange: (v: string[]) => void
-  hasAdultSelection: boolean
   hasMedia: boolean
-  removeAdultLabel: () => void
+  updateAdultLabels: (labels: AdultSelfLabel[]) => void
+  updateOtherLabels: (labels: OtherSelfLabel[]) => void
 }) {
   const {_} = useLingui()
   const control = Dialog.useDialogContext()
@@ -95,104 +100,153 @@ function DialogInner({
     <Dialog.ScrollableInner
       label={_(msg`Add a content warning`)}
       style={[{maxWidth: 500}, a.w_full]}>
-      <View style={[a.flex_1, a.gap_md]}>
-        <Text style={[a.text_2xl, a.font_bold]}>
-          <Trans>Add a content warning</Trans>
-        </Text>
-
-        <View
-          style={[
-            a.border,
-            a.p_md,
-            t.atoms.border_contrast_high,
-            a.rounded_md,
-          ]}>
-          <View
-            style={[a.flex_row, a.align_center, a.justify_between, a.pb_sm]}>
-            <Text style={[a.font_bold, a.text_lg]}>
-              <Trans>Adult Content</Trans>
-            </Text>
+      <View style={[a.flex_1]}>
+        <View style={[a.gap_sm]}>
+          <Text style={[a.text_2xl, a.font_bold]}>
+            <Trans>Add a content warning</Trans>
+          </Text>
+          <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}>
+            {hasMedia ? (
+              <Trans>
+                Choose self-labels that are applicable for the media you are
+                posting. If none are selected, this post is suitable for all
+                audiences.
+              </Trans>
+            ) : (
+              <Trans>
+                There are no self-labels that can be applied to this post.
+              </Trans>
+            )}
+          </Text>
+        </View>
 
-            <Button
-              label={_(msg`Remove`)}
-              variant="ghost"
-              color="primary"
-              size="tiny"
-              onPress={removeAdultLabel}
-              disabled={!hasAdultSelection}
-              style={{opacity: hasAdultSelection ? 1 : 0}}
-              aria-hidden={!hasAdultSelection}>
-              <ButtonText>
-                <Trans>Remove</Trans>
-              </ButtonText>
-            </Button>
-          </View>
+        <View style={[a.my_md, a.gap_lg]}>
           {hasMedia ? (
             <>
-              <ToggleButton.Group
-                label={_(msg`Adult Content labels`)}
-                values={labels}
-                onChange={values => {
-                  onChange(values)
-                  LayoutAnimation.configureNext(
-                    LayoutAnimation.Presets.easeInEaseOut,
-                  )
-                }}>
-                <ToggleButton.Button name="sexual" label={_(msg`Suggestive`)}>
-                  <ToggleButton.ButtonText>
-                    <Trans>Suggestive</Trans>
-                  </ToggleButton.ButtonText>
-                </ToggleButton.Button>
-                <ToggleButton.Button name="nudity" label={_(msg`Nudity`)}>
-                  <ToggleButton.ButtonText>
-                    <Trans>Nudity</Trans>
-                  </ToggleButton.ButtonText>
-                </ToggleButton.Button>
-                <ToggleButton.Button name="porn" label={_(msg`Porn`)}>
-                  <ToggleButton.ButtonText>
-                    <Trans>Porn</Trans>
-                  </ToggleButton.ButtonText>
-                </ToggleButton.Button>
-              </ToggleButton.Group>
-
-              <Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
-                {labels.includes('sexual') ? (
-                  <Trans>Pictures meant for adults.</Trans>
-                ) : labels.includes('nudity') ? (
-                  <Trans>Artistic or non-erotic nudity.</Trans>
-                ) : labels.includes('porn') ? (
-                  <Trans>Sexual activity or erotic nudity.</Trans>
-                ) : (
-                  <Trans>If none are selected, suitable for all ages.</Trans>
-                )}
-              </Text>
+              <View>
+                <View
+                  style={[
+                    a.flex_row,
+                    a.align_center,
+                    a.justify_between,
+                    a.pb_sm,
+                  ]}>
+                  <Text style={[a.font_bold, a.text_lg]}>
+                    <Trans>Adult Content</Trans>
+                  </Text>
+                </View>
+                <View
+                  style={[
+                    a.p_md,
+                    a.rounded_sm,
+                    a.border,
+                    t.atoms.border_contrast_medium,
+                  ]}>
+                  <Toggle.Group
+                    label={_(msg`Adult Content labels`)}
+                    values={labels}
+                    onChange={values => {
+                      updateAdultLabels(values as AdultSelfLabel[])
+                    }}>
+                    <View style={[a.gap_sm]}>
+                      <Toggle.Item name="sexual" label={_(msg`Suggestive`)}>
+                        <Toggle.Radio />
+                        <Toggle.LabelText>
+                          <Trans>Suggestive</Trans>
+                        </Toggle.LabelText>
+                      </Toggle.Item>
+                      <Toggle.Item name="nudity" label={_(msg`Nudity`)}>
+                        <Toggle.Radio />
+                        <Toggle.LabelText>
+                          <Trans>Nudity</Trans>
+                        </Toggle.LabelText>
+                      </Toggle.Item>
+                      <Toggle.Item name="porn" label={_(msg`Porn`)}>
+                        <Toggle.Radio />
+                        <Toggle.LabelText>
+                          <Trans>Porn</Trans>
+                        </Toggle.LabelText>
+                      </Toggle.Item>
+                    </View>
+                  </Toggle.Group>
+                  <Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
+                    {labels.includes('sexual') ? (
+                      <Trans>Pictures meant for adults.</Trans>
+                    ) : labels.includes('nudity') ? (
+                      <Trans>Artistic or non-erotic nudity.</Trans>
+                    ) : labels.includes('porn') ? (
+                      <Trans>Sexual activity or erotic nudity.</Trans>
+                    ) : (
+                      <Trans>Does not contain adult content.</Trans>
+                    )}
+                  </Text>
+                </View>
+              </View>
+              <View>
+                <View
+                  style={[
+                    a.flex_row,
+                    a.align_center,
+                    a.justify_between,
+                    a.pb_sm,
+                  ]}>
+                  <Text style={[a.font_bold, a.text_lg]}>
+                    <Trans>Other</Trans>
+                  </Text>
+                </View>
+                <View
+                  style={[
+                    a.p_md,
+                    a.rounded_sm,
+                    a.border,
+                    t.atoms.border_contrast_medium,
+                  ]}>
+                  <Toggle.Group
+                    label={_(msg`Adult Content labels`)}
+                    values={labels}
+                    onChange={values => {
+                      updateOtherLabels(values as OtherSelfLabel[])
+                    }}>
+                    <Toggle.Item
+                      name="graphic-media"
+                      label={_(msg`Graphic Media`)}>
+                      <Toggle.Checkbox />
+                      <Toggle.LabelText>
+                        <Trans>Graphic Media</Trans>
+                      </Toggle.LabelText>
+                    </Toggle.Item>
+                  </Toggle.Group>
+                  <Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
+                    {labels.includes('graphic-media') ? (
+                      <Trans>
+                        Media that may be disturbing or inappropriate for some
+                        audiences.
+                      </Trans>
+                    ) : (
+                      <Trans>
+                        Does not contain graphic or disturbing content.
+                      </Trans>
+                    )}
+                  </Text>
+                </View>
+              </View>
             </>
-          ) : (
-            <View>
-              <Text style={t.atoms.text_contrast_medium}>
-                <Trans>
-                  <Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
-                    Not Applicable.
-                  </Text>{' '}
-                  This warning is only available for posts with media attached.
-                </Trans>
-              </Text>
-            </View>
-          )}
+          ) : null}
         </View>
       </View>
 
-      <Button
-        label={_(msg`Done`)}
-        onPress={() => control.close()}
-        color="primary"
-        size="large"
-        variant="solid"
-        style={a.mt_xl}>
-        <ButtonText>
-          <Trans>Done</Trans>
-        </ButtonText>
-      </Button>
+      <View style={[a.mt_sm]}>
+        <Button
+          label={_(msg`Done`)}
+          onPress={() => control.close()}
+          color="primary"
+          size={isWeb ? 'small' : 'large'}
+          variant="solid">
+          <ButtonText>
+            <Trans>Done</Trans>
+          </ButtonText>
+        </Button>
+      </View>
     </Dialog.ScrollableInner>
   )
 }
diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts
index e37690342..049488f3a 100644
--- a/src/view/com/composer/state/composer.ts
+++ b/src/view/com/composer/state/composer.ts
@@ -1,6 +1,7 @@
 import {ImagePickerAsset} from 'expo-image-picker'
 import {AppBskyFeedPostgate, RichText} from '@atproto/api'
 
+import {SelfLabel} from '#/lib/moderation'
 import {insertMentionAt} from '#/lib/strings/mention-manip'
 import {
   isBskyPostUrl,
@@ -48,7 +49,7 @@ export type EmbedDraft = {
 
 export type ComposerDraft = {
   richtext: RichText
-  labels: string[]
+  labels: SelfLabel[]
   postgate: AppBskyFeedPostgate.Record
   threadgate: ThreadgateAllowUISetting[]
   embed: EmbedDraft
@@ -56,7 +57,7 @@ export type ComposerDraft = {
 
 export type ComposerAction =
   | {type: 'update_richtext'; richtext: RichText}
-  | {type: 'update_labels'; labels: string[]}
+  | {type: 'update_labels'; labels: SelfLabel[]}
   | {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
   | {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
   | {type: 'embed_add_images'; images: ComposerImage[]}