Merge branch 'pnbx/fix-consume' of https://github.com/bluesky-social/social-app into pnbx/fix-consume
This commit is contained in:
@@ -1,30 +1,40 @@
|
||||
import {View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
import {AppBskyEmbedVideo, AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyEmbedVideo,
|
||||
AppBskyFeedDefs,
|
||||
AppBskyFeedPost,
|
||||
ModerationDecision,
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {formatCount} from '#/view/com/util/numeric/format'
|
||||
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {VideoFeedSourceContext} from '#/screens/VideoFeed/types'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {BLUE_HUE} from '#/alf/util/colorGeneration'
|
||||
import {select} from '#/alf/util/themeSelector'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {EyeSlash_Stroke2_Corner0_Rounded as Eye} from '#/components/icons/EyeSlash'
|
||||
import {Heart2_Stroke2_Corner0_Rounded as Heart} from '#/components/icons/Heart2'
|
||||
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
|
||||
import {Link} from '#/components/Link'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import * as Hider from '#/components/moderation/Hider'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function VideoPostCard({
|
||||
post,
|
||||
sourceContext,
|
||||
moderation,
|
||||
}: {
|
||||
post: AppBskyFeedDefs.PostView
|
||||
sourceContext: VideoFeedSourceContext
|
||||
moderation: ModerationDecision
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_, i18n} = useLingui()
|
||||
@@ -70,82 +80,129 @@ export function VideoPostCard({
|
||||
justifyContent: undefined,
|
||||
},
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
a.justify_center,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
{
|
||||
backgroundColor: black,
|
||||
aspectRatio: 9 / 16,
|
||||
},
|
||||
]}>
|
||||
<Image
|
||||
source={{uri: thumbnail}}
|
||||
style={[a.w_full, a.h_full, {opacity: pressed ? 0.8 : 1}]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<MediaInsetBorder />
|
||||
|
||||
<View style={[a.absolute, a.inset_0]}>
|
||||
<Hider.Outer modui={moderation.ui('contentList')}>
|
||||
<Hider.Mask>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.pt_2xl,
|
||||
a.justify_center,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
{
|
||||
top: 'auto',
|
||||
backgroundColor: black,
|
||||
aspectRatio: 9 / 16,
|
||||
},
|
||||
]}>
|
||||
<LinearGradient
|
||||
colors={[black, 'rgba(22, 24, 35, 0)']}
|
||||
locations={[0.02, 1]}
|
||||
start={{x: 0, y: 1}}
|
||||
end={{x: 0, y: 0}}
|
||||
style={[a.absolute, a.inset_0, {opacity: 0.9}]}
|
||||
<Image
|
||||
source={{uri: thumbnail}}
|
||||
style={[a.w_full, a.h_full, {opacity: pressed ? 0.8 : 1}]}
|
||||
accessibilityIgnoresInvertColors
|
||||
blurRadius={40}
|
||||
/>
|
||||
|
||||
<View style={[a.relative, a.z_10, a.p_md, a.flex_row, a.gap_md]}>
|
||||
{likeCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Heart size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, likeCount)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{repostCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Repost size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, repostCount)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<MediaInsetBorder />
|
||||
<View
|
||||
style={[a.absolute, a.inset_0, a.justify_center, a.align_center]}>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
{
|
||||
backgroundColor: 'black',
|
||||
opacity: 0.2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View style={[a.align_center, a.gap_xs]}>
|
||||
<Eye size="lg" fill="white" />
|
||||
<Text style={[a.text_sm, {color: 'white'}]}>
|
||||
{_(msg`Hidden`)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[a.pr_xs, {paddingTop: 6, gap: 4}]}>
|
||||
{text && (
|
||||
<Text style={[a.text_md, a.leading_snug]} numberOfLines={2}>
|
||||
{text}
|
||||
</Text>
|
||||
)}
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
|
||||
<PreviewableUserAvatar type="user" size={20} profile={post.author} />
|
||||
<Text
|
||||
<VideoPostCardTextPlaceholder author={post.author} />
|
||||
</Hider.Mask>
|
||||
<Hider.Content>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_sm,
|
||||
a.leading_tight,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
{sanitizeHandle(post.author.handle, '@')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
a.justify_center,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
{
|
||||
backgroundColor: black,
|
||||
aspectRatio: 9 / 16,
|
||||
},
|
||||
]}>
|
||||
<Image
|
||||
source={{uri: thumbnail}}
|
||||
style={[a.w_full, a.h_full, {opacity: pressed ? 0.8 : 1}]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<MediaInsetBorder />
|
||||
|
||||
<View style={[a.absolute, a.inset_0]}>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.pt_2xl,
|
||||
{
|
||||
top: 'auto',
|
||||
},
|
||||
]}>
|
||||
<LinearGradient
|
||||
colors={[black, 'rgba(22, 24, 35, 0)']}
|
||||
locations={[0.02, 1]}
|
||||
start={{x: 0, y: 1}}
|
||||
end={{x: 0, y: 0}}
|
||||
style={[a.absolute, a.inset_0, {opacity: 0.9}]}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[a.relative, a.z_10, a.p_md, a.flex_row, a.gap_md]}>
|
||||
{likeCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Heart size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, likeCount)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
{repostCount > 0 && (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
|
||||
<Repost size="sm" fill="white" />
|
||||
<Text style={[a.text_sm, a.font_bold, {color: 'white'}]}>
|
||||
{formatCount(i18n, repostCount)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[a.pr_xs, {paddingTop: 6, gap: 4}]}>
|
||||
{text && (
|
||||
<Text style={[a.text_md, a.leading_snug]} numberOfLines={2}>
|
||||
{text}
|
||||
</Text>
|
||||
)}
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
|
||||
<UserAvatar type="user" size={20} avatar={post.author.avatar} />
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_sm,
|
||||
a.leading_tight,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
{sanitizeHandle(post.author.handle, '@')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Hider.Content>
|
||||
</Hider.Outer>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -171,6 +228,20 @@ export function VideoPostCardPlaceholder() {
|
||||
]}>
|
||||
<MediaInsetBorder />
|
||||
</View>
|
||||
<VideoPostCardTextPlaceholder />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function VideoPostCardTextPlaceholder({
|
||||
author,
|
||||
}: {
|
||||
author?: AppBskyActorDefs.ProfileViewBasic
|
||||
}) {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View style={[a.flex_1]}>
|
||||
<View style={[a.pr_xs, {paddingTop: 8, gap: 6}]}>
|
||||
<View
|
||||
style={[
|
||||
@@ -193,28 +264,44 @@ export function VideoPostCardPlaceholder() {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
t.atoms.bg_contrast_50,
|
||||
{
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_xs,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
height: 12,
|
||||
width: '75%',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
{author ? (
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
|
||||
<UserAvatar type="user" size={20} avatar={author.avatar} />
|
||||
<Text
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_sm,
|
||||
a.leading_tight,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}
|
||||
numberOfLines={1}>
|
||||
{sanitizeHandle(author.handle, '@')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[a.flex_row, a.gap_xs, a.align_center]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
t.atoms.bg_contrast_50,
|
||||
{
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_xs,
|
||||
t.atoms.bg_contrast_25,
|
||||
{
|
||||
height: 12,
|
||||
width: '75%',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -19,7 +19,10 @@ export function PostFeedVideoGridRow({
|
||||
const gutters = useGutters(['base', 'base', 0, 'base'])
|
||||
const posts = slices
|
||||
.filter(slice => AppBskyEmbedVideo.isView(slice.post.embed))
|
||||
.map(slice => slice.post)
|
||||
.map(slice => ({
|
||||
post: slice.post,
|
||||
moderation: slice.moderation,
|
||||
}))
|
||||
|
||||
/**
|
||||
* This should not happen because we should be filtering out posts without
|
||||
@@ -31,8 +34,12 @@ export function PostFeedVideoGridRow({
|
||||
<View style={[gutters]}>
|
||||
<View style={[a.flex_row, a.gap_sm]}>
|
||||
{posts.map(post => (
|
||||
<View key={post.uri} style={[a.flex_1]}>
|
||||
<VideoPostCard post={post} sourceContext={sourceContext} />
|
||||
<View key={post.post.uri} style={[a.flex_1]}>
|
||||
<VideoPostCard
|
||||
post={post.post}
|
||||
sourceContext={sourceContext}
|
||||
moderation={post.moderation}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@@ -323,37 +323,44 @@ let PostFeed = ({
|
||||
})
|
||||
} else if (data) {
|
||||
let sliceIndex = -1
|
||||
for (const page of data?.pages) {
|
||||
if (isVideoFeed && isNative) {
|
||||
const rows: FeedPostSliceItem[][] = []
|
||||
for (let i = 0; i < page.slices.length; i++) {
|
||||
const slice = page.slices[i]
|
||||
const root = slice.items.at(0)
|
||||
if (!root) continue
|
||||
// TODO test this
|
||||
if (!AppBskyEmbedVideo.isView(root.post.embed)) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
const cols = gtMobile ? 3 : 2
|
||||
if (i % cols === 0) {
|
||||
rows.push([root])
|
||||
} else {
|
||||
rows[rows.length - 1].push(root)
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
sliceIndex++
|
||||
|
||||
arr.push({
|
||||
type: 'videoGridRow',
|
||||
key: row.map(r => r._reactKey).join('-'),
|
||||
slices: row,
|
||||
sourceFeedUri: feedUri,
|
||||
})
|
||||
if (isVideoFeed && isNative) {
|
||||
const rows: FeedPostSliceItem[][] = []
|
||||
let slices: {slice: FeedPostSlice; index: number}[] = []
|
||||
for (const page of data.pages) {
|
||||
for (const slice of page.slices) {
|
||||
slices.push({slice, index: sliceIndex++})
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
for (let i = 0; i < slices.length; i++) {
|
||||
const slice = slices[i]
|
||||
const root = slice.slice.items.at(0)
|
||||
if (!root) continue
|
||||
// TODO test this
|
||||
if (!AppBskyEmbedVideo.isView(root.post.embed)) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
const cols = gtMobile ? 3 : 2
|
||||
if (i % cols === 0) {
|
||||
rows.push([root])
|
||||
} else {
|
||||
rows[rows.length - 1].push(root)
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
sliceIndex++
|
||||
arr.push({
|
||||
type: 'videoGridRow',
|
||||
key: row.map(r => r._reactKey).join('-'),
|
||||
slices: row,
|
||||
sourceFeedUri: feedUri,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for (const page of data?.pages) {
|
||||
for (const slice of page.slices) {
|
||||
sliceIndex++
|
||||
|
||||
|
||||
Reference in New Issue
Block a user