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
}