* 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 9e3f2f4374)

* 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 3d1be4c0f1
Author: Samuel Newman <mozzius@protonmail.com>
Date:   Fri Jan 17 01:08:05 2025 +0000

    extend animation

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

    fix gap

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

    thinner bar, format time

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

    fix 2 in 3 screens

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

    rm unneeded var

commit 7e0e100177
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>
This commit is contained in:
Hailey
2025-01-19 17:17:41 -08:00
committed by GitHub
parent cb02065550
commit 34582edf3e
38 changed files with 3165 additions and 126 deletions
+13 -1
View File
@@ -1,11 +1,12 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs} from '@atproto/api'
import {AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {NavigationProp, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {VIDEO_FEED_URIS} from '#/lib/constants'
import {ComposeIcon2} from '#/lib/icons'
import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers'
import {AllNavigatorParams} from '#/lib/routes/types'
@@ -15,6 +16,7 @@ import {isNative} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
import {useSetHomeBadge} from '#/state/home-badge'
import {SavedFeedSourceInfo} from '#/state/queries/feed'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {truncateAndInvalidate} from '#/state/queries/util'
@@ -39,6 +41,7 @@ export function FeedPage({
renderEmptyState,
renderEndOfFeed,
savedFeedConfig,
feedInfo,
}: {
testID?: string
feed: FeedDescriptor
@@ -48,6 +51,7 @@ export function FeedPage({
renderEmptyState: () => JSX.Element
renderEndOfFeed?: () => JSX.Element
savedFeedConfig?: AppBskyActorDefs.SavedFeed
feedInfo: SavedFeedSourceInfo
}) {
const {hasSession} = useSession()
const {_} = useLingui()
@@ -61,6 +65,13 @@ export function FeedPage({
const scrollElRef = React.useRef<ListMethods>(null)
const [hasNew, setHasNew] = React.useState(false)
const setHomeBadge = useSetHomeBadge()
const isVideoFeed = React.useMemo(() => {
const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri)
const feedIsVideoMode =
feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO
const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode
return isNative && _isVideoFeed
}, [feedInfo])
React.useEffect(() => {
if (isPageFocused) {
@@ -134,6 +145,7 @@ export function FeedPage({
renderEndOfFeed={renderEndOfFeed}
headerOffset={headerOffset}
savedFeedConfig={savedFeedConfig}
isVideoFeed={isVideoFeed}
/>
</FeedFeedbackProvider>
</MainScrollProvider>
@@ -40,7 +40,6 @@ export function PostThreadComposePrompt({
t.atoms.border_contrast_low,
t.atoms.bg,
]}
onPressIn={ios(() => playHaptic('Light'))}
onPress={() => {
onPressCompose()
playHaptic('Light')
+197 -76
View File
@@ -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
}
@@ -80,6 +86,17 @@ type FeedRow =
indexInSlice: number
showReplyTo: boolean
}
| {
type: 'videoGridRowPlaceholder'
key: string
}
| {
type: 'videoGridRow'
key: string
items: FeedPostSliceItem[]
sourceFeedUri: string
feedContexts: (string | undefined)[]
}
| {
type: 'sliceViewFullThread'
key: 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) {
sliceIndex++
if (hasSession) {
if (feedKind === 'discover') {
if (sliceIndex === 0) {
if (showProgressIntersitial) {
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) {
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[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,
})
}
}
}
}
@@ -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,
],
)
+3 -3
View File
@@ -152,6 +152,9 @@ let List = React.forwardRef<ListMethods, ListProps>(
return (
<FlatList_INTERNAL
showsVerticalScrollIndicator={!isAndroid} // overridable
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
{...props}
automaticallyAdjustsScrollIndicatorInsets={
automaticallyAdjustsScrollIndicatorInsets
@@ -166,9 +169,6 @@ let List = React.forwardRef<ListMethods, ListProps>(
onScroll={scrollHandler}
scrollsToTop={!activeLightbox}
scrollEventThrottle={1}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
showsVerticalScrollIndicator={!isAndroid}
style={style}
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
ref={ref}
+1 -1
View File
@@ -69,7 +69,7 @@ let PostCtrls = ({
style?: StyleProp<ViewStyle>
onPressReply: () => void
onPostReply?: (postUri: string | undefined) => void
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const t = useTheme()