Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Composer - Self label dialog ALF rewrite (#4354)

authored by

Samuel Newman and committed by
GitHub
6bc00f8d c3d0cc55

+182 -278
+1 -1
src/components/dialogs/GifSelect.tsx
··· 37 37 onSelectGif: onSelectGifProp, 38 38 }: { 39 39 controlRef: React.RefObject<{open: () => void}> 40 - onClose: () => void 40 + onClose?: () => void 41 41 onSelectGif: (gif: Gif) => void 42 42 }) { 43 43 const control = Dialog.useDialogControl()
+1 -1
src/components/forms/ToggleButton.tsx
··· 36 36 37 37 export function Button({children, ...props}: ItemProps) { 38 38 return ( 39 - <Toggle.Item {...props} style={[a.flex_grow]}> 39 + <Toggle.Item {...props} style={[a.flex_grow, a.flex_1]}> 40 40 <ButtonInner>{children}</ButtonInner> 41 41 </Toggle.Item> 42 42 )
-8
src/state/modals/index.tsx
··· 42 42 name: 'delete-account' 43 43 } 44 44 45 - export interface SelfLabelModal { 46 - name: 'self-label' 47 - labels: string[] 48 - hasMedia: boolean 49 - onChange: (labels: string[]) => void 50 - } 51 - 52 45 export interface ChangeHandleModal { 53 46 name: 'change-handle' 54 47 onChanged: () => void ··· 120 113 121 114 // Posts 122 115 | CropImageModal 123 - | SelfLabelModal 124 116 125 117 // Bluesky access 126 118 | WaitlistModal
+1 -9
src/view/com/composer/Composer.tsx
··· 499 499 openEmojiPicker?.(textInput.current?.getCursorPosition()) 500 500 }, [openEmojiPicker]) 501 501 502 - const focusTextInput = useCallback(() => { 503 - textInput.current?.focus() 504 - }, []) 505 - 506 502 const onSelectGif = useCallback((gif: Gif) => { 507 503 dispatch({type: 'embed_add_gif', gif}) 508 504 }, []) ··· 808 804 disabled={!canSelectImages} 809 805 onAdd={onImageAdd} 810 806 /> 811 - <SelectGifBtn 812 - onClose={focusTextInput} 813 - onSelectGif={onSelectGif} 814 - disabled={hasMedia} 815 - /> 807 + <SelectGifBtn onSelectGif={onSelectGif} disabled={hasMedia} /> 816 808 {!isMobile ? ( 817 809 <Button 818 810 onPress={onEmojiButtonPress}
+178 -47
src/view/com/composer/labels/LabelsBtn.tsx
··· 1 1 import React from 'react' 2 - import {Keyboard, StyleSheet} from 'react-native' 3 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 - import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' 5 - import {msg} from '@lingui/macro' 2 + import {Keyboard, LayoutAnimation, View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 6 4 import {useLingui} from '@lingui/react' 7 5 8 - import {useModalControls} from '#/state/modals' 9 - import {usePalette} from 'lib/hooks/usePalette' 10 - import {ShieldExclamation} from 'lib/icons' 11 - import {isNative} from 'platform/detection' 12 - import {Button} from 'view/com/util/forms/Button' 6 + import {ShieldExclamation} from '#/lib/icons' 7 + import {atoms as a, useTheme} from '#/alf' 8 + import {Button, ButtonText} from '#/components/Button' 9 + import * as Dialog from '#/components/Dialog' 10 + import * as ToggleButton from '#/components/forms/ToggleButton' 11 + import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 12 + import {Text} from '#/components/Typography' 13 + 14 + const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] 13 15 14 16 export function LabelsBtn({ 15 17 labels, ··· 20 22 hasMedia: boolean 21 23 onChange: (v: string[]) => void 22 24 }) { 23 - const pal = usePalette('default') 25 + const control = Dialog.useDialogControl() 26 + const t = useTheme() 24 27 const {_} = useLingui() 25 - const {openModal} = useModalControls() 28 + 29 + const removeAdultLabel = () => { 30 + const final = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l)) 31 + onChange(final) 32 + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) 33 + } 34 + 35 + const hasAdultSelection = 36 + labels.includes('sexual') || 37 + labels.includes('nudity') || 38 + labels.includes('porn') 39 + 40 + if (!hasMedia && hasAdultSelection) { 41 + removeAdultLabel() 42 + } 26 43 27 44 return ( 28 - <Button 29 - type="default-light" 30 - testID="labelsBtn" 31 - style={[styles.button, !hasMedia && styles.dimmed]} 32 - accessibilityLabel={_(msg`Content warnings`)} 33 - accessibilityHint="" 34 - onPress={() => { 35 - if (isNative) { 36 - if (Keyboard.isVisible()) { 37 - Keyboard.dismiss() 38 - } 39 - } 40 - openModal({name: 'self-label', labels, hasMedia, onChange}) 41 - }}> 42 - <ShieldExclamation style={pal.link} size={24} /> 43 - {labels.length > 0 ? ( 44 - <FontAwesomeIcon 45 - icon="check" 46 - size={16} 47 - style={pal.link as FontAwesomeIconStyle} 45 + <> 46 + <Button 47 + testID="labelsBtn" 48 + style={!hasMedia && {opacity: 0.4}} 49 + label={_(msg`Content warnings`)} 50 + accessibilityHint={_( 51 + msg`Opens a dialog to add a content warning to your post`, 52 + )} 53 + onPress={() => { 54 + Keyboard.dismiss() 55 + control.open() 56 + }}> 57 + <ShieldExclamation style={{color: t.palette.primary_500}} size={24} /> 58 + {labels.length > 0 ? ( 59 + <Check size="sm" fill={t.palette.primary_500} /> 60 + ) : null} 61 + </Button> 62 + 63 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 64 + <Dialog.Handle /> 65 + <DialogInner 66 + labels={labels} 67 + onChange={onChange} 68 + hasAdultSelection={hasAdultSelection} 69 + hasMedia={hasMedia} 70 + removeAdultLabel={removeAdultLabel} 48 71 /> 49 - ) : null} 50 - </Button> 72 + </Dialog.Outer> 73 + </> 51 74 ) 52 75 } 53 76 54 - const styles = StyleSheet.create({ 55 - button: { 56 - flexDirection: 'row', 57 - alignItems: 'center', 58 - paddingVertical: 2, 59 - paddingHorizontal: 6, 60 - }, 61 - dimmed: { 62 - opacity: 0.4, 63 - }, 64 - label: { 65 - maxWidth: 100, 66 - }, 67 - }) 77 + function DialogInner({ 78 + labels, 79 + onChange, 80 + hasAdultSelection, 81 + hasMedia, 82 + removeAdultLabel, 83 + }: { 84 + labels: string[] 85 + onChange: (v: string[]) => void 86 + hasAdultSelection: boolean 87 + hasMedia: boolean 88 + removeAdultLabel: () => void 89 + }) { 90 + const {_} = useLingui() 91 + const control = Dialog.useDialogContext() 92 + const t = useTheme() 93 + 94 + return ( 95 + <Dialog.ScrollableInner 96 + label={_(msg`Add a content warning`)} 97 + style={[{maxWidth: 500}, a.w_full]}> 98 + <View style={[a.flex_1, a.gap_md]}> 99 + <Text style={[a.text_2xl, a.font_bold]}> 100 + <Trans>Add a content warning</Trans> 101 + </Text> 102 + 103 + <View 104 + style={[ 105 + a.border, 106 + a.p_md, 107 + t.atoms.border_contrast_high, 108 + a.rounded_md, 109 + ]}> 110 + <View 111 + style={[a.flex_row, a.align_center, a.justify_between, a.pb_sm]}> 112 + <Text style={[a.font_bold, a.text_lg]}> 113 + <Trans>Adult Content</Trans> 114 + </Text> 115 + 116 + <Button 117 + label={_(msg`Remove`)} 118 + variant="ghost" 119 + color="primary" 120 + size="tiny" 121 + onPress={removeAdultLabel} 122 + disabled={!hasAdultSelection} 123 + style={{opacity: hasAdultSelection ? 1 : 0}} 124 + aria-hidden={!hasAdultSelection}> 125 + <ButtonText> 126 + <Trans>Remove</Trans> 127 + </ButtonText> 128 + </Button> 129 + </View> 130 + {hasMedia ? ( 131 + <> 132 + <ToggleButton.Group 133 + label={_(msg`Adult Content labels`)} 134 + values={labels} 135 + onChange={values => { 136 + onChange(values) 137 + LayoutAnimation.configureNext( 138 + LayoutAnimation.Presets.easeInEaseOut, 139 + ) 140 + }}> 141 + <ToggleButton.Button name="sexual" label={_(msg`Suggestive`)}> 142 + <ToggleButton.ButtonText> 143 + <Trans>Suggestive</Trans> 144 + </ToggleButton.ButtonText> 145 + </ToggleButton.Button> 146 + <ToggleButton.Button name="nudity" label={_(msg`Nudity`)}> 147 + <ToggleButton.ButtonText> 148 + <Trans>Nudity</Trans> 149 + </ToggleButton.ButtonText> 150 + </ToggleButton.Button> 151 + <ToggleButton.Button name="porn" label={_(msg`Porn`)}> 152 + <ToggleButton.ButtonText> 153 + <Trans>Porn</Trans> 154 + </ToggleButton.ButtonText> 155 + </ToggleButton.Button> 156 + </ToggleButton.Group> 157 + 158 + <Text style={[a.mt_sm, t.atoms.text_contrast_medium]}> 159 + {labels.includes('sexual') ? ( 160 + <Trans>Pictures meant for adults.</Trans> 161 + ) : labels.includes('nudity') ? ( 162 + <Trans>Artistic or non-erotic nudity.</Trans> 163 + ) : labels.includes('porn') ? ( 164 + <Trans>Sexual activity or erotic nudity.</Trans> 165 + ) : ( 166 + <Trans>If none are selected, suitable for all ages.</Trans> 167 + )} 168 + </Text> 169 + </> 170 + ) : ( 171 + <View> 172 + <Text style={t.atoms.text_contrast_medium}> 173 + <Trans> 174 + <Text style={[a.font_bold, t.atoms.text_contrast_medium]}> 175 + Not Applicable. 176 + </Text>{' '} 177 + This warning is only available for posts with media attached. 178 + </Trans> 179 + </Text> 180 + </View> 181 + )} 182 + </View> 183 + </View> 184 + 185 + <Button 186 + label={_(msg`Done`)} 187 + onPress={() => control.close()} 188 + color="primary" 189 + size="large" 190 + variant="solid" 191 + style={a.mt_xl}> 192 + <ButtonText> 193 + <Trans>Done</Trans> 194 + </ButtonText> 195 + </Button> 196 + </Dialog.ScrollableInner> 197 + ) 198 + }
+1 -1
src/view/com/composer/photos/SelectGifBtn.tsx
··· 11 11 import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif' 12 12 13 13 type Props = { 14 - onClose: () => void 14 + onClose?: () => void 15 15 onSelectGif: (gif: Gif) => void 16 16 disabled?: boolean 17 17 }
-4
src/view/com/modals/Modal.tsx
··· 19 19 import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' 20 20 import * as LinkWarningModal from './LinkWarning' 21 21 import * as ListAddUserModal from './ListAddRemoveUsers' 22 - import * as SelfLabelModal from './SelfLabel' 23 22 import * as UserAddRemoveListsModal from './UserAddRemoveLists' 24 23 import * as VerifyEmailModal from './VerifyEmail' 25 24 ··· 66 65 } else if (activeModal?.name === 'delete-account') { 67 66 snapPoints = DeleteAccountModal.snapPoints 68 67 element = <DeleteAccountModal.Component /> 69 - } else if (activeModal?.name === 'self-label') { 70 - snapPoints = SelfLabelModal.snapPoints 71 - element = <SelfLabelModal.Component {...activeModal} /> 72 68 } else if (activeModal?.name === 'change-handle') { 73 69 snapPoints = ChangeHandleModal.snapPoints 74 70 element = <ChangeHandleModal.Component {...activeModal} />
-3
src/view/com/modals/Modal.web.tsx
··· 19 19 import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' 20 20 import * as LinkWarningModal from './LinkWarning' 21 21 import * as ListAddUserModal from './ListAddRemoveUsers' 22 - import * as SelfLabelModal from './SelfLabel' 23 22 import * as UserAddRemoveLists from './UserAddRemoveLists' 24 23 import * as VerifyEmailModal from './VerifyEmail' 25 24 ··· 72 71 element = <CropImageModal.Component {...modal} /> 73 72 } else if (modal.name === 'delete-account') { 74 73 element = <DeleteAccountModal.Component /> 75 - } else if (modal.name === 'self-label') { 76 - element = <SelfLabelModal.Component {...modal} /> 77 74 } else if (modal.name === 'change-handle') { 78 75 element = <ChangeHandleModal.Component {...modal} /> 79 76 } else if (modal.name === 'invite-codes') {
-204
src/view/com/modals/SelfLabel.tsx
··· 1 - import React, {useState} from 'react' 2 - import {StyleSheet, TouchableOpacity, View} from 'react-native' 3 - import {msg, Trans} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 5 - 6 - import {useModalControls} from '#/state/modals' 7 - import {usePalette} from 'lib/hooks/usePalette' 8 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 9 - import {colors, s} from 'lib/styles' 10 - import {isWeb} from 'platform/detection' 11 - import {ScrollView} from 'view/com/modals/util' 12 - import {Button} from '../util/forms/Button' 13 - import {SelectableBtn} from '../util/forms/SelectableBtn' 14 - import {Text} from '../util/text/Text' 15 - 16 - const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] 17 - 18 - export const snapPoints = ['50%'] 19 - 20 - export function Component({ 21 - labels, 22 - hasMedia, 23 - onChange, 24 - }: { 25 - labels: string[] 26 - hasMedia: boolean 27 - onChange: (labels: string[]) => void 28 - }) { 29 - const pal = usePalette('default') 30 - const {closeModal} = useModalControls() 31 - const {isMobile} = useWebMediaQueries() 32 - const [selected, setSelected] = useState(labels) 33 - const {_} = useLingui() 34 - 35 - const toggleAdultLabel = (label: string) => { 36 - const hadLabel = selected.includes(label) 37 - const stripped = selected.filter(l => !ADULT_CONTENT_LABELS.includes(l)) 38 - const final = !hadLabel ? stripped.concat([label]) : stripped 39 - setSelected(final) 40 - onChange(final) 41 - } 42 - 43 - const removeAdultLabel = () => { 44 - const final = selected.filter(l => !ADULT_CONTENT_LABELS.includes(l)) 45 - setSelected(final) 46 - onChange(final) 47 - } 48 - 49 - const hasAdultSelection = 50 - selected.includes('sexual') || 51 - selected.includes('nudity') || 52 - selected.includes('porn') 53 - return ( 54 - <View testID="selfLabelModal" style={[pal.view, styles.container]}> 55 - <View style={styles.titleSection}> 56 - <Text type="title-lg" style={[pal.text, styles.title]}> 57 - <Trans>Add a content warning</Trans> 58 - </Text> 59 - </View> 60 - 61 - <ScrollView> 62 - <View 63 - style={[ 64 - styles.section, 65 - pal.border, 66 - {borderBottomWidth: 1, paddingHorizontal: isMobile ? 20 : 0}, 67 - ]}> 68 - <View 69 - style={{ 70 - flexDirection: 'row', 71 - alignItems: 'center', 72 - justifyContent: 'space-between', 73 - paddingBottom: 8, 74 - }}> 75 - <Text type="title" style={pal.text}> 76 - <Trans>Adult Content</Trans> 77 - </Text> 78 - {hasAdultSelection ? ( 79 - <Button 80 - type="default-light" 81 - onPress={removeAdultLabel} 82 - style={{paddingTop: 0, paddingBottom: 0, paddingRight: 0}}> 83 - <Text type="md" style={pal.link}> 84 - <Trans>Remove</Trans> 85 - </Text> 86 - </Button> 87 - ) : null} 88 - </View> 89 - {hasMedia ? ( 90 - <> 91 - <View style={s.flexRow}> 92 - <SelectableBtn 93 - testID="sexualLabelBtn" 94 - selected={selected.includes('sexual')} 95 - left 96 - label={_(msg`Suggestive`)} 97 - onSelect={() => toggleAdultLabel('sexual')} 98 - accessibilityHint="" 99 - style={s.flex1} 100 - /> 101 - <SelectableBtn 102 - testID="nudityLabelBtn" 103 - selected={selected.includes('nudity')} 104 - label={_(msg`Nudity`)} 105 - onSelect={() => toggleAdultLabel('nudity')} 106 - accessibilityHint="" 107 - style={s.flex1} 108 - /> 109 - <SelectableBtn 110 - testID="pornLabelBtn" 111 - selected={selected.includes('porn')} 112 - label={_(msg`Porn`)} 113 - right 114 - onSelect={() => toggleAdultLabel('porn')} 115 - accessibilityHint="" 116 - style={s.flex1} 117 - /> 118 - </View> 119 - 120 - <Text style={[pal.text, styles.adultExplainer]}> 121 - {selected.includes('sexual') ? ( 122 - <Trans>Pictures meant for adults.</Trans> 123 - ) : selected.includes('nudity') ? ( 124 - <Trans>Artistic or non-erotic nudity.</Trans> 125 - ) : selected.includes('porn') ? ( 126 - <Trans>Sexual activity or erotic nudity.</Trans> 127 - ) : ( 128 - <Trans>If none are selected, suitable for all ages.</Trans> 129 - )} 130 - </Text> 131 - </> 132 - ) : ( 133 - <View> 134 - <Text style={[pal.textLight]}> 135 - <Trans> 136 - <Text type="md-bold" style={[pal.textLight]}> 137 - Not Applicable. 138 - </Text>{' '} 139 - This warning is only available for posts with media attached. 140 - </Trans> 141 - </Text> 142 - </View> 143 - )} 144 - </View> 145 - </ScrollView> 146 - 147 - <View style={[styles.btnContainer, pal.borderDark]}> 148 - <TouchableOpacity 149 - testID="confirmBtn" 150 - onPress={() => { 151 - closeModal() 152 - }} 153 - style={styles.btn} 154 - accessibilityRole="button" 155 - accessibilityLabel={_(msg`Confirm`)} 156 - accessibilityHint=""> 157 - <Text style={[s.white, s.bold, s.f18]}> 158 - <Trans context="action">Done</Trans> 159 - </Text> 160 - </TouchableOpacity> 161 - </View> 162 - </View> 163 - ) 164 - } 165 - 166 - const styles = StyleSheet.create({ 167 - container: { 168 - flex: 1, 169 - paddingBottom: isWeb ? 0 : 40, 170 - }, 171 - titleSection: { 172 - paddingTop: isWeb ? 0 : 4, 173 - paddingBottom: isWeb ? 14 : 10, 174 - }, 175 - title: { 176 - textAlign: 'center', 177 - fontWeight: '600', 178 - marginBottom: 5, 179 - }, 180 - description: { 181 - textAlign: 'center', 182 - paddingHorizontal: 32, 183 - }, 184 - section: { 185 - borderTopWidth: 1, 186 - paddingVertical: 20, 187 - }, 188 - adultExplainer: { 189 - paddingLeft: 5, 190 - paddingTop: 10, 191 - }, 192 - btn: { 193 - flexDirection: 'row', 194 - alignItems: 'center', 195 - justifyContent: 'center', 196 - borderRadius: 32, 197 - padding: 14, 198 - backgroundColor: colors.blue3, 199 - }, 200 - btnContainer: { 201 - paddingTop: 20, 202 - paddingHorizontal: 20, 203 - }, 204 - })