diff options
Diffstat (limited to 'src/view/com/util')
-rw-r--r-- | src/view/com/util/UserAvatar.tsx | 22 | ||||
-rw-r--r-- | src/view/com/util/UserBanner.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/moderation/ContentHider.tsx | 120 | ||||
-rw-r--r-- | src/view/com/util/moderation/ImageHider.tsx | 80 | ||||
-rw-r--r-- | src/view/com/util/moderation/PostAlerts.tsx | 68 | ||||
-rw-r--r-- | src/view/com/util/moderation/PostHider.tsx | 131 | ||||
-rw-r--r-- | src/view/com/util/moderation/ProfileHeaderAlerts.tsx | 76 | ||||
-rw-r--r-- | src/view/com/util/moderation/ProfileHeaderWarnings.tsx | 44 | ||||
-rw-r--r-- | src/view/com/util/moderation/ScreenHider.tsx | 57 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/QuoteEmbed.tsx | 68 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 41 |
11 files changed, 395 insertions, 316 deletions
diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index d999ffb31..0f34f75aa 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -3,6 +3,7 @@ import {StyleSheet, View} from 'react-native' import Svg, {Circle, Rect, Path} from 'react-native-svg' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {HighPriorityImage} from 'view/com/util/images/Image' +import {ModerationUI} from '@atproto/api' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' import { usePhotoLibraryPermission, @@ -13,7 +14,6 @@ import {colors} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {isWeb, isAndroid} from 'platform/detection' import {Image as RNImage} from 'react-native-image-crop-picker' -import {AvatarModeration} from 'lib/labeling/types' import {UserPreviewLink} from './UserPreviewLink' import {DropdownItem, NativeDropdown} from './forms/NativeDropdown' @@ -23,7 +23,7 @@ interface BaseUserAvatarProps { type?: Type size: number avatar?: string | null - moderation?: AvatarModeration + moderation?: ModerationUI } interface UserAvatarProps extends BaseUserAvatarProps { @@ -213,20 +213,20 @@ export function UserAvatar({ ], ) - const warning = useMemo(() => { - if (!moderation?.warn) { + const alert = useMemo(() => { + if (!moderation?.alert) { return null } return ( - <View style={[styles.warningIconContainer, pal.view]}> + <View style={[styles.alertIconContainer, pal.view]}> <FontAwesomeIcon icon="exclamation-circle" - style={styles.warningIcon} + style={styles.alertIcon} size={Math.floor(size / 3)} /> </View> ) - }, [moderation?.warn, size, pal]) + }, [moderation?.alert, size, pal]) // onSelectNewAvatar is only passed as prop on the EditProfile component return onSelectNewAvatar ? ( @@ -259,12 +259,12 @@ export function UserAvatar({ source={{uri: avatar}} blurRadius={moderation?.blur ? BLUR_AMOUNT : 0} /> - {warning} + {alert} </View> ) : ( <View style={{width: size, height: size}}> <DefaultAvatar type={type} size={size} /> - {warning} + {alert} </View> ) } @@ -289,13 +289,13 @@ const styles = StyleSheet.create({ justifyContent: 'center', backgroundColor: colors.gray5, }, - warningIconContainer: { + alertIconContainer: { position: 'absolute', right: 0, bottom: 0, borderRadius: 100, }, - warningIcon: { + alertIcon: { color: colors.red3, }, }) diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index b7e91b5dd..7c5c583c2 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -1,6 +1,7 @@ import React, {useMemo} from 'react' import {StyleSheet, View} from 'react-native' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {ModerationUI} from '@atproto/api' import {Image} from 'expo-image' import {colors} from 'lib/styles' import {openCamera, openCropper, openPicker} from '../../../lib/media/picker' @@ -10,7 +11,6 @@ import { useCameraPermission, } from 'lib/hooks/usePermissions' import {usePalette} from 'lib/hooks/usePalette' -import {AvatarModeration} from 'lib/labeling/types' import {isWeb, isAndroid} from 'platform/detection' import {Image as RNImage} from 'react-native-image-crop-picker' import {NativeDropdown, DropdownItem} from './forms/NativeDropdown' @@ -21,7 +21,7 @@ export function UserBanner({ onSelectNewBanner, }: { banner?: string | null - moderation?: AvatarModeration + moderation?: ModerationUI onSelectNewBanner?: (img: RNImage | null) => void }) { const store = useStores() diff --git a/src/view/com/util/moderation/ContentHider.tsx b/src/view/com/util/moderation/ContentHider.tsx index ac5c8395d..6be2f8be0 100644 --- a/src/view/com/util/moderation/ContentHider.tsx +++ b/src/view/com/util/moderation/ContentHider.tsx @@ -1,36 +1,32 @@ import React from 'react' import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' import {usePalette} from 'lib/hooks/usePalette' +import {ModerationUI} from '@atproto/api' import {Text} from '../text/Text' -import {addStyle} from 'lib/styles' -import {ModerationBehavior, ModerationBehaviorCode} from 'lib/labeling/types' +import {InfoCircleIcon} from 'lib/icons' +import {describeModerationCause} from 'lib/moderation' +import {useStores} from 'state/index' +import {isDesktopWeb} from 'platform/detection' export function ContentHider({ testID, moderation, + ignoreMute, style, - containerStyle, + childContainerStyle, children, }: React.PropsWithChildren<{ testID?: string - moderation: ModerationBehavior + moderation: ModerationUI + ignoreMute?: boolean style?: StyleProp<ViewStyle> - containerStyle?: StyleProp<ViewStyle> + childContainerStyle?: StyleProp<ViewStyle> }>) { + const store = useStores() 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 || - moderation.behavior === ModerationBehaviorCode.Warn || - moderation.behavior === ModerationBehaviorCode.WarnImages - ) { + if (!moderation.blur || (ignoreMute && moderation.cause?.type === 'muted')) { return ( <View testID={testID} style={style}> {children} @@ -38,73 +34,61 @@ export function ContentHider({ ) } - if (moderation.behavior === ModerationBehaviorCode.Hide) { - return null - } - + const desc = describeModerationCause(moderation.cause, 'content') return ( - <View style={[styles.container, pal.view, pal.border, containerStyle]}> + <View testID={testID} style={style}> <Pressable - onPress={override ? onPressHide : onPressShow} - 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' - } - style={[ - styles.description, - pal.viewLight, - override && styles.descriptionOpen, - ]}> - <Text type="md" style={pal.textLight}> - {moderation.reason || 'Content warning'} + onPress={() => { + if (!moderation.noOverride) { + setOverride(v => !v) + } + }} + accessibilityRole="button" + accessibilityHint={override ? 'Hide the content' : 'Show the content'} + accessibilityLabel="" + style={[styles.cover, pal.viewLight]}> + <Pressable + onPress={() => { + store.shell.openModal({ + name: 'moderation-details', + context: 'content', + moderation, + }) + }} + accessibilityRole="button" + accessibilityLabel="Learn more about this warning" + accessibilityHint=""> + <InfoCircleIcon size={18} style={pal.text} /> + </Pressable> + <Text type="lg" style={pal.text}> + {desc.name} </Text> - <View style={styles.showBtn}> - <Text type="md-medium" style={pal.link}> - {override ? 'Hide' : 'Show'} - </Text> - </View> - </Pressable> - {override && ( - <View style={[styles.childrenContainer, pal.border]}> - <View testID={testID} style={addStyle(style, styles.child)}> - {children} + {!moderation.noOverride && ( + <View style={styles.showBtn}> + <Text type="xl" style={pal.link}> + {override ? 'Hide' : 'Show'} + </Text> </View> - </View> - )} + )} + </Pressable> + {override && <View style={childContainerStyle}>{children}</View>} </View> ) } const styles = StyleSheet.create({ - container: { - marginBottom: 10, - borderWidth: 1, - borderRadius: 12, - }, - description: { + cover: { flexDirection: 'row', alignItems: 'center', + gap: 4, + borderRadius: 8, + marginTop: 4, paddingVertical: 14, paddingLeft: 14, - paddingRight: 18, - borderRadius: 12, - }, - descriptionOpen: { - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0, - }, - icon: { - marginRight: 10, + paddingRight: isDesktopWeb ? 18 : 22, }, showBtn: { marginLeft: 'auto', + alignSelf: 'center', }, - childrenContainer: { - paddingHorizontal: 12, - paddingTop: 8, - }, - child: {}, }) diff --git a/src/view/com/util/moderation/ImageHider.tsx b/src/view/com/util/moderation/ImageHider.tsx deleted file mode 100644 index 40c9d0a21..000000000 --- a/src/view/com/util/moderation/ImageHider.tsx +++ /dev/null @@ -1,80 +0,0 @@ -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 {ModerationBehavior, ModerationBehaviorCode} from 'lib/labeling/types' -import {isDesktopWeb} from 'platform/detection' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' - -export function ImageHider({ - testID, - moderation, - style, - children, -}: React.PropsWithChildren<{ - testID?: string - moderation: ModerationBehavior - style?: StyleProp<ViewStyle> -}>) { - const pal = usePalette('default') - const [override, setOverride] = React.useState(false) - const onPressToggle = React.useCallback(() => { - setOverride(v => !v) - }, [setOverride]) - - if (moderation.behavior === ModerationBehaviorCode.Hide) { - return null - } - - if (moderation.behavior !== ModerationBehaviorCode.WarnImages) { - return ( - <View testID={testID} style={style}> - {children} - </View> - ) - } - - return ( - <View testID={testID} style={style}> - <View style={[styles.cover, pal.viewLight]}> - <Pressable - onPress={onPressToggle} - style={[styles.toggleBtn]} - accessibilityLabel="Show image" - accessibilityHint=""> - <FontAwesomeIcon - icon={override ? 'eye' : ['far', 'eye-slash']} - size={24} - style={pal.text as FontAwesomeIconStyle} - /> - <Text type="lg" style={pal.text}> - {moderation.reason || 'Content warning'} - </Text> - <View style={styles.flex1} /> - <Text type="xl-bold" style={pal.link}> - {override ? 'Hide' : 'Show'} - </Text> - </Pressable> - </View> - {override && children} - </View> - ) -} - -const styles = StyleSheet.create({ - cover: { - borderRadius: 8, - marginTop: 4, - }, - toggleBtn: { - flexDirection: 'row', - gap: 8, - alignItems: 'center', - paddingHorizontal: isDesktopWeb ? 24 : 20, - paddingVertical: isDesktopWeb ? 20 : 18, - }, - flex1: { - flex: 1, - }, -}) diff --git a/src/view/com/util/moderation/PostAlerts.tsx b/src/view/com/util/moderation/PostAlerts.tsx new file mode 100644 index 000000000..45937c2d8 --- /dev/null +++ b/src/view/com/util/moderation/PostAlerts.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import {Pressable, StyleProp, StyleSheet, ViewStyle} from 'react-native' +import {ModerationUI} from '@atproto/api' +import {Text} from '../text/Text' +import {usePalette} from 'lib/hooks/usePalette' +import {InfoCircleIcon} from 'lib/icons' +import {describeModerationCause} from 'lib/moderation' +import {useStores} from 'state/index' + +export function PostAlerts({ + moderation, + includeMute, + style, +}: { + moderation: ModerationUI + includeMute?: boolean + style?: StyleProp<ViewStyle> +}) { + const store = useStores() + const pal = usePalette('default') + + const shouldAlert = + !!moderation.cause && + (moderation.alert || + (includeMute && moderation.blur && moderation.cause?.type === 'muted')) + if (!shouldAlert) { + return null + } + + const desc = describeModerationCause(moderation.cause, 'content') + return ( + <Pressable + onPress={() => { + store.shell.openModal({ + name: 'moderation-details', + context: 'content', + moderation, + }) + }} + accessibilityRole="button" + accessibilityLabel="Learn more about this warning" + accessibilityHint="" + style={[styles.container, pal.viewLight, style]}> + <InfoCircleIcon style={pal.text} size={18} /> + <Text type="lg" style={pal.text}> + {desc.name} + </Text> + <Text type="lg" style={[pal.link, styles.learnMoreBtn]}> + Learn More + </Text> + </Pressable> + ) +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + paddingVertical: 8, + paddingLeft: 14, + paddingHorizontal: 16, + borderRadius: 8, + }, + learnMoreBtn: { + marginLeft: 'auto', + }, +}) diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/view/com/util/moderation/PostHider.tsx index f2b6dbddd..dc74d3e39 100644 --- a/src/view/com/util/moderation/PostHider.tsx +++ b/src/view/com/util/moderation/PostHider.tsx @@ -1,17 +1,20 @@ import React, {ComponentProps} from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {StyleSheet, Pressable, View} from 'react-native' +import {ModerationUI} from '@atproto/api' import {usePalette} from 'lib/hooks/usePalette' import {Link} from '../Link' import {Text} from '../text/Text' import {addStyle} from 'lib/styles' -import {ModerationBehaviorCode, ModerationBehavior} from 'lib/labeling/types' +import {describeModerationCause} from 'lib/moderation' +import {InfoCircleIcon} from 'lib/icons' +import {useStores} from 'state/index' +import {isDesktopWeb} from 'platform/detection' interface Props extends ComponentProps<typeof Link> { // testID?: string // href?: string // style: StyleProp<ViewStyle> - moderation: ModerationBehavior + moderation: ModerationUI } export function PostHider({ @@ -22,60 +25,71 @@ export function PostHider({ children, ...props }: Props) { + const store = useStores() const pal = usePalette('default') const [override, setOverride] = React.useState(false) - const bg = override ? pal.viewLight : pal.view - if (moderation.behavior === ModerationBehaviorCode.Hide) { - return null - } - - if (moderation.behavior === ModerationBehaviorCode.Warn) { + if (!moderation.blur) { return ( - <> - <View style={[styles.description, bg, pal.border]}> - <FontAwesomeIcon - icon={['far', 'eye-slash']} - style={[styles.icon, pal.text]} - /> - <Text type="md" style={pal.textLight}> - {moderation.reason || 'Content warning'} - </Text> - <TouchableOpacity - style={styles.showBtn} - onPress={() => setOverride(v => !v)} - accessibilityRole="button"> - <Text type="md" style={pal.link}> - {override ? 'Hide' : 'Show'} post - </Text> - </TouchableOpacity> - </View> - {override && ( - <View style={[styles.childrenContainer, pal.border, bg]}> - <Link - testID={testID} - style={addStyle(style, styles.child)} - href={href} - noFeedback> - {children} - </Link> - </View> - )} - </> + <Link + testID={testID} + style={style} + href={href} + noFeedback + accessible={false} + {...props}> + {children} + </Link> ) } - // NOTE: any further label enforcement should occur in ContentContainer + const desc = describeModerationCause(moderation.cause, 'content') return ( - <Link - testID={testID} - style={style} - href={href} - noFeedback - accessible={false} - {...props}> - {children} - </Link> + <> + <Pressable + onPress={() => { + if (!moderation.noOverride) { + setOverride(v => !v) + } + }} + accessibilityRole="button" + accessibilityHint={override ? 'Hide the content' : 'Show the content'} + accessibilityLabel="" + style={[styles.description, pal.viewLight]}> + <Pressable + onPress={() => { + store.shell.openModal({ + name: 'moderation-details', + context: 'content', + moderation, + }) + }} + accessibilityRole="button" + accessibilityLabel="Learn more about this warning" + accessibilityHint=""> + <InfoCircleIcon size={18} style={pal.text} /> + </Pressable> + <Text type="lg" style={pal.text}> + {desc.name} + </Text> + {!moderation.noOverride && ( + <Text type="xl" style={[styles.showBtn, pal.link]}> + {override ? 'Hide' : 'Show'} + </Text> + )} + </Pressable> + {override && ( + <View style={[styles.childrenContainer, pal.border, pal.viewLight]}> + <Link + testID={testID} + style={addStyle(style, styles.child)} + href={href} + noFeedback> + {children} + </Link> + </View> + )} + </> ) } @@ -83,22 +97,23 @@ const styles = StyleSheet.create({ description: { flexDirection: 'row', alignItems: 'center', + gap: 4, paddingVertical: 14, - paddingHorizontal: 18, - borderTopWidth: 1, - }, - icon: { - marginRight: 10, + paddingLeft: 18, + paddingRight: isDesktopWeb ? 18 : 22, + marginTop: 1, }, showBtn: { marginLeft: 'auto', + alignSelf: 'center', }, childrenContainer: { - paddingHorizontal: 6, + paddingHorizontal: 4, paddingBottom: 6, }, child: { - borderWidth: 1, - borderRadius: 12, + borderWidth: 0, + borderTopWidth: 0, + borderRadius: 8, }, }) diff --git a/src/view/com/util/moderation/ProfileHeaderAlerts.tsx b/src/view/com/util/moderation/ProfileHeaderAlerts.tsx new file mode 100644 index 000000000..3cc3b5b9e --- /dev/null +++ b/src/view/com/util/moderation/ProfileHeaderAlerts.tsx @@ -0,0 +1,76 @@ +import React from 'react' +import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import {ProfileModeration} from '@atproto/api' +import {Text} from '../text/Text' +import {usePalette} from 'lib/hooks/usePalette' +import {InfoCircleIcon} from 'lib/icons' +import { + describeModerationCause, + getProfileModerationCauses, +} from 'lib/moderation' +import {useStores} from 'state/index' + +export function ProfileHeaderAlerts({ + moderation, + style, +}: { + moderation: ProfileModeration + style?: StyleProp<ViewStyle> +}) { + const store = useStores() + const pal = usePalette('default') + + const causes = getProfileModerationCauses(moderation) + if (!causes.length) { + return null + } + + return ( + <View style={styles.grid}> + {causes.map(cause => { + const desc = describeModerationCause(cause, 'account') + return ( + <Pressable + testID="profileHeaderAlert" + key={desc.name} + onPress={() => { + store.shell.openModal({ + name: 'moderation-details', + context: 'content', + moderation: {cause}, + }) + }} + accessibilityRole="button" + accessibilityLabel="Learn more about this warning" + accessibilityHint="" + style={[styles.container, pal.viewLight, style]}> + <InfoCircleIcon style={pal.text} size={24} /> + <Text type="lg" style={pal.text}> + {desc.name} + </Text> + <Text type="lg" style={[pal.link, styles.learnMoreBtn]}> + Learn More + </Text> + </Pressable> + ) + })} + </View> + ) +} + +const styles = StyleSheet.create({ + grid: { + gap: 4, + }, + container: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + }, + learnMoreBtn: { + marginLeft: 'auto', + }, +}) diff --git a/src/view/com/util/moderation/ProfileHeaderWarnings.tsx b/src/view/com/util/moderation/ProfileHeaderWarnings.tsx deleted file mode 100644 index 7a1a8e295..000000000 --- a/src/view/com/util/moderation/ProfileHeaderWarnings.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import {StyleSheet, View} from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {Text} from '../text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {ModerationBehavior, ModerationBehaviorCode} from 'lib/labeling/types' - -export function ProfileHeaderWarnings({ - moderation, -}: { - moderation: ModerationBehavior -}) { - const palErr = usePalette('error') - if (moderation.behavior === ModerationBehaviorCode.Show) { - return null - } - return ( - <View style={[styles.container, palErr.border, palErr.view]}> - <FontAwesomeIcon - icon="circle-exclamation" - style={palErr.text as FontAwesomeIconStyle} - size={20} - /> - <Text style={palErr.text}> - This account has been flagged: {moderation.reason} - </Text> - </View> - ) -} - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'center', - gap: 10, - borderWidth: 1, - borderRadius: 6, - paddingHorizontal: 10, - paddingVertical: 8, - }, -}) diff --git a/src/view/com/util/moderation/ScreenHider.tsx b/src/view/com/util/moderation/ScreenHider.tsx index 2e7b07e1a..b76b1101c 100644 --- a/src/view/com/util/moderation/ScreenHider.tsx +++ b/src/view/com/util/moderation/ScreenHider.tsx @@ -1,16 +1,24 @@ import React from 'react' -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import { + TouchableWithoutFeedback, + StyleProp, + StyleSheet, + View, + ViewStyle, +} from 'react-native' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' import {useNavigation} from '@react-navigation/native' +import {ModerationUI} from '@atproto/api' import {usePalette} from 'lib/hooks/usePalette' import {NavigationProp} from 'lib/routes/types' import {Text} from '../text/Text' import {Button} from '../forms/Button' import {isDesktopWeb} from 'platform/detection' -import {ModerationBehaviorCode, ModerationBehavior} from 'lib/labeling/types' +import {describeModerationCause} from 'lib/moderation' +import {useStores} from 'state/index' export function ScreenHider({ testID, @@ -22,24 +30,17 @@ export function ScreenHider({ }: React.PropsWithChildren<{ testID?: string screenDescription: string - moderation: ModerationBehavior + moderation: ModerationUI style?: StyleProp<ViewStyle> containerStyle?: StyleProp<ViewStyle> }>) { + const store = useStores() const pal = usePalette('default') const palInverted = usePalette('inverted') const [override, setOverride] = React.useState(false) const navigation = useNavigation<NavigationProp>() - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - if (moderation.behavior !== ModerationBehaviorCode.Hide || override) { + if (!moderation.blur || override) { return ( <View testID={testID} style={style}> {children} @@ -47,6 +48,7 @@ export function ScreenHider({ ) } + const desc = describeModerationCause(moderation.cause, 'account') return ( <View style={[styles.container, pal.view, containerStyle]}> <View style={styles.iconContainer}> @@ -63,11 +65,38 @@ export function ScreenHider({ </Text> <Text type="2xl" style={[styles.description, pal.textLight]}> This {screenDescription} has been flagged:{' '} - {moderation.reason || 'Content warning'} + <Text type="2xl-medium" style={pal.text}> + {desc.name} + </Text> + .{' '} + <TouchableWithoutFeedback + onPress={() => { + store.shell.openModal({ + name: 'moderation-details', + context: 'account', + moderation, + }) + }} + accessibilityRole="button" + accessibilityLabel="Learn more about this warning" + accessibilityHint=""> + <Text type="2xl" style={pal.link}> + Learn More + </Text> + </TouchableWithoutFeedback> </Text> {!isDesktopWeb && <View style={styles.spacer} />} <View style={styles.btnContainer}> - <Button type="inverted" onPress={onPressBack} style={styles.btn}> + <Button + type="inverted" + onPress={() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('Home') + } + }} + style={styles.btn}> <Text type="button-lg" style={pal.textInverted}> Go back </Text> diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index 4995562ac..9f11fe48c 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -1,6 +1,11 @@ import React from 'react' -import {StyleProp, StyleSheet, ViewStyle} from 'react-native' -import {AppBskyEmbedImages, AppBskyEmbedRecordWithMedia} from '@atproto/api' +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' +import { + AppBskyEmbedRecord, + AppBskyFeedPost, + AppBskyEmbedImages, + AppBskyEmbedRecordWithMedia, +} from '@atproto/api' import {AtUri} from '@atproto/api' import {PostMeta} from '../PostMeta' import {Link} from '../Link' @@ -9,6 +14,55 @@ import {usePalette} from 'lib/hooks/usePalette' import {ComposerOptsQuote} from 'state/models/ui/shell' import {PostEmbeds} from '.' import {makeProfileLink} from 'lib/routes/links' +import {InfoCircleIcon} from 'lib/icons' + +export function MaybeQuoteEmbed({ + embed, + style, +}: { + embed: AppBskyEmbedRecord.View + style?: StyleProp<ViewStyle> +}) { + const pal = usePalette('default') + if ( + AppBskyEmbedRecord.isViewRecord(embed.record) && + AppBskyFeedPost.isRecord(embed.record.value) && + AppBskyFeedPost.validateRecord(embed.record.value).success + ) { + return ( + <QuoteEmbed + quote={{ + author: embed.record.author, + cid: embed.record.cid, + uri: embed.record.uri, + indexedAt: embed.record.indexedAt, + text: embed.record.value.text, + embeds: embed.record.embeds, + }} + style={style} + /> + ) + } else if (AppBskyEmbedRecord.isViewBlocked(embed.record)) { + return ( + <View style={[styles.errorContainer, pal.borderDark]}> + <InfoCircleIcon size={18} style={pal.text} /> + <Text type="lg" style={pal.text}> + Blocked + </Text> + </View> + ) + } else if (AppBskyEmbedRecord.isViewNotFound(embed.record)) { + return ( + <View style={[styles.errorContainer, pal.borderDark]}> + <InfoCircleIcon size={18} style={pal.text} /> + <Text type="lg" style={pal.text}> + Deleted + </Text> + </View> + ) + } + return null +} export function QuoteEmbed({ quote, @@ -76,4 +130,14 @@ const styles = StyleSheet.create({ paddingLeft: 13, paddingRight: 8, }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + borderRadius: 8, + marginTop: 8, + paddingVertical: 14, + paddingHorizontal: 14, + borderWidth: 1, + }, }) diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 7ffebff54..627110495 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -12,7 +12,6 @@ import { AppBskyEmbedExternal, AppBskyEmbedRecord, AppBskyEmbedRecordWithMedia, - AppBskyFeedPost, AppBskyFeedDefs, AppBskyGraphDefs, } from '@atproto/api' @@ -24,7 +23,7 @@ import {usePalette} from 'lib/hooks/usePalette' import {YoutubeEmbed} from './YoutubeEmbed' import {ExternalLinkEmbed} from './ExternalLinkEmbed' import {getYoutubeVideoId} from 'lib/strings/url-helpers' -import QuoteEmbed from './QuoteEmbed' +import {MaybeQuoteEmbed} from './QuoteEmbed' import {AutoSizedImage} from '../images/AutoSizedImage' import {CustomFeedEmbed} from './CustomFeedEmbed' import {ListEmbed} from './ListEmbed' @@ -49,25 +48,11 @@ export function PostEmbeds({ // quote post with media // = - if ( - AppBskyEmbedRecordWithMedia.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record.record) && - AppBskyFeedPost.isRecord(embed.record.record.value) && - AppBskyFeedPost.validateRecord(embed.record.record.value).success - ) { + if (AppBskyEmbedRecordWithMedia.isView(embed)) { return ( <View style={[styles.stackContainer, style]}> <PostEmbeds embed={embed.media} /> - <QuoteEmbed - quote={{ - author: embed.record.record.author, - cid: embed.record.record.cid, - uri: embed.record.record.uri, - indexedAt: embed.record.record.indexedAt, - text: embed.record.record.value.text, - embeds: embed.record.record.embeds, - }} - /> + <MaybeQuoteEmbed embed={embed.record} /> </View> ) } @@ -75,25 +60,7 @@ export function PostEmbeds({ // quote post // = if (AppBskyEmbedRecord.isView(embed)) { - if ( - AppBskyEmbedRecord.isViewRecord(embed.record) && - AppBskyFeedPost.isRecord(embed.record.value) && - AppBskyFeedPost.validateRecord(embed.record.value).success - ) { - return ( - <QuoteEmbed - quote={{ - author: embed.record.author, - cid: embed.record.cid, - uri: embed.record.uri, - indexedAt: embed.record.indexedAt, - text: embed.record.value.text, - embeds: embed.record.embeds, - }} - style={style} - /> - ) - } + return <MaybeQuoteEmbed embed={embed} style={style} /> } // image embed |