From 6d242c389181b5a31b2586cf8ceb620e29a055d8 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 16 Jan 2025 10:56:20 -0600 Subject: [PATCH] Add grid --- src/components/VideoPostCard.tsx | 131 +++++++++++++ src/components/feeds/PostFeedVideoGridRow.tsx | 20 ++ src/view/com/posts/PostFeed.tsx | 183 +++++++++++------- 3 files changed, 260 insertions(+), 74 deletions(-) create mode 100644 src/components/VideoPostCard.tsx create mode 100644 src/components/feeds/PostFeedVideoGridRow.tsx diff --git a/src/components/VideoPostCard.tsx b/src/components/VideoPostCard.tsx new file mode 100644 index 0000000000..70a2a0d608 --- /dev/null +++ b/src/components/VideoPostCard.tsx @@ -0,0 +1,131 @@ +import {Pressable,View} from 'react-native' +import {Image} from 'expo-image' +import {LinearGradient} from 'expo-linear-gradient' +import {AppBskyEmbedVideo} from '@atproto/api' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {sanitizeHandle} from '#/lib/strings/handles' +import {FeedPostSliceItem} from '#/state/queries/post-feed' +import {formatCount} from '#/view/com/util/numeric/format' +import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' +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 {Heart2_Stroke2_Corner0_Rounded as Heart} from '#/components/icons/Heart2' +import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost' +import {Text} from '#/components/Typography' + +export function VideoPostCard({post}: {post: FeedPostSliceItem}) { + const t = useTheme() + const {_, i18n} = useLingui() + const embed = post.post.embed + const { + state: hovered, + onIn: onHoverIn, + onOut: onHoverOut, + } = useInteractionState() + + if (!AppBskyEmbedVideo.isView(embed)) { + // TODO unavailable? + return null + } + + const {thumbnail} = embed + const black = select(t.name, { + light: t.atoms.bg_contrast_25.backgroundColor, + dark: t.atoms.bg_contrast_25.backgroundColor, + dim: `hsl(${BLUE_HUE}, 28%, 6%)`, + }) + + return ( + + + + + + + + + + + + + + + {formatCount(i18n, post.post.likeCount || 0)} + + + + + + {formatCount(i18n, post.post.repostCount || 0)} + + + + + + + + + + {sanitizeHandle(post.post.author.handle, '@')} + + + + ) +} diff --git a/src/components/feeds/PostFeedVideoGridRow.tsx b/src/components/feeds/PostFeedVideoGridRow.tsx new file mode 100644 index 0000000000..c82ee9f7c3 --- /dev/null +++ b/src/components/feeds/PostFeedVideoGridRow.tsx @@ -0,0 +1,20 @@ +import {View} from 'react-native' + +import {FeedPostSliceItem} from '#/state/queries/post-feed' +import {atoms as a, useGutters} from '#/alf' +import {VideoPostCard} from '#/components/VideoPostCard' + +export function PostFeedVideoGridRow({posts}: {posts: FeedPostSliceItem[]}) { + const gutters = useGutters(['base', 'base', 0, 'base']) + return ( + + + {posts.map(post => ( + + + + ))} + + + ) +} diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index a06230fd68..3bc7a669c0 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -33,6 +33,7 @@ import { FeedDescriptor, FeedParams, FeedPostSlice, + FeedPostSliceItem, pollLatest, RQKEY, usePostFeedQuery, @@ -48,6 +49,7 @@ import { SuggestedFollows, VideoModeEntranceInterstitial, } from '#/components/FeedInterstitials' +import {PostFeedVideoGridRow} from '#/components/feeds/PostFeedVideoGridRow' import {TrendingInterstitial} from '#/components/interstitials/Trending' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FeedShutdownMsg} from './FeedShutdownMsg' @@ -77,7 +79,7 @@ type FeedRow = key: string } | { - type: 'slice' + type: 'slice' // TODO can we remove? key: string slice: FeedPostSlice } @@ -88,6 +90,11 @@ type FeedRow = indexInSlice: number showReplyTo: boolean } + | { + type: 'videoGridRow' + key: string + posts: FeedPostSliceItem[] + } | { type: 'sliceViewFullThread' key: string @@ -175,7 +182,7 @@ let PostFeed = ({ const checkForNewRef = React.useRef<(() => void) | null>(null) const lastFetchRef = React.useRef(Date.now()) const [feedType, feedUri, feedTab] = feed.split('|') - const {gtTablet} = useBreakpoints() + const {gtMobile, gtTablet} = useBreakpoints() const opts = React.useMemo( () => ({enabled, ignoreFilterFor}), @@ -318,88 +325,113 @@ let PostFeed = ({ } else if (data) { let sliceIndex = -1 for (const page of data?.pages) { - for (const slice of page.slices) { - sliceIndex++ + if (feedKind === 'thevids') { + if (sliceIndex === -1) { + arr.push({ + type: 'videoModeEntrance', + key: 'videoModeEntrance', + }) + } - if (hasSession) { - if (feedKind === 'discover') { - if (sliceIndex === 0) { - if (showProgressIntersitial) { - arr.push({ - type: 'interstitialProgressGuide', - key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, - }) - } - if (!gtTablet && !trendingDisabled) { - arr.push({ - type: 'interstitialTrending', - key: 'interstitial2-' + sliceIndex + '-' + lastFetchedAt, - }) - } - } else if (sliceIndex === 30) { - arr.push({ - type: 'interstitialFollows', - key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, - }) - } - } else if (feedKind === 'profile') { - if (sliceIndex === 5) { - arr.push({ - type: 'interstitialFollows', - key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, - }) - } - } else if (feedKind === 'thevids') { - if (sliceIndex === 0) { - arr.push({ - type: 'videoModeEntrance', - key: - 'videoModeEntrance-' + sliceIndex + '-' + lastFetchedAt, - }) - } + 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 + const cols = gtMobile ? 3 : 2 + if (i % cols === 0) { + rows.push([root]) + } else { + rows[rows.length - 1].push(root) } } - if (slice.isIncompleteThread && slice.items.length >= 3) { - const beforeLast = slice.items.length - 2 - const last = slice.items.length - 1 + for (const row of rows) { + sliceIndex++ + arr.push({ - type: 'sliceItem', - key: slice.items[0]._reactKey, - slice: slice, - indexInSlice: 0, - showReplyTo: false, + type: 'videoGridRow', + key: row.map(r => r._reactKey).join('-'), + posts: row, }) - arr.push({ - type: 'sliceViewFullThread', - key: slice._reactKey + '-viewFullThread', - uri: slice.items[0].uri, - }) - arr.push({ - type: 'sliceItem', - key: slice.items[beforeLast]._reactKey, - slice: slice, - indexInSlice: beforeLast, - showReplyTo: - slice.items[beforeLast].parentAuthor?.did !== - slice.items[beforeLast].post.author.did, - }) - arr.push({ - type: 'sliceItem', - key: slice.items[last]._reactKey, - slice: slice, - indexInSlice: last, - showReplyTo: false, - }) - } else { - for (let i = 0; i < slice.items.length; i++) { + } + } else { + for (const slice of page.slices) { + sliceIndex++ + + if (hasSession) { + if (feedKind === 'discover') { + if (sliceIndex === 0) { + if (showProgressIntersitial) { + arr.push({ + type: 'interstitialProgressGuide', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } + if (!gtTablet && !trendingDisabled) { + arr.push({ + type: 'interstitialTrending', + key: + 'interstitial2-' + sliceIndex + '-' + lastFetchedAt, + }) + } + } else if (sliceIndex === 30) { + arr.push({ + type: 'interstitialFollows', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } + } else if (feedKind === 'profile') { + if (sliceIndex === 5) { + arr.push({ + type: 'interstitialFollows', + key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt, + }) + } + } + } + + if (slice.isIncompleteThread && slice.items.length >= 3) { + const beforeLast = slice.items.length - 2 + const last = slice.items.length - 1 arr.push({ type: 'sliceItem', - key: slice.items[i]._reactKey, + key: slice.items[0]._reactKey, slice: slice, - indexInSlice: i, - showReplyTo: i === 0, + indexInSlice: 0, + showReplyTo: false, }) + arr.push({ + type: 'sliceViewFullThread', + key: slice._reactKey + '-viewFullThread', + uri: slice.items[0].uri, + }) + arr.push({ + type: 'sliceItem', + key: slice.items[beforeLast]._reactKey, + slice: slice, + indexInSlice: beforeLast, + showReplyTo: + slice.items[beforeLast].parentAuthor?.did !== + slice.items[beforeLast].post.author.did, + }) + arr.push({ + type: 'sliceItem', + key: slice.items[last]._reactKey, + slice: slice, + indexInSlice: last, + showReplyTo: false, + }) + } else { + for (let i = 0; i < slice.items.length; i++) { + arr.push({ + type: 'sliceItem', + key: slice.items[i]._reactKey, + slice: slice, + indexInSlice: i, + showReplyTo: i === 0, + }) + } } } } @@ -432,6 +464,7 @@ let PostFeed = ({ showProgressIntersitial, trendingDisabled, gtTablet, + gtMobile, ]) // events @@ -556,6 +589,8 @@ let PostFeed = ({ ) } else if (row.type === 'sliceViewFullThread') { return + } else if (row.type === 'videoGridRow') { + return } else { return null }