Merge branch 'main' into starter-packs

This commit is contained in:
Hailey
2024-06-18 18:26:59 -07:00
15 changed files with 307 additions and 132 deletions
+40 -7
View File
@@ -8,6 +8,7 @@ import {
} from 'react-native'
import {
AppBskyActorDefs,
AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
@@ -51,6 +52,7 @@ import {TimeElapsed} from '../util/TimeElapsed'
import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
import {parseTenorGif} from '#/lib/strings/embed-player'
import {StarterPackIcon} from '#/components/icons/StarterPackIcon'
const MAX_AUTHORS = 5
@@ -487,17 +489,48 @@ function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
const pal = usePalette('default')
if (post && AppBskyFeedPost.isRecord(post?.record)) {
const text = post.record.text
const images = AppBskyEmbedImages.isView(post.embed)
? post.embed.images
: AppBskyEmbedRecordWithMedia.isView(post.embed) &&
AppBskyEmbedImages.isView(post.embed.media)
? post.embed.media.images
: undefined
let images
let isGif = false
if (AppBskyEmbedImages.isView(post.embed)) {
images = post.embed.images
} else if (
AppBskyEmbedRecordWithMedia.isView(post.embed) &&
AppBskyEmbedImages.isView(post.embed.media)
) {
images = post.embed.media.images
} else if (
AppBskyEmbedExternal.isView(post.embed) &&
post.embed.external.thumb
) {
let url: URL | undefined
try {
url = new URL(post.embed.external.uri)
} catch {}
if (url) {
const {success} = parseTenorGif(url)
if (success) {
isGif = true
images = [
{
thumb: post.embed.external.thumb,
alt: post.embed.external.title,
fullsize: post.embed.external.thumb,
},
]
}
}
}
return (
<>
{text?.length > 0 && <Text style={pal.textLight}>{text}</Text>}
{images && images.length > 0 && (
<ImageHorzList images={images} style={styles.additionalPostImages} />
<ImageHorzList
images={images}
style={styles.additionalPostImages}
gif={isGif}
/>
)}
</>
)
+2 -2
View File
@@ -180,7 +180,7 @@ const desktopStyles = StyleSheet.create({
position: 'absolute',
left: 0,
right: 0,
bottom: -1,
top: '100%',
borderBottomWidth: 1,
},
})
@@ -207,7 +207,7 @@ const mobileStyles = StyleSheet.create({
position: 'absolute',
left: 0,
right: 0,
bottom: -1,
top: '100%',
borderBottomWidth: hairlineWidth,
},
})
+5 -1
View File
@@ -331,7 +331,11 @@ export function PostThread({
<PostThreadShowHiddenReplies
type={item === SHOW_HIDDEN_REPLIES ? 'hidden' : 'muted'}
onPress={() =>
setHiddenRepliesState(HiddenRepliesState.ShowAndOverridePostHider)
setHiddenRepliesState(
item === SHOW_HIDDEN_REPLIES
? HiddenRepliesState.Show
: HiddenRepliesState.ShowAndOverridePostHider,
)
}
hideTopBorder={index === 0}
/>
+33 -20
View File
@@ -56,6 +56,7 @@ interface FeedItemProps {
isThreadParent?: boolean
feedContext: string | undefined
hideTopBorder?: boolean
isParentBlocked?: boolean
}
export function FeedItem({
@@ -70,6 +71,7 @@ export function FeedItem({
isThreadLastChild,
isThreadParent,
hideTopBorder,
isParentBlocked,
}: FeedItemProps & {post: AppBskyFeedDefs.PostView}): React.ReactNode {
const postShadowed = usePostShadow(post)
const richText = useMemo(
@@ -100,6 +102,7 @@ export function FeedItem({
isThreadLastChild={isThreadLastChild}
isThreadParent={isThreadParent}
hideTopBorder={hideTopBorder}
isParentBlocked={isParentBlocked}
/>
)
}
@@ -119,6 +122,7 @@ let FeedItemInner = ({
isThreadLastChild,
isThreadParent,
hideTopBorder,
isParentBlocked,
}: FeedItemProps & {
richText: RichTextAPI
post: Shadow<AppBskyFeedDefs.PostView>
@@ -320,7 +324,7 @@ let FeedItemInner = ({
onOpenAuthor={onOpenAuthor}
/>
{!isThreadChild && showReplyTo && parentAuthor && (
<ReplyToLabel profile={parentAuthor} />
<ReplyToLabel blocked={isParentBlocked} profile={parentAuthor} />
)}
<LabelsOnMyPost post={post} />
<PostContent
@@ -409,9 +413,14 @@ let PostContent = ({
}
PostContent = memo(PostContent)
function ReplyToLabel({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) {
function ReplyToLabel({
profile,
blocked,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
blocked?: boolean
}) {
const pal = usePalette('default')
return (
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
<FontAwesomeIcon
@@ -424,23 +433,27 @@ function ReplyToLabel({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) {
style={[pal.textLight, s.mr2]}
lineHeight={1.2}
numberOfLines={1}>
<Trans context="description">
Reply to{' '}
<ProfileHoverCard inline did={profile.did}>
<TextLinkOnWebOnly
type="md"
style={pal.textLight}
lineHeight={1.2}
numberOfLines={1}
href={makeProfileLink(profile)}
text={
profile.displayName
? sanitizeDisplayName(profile.displayName)
: sanitizeHandle(profile.handle)
}
/>
</ProfileHoverCard>
</Trans>
{blocked ? (
<Trans context="description">Reply to a blocked post</Trans>
) : (
<Trans context="description">
Reply to{' '}
<ProfileHoverCard inline did={profile.did}>
<TextLinkOnWebOnly
type="md"
style={pal.textLight}
lineHeight={1.2}
numberOfLines={1}
href={makeProfileLink(profile)}
text={
profile.displayName
? sanitizeDisplayName(profile.displayName)
: sanitizeHandle(profile.handle)
}
/>
</ProfileHoverCard>
</Trans>
)}
</Text>
</View>
)
+4
View File
@@ -34,6 +34,7 @@ let FeedSlice = ({
isThreadParent={isThreadParentAt(slice.items, 0)}
isThreadChild={isThreadChildAt(slice.items, 0)}
hideTopBorder={hideTopBorder}
isParentBlocked={slice.items[0].isParentBlocked}
/>
<FeedItem
key={slice.items[1]._reactKey}
@@ -46,6 +47,7 @@ let FeedSlice = ({
moderation={slice.items[1].moderation}
isThreadParent={isThreadParentAt(slice.items, 1)}
isThreadChild={isThreadChildAt(slice.items, 1)}
isParentBlocked={slice.items[1].isParentBlocked}
/>
<ViewFullThread slice={slice} />
<FeedItem
@@ -59,6 +61,7 @@ let FeedSlice = ({
moderation={slice.items[last].moderation}
isThreadParent={isThreadParentAt(slice.items, last)}
isThreadChild={isThreadChildAt(slice.items, last)}
isParentBlocked={slice.items[2].isParentBlocked}
isThreadLastChild
/>
</>
@@ -82,6 +85,7 @@ let FeedSlice = ({
isThreadLastChild={
isThreadChildAt(slice.items, i) && slice.items.length === i + 1
}
isParentBlocked={slice.items[i].isParentBlocked}
hideTopBorder={hideTopBorder && i === 0}
/>
))}
+24 -11
View File
@@ -35,6 +35,7 @@ export type UserAvatarType = 'user' | 'algo' | 'list' | 'labeler'
interface BaseUserAvatarProps {
type?: UserAvatarType
shape?: 'circle' | 'square'
size: number
avatar?: string | null
}
@@ -60,12 +61,16 @@ const BLUR_AMOUNT = isWeb ? 5 : 100
let DefaultAvatar = ({
type,
shape: overrideShape,
size,
}: {
type: UserAvatarType
shape?: 'square' | 'circle'
size: number
}): React.ReactNode => {
const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square')
if (type === 'algo') {
// TODO: shape=circle
// Font Awesome Pro 6.4.0 by @fontawesome -https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.
return (
<Svg
@@ -84,6 +89,7 @@ let DefaultAvatar = ({
)
}
if (type === 'list') {
// TODO: shape=circle
// Font Awesome Pro 6.4.0 by @fontawesome -https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc.
return (
<Svg
@@ -117,14 +123,18 @@ let DefaultAvatar = ({
viewBox="0 0 32 32"
fill="none"
stroke="none">
<Rect
x="0"
y="0"
width="32"
height="32"
rx="3"
fill={tokens.color.temp_purple}
/>
{finalShape === 'square' ? (
<Rect
x="0"
y="0"
width="32"
height="32"
rx="3"
fill={tokens.color.temp_purple}
/>
) : (
<Circle cx="16" cy="16" r="16" fill={tokens.color.temp_purple} />
)}
<Path
d="M24 9.75L16 7L8 9.75V15.9123C8 20.8848 12 23 16 25.1579C20 23 24 20.8848 24 15.9123V9.75Z"
stroke="white"
@@ -135,6 +145,7 @@ let DefaultAvatar = ({
</Svg>
)
}
// TODO: shape=square
return (
<Svg
testID="userAvatarFallback"
@@ -159,6 +170,7 @@ export {DefaultAvatar}
let UserAvatar = ({
type = 'user',
shape: overrideShape,
size,
avatar,
moderation,
@@ -166,9 +178,10 @@ let UserAvatar = ({
}: UserAvatarProps): React.ReactNode => {
const pal = usePalette('default')
const backgroundColor = pal.colors.backgroundLight
const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square')
const aviStyle = useMemo(() => {
if (type === 'algo' || type === 'list' || type === 'labeler') {
if (finalShape === 'square') {
return {
width: size,
height: size,
@@ -182,7 +195,7 @@ let UserAvatar = ({
borderRadius: Math.floor(size / 2),
backgroundColor,
}
}, [type, size, backgroundColor])
}, [finalShape, size, backgroundColor])
const alert = useMemo(() => {
if (!moderation?.alert) {
@@ -224,7 +237,7 @@ let UserAvatar = ({
</View>
) : (
<View style={{width: size, height: size}}>
<DefaultAvatar type={type} size={size} />
<DefaultAvatar type={type} shape={finalShape} size={size} />
{alert}
</View>
)
+39 -18
View File
@@ -2,39 +2,60 @@ import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {Image} from 'expo-image'
import {AppBskyEmbedImages} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {atoms as a} from '#/alf'
import {Text} from '#/components/Typography'
interface Props {
images: AppBskyEmbedImages.ViewImage[]
style?: StyleProp<ViewStyle>
gif?: boolean
}
export function ImageHorzList({images, style}: Props) {
export function ImageHorzList({images, style, gif}: Props) {
return (
<View style={[styles.flexRow, style]}>
<View style={[a.flex_row, a.gap_xs, style]}>
{images.map(({thumb, alt}) => (
<Image
<View
key={thumb}
source={{uri: thumb}}
style={styles.image}
accessible={true}
accessibilityIgnoresInvertColors
accessibilityHint={alt}
accessibilityLabel=""
/>
style={[a.relative, a.flex_1, {aspectRatio: 1, maxWidth: 100}]}>
<Image
key={thumb}
source={{uri: thumb}}
style={[a.flex_1, a.rounded_xs]}
accessible={true}
accessibilityIgnoresInvertColors
accessibilityHint={alt}
accessibilityLabel=""
/>
{gif && (
<View style={styles.altContainer}>
<Text style={styles.alt}>
<Trans>GIF</Trans>
</Text>
</View>
)}
</View>
))}
</View>
)
}
const styles = StyleSheet.create({
flexRow: {
flexDirection: 'row',
gap: 5,
altContainer: {
backgroundColor: 'rgba(0, 0, 0, 0.75)',
borderRadius: 6,
paddingHorizontal: 6,
paddingVertical: 3,
position: 'absolute',
right: 5,
bottom: 5,
zIndex: 2,
},
image: {
maxWidth: 100,
aspectRatio: 1,
flex: 1,
borderRadius: 4,
alt: {
color: 'white',
fontSize: 7,
fontWeight: 'bold',
},
})