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) e.GET("/messages/:conversation", server.WebGeneric)
// temp // temp
e.GET("/temp-yolo", server.WebGeneric) e.GET("/temp-vibe", server.WebGeneric)
// profile endpoints; only first populates info // profile endpoints; only first populates info
e.GET("/profile/:handleOrDID", server.WebProfile) 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 {useTheme} from '#/alf'
import {router} from '#/routes' import {router} from '#/routes'
import {Referrer} from '../modules/expo-bluesky-swiss-army' 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 {AboutSettingsScreen} from './screens/Settings/AboutSettings'
import {AccessibilitySettingsScreen} from './screens/Settings/AccessibilitySettings' import {AccessibilitySettingsScreen} from './screens/Settings/AccessibilitySettings'
import {AccountSettingsScreen} from './screens/Settings/AccountSettings' 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}} options={{title: title(msg`Edit your starter pack`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="TempYolo" name="TempVibe"
getComponent={() => YoloScreen} getComponent={() => VibeScreen}
options={{title: title(msg`Yolo mode`), requireAuth: true}} options={{
title: title(msg`Vibe`),
requireAuth: true,
}}
/> />
</> </>
) )
+1 -1
View File
@@ -538,7 +538,7 @@ export function VideoModeEntranceInterstitial() {
</Text> </Text>
<Link <Link
label={_(msg`Start watching`)} label={_(msg`Start watching`)}
to="/temp-yolo" to="/temp-vibe"
size="small" size="small"
// why don't the gradient ones work? // why don't the gradient ones work?
color="secondary_inverted" color="secondary_inverted"
+1 -1
View File
@@ -109,7 +109,7 @@ export const BSKY_FEED_OWNER_DIDS = [
export const DISCOVER_FEED_URI = export const DISCOVER_FEED_URI =
'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' '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' 'at://did:plc:yofh3kx63drvfljkibw5zuxo/app.bsky.feed.generator/thevids'
export const DISCOVER_SAVED_FEED = { export const DISCOVER_SAVED_FEED = {
type: 'feed', type: 'feed',
+1 -1
View File
@@ -57,7 +57,7 @@ export type CommonNavigatorParams = {
StarterPackShort: {code: string} StarterPackShort: {code: string}
StarterPackWizard: undefined StarterPackWizard: undefined
StarterPackEdit: {rkey?: string} StarterPackEdit: {rkey?: string}
TempYolo: undefined TempVibe: undefined
} }
export type BottomTabNavigatorParams = CommonNavigatorParams & { export type BottomTabNavigatorParams = CommonNavigatorParams & {
+1 -1
View File
@@ -65,5 +65,5 @@ export const router = new Router({
StarterPackShort: '/starter-pack-short/:code', StarterPackShort: '/starter-pack-short/:code',
StarterPackWizard: '/starter-pack/create', StarterPackWizard: '/starter-pack/create',
// temp // 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 { import {
ListRenderItem, ListRenderItem,
SafeAreaView,
useWindowDimensions, useWindowDimensions,
View, View,
ViewToken, ViewToken,
} from 'react-native' } 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 {useEvent} from 'expo'
import {BlurView} from 'expo-blur' import {BlurView} from 'expo-blur'
import {useVideoPlayer, VideoPlayer, VideoView} from 'expo-video' import {useVideoPlayer, VideoPlayer, VideoView} from 'expo-video'
import {AppBskyEmbedVideo, AppBskyFeedDefs} from '@atproto/api' import {AppBskyEmbedVideo, AppBskyFeedDefs} from '@atproto/api'
import {Trans} from '@lingui/macro' 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 {NativeStackScreenProps} from '@react-navigation/native-stack'
import {THEVIDS_FEED_URI} from '#/lib/constants' import {VIBES_FEED_URI} from '#/lib/constants'
import {CommonNavigatorParams} from '#/lib/routes/types' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {FeedPostSliceItem, usePostFeedQuery} from '#/state/queries/post-feed' import {FeedPostSliceItem, usePostFeedQuery} from '#/state/queries/post-feed'
import {useSetMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode} from '#/state/shell'
import {useSetLightStatusBar} from '#/state/shell/light-status-bar'
import {List} from '#/view/com/util/List' 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 * as Layout from '#/components/Layout'
import {ListFooter} from '#/components/Lists' import {ListFooter} from '#/components/Lists'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'TempVibe'>
export function YoloScreen({}: Props) { export function VibeScreen({}: Props) {
const {top} = useSafeAreaInsets() const {top} = useSafeAreaInsets()
const t = useTheme()
const [headerHeight, setHeaderHeight] = useState(0) const [headerHeight, setHeaderHeight] = useState(0)
const setMinShellMode = useSetMinimalShellMode() const setMinShellMode = useSetMinimalShellMode()
@@ -40,13 +47,14 @@ export function YoloScreen({}: Props) {
}, [setMinShellMode]), }, [setMinShellMode]),
) )
useSetLightStatusBar(true)
return ( return (
<ThemeProvider theme="dark">
<Layout.Screen noInsetTop style={{backgroundColor: 'black'}}> <Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
<BlurView <BlurView
intensity={100} intensity={25}
tint={ tint="systemThinMaterialDark"
t.scheme === 'dark' ? 'systemMaterialDark' : 'systemMaterialLight'
}
style={[ style={[
a.absolute, a.absolute,
a.z_10, a.z_10,
@@ -59,7 +67,8 @@ export function YoloScreen({}: Props) {
<Layout.Header.BackButton /> <Layout.Header.BackButton />
<Layout.Header.Content> <Layout.Header.Content>
<Layout.Header.TitleText> <Layout.Header.TitleText>
<Trans>Yolo mode</Trans> {/* TODO: needs to be feed name */}
<Trans>Vibes (wip)</Trans>
</Layout.Header.TitleText> </Layout.Header.TitleText>
</Layout.Header.Content> </Layout.Header.Content>
<Layout.Header.Slot /> <Layout.Header.Slot />
@@ -67,10 +76,12 @@ export function YoloScreen({}: Props) {
</BlurView> </BlurView>
<YoloFeed headerHeight={headerHeight} /> <YoloFeed headerHeight={headerHeight} />
</Layout.Screen> </Layout.Screen>
</ThemeProvider>
) )
} }
function YoloFeed({headerHeight}: {headerHeight: number}) { function YoloFeed({headerHeight}: {headerHeight: number}) {
const isFocused = useIsFocused()
const { const {
data, data,
isFetching, isFetching,
@@ -78,7 +89,7 @@ function YoloFeed({headerHeight}: {headerHeight: number}) {
hasNextPage, hasNextPage,
isFetchingNextPage, isFetchingNextPage,
fetchNextPage, fetchNextPage,
} = usePostFeedQuery(`feedgen|${THEVIDS_FEED_URI}`) } = usePostFeedQuery(`feedgen|${VIBES_FEED_URI}`)
const player1 = useVideoPlayer('', p => { const player1 = useVideoPlayer('', p => {
p.loop = true p.loop = true
@@ -106,17 +117,17 @@ function YoloFeed({headerHeight}: {headerHeight: number}) {
const player = [player1, player2, player3][index % 3] const player = [player1, player2, player3][index % 3]
return ( return (
<VideoScreen <VibeItem
player={player} player={player}
post={post} post={post}
embed={post.embed} embed={post.embed}
loaded={Math.abs(index - currentIndex) < 2} loaded={Math.abs(index - currentIndex) < 2}
active={index === currentIndex} active={isFocused && index === currentIndex}
headerHeight={headerHeight} headerHeight={headerHeight}
/> />
) )
}, },
[player1, player2, player3, currentIndex, headerHeight], [player1, player2, player3, currentIndex, headerHeight, isFocused],
) )
const onViewableItemsChanged = useCallback( const onViewableItemsChanged = useCallback(
@@ -159,7 +170,7 @@ function keyExtractor(item: FeedPostSliceItem) {
return item._reactKey return item._reactKey
} }
function VideoScreen({ function VibeItem({
player, player,
embed, embed,
active, active,
@@ -181,17 +192,34 @@ function VideoScreen({
oldSource: {uri?: string} 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(() => { useEffect(() => {
if (loaded && sourceChangeEvent?.source?.uri !== source) { if (loaded && sourceChangeEvent?.source?.uri !== source) {
player.replace(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(() => { useEffect(() => {
if (active) { if (active) {
console.log('play (effect)')
player.play() player.play()
} } else {
return () => { // should be a cleanup function, but that causes a crash
player.pause() player.pause()
} }
}, [active, player]) }, [active, player])
@@ -202,7 +230,9 @@ function VideoScreen({
height, height,
width, width,
}}> }}>
<SafeAreaView style={[a.flex_1, {paddingTop: headerHeight}]}> <SafeAreaView
edges={['left', 'right', 'bottom']}
style={[a.flex_1, {paddingTop: headerHeight}]}>
{active && ( {active && (
<VideoView <VideoView
style={[a.flex_1]} style={[a.flex_1]}
@@ -210,7 +240,51 @@ function VideoScreen({
nativeControls={false} nativeControls={false}
/> />
)} )}
<VibeOverlay player={player} />
</SafeAreaView> </SafeAreaView>
</View> </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 { import {
DISCOVER_FEED_URI, DISCOVER_FEED_URI,
KNOWN_SHUTDOWN_FEEDS, KNOWN_SHUTDOWN_FEEDS,
THEVIDS_FEED_URI, VIBES_FEED_URI,
} from '#/lib/constants' } from '#/lib/constants'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
@@ -287,7 +287,7 @@ let PostFeed = ({
feedKind = 'following' feedKind = 'following'
} else if (feedUri === DISCOVER_FEED_URI) { } else if (feedUri === DISCOVER_FEED_URI) {
feedKind = 'discover' feedKind = 'discover'
} else if (feedUri === THEVIDS_FEED_URI) { } else if (feedUri === VIBES_FEED_URI) {
feedKind = 'thevids' feedKind = 'thevids'
} else if ( } else if (
feedType === 'author' && feedType === 'author' &&