about summary refs log tree commit diff
path: root/src/view/com/posts/PostFeed.tsx
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2025-01-19 17:17:41 -0800
committerGitHub <noreply@github.com>2025-01-19 17:17:41 -0800
commit34582edf3ea17789684100172d6dd496220482b0 (patch)
treeca29a927bf015107a60a867d8266274c8a78de49 /src/view/com/posts/PostFeed.tsx
parentcb020655504dd0d39f8e91fd517f14dc4a82c307 (diff)
downloadvoidsky-34582edf3ea17789684100172d6dd496220482b0.tar.zst
yolo (#7499)
* tweaks to constants (#7478)

* add did

* use correct did

* typo

* tweak

* Prevent Drawer gesture conflicting with Suggestions scroll (#7468)

* Extract BlockDrawerGeesture

* Block drawer when scrolling interstitials

(cherry picked from commit 9e3f2f43745eed9c71cb985e48135b7363d91aa9)

* yolo interstitial

* yolo mode

* right swipe

* fix nav gesture

* vibe controls

* collapsible post text

* rm blurview, cover for tall videos

* smarter video source handling

* use thumbnails, improve perf significantly

* better android loading

* improve aspect ratio

* optimize source changes

* rm spinner on ios

* whoops, remove debug `false`

* FIX WRONG VIDEOS SHOWING UP

* don't spring on way down

* release video players when leaving screen

* remove jank animation

* Add grid

* improve contract, fix double tap

* Filter out posts without videos

* Only do grid on native

* Pipe through feedSourceUri and link to feed

* Handle passed through params

* Partial revert, just filter posts to start at index

* Clean up cards, remove entry interstitial

* Tweak handle

* Change constant name

* Rename some things

* Make types legit

* Clean up more naming

* Add placeholder for grid view

* Handle web, set up new organization

* Begin work on Header

* Replace types

* Squashed commit of the following:

commit 3d1be4c0f19789dd3c5a3572ec1acd744a2edb80
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 01:08:05 2025 +0000

    extend animation

commit c9f199413b018efcbd9d8d2a58dd05eb41e7acb7
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 01:01:24 2025 +0000

    fix gap

commit 22e520795f50efda176f21a5e967cb27d0cdd907
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 00:50:16 2025 +0000

    thinner bar, format time

commit c32427f21405294ed3567545629a2964c4af59fe
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 00:47:57 2025 +0000

    fix 2 in 3 screens

commit cbf84c08d64ca0a08ba9070ef5db918f89aa4296
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 00:45:46 2025 +0000

    rm unneeded var

commit 7e0e100177bb1cd0e64c0841bb7685c7f1eb857f
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 00:41:18 2025 +0000

    scrubberrrrr

* use white with opacity rather than gray

* Simultaneous gesture

* cleanup attempt

* fix jank

* link to profile on press

* fix jitter fr this time

* mostly fix android flicker

* Maybe fix row generation

* Add content hider to video card

* emoji in post text

* reduce update rate

* fix type error

* Fix grid layout trailing single item

* Add Discover interstitial, settings, includes pin for now

* Explore interstitial, handle dimissal, pinning, compact card

* Only use grid placeholder on native

* Update events

* Add feature gate

* android nav bar fixes + lower update speed

* fix interval + decel rate on interstitials

* attempt to fix broken scrub on android (not working)

* follow button

* Part out the interstitials for perf, add view more

* Remove prod web route

* Wrap interstitials with BlockDrawerGesture

* Bring video cropping in line with images (#7462)

* Mimic image cropping for videos on web

* Same on native

* Rename variables for clarity

* Fix Android scrubbing

* Add FeedFeedbackProvider

* Remove swipe gesture

* fix light status bar behaviour

* bump

* feedback

* Copy pasta to new location

* Copy pasta part deux

* Filter only videos

* Make whole text clickable to expand

(cherry picked from commit 4cf31120779f4e06eb4c296b3d4b53814d432b07)

* move scrubber to own file

* end card

* add icon to end card

* add min view time to viewability config

* play haptic on like

* tweak feedback

* tweak feedback again

* Moderation

(cherry picked from commit 6b6b471cfb363031284b3e7a1f6e0ade3ac4ae47)

* remove bad check

* fix feedback for new video grid

* change prop name to items as well

* Simplify logic

* Fix mod footer

* Give scrubber more space on android

* Add subtle track behind scrubber, adjust opacity

* wire in feed context again...

* Add better a11y desc to card

* Fix key issue

* Update a11y copy

* Fix scrubber height

* improve scrubber animation

* Make follow button more obvious

* Make header back button more clear

* Disable interactions with actual video el

* keep content away from the bottom safe area

* fix blur

* fix moderation issue

* improve contrast on mod screen

* Make moderation static per item

* Memoize rows

* Optimizations

* Take video moderation into account

* Only blur titles for list blur

* Change copy

* Bump blur radius

* animate text in both directions

* Rm unused field

* Filter by root early

* Refactor for clarity

* add compose prompt to scrubber

* rm log

* tweak gradient

* Bump SDK, use contentMode to power video feed

* Ensure ProfileFeed view also supports video feed

* improve scrubber on android

* rm border from footer

* Update prod video feed did

* Separate caches

* Add lil hover to View More

* Fix undefined logic, remove header for interstitial

* Ungate

* Fix stuckness

* remove extra useless map

* Fix effect cleanup

* Send seen without cleanup

* Simplify react stuff

* Earlier early return to avoid loading flash

* remove scrubber placeholder

* Remove opacity hack

* Render useEvent conditionally

* Fix Android flash

---------

Co-authored-by: dan <dan.abramov@gmail.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: Eric Bailey <git@esb.lol>
Diffstat (limited to 'src/view/com/posts/PostFeed.tsx')
-rw-r--r--src/view/com/posts/PostFeed.tsx271
1 files changed, 196 insertions, 75 deletions
diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx
index f9b2e6e76..554415faf 100644
--- a/src/view/com/posts/PostFeed.tsx
+++ b/src/view/com/posts/PostFeed.tsx
@@ -9,7 +9,7 @@ import {
   View,
   ViewStyle,
 } from 'react-native'
-import {AppBskyActorDefs} from '@atproto/api'
+import {AppBskyActorDefs, AppBskyEmbedVideo} from '@atproto/api'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useQueryClient} from '@tanstack/react-query'
@@ -20,7 +20,7 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {logEvent} from '#/lib/statsig/statsig'
 import {useTheme} from '#/lib/ThemeContext'
 import {logger} from '#/logger'
-import {isIOS, isWeb} from '#/platform/detection'
+import {isIOS, isNative, isWeb} from '#/platform/detection'
 import {listenPostCreated} from '#/state/events'
 import {useFeedFeedbackContext} from '#/state/feed-feedback'
 import {useTrendingSettings} from '#/state/preferences/trending'
@@ -29,18 +29,24 @@ import {
   FeedDescriptor,
   FeedParams,
   FeedPostSlice,
+  FeedPostSliceItem,
   pollLatest,
   RQKEY,
   usePostFeedQuery,
 } from '#/state/queries/post-feed'
 import {useSession} from '#/state/session'
 import {useProgressGuide} from '#/state/shell/progress-guide'
+import {List, ListRef} from '#/view/com/util/List'
+import {PostFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
+import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
 import {useBreakpoints} from '#/alf'
 import {ProgressGuide, SuggestedFollows} from '#/components/FeedInterstitials'
+import {
+  PostFeedVideoGridRow,
+  PostFeedVideoGridRowPlaceholder,
+} from '#/components/feeds/PostFeedVideoGridRow'
 import {TrendingInterstitial} from '#/components/interstitials/Trending'
-import {List, ListRef} from '../util/List'
-import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
-import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
+import {TrendingVideos as TrendingVideosInterstitial} from '#/components/interstitials/TrendingVideos'
 import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
 import {FeedShutdownMsg} from './FeedShutdownMsg'
 import {PostFeedErrorMessage} from './PostFeedErrorMessage'
@@ -69,7 +75,7 @@ type FeedRow =
       key: string
     }
   | {
-      type: 'slice'
+      type: 'slice' // TODO can we remove?
       key: string
       slice: FeedPostSlice
     }
@@ -81,6 +87,17 @@ type FeedRow =
       showReplyTo: boolean
     }
   | {
+      type: 'videoGridRowPlaceholder'
+      key: string
+    }
+  | {
+      type: 'videoGridRow'
+      key: string
+      items: FeedPostSliceItem[]
+      sourceFeedUri: string
+      feedContexts: (string | undefined)[]
+    }
+  | {
       type: 'sliceViewFullThread'
       key: string
       uri: string
@@ -97,12 +114,28 @@ type FeedRow =
       type: 'interstitialTrending'
       key: string
     }
+  | {
+      type: 'interstitialTrendingVideos'
+      key: string
+    }
 
-export function getFeedPostSlice(feedRow: FeedRow): FeedPostSlice | null {
+export function getItemsForFeedback(feedRow: FeedRow):
+  | {
+      item: FeedPostSliceItem
+      feedContext: string | undefined
+    }[] {
   if (feedRow.type === 'sliceItem') {
-    return feedRow.slice
+    return feedRow.slice.items.map(item => ({
+      item,
+      feedContext: feedRow.slice.feedContext,
+    }))
+  } else if (feedRow.type === 'videoGridRow') {
+    return feedRow.items.map((item, i) => ({
+      item,
+      feedContext: feedRow.feedContexts[i],
+    }))
   } else {
-    return null
+    return []
   }
 }
 
@@ -131,6 +164,7 @@ let PostFeed = ({
   extraData,
   savedFeedConfig,
   initialNumToRender: initialNumToRenderOverride,
+  isVideoFeed = false,
 }: {
   feed: FeedDescriptor
   feedParams?: FeedParams
@@ -152,6 +186,7 @@ let PostFeed = ({
   extraData?: any
   savedFeedConfig?: AppBskyActorDefs.SavedFeed
   initialNumToRender?: number
+  isVideoFeed?: boolean
 }): React.ReactNode => {
   const theme = useTheme()
   const {_} = useLingui()
@@ -163,8 +198,10 @@ let PostFeed = ({
   const checkForNewRef = React.useRef<(() => void) | null>(null)
   const lastFetchRef = React.useRef<number>(Date.now())
   const [feedType, feedUri, feedTab] = feed.split('|')
-  const {gtTablet} = useBreakpoints()
+  const {gtMobile, gtTablet} = useBreakpoints()
+  const areVideoFeedsEnabled = isNative
 
+  const feedCacheKey = feedParams?.feedCacheKey
   const opts = React.useMemo(
     () => ({enabled, ignoreFilterFor}),
     [enabled, ignoreFilterFor],
@@ -267,10 +304,10 @@ let PostFeed = ({
   const showProgressIntersitial =
     (followProgressGuide || followAndLikeProgressGuide) && !isDesktop
 
-  const {trendingDisabled} = useTrendingSettings()
+  const {trendingDisabled, trendingVideoDisabled} = useTrendingSettings()
 
   const feedItems: FeedRow[] = React.useMemo(() => {
-    let feedKind: 'following' | 'discover' | 'profile' | undefined
+    let feedKind: 'following' | 'discover' | 'profile' | 'thevids' | undefined
     if (feedType === 'following') {
       feedKind = 'following'
     } else if (feedUri === DISCOVER_FEED_URI) {
@@ -303,81 +340,132 @@ let PostFeed = ({
         })
       } else if (data) {
         let sliceIndex = -1
-        for (const page of data?.pages) {
-          for (const slice of page.slices) {
+
+        if (isVideoFeed) {
+          const videos: {
+            item: FeedPostSliceItem
+            feedContext: string | undefined
+          }[] = []
+          for (const page of data.pages) {
+            for (const slice of page.slices) {
+              const item = slice.items.at(0)
+              if (item && AppBskyEmbedVideo.isView(item.post.embed)) {
+                videos.push({item, feedContext: slice.feedContext})
+              }
+            }
+          }
+
+          const rows: {
+            item: FeedPostSliceItem
+            feedContext: string | undefined
+          }[][] = []
+          for (let i = 0; i < videos.length; i++) {
+            const video = videos[i]
+            const item = video.item
+            const cols = gtMobile ? 3 : 2
+            const rowItem = {item, feedContext: video.feedContext}
+            if (i % cols === 0) {
+              rows.push([rowItem])
+            } else {
+              rows[rows.length - 1].push(rowItem)
+            }
+          }
+
+          for (const row of rows) {
             sliceIndex++
+            arr.push({
+              type: 'videoGridRow',
+              key: row.map(r => r.item._reactKey).join('-'),
+              items: row.map(r => r.item),
+              sourceFeedUri: feedUri,
+              feedContexts: row.map(r => r.feedContext),
+            })
+          }
+        } else {
+          for (const page of data?.pages) {
+            for (const slice of page.slices) {
+              sliceIndex++
 
-            if (hasSession) {
-              if (feedKind === 'discover') {
-                if (sliceIndex === 0) {
-                  if (showProgressIntersitial) {
+              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 === 15) {
+                    if (areVideoFeedsEnabled && !trendingVideoDisabled) {
+                      arr.push({
+                        type: 'interstitialTrendingVideos',
+                        key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
+                      })
+                    }
+                  } else if (sliceIndex === 30) {
                     arr.push({
-                      type: 'interstitialProgressGuide',
+                      type: 'interstitialFollows',
                       key: 'interstitial-' + sliceIndex + '-' + lastFetchedAt,
                     })
                   }
-                  if (!gtTablet && !trendingDisabled) {
+                } else if (feedKind === 'profile') {
+                  if (sliceIndex === 5) {
                     arr.push({
-                      type: 'interstitialTrending',
-                      key: 'interstitial2-' + sliceIndex + '-' + lastFetchedAt,
+                      type: 'interstitialFollows',
+                      key: 'interstitial-' + 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[0]._reactKey,
-                slice: slice,
-                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++) {
+              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[0]._reactKey,
+                  slice: slice,
+                  indexInSlice: 0,
+                  showReplyTo: false,
+                })
+                arr.push({
+                  type: 'sliceViewFullThread',
+                  key: slice._reactKey + '-viewFullThread',
+                  uri: slice.items[0].uri,
+                })
                 arr.push({
                   type: 'sliceItem',
-                  key: slice.items[i]._reactKey,
+                  key: slice.items[beforeLast]._reactKey,
                   slice: slice,
-                  indexInSlice: i,
-                  showReplyTo: i === 0,
+                  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,
+                  })
+                }
               }
             }
           }
@@ -390,10 +478,17 @@ let PostFeed = ({
         })
       }
     } else {
-      arr.push({
-        type: 'loading',
-        key: 'loading',
-      })
+      if (isVideoFeed) {
+        arr.push({
+          type: 'videoGridRowPlaceholder',
+          key: 'videoGridRowPlaceholder',
+        })
+      } else {
+        arr.push({
+          type: 'loading',
+          key: 'loading',
+        })
+      }
     }
 
     return arr
@@ -409,7 +504,11 @@ let PostFeed = ({
     hasSession,
     showProgressIntersitial,
     trendingDisabled,
+    trendingVideoDisabled,
     gtTablet,
+    gtMobile,
+    isVideoFeed,
+    areVideoFeedsEnabled,
   ])
 
   // events
@@ -498,6 +597,8 @@ let PostFeed = ({
         return <ProgressGuide />
       } else if (row.type === 'interstitialTrending') {
         return <TrendingInterstitial />
+      } else if (row.type === 'interstitialTrendingVideos') {
+        return <TrendingVideosInterstitial />
       } else if (row.type === 'sliceItem') {
         const slice = row.slice
         if (slice.isFallbackMarker) {
@@ -532,6 +633,25 @@ let PostFeed = ({
         )
       } else if (row.type === 'sliceViewFullThread') {
         return <ViewFullThread uri={row.uri} />
+      } else if (row.type === 'videoGridRowPlaceholder') {
+        return (
+          <View>
+            <PostFeedVideoGridRowPlaceholder />
+            <PostFeedVideoGridRowPlaceholder />
+            <PostFeedVideoGridRowPlaceholder />
+          </View>
+        )
+      } else if (row.type === 'videoGridRow') {
+        return (
+          <PostFeedVideoGridRow
+            items={row.items}
+            sourceContext={{
+              type: 'feedgen',
+              uri: row.sourceFeedUri,
+              sourceInterstitial: feedCacheKey ?? 'none',
+            }}
+          />
+        )
       } else {
         return null
       }
@@ -545,6 +665,7 @@ let PostFeed = ({
       _,
       onPressRetryLoadMore,
       feedUri,
+      feedCacheKey,
     ],
   )