right swipe
This commit is contained in:
@@ -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
@@ -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,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
@@ -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,37 +47,41 @@ export function YoloScreen({}: Props) {
|
|||||||
}, [setMinShellMode]),
|
}, [setMinShellMode]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useSetLightStatusBar(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
|
<ThemeProvider theme="dark">
|
||||||
<BlurView
|
<Layout.Screen noInsetTop style={{backgroundColor: 'black'}}>
|
||||||
intensity={100}
|
<BlurView
|
||||||
tint={
|
intensity={25}
|
||||||
t.scheme === 'dark' ? 'systemMaterialDark' : 'systemMaterialLight'
|
tint="systemThinMaterialDark"
|
||||||
}
|
style={[
|
||||||
style={[
|
a.absolute,
|
||||||
a.absolute,
|
a.z_10,
|
||||||
a.z_10,
|
{top: 0, left: 0, right: 0, paddingTop: top},
|
||||||
{top: 0, left: 0, right: 0, paddingTop: top},
|
]}
|
||||||
]}
|
onLayout={({nativeEvent}) =>
|
||||||
onLayout={({nativeEvent}) =>
|
setHeaderHeight(nativeEvent.layout.height)
|
||||||
setHeaderHeight(nativeEvent.layout.height)
|
}>
|
||||||
}>
|
<Layout.Header.Outer>
|
||||||
<Layout.Header.Outer>
|
<Layout.Header.BackButton />
|
||||||
<Layout.Header.BackButton />
|
<Layout.Header.Content>
|
||||||
<Layout.Header.Content>
|
<Layout.Header.TitleText>
|
||||||
<Layout.Header.TitleText>
|
{/* TODO: needs to be feed name */}
|
||||||
<Trans>Yolo mode</Trans>
|
<Trans>Vibes (wip)</Trans>
|
||||||
</Layout.Header.TitleText>
|
</Layout.Header.TitleText>
|
||||||
</Layout.Header.Content>
|
</Layout.Header.Content>
|
||||||
<Layout.Header.Slot />
|
<Layout.Header.Slot />
|
||||||
</Layout.Header.Outer>
|
</Layout.Header.Outer>
|
||||||
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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' &&
|
||||||
|
|||||||
Reference in New Issue
Block a user