right swipe

This commit is contained in:
Samuel Newman
2025-01-15 14:35:25 +00:00
parent dfc4214133
commit d459e4032d
8 changed files with 133 additions and 56 deletions
+1 -1
View File
@@ -273,7 +273,7 @@ func serve(cctx *cli.Context) error {
e.GET("/messages/:conversation", server.WebGeneric)
// temp
e.GET("/temp-yolo", server.WebGeneric)
e.GET("/temp-vibe", server.WebGeneric)
// profile endpoints; only first populates info
e.GET("/profile/:handleOrDID", server.WebProfile)
+7 -4
View File
@@ -89,7 +89,7 @@ import {Wizard} from '#/screens/StarterPack/Wizard'
import {useTheme} from '#/alf'
import {router} from '#/routes'
import {Referrer} from '../modules/expo-bluesky-swiss-army'
import {YoloScreen} from './screens/Feeds/YoloScreen'
import {VibeScreen} from './screens/Feeds/VibeScreen'
import {AboutSettingsScreen} from './screens/Settings/AboutSettings'
import {AccessibilitySettingsScreen} from './screens/Settings/AccessibilitySettings'
import {AccountSettingsScreen} from './screens/Settings/AccountSettings'
@@ -424,9 +424,12 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
options={{title: title(msg`Edit your starter pack`), requireAuth: true}}
/>
<Stack.Screen
name="TempYolo"
getComponent={() => YoloScreen}
options={{title: title(msg`Yolo mode`), requireAuth: true}}
name="TempVibe"
getComponent={() => VibeScreen}
options={{
title: title(msg`Vibe`),
requireAuth: true,
}}
/>
</>
)
+1 -1
View File
@@ -538,7 +538,7 @@ export function VideoModeEntranceInterstitial() {
</Text>
<Link
label={_(msg`Start watching`)}
to="/temp-yolo"
to="/temp-vibe"
size="small"
// why don't the gradient ones work?
color="secondary_inverted"
+1 -1
View File
@@ -109,7 +109,7 @@ export const BSKY_FEED_OWNER_DIDS = [
export const DISCOVER_FEED_URI =
'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot'
export const THEVIDS_FEED_URI =
export const VIBES_FEED_URI =
'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids'
export const DISCOVER_SAVED_FEED = {
type: 'feed',
+1 -1
View File
@@ -57,7 +57,7 @@ export type CommonNavigatorParams = {
StarterPackShort: {code: string}
StarterPackWizard: undefined
StarterPackEdit: {rkey?: string}
TempYolo: undefined
TempVibe: undefined
}
export type BottomTabNavigatorParams = CommonNavigatorParams & {
+1 -1
View File
@@ -65,5 +65,5 @@ export const router = new Router({
StarterPackShort: '/starter-pack-short/:code',
StarterPackWizard: '/starter-pack/create',
// temp
TempYolo: '/temp-yolo',
TempVibe: '/temp-vibe',
})
@@ -1,33 +1,40 @@
import {useCallback, useEffect, useState} from 'react'
import {useCallback, useEffect, useMemo, useState} from 'react'
import {
ListRenderItem,
SafeAreaView,
useWindowDimensions,
View,
ViewToken,
} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
import {runOnJS} from 'react-native-reanimated'
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
import {useEvent} from 'expo'
import {BlurView} from 'expo-blur'
import {useVideoPlayer, VideoPlayer, VideoView} from 'expo-video'
import {AppBskyEmbedVideo, AppBskyFeedDefs} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {useFocusEffect} from '@react-navigation/native'
import {
useFocusEffect,
useIsFocused,
useNavigation,
} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {THEVIDS_FEED_URI} from '#/lib/constants'
import {CommonNavigatorParams} from '#/lib/routes/types'
import {VIBES_FEED_URI} from '#/lib/constants'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {FeedPostSliceItem, usePostFeedQuery} from '#/state/queries/post-feed'
import {useSetMinimalShellMode} from '#/state/shell'
import {useSetLightStatusBar} from '#/state/shell/light-status-bar'
import {List} from '#/view/com/util/List'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, ThemeProvider} from '#/alf'
import {Button} from '#/components/Button'
import * as Layout from '#/components/Layout'
import {ListFooter} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
export function YoloScreen({}: Props) {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'TempVibe'>
export function VibeScreen({}: Props) {
const {top} = useSafeAreaInsets()
const t = useTheme()
const [headerHeight, setHeaderHeight] = useState(0)
const setMinShellMode = useSetMinimalShellMode()
@@ -40,37 +47,41 @@ export function YoloScreen({}: Props) {
}, [setMinShellMode]),
)
useSetLightStatusBar(true)
return (
<Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
<BlurView
intensity={100}
tint={
t.scheme === 'dark' ? 'systemMaterialDark' : 'systemMaterialLight'
}
style={[
a.absolute,
a.z_10,
{top: 0, left: 0, right: 0, paddingTop: top},
]}
onLayout={({nativeEvent}) =>
setHeaderHeight(nativeEvent.layout.height)
}>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Yolo mode</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
</BlurView>
<YoloFeed headerHeight={headerHeight} />
</Layout.Screen>
<ThemeProvider theme="dark">
<Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
<BlurView
intensity={25}
tint="systemThinMaterialDark"
style={[
a.absolute,
a.z_10,
{top: 0, left: 0, right: 0, paddingTop: top},
]}
onLayout={({nativeEvent}) =>
setHeaderHeight(nativeEvent.layout.height)
}>
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
{/* TODO: needs to be feed name */}
<Trans>Vibes (wip)</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
</BlurView>
<YoloFeed headerHeight={headerHeight} />
</Layout.Screen>
</ThemeProvider>
)
}
function YoloFeed({headerHeight}: {headerHeight: number}) {
const isFocused = useIsFocused()
const {
data,
isFetching,
@@ -78,7 +89,7 @@ function YoloFeed({headerHeight}: {headerHeight: number}) {
hasNextPage,
isFetchingNextPage,
fetchNextPage,
} = usePostFeedQuery(`feedgen|${THEVIDS_FEED_URI}`)
} = usePostFeedQuery(`feedgen|${VIBES_FEED_URI}`)
const player1 = useVideoPlayer('', p => {
p.loop = true
@@ -106,17 +117,17 @@ function YoloFeed({headerHeight}: {headerHeight: number}) {
const player = [player1, player2, player3][index % 3]
return (
<VideoScreen
<VibeItem
player={player}
post={post}
embed={post.embed}
loaded={Math.abs(index - currentIndex) < 2}
active={index === currentIndex}
active={isFocused && index === currentIndex}
headerHeight={headerHeight}
/>
)
},
[player1, player2, player3, currentIndex, headerHeight],
[player1, player2, player3, currentIndex, headerHeight, isFocused],
)
const onViewableItemsChanged = useCallback(
@@ -159,7 +170,7 @@ function keyExtractor(item: FeedPostSliceItem) {
return item._reactKey
}
function VideoScreen({
function VibeItem({
player,
embed,
active,
@@ -181,17 +192,34 @@ function VideoScreen({
oldSource: {uri?: string}
}
// for initial video - useEffect will handle the typical case where
// videos have a chance to preload
const maybePlay = useNonReactiveCallback(() => {
if (active && !player.playing) {
console.log('play (nonreactive)')
player.play()
}
})
useEffect(() => {
if (loaded && sourceChangeEvent?.source?.uri !== source) {
player.replace(source)
// play next tick
const timeout = setTimeout(() => {
maybePlay()
}, 0)
return () => {
clearTimeout(timeout)
}
}
}, [sourceChangeEvent?.source?.uri, loaded, source, player])
}, [sourceChangeEvent?.source?.uri, loaded, source, player, maybePlay])
useEffect(() => {
if (active) {
console.log('play (effect)')
player.play()
}
return () => {
} else {
// should be a cleanup function, but that causes a crash
player.pause()
}
}, [active, player])
@@ -202,7 +230,9 @@ function VideoScreen({
height,
width,
}}>
<SafeAreaView style={[a.flex_1, {paddingTop: headerHeight}]}>
<SafeAreaView
edges={['left', 'right', 'bottom']}
style={[a.flex_1, {paddingTop: headerHeight}]}>
{active && (
<VideoView
style={[a.flex_1]}
@@ -210,7 +240,51 @@ function VideoScreen({
nativeControls={false}
/>
)}
<VibeOverlay player={player} />
</SafeAreaView>
</View>
)
}
function VibeOverlay({player}: {player: VideoPlayer}) {
const navigation = useNavigation<NavigationProp>()
const pushToProfile = useNonReactiveCallback(() => {
navigation.navigate('Profile', {name: 'lulaoficial.bsky.social'})
})
const togglePlayPause = () => {
if (player.playing) {
player.pause()
} else {
player.play()
}
}
const gesture = useMemo(() => {
const dragLeftGesture = Gesture.Pan()
.onEnd(evt => {
'worklet'
if (evt.translationX < -100) {
runOnJS(pushToProfile)()
}
})
.activeOffsetX([-10, 10])
.failOffsetY([-10, 10])
.maxPointers(1)
return dragLeftGesture
}, [pushToProfile])
return (
<View style={[a.absolute, a.inset_0]}>
<GestureDetector gesture={gesture}>
<Button
label="Toggle play/pause"
onPress={togglePlayPause}
style={[a.flex_1]}>
<View />
</Button>
</GestureDetector>
</View>
)
}
+2 -2
View File
@@ -17,7 +17,7 @@ import {useQueryClient} from '@tanstack/react-query'
import {
DISCOVER_FEED_URI,
KNOWN_SHUTDOWN_FEEDS,
THEVIDS_FEED_URI,
VIBES_FEED_URI,
} from '#/lib/constants'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
@@ -287,7 +287,7 @@ let PostFeed = ({
feedKind = 'following'
} else if (feedUri === DISCOVER_FEED_URI) {
feedKind = 'discover'
} else if (feedUri === THEVIDS_FEED_URI) {
} else if (feedUri === VIBES_FEED_URI) {
feedKind = 'thevids'
} else if (
feedType === 'author' &&