0ac15920a4
* WIP * Alignment with icon * Add create/remove prompts * Fill out check dialog a bit * Reorg * Handle was verified state * Add warning to edit profile * Add warning to handle dialog * Decent alignment in posts on all platforms * Refactor alignment for posts, chatlist, hover card * Disable on profile * Convo header * Compute simple verification state * Add other icon, rename, integrate * Swap in simple state for profile edits * Clean up utility hooks * Add verifications UI to dialog * Add edu nux * Revert change * Fix wrapping of check on profile * Rename * Fix gap under PostMeta * Update check dialogs * Handle takendown verifiers in check dialog * alf composer reply to * Refactor verification state * Add create/remove mutations, non-functional for now * Fix up post-rebase * Add check to first author noty * Do cache updates after mutations * DRY up hook, add to profile updates too * Add to drawer * Update account list * Adapt to new types * Hook up mutations * Use profile shadow in feeds * Add to settings * Shadow currentAccountProfile * Add invalid state to verifications * Fix alignment and overflow in Settings and Drawer * Re-integrate post rebase * Remove debug code * Update copy * Add unverified notification support * Remove link * Make sure dialog closes * Update URL * Add settings screen * Integrate new setting into verification states * Add metrics, bump package, fix bad import * NUX fixes * Update copy * Fixes * Update types * fix search autocomplete * fix lint * add display name warning to new dialog * update default prefs * Add parsing support for notifications * Bump pkg * Tweak noty styles * Adjust check alignment * Tweak check alignment * Fix badge for verifier * Modify copy --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> Co-authored-by: Paul Frazee <pfrazee@gmail.com>
233 lines
6.9 KiB
TypeScript
233 lines
6.9 KiB
TypeScript
import {useCallback, useMemo, useState} from 'react'
|
|
import {LayoutAnimation, Pressable, View} from 'react-native'
|
|
import {Image} from 'expo-image'
|
|
import {
|
|
AppBskyEmbedImages,
|
|
AppBskyEmbedRecord,
|
|
AppBskyEmbedRecordWithMedia,
|
|
AppBskyFeedPost,
|
|
} from '@atproto/api'
|
|
import {msg} from '@lingui/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
|
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
|
import {type ComposerOptsPostRef} from '#/state/shell/composer'
|
|
import {MaybeQuoteEmbed} from '#/view/com/util/post-embeds/QuoteEmbed'
|
|
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
|
import {atoms as a, useTheme} from '#/alf'
|
|
import {Text} from '#/components/Typography'
|
|
import {useSimpleVerificationState} from '#/components/verification'
|
|
import {VerificationCheck} from '#/components/verification/VerificationCheck'
|
|
|
|
export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
|
const t = useTheme()
|
|
const {_} = useLingui()
|
|
const {embed} = replyTo
|
|
|
|
const [showFull, setShowFull] = useState(false)
|
|
|
|
const onPress = useCallback(() => {
|
|
setShowFull(prev => !prev)
|
|
LayoutAnimation.configureNext({
|
|
duration: 350,
|
|
update: {type: 'spring', springDamping: 0.7},
|
|
})
|
|
}, [])
|
|
|
|
const quoteEmbed = useMemo(() => {
|
|
if (
|
|
AppBskyEmbedRecord.isView(embed) &&
|
|
AppBskyEmbedRecord.isViewRecord(embed.record) &&
|
|
AppBskyFeedPost.isRecord(embed.record.value)
|
|
) {
|
|
return embed
|
|
} else if (
|
|
AppBskyEmbedRecordWithMedia.isView(embed) &&
|
|
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
|
|
AppBskyFeedPost.isRecord(embed.record.record.value)
|
|
) {
|
|
return embed.record
|
|
}
|
|
return null
|
|
}, [embed])
|
|
|
|
const images = useMemo(() => {
|
|
if (AppBskyEmbedImages.isView(embed)) {
|
|
return embed.images
|
|
} else if (
|
|
AppBskyEmbedRecordWithMedia.isView(embed) &&
|
|
AppBskyEmbedImages.isView(embed.media)
|
|
) {
|
|
return embed.media.images
|
|
}
|
|
}, [embed])
|
|
|
|
const verification = useSimpleVerificationState({profile: replyTo.author})
|
|
|
|
return (
|
|
<Pressable
|
|
style={[
|
|
a.flex_row,
|
|
a.align_start,
|
|
a.pt_xs,
|
|
a.pb_lg,
|
|
a.mb_md,
|
|
a.mx_lg,
|
|
a.border_b,
|
|
t.atoms.border_contrast_medium,
|
|
]}
|
|
onPress={onPress}
|
|
accessibilityRole="button"
|
|
accessibilityLabel={_(
|
|
msg`Expand or collapse the full post you are replying to`,
|
|
)}
|
|
accessibilityHint="">
|
|
<PreviewableUserAvatar
|
|
size={50}
|
|
profile={replyTo.author}
|
|
moderation={replyTo.moderation?.ui('avatar')}
|
|
type={replyTo.author.associated?.labeler ? 'labeler' : 'user'}
|
|
disableNavigation={true}
|
|
/>
|
|
<View style={[a.flex_1, a.pl_md, a.pr_sm, a.gap_2xs]}>
|
|
<View style={[a.flex_row, a.align_center, a.pr_xs]}>
|
|
<Text
|
|
style={[a.font_bold, a.text_md, a.flex_shrink]}
|
|
numberOfLines={1}
|
|
emoji>
|
|
{sanitizeDisplayName(
|
|
replyTo.author.displayName ||
|
|
sanitizeHandle(replyTo.author.handle),
|
|
)}
|
|
</Text>
|
|
{verification.showBadge && (
|
|
<View style={[a.pl_xs]}>
|
|
<VerificationCheck
|
|
width={14}
|
|
verifier={verification.role === 'verifier'}
|
|
/>
|
|
</View>
|
|
)}
|
|
</View>
|
|
<View style={[a.flex_row, a.gap_md]}>
|
|
<View style={[a.flex_1, a.flex_grow]}>
|
|
<Text
|
|
style={[a.text_md]}
|
|
numberOfLines={!showFull ? 6 : undefined}
|
|
emoji>
|
|
{replyTo.text}
|
|
</Text>
|
|
</View>
|
|
{images && !replyTo.moderation?.ui('contentMedia').blur && (
|
|
<ComposerReplyToImages images={images} showFull={showFull} />
|
|
)}
|
|
</View>
|
|
{showFull && quoteEmbed && <MaybeQuoteEmbed embed={quoteEmbed} />}
|
|
</View>
|
|
</Pressable>
|
|
)
|
|
}
|
|
|
|
function ComposerReplyToImages({
|
|
images,
|
|
}: {
|
|
images: AppBskyEmbedImages.ViewImage[]
|
|
showFull: boolean
|
|
}) {
|
|
return (
|
|
<View
|
|
style={[
|
|
a.rounded_xs,
|
|
a.overflow_hidden,
|
|
a.mt_2xs,
|
|
a.mx_xs,
|
|
{
|
|
height: 64,
|
|
width: 64,
|
|
},
|
|
]}>
|
|
{(images.length === 1 && (
|
|
<Image
|
|
source={{uri: images[0].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
)) ||
|
|
(images.length === 2 && (
|
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
|
<Image
|
|
source={{uri: images[0].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
<Image
|
|
source={{uri: images[1].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
</View>
|
|
)) ||
|
|
(images.length === 3 && (
|
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
|
<Image
|
|
source={{uri: images[0].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
<View style={[a.flex_1, a.gap_2xs]}>
|
|
<Image
|
|
source={{uri: images[1].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
<Image
|
|
source={{uri: images[2].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
</View>
|
|
</View>
|
|
)) ||
|
|
(images.length === 4 && (
|
|
<View style={[a.flex_1, a.gap_2xs]}>
|
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
|
<Image
|
|
source={{uri: images[0].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
<Image
|
|
source={{uri: images[1].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
</View>
|
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
|
<Image
|
|
source={{uri: images[2].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
<Image
|
|
source={{uri: images[3].thumb}}
|
|
style={[a.flex_1]}
|
|
cachePolicy="memory-disk"
|
|
accessibilityIgnoresInvertColors
|
|
/>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
)
|
|
}
|