Composer - Self label dialog ALF rewrite (#4354)

This commit is contained in:
Samuel Newman
2024-10-16 18:52:14 +03:00
committed by GitHub
parent c3d0cc55d9
commit 6bc00f8d71
9 changed files with 182 additions and 278 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ export function GifSelectDialog({
onSelectGif: onSelectGifProp, onSelectGif: onSelectGifProp,
}: { }: {
controlRef: React.RefObject<{open: () => void}> controlRef: React.RefObject<{open: () => void}>
onClose: () => void onClose?: () => void
onSelectGif: (gif: Gif) => void onSelectGif: (gif: Gif) => void
}) { }) {
const control = Dialog.useDialogControl() const control = Dialog.useDialogControl()
+1 -1
View File
@@ -36,7 +36,7 @@ export function Group({children, multiple, ...props}: GroupProps) {
export function Button({children, ...props}: ItemProps) { export function Button({children, ...props}: ItemProps) {
return ( return (
<Toggle.Item {...props} style={[a.flex_grow]}> <Toggle.Item {...props} style={[a.flex_grow, a.flex_1]}>
<ButtonInner>{children}</ButtonInner> <ButtonInner>{children}</ButtonInner>
</Toggle.Item> </Toggle.Item>
) )
-8
View File
@@ -42,13 +42,6 @@ export interface DeleteAccountModal {
name: 'delete-account' name: 'delete-account'
} }
export interface SelfLabelModal {
name: 'self-label'
labels: string[]
hasMedia: boolean
onChange: (labels: string[]) => void
}
export interface ChangeHandleModal { export interface ChangeHandleModal {
name: 'change-handle' name: 'change-handle'
onChanged: () => void onChanged: () => void
@@ -120,7 +113,6 @@ export type Modal =
// Posts // Posts
| CropImageModal | CropImageModal
| SelfLabelModal
// Bluesky access // Bluesky access
| WaitlistModal | WaitlistModal
+1 -9
View File
@@ -499,10 +499,6 @@ export const ComposePost = ({
openEmojiPicker?.(textInput.current?.getCursorPosition()) openEmojiPicker?.(textInput.current?.getCursorPosition())
}, [openEmojiPicker]) }, [openEmojiPicker])
const focusTextInput = useCallback(() => {
textInput.current?.focus()
}, [])
const onSelectGif = useCallback((gif: Gif) => { const onSelectGif = useCallback((gif: Gif) => {
dispatch({type: 'embed_add_gif', gif}) dispatch({type: 'embed_add_gif', gif})
}, []) }, [])
@@ -808,11 +804,7 @@ export const ComposePost = ({
disabled={!canSelectImages} disabled={!canSelectImages}
onAdd={onImageAdd} onAdd={onImageAdd}
/> />
<SelectGifBtn <SelectGifBtn onSelectGif={onSelectGif} disabled={hasMedia} />
onClose={focusTextInput}
onSelectGif={onSelectGif}
disabled={hasMedia}
/>
{!isMobile ? ( {!isMobile ? (
<Button <Button
onPress={onEmojiButtonPress} onPress={onEmojiButtonPress}
+178 -47
View File
@@ -1,15 +1,17 @@
import React from 'react' import React from 'react'
import {Keyboard, StyleSheet} from 'react-native' import {Keyboard, LayoutAnimation, View} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro'
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {ShieldExclamation} from '#/lib/icons'
import {usePalette} from 'lib/hooks/usePalette' import {atoms as a, useTheme} from '#/alf'
import {ShieldExclamation} from 'lib/icons' import {Button, ButtonText} from '#/components/Button'
import {isNative} from 'platform/detection' import * as Dialog from '#/components/Dialog'
import {Button} from 'view/com/util/forms/Button' import * as ToggleButton from '#/components/forms/ToggleButton'
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({ export function LabelsBtn({
labels, labels,
@@ -20,48 +22,177 @@ export function LabelsBtn({
hasMedia: boolean hasMedia: boolean
onChange: (v: string[]) => void onChange: (v: string[]) => void
}) { }) {
const pal = usePalette('default') const control = Dialog.useDialogControl()
const t = useTheme()
const {_} = useLingui() const {_} = useLingui()
const {openModal} = useModalControls()
const removeAdultLabel = () => {
const final = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l))
onChange(final)
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
const hasAdultSelection =
labels.includes('sexual') ||
labels.includes('nudity') ||
labels.includes('porn')
if (!hasMedia && hasAdultSelection) {
removeAdultLabel()
}
return ( return (
<Button <>
type="default-light" <Button
testID="labelsBtn" testID="labelsBtn"
style={[styles.button, !hasMedia && styles.dimmed]} style={!hasMedia && {opacity: 0.4}}
accessibilityLabel={_(msg`Content warnings`)} label={_(msg`Content warnings`)}
accessibilityHint="" accessibilityHint={_(
onPress={() => { msg`Opens a dialog to add a content warning to your post`,
if (isNative) { )}
if (Keyboard.isVisible()) { onPress={() => {
Keyboard.dismiss() Keyboard.dismiss()
} control.open()
} }}>
openModal({name: 'self-label', labels, hasMedia, onChange}) <ShieldExclamation style={{color: t.palette.primary_500}} size={24} />
}}> {labels.length > 0 ? (
<ShieldExclamation style={pal.link} size={24} /> <Check size="sm" fill={t.palette.primary_500} />
{labels.length > 0 ? ( ) : null}
<FontAwesomeIcon </Button>
icon="check"
size={16} <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
style={pal.link as FontAwesomeIconStyle} <Dialog.Handle />
<DialogInner
labels={labels}
onChange={onChange}
hasAdultSelection={hasAdultSelection}
hasMedia={hasMedia}
removeAdultLabel={removeAdultLabel}
/> />
) : null} </Dialog.Outer>
</Button> </>
) )
} }
const styles = StyleSheet.create({ function DialogInner({
button: { labels,
flexDirection: 'row', onChange,
alignItems: 'center', hasAdultSelection,
paddingVertical: 2, hasMedia,
paddingHorizontal: 6, removeAdultLabel,
}, }: {
dimmed: { labels: string[]
opacity: 0.4, onChange: (v: string[]) => void
}, hasAdultSelection: boolean
label: { hasMedia: boolean
maxWidth: 100, removeAdultLabel: () => void
}, }) {
}) const {_} = useLingui()
const control = Dialog.useDialogContext()
const t = useTheme()
return (
<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>
<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>
{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>
<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>
)}
</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>
</Dialog.ScrollableInner>
)
}
@@ -11,7 +11,7 @@ import {GifSelectDialog} from '#/components/dialogs/GifSelect'
import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif' import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
type Props = { type Props = {
onClose: () => void onClose?: () => void
onSelectGif: (gif: Gif) => void onSelectGif: (gif: Gif) => void
disabled?: boolean disabled?: boolean
} }
-4
View File
@@ -19,7 +19,6 @@ import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguages
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
import * as LinkWarningModal from './LinkWarning' import * as LinkWarningModal from './LinkWarning'
import * as ListAddUserModal from './ListAddRemoveUsers' import * as ListAddUserModal from './ListAddRemoveUsers'
import * as SelfLabelModal from './SelfLabel'
import * as UserAddRemoveListsModal from './UserAddRemoveLists' import * as UserAddRemoveListsModal from './UserAddRemoveLists'
import * as VerifyEmailModal from './VerifyEmail' import * as VerifyEmailModal from './VerifyEmail'
@@ -66,9 +65,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'delete-account') { } else if (activeModal?.name === 'delete-account') {
snapPoints = DeleteAccountModal.snapPoints snapPoints = DeleteAccountModal.snapPoints
element = <DeleteAccountModal.Component /> element = <DeleteAccountModal.Component />
} else if (activeModal?.name === 'self-label') {
snapPoints = SelfLabelModal.snapPoints
element = <SelfLabelModal.Component {...activeModal} />
} else if (activeModal?.name === 'change-handle') { } else if (activeModal?.name === 'change-handle') {
snapPoints = ChangeHandleModal.snapPoints snapPoints = ChangeHandleModal.snapPoints
element = <ChangeHandleModal.Component {...activeModal} /> element = <ChangeHandleModal.Component {...activeModal} />
-3
View File
@@ -19,7 +19,6 @@ import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguages
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
import * as LinkWarningModal from './LinkWarning' import * as LinkWarningModal from './LinkWarning'
import * as ListAddUserModal from './ListAddRemoveUsers' import * as ListAddUserModal from './ListAddRemoveUsers'
import * as SelfLabelModal from './SelfLabel'
import * as UserAddRemoveLists from './UserAddRemoveLists' import * as UserAddRemoveLists from './UserAddRemoveLists'
import * as VerifyEmailModal from './VerifyEmail' import * as VerifyEmailModal from './VerifyEmail'
@@ -72,8 +71,6 @@ function Modal({modal}: {modal: ModalIface}) {
element = <CropImageModal.Component {...modal} /> element = <CropImageModal.Component {...modal} />
} else if (modal.name === 'delete-account') { } else if (modal.name === 'delete-account') {
element = <DeleteAccountModal.Component /> element = <DeleteAccountModal.Component />
} else if (modal.name === 'self-label') {
element = <SelfLabelModal.Component {...modal} />
} else if (modal.name === 'change-handle') { } else if (modal.name === 'change-handle') {
element = <ChangeHandleModal.Component {...modal} /> element = <ChangeHandleModal.Component {...modal} />
} else if (modal.name === 'invite-codes') { } else if (modal.name === 'invite-codes') {
-204
View File
@@ -1,204 +0,0 @@
import React, {useState} from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {colors, s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {ScrollView} from 'view/com/modals/util'
import {Button} from '../util/forms/Button'
import {SelectableBtn} from '../util/forms/SelectableBtn'
import {Text} from '../util/text/Text'
const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
export const snapPoints = ['50%']
export function Component({
labels,
hasMedia,
onChange,
}: {
labels: string[]
hasMedia: boolean
onChange: (labels: string[]) => void
}) {
const pal = usePalette('default')
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const [selected, setSelected] = useState(labels)
const {_} = useLingui()
const toggleAdultLabel = (label: string) => {
const hadLabel = selected.includes(label)
const stripped = selected.filter(l => !ADULT_CONTENT_LABELS.includes(l))
const final = !hadLabel ? stripped.concat([label]) : stripped
setSelected(final)
onChange(final)
}
const removeAdultLabel = () => {
const final = selected.filter(l => !ADULT_CONTENT_LABELS.includes(l))
setSelected(final)
onChange(final)
}
const hasAdultSelection =
selected.includes('sexual') ||
selected.includes('nudity') ||
selected.includes('porn')
return (
<View testID="selfLabelModal" style={[pal.view, styles.container]}>
<View style={styles.titleSection}>
<Text type="title-lg" style={[pal.text, styles.title]}>
<Trans>Add a content warning</Trans>
</Text>
</View>
<ScrollView>
<View
style={[
styles.section,
pal.border,
{borderBottomWidth: 1, paddingHorizontal: isMobile ? 20 : 0},
]}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingBottom: 8,
}}>
<Text type="title" style={pal.text}>
<Trans>Adult Content</Trans>
</Text>
{hasAdultSelection ? (
<Button
type="default-light"
onPress={removeAdultLabel}
style={{paddingTop: 0, paddingBottom: 0, paddingRight: 0}}>
<Text type="md" style={pal.link}>
<Trans>Remove</Trans>
</Text>
</Button>
) : null}
</View>
{hasMedia ? (
<>
<View style={s.flexRow}>
<SelectableBtn
testID="sexualLabelBtn"
selected={selected.includes('sexual')}
left
label={_(msg`Suggestive`)}
onSelect={() => toggleAdultLabel('sexual')}
accessibilityHint=""
style={s.flex1}
/>
<SelectableBtn
testID="nudityLabelBtn"
selected={selected.includes('nudity')}
label={_(msg`Nudity`)}
onSelect={() => toggleAdultLabel('nudity')}
accessibilityHint=""
style={s.flex1}
/>
<SelectableBtn
testID="pornLabelBtn"
selected={selected.includes('porn')}
label={_(msg`Porn`)}
right
onSelect={() => toggleAdultLabel('porn')}
accessibilityHint=""
style={s.flex1}
/>
</View>
<Text style={[pal.text, styles.adultExplainer]}>
{selected.includes('sexual') ? (
<Trans>Pictures meant for adults.</Trans>
) : selected.includes('nudity') ? (
<Trans>Artistic or non-erotic nudity.</Trans>
) : selected.includes('porn') ? (
<Trans>Sexual activity or erotic nudity.</Trans>
) : (
<Trans>If none are selected, suitable for all ages.</Trans>
)}
</Text>
</>
) : (
<View>
<Text style={[pal.textLight]}>
<Trans>
<Text type="md-bold" style={[pal.textLight]}>
Not Applicable.
</Text>{' '}
This warning is only available for posts with media attached.
</Trans>
</Text>
</View>
)}
</View>
</ScrollView>
<View style={[styles.btnContainer, pal.borderDark]}>
<TouchableOpacity
testID="confirmBtn"
onPress={() => {
closeModal()
}}
style={styles.btn}
accessibilityRole="button"
accessibilityLabel={_(msg`Confirm`)}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>
<Trans context="action">Done</Trans>
</Text>
</TouchableOpacity>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isWeb ? 0 : 40,
},
titleSection: {
paddingTop: isWeb ? 0 : 4,
paddingBottom: isWeb ? 14 : 10,
},
title: {
textAlign: 'center',
fontWeight: '600',
marginBottom: 5,
},
description: {
textAlign: 'center',
paddingHorizontal: 32,
},
section: {
borderTopWidth: 1,
paddingVertical: 20,
},
adultExplainer: {
paddingLeft: 5,
paddingTop: 10,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 32,
padding: 14,
backgroundColor: colors.blue3,
},
btnContainer: {
paddingTop: 20,
paddingHorizontal: 20,
},
})