Bluesky app fork with some witchin' additions 💫

[Video] 🫧 Move logic around by platform (#5003)

authored by hailey.at and committed by

GitHub 5ae0d40a 9aa2b2d1

+77 -95
+1 -1
src/App.native.tsx
··· 52 52 import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' 53 53 import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies' 54 54 import {TestCtrls} from '#/view/com/testing/TestCtrls' 55 - import {ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoContext' 55 + import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoNativeContext' 56 56 import * as Toast from '#/view/com/util/Toast' 57 57 import {Shell} from '#/view/shell' 58 58 import {ThemeProvider as Alf} from '#/alf'
+1 -1
src/App.web.tsx
··· 40 40 import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' 41 41 import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' 42 42 import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies' 43 - import {ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoContext' 43 + import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext' 44 44 import * as Toast from '#/view/com/util/Toast' 45 45 import {ToastContainer} from '#/view/com/util/Toast.web' 46 46 import {Shell} from '#/view/shell/index'
+23 -28
src/view/com/util/post-embeds/ActiveVideoContext.tsx src/view/com/util/post-embeds/ActiveVideoWebContext.tsx
··· 8 8 } from 'react' 9 9 import {useWindowDimensions} from 'react-native' 10 10 11 - import {isNative} from '#/platform/detection' 12 - import {VideoPlayerProvider} from './VideoPlayerContext' 11 + import {isNative, isWeb} from '#/platform/detection' 13 12 14 - const ActiveVideoContext = React.createContext<{ 13 + const Context = React.createContext<{ 15 14 activeViewId: string | null 16 - setActiveView: (viewId: string, src: string) => void 15 + setActiveView: (viewId: string) => void 17 16 sendViewPosition: (viewId: string, y: number) => void 18 17 } | null>(null) 19 18 20 - export function ActiveVideoProvider({children}: {children: React.ReactNode}) { 19 + export function Provider({children}: {children: React.ReactNode}) { 20 + if (!isWeb) { 21 + throw new Error('ActiveVideoWebContext may onl be used on web.') 22 + } 23 + 21 24 const [activeViewId, setActiveViewId] = useState<string | null>(null) 22 25 const activeViewLocationRef = useRef(Infinity) 23 - const [source, setSource] = useState<string | null>(null) 24 26 const {height: windowHeight} = useWindowDimensions() 25 27 26 28 // minimising re-renders by using refs ··· 31 33 }, [activeViewId]) 32 34 33 35 const setActiveView = useCallback( 34 - (viewId: string, src: string) => { 36 + (viewId: string) => { 35 37 setActiveViewId(viewId) 36 - setSource(src) 37 38 manuallySetRef.current = true 38 39 // we don't know the exact position, but it's definitely on screen 39 40 // so just guess that it's in the middle. Any value is fine ··· 88 89 [activeViewId, setActiveView, sendViewPosition], 89 90 ) 90 91 91 - return ( 92 - <ActiveVideoContext.Provider value={value}> 93 - <VideoPlayerProvider source={source ?? ''}> 94 - {children} 95 - </VideoPlayerProvider> 96 - </ActiveVideoContext.Provider> 97 - ) 92 + return <Context.Provider value={value}>{children}</Context.Provider> 98 93 } 99 94 100 - export function useActiveVideoView({source}: {source: string}) { 101 - const context = React.useContext(ActiveVideoContext) 95 + export function useActiveVideoWeb() { 96 + const context = React.useContext(Context) 102 97 if (!context) { 103 - throw new Error('useActiveVideo must be used within a ActiveVideoProvider') 98 + throw new Error( 99 + 'useActiveVideoWeb must be used within a ActiveVideoWebProvider', 100 + ) 104 101 } 102 + 103 + const {activeViewId, setActiveView, sendViewPosition} = context 105 104 const id = useId() 106 105 107 106 return { 108 - active: context.activeViewId === id, 109 - setActive: useCallback( 110 - () => context.setActiveView(id, source), 111 - [context, id, source], 112 - ), 113 - currentActiveView: context.activeViewId, 114 - sendPosition: useCallback( 115 - (y: number) => context.sendViewPosition(id, y), 116 - [context, id], 117 - ), 107 + active: activeViewId === id, 108 + setActive: () => { 109 + setActiveView(id) 110 + }, 111 + currentActiveView: activeViewId, 112 + sendPosition: (y: number) => sendViewPosition(id, y), 118 113 } 119 114 }
+40
src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx
··· 1 + import React from 'react' 2 + import {useVideoPlayer, VideoPlayer} from 'expo-video' 3 + 4 + import {isNative} from '#/platform/detection' 5 + 6 + const Context = React.createContext<{ 7 + activeSource: string | null 8 + setActiveSource: (src: string) => void 9 + player: VideoPlayer 10 + } | null>(null) 11 + 12 + export function Provider({children}: {children: React.ReactNode}) { 13 + if (!isNative) { 14 + throw new Error('ActiveVideoProvider may only be used on native.') 15 + } 16 + 17 + const [activeSource, setActiveSource] = React.useState('') 18 + 19 + const player = useVideoPlayer(activeSource, p => { 20 + p.muted = true 21 + p.loop = true 22 + p.play() 23 + }) 24 + 25 + return ( 26 + <Context.Provider value={{activeSource, setActiveSource, player}}> 27 + {children} 28 + </Context.Provider> 29 + ) 30 + } 31 + 32 + export function useActiveVideoNative() { 33 + const context = React.useContext(Context) 34 + if (!context) { 35 + throw new Error( 36 + 'useActiveVideoNative must be used within a ActiveVideoNativeProvider', 37 + ) 38 + } 39 + return context 40 + }
+8 -5
src/view/com/util/post-embeds/VideoEmbed.tsx
··· 9 9 import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' 10 10 import {VisibilityView} from '../../../../../modules/expo-bluesky-swiss-army' 11 11 import {ErrorBoundary} from '../ErrorBoundary' 12 - import {useActiveVideoView} from './ActiveVideoContext' 12 + import {useActiveVideoNative} from './ActiveVideoNativeContext' 13 13 import * as VideoFallback from './VideoEmbedInner/VideoFallback' 14 14 15 15 export function VideoEmbed({source}: {source: string}) { 16 16 const t = useTheme() 17 - const {active, setActive} = useActiveVideoView({source}) 17 + const {activeSource, setActiveSource} = useActiveVideoNative() 18 + const isActive = source === activeSource 18 19 const {_} = useLingui() 19 20 20 21 const [key, setKey] = useState(0) ··· 40 41 enabled={true} 41 42 onChangeStatus={isActive => { 42 43 if (isActive) { 43 - setActive() 44 + setActiveSource(source) 44 45 } 45 46 }}> 46 - {active ? ( 47 + {isActive ? ( 47 48 <VideoEmbedInnerNative /> 48 49 ) : ( 49 50 <Button 50 51 style={[a.flex_1, t.atoms.bg_contrast_25]} 51 - onPress={setActive} 52 + onPress={() => { 53 + setActiveSource(source) 54 + }} 52 55 label={_(msg`Play video`)} 53 56 variant="ghost" 54 57 color="secondary"
+2 -2
src/view/com/util/post-embeds/VideoEmbed.web.tsx
··· 8 8 } from 'view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb' 9 9 import {atoms as a, useTheme} from '#/alf' 10 10 import {ErrorBoundary} from '../ErrorBoundary' 11 - import {useActiveVideoView} from './ActiveVideoContext' 11 + import {useActiveVideoWeb} from './ActiveVideoWebContext' 12 12 import * as VideoFallback from './VideoEmbedInner/VideoFallback' 13 13 14 14 export function VideoEmbed({source}: {source: string}) { 15 15 const t = useTheme() 16 16 const ref = useRef<HTMLDivElement>(null) 17 17 const {active, setActive, sendPosition, currentActiveView} = 18 - useActiveVideoView({source}) 18 + useActiveVideoWeb() 19 19 const [onScreen, setOnScreen] = useState(false) 20 20 21 21 useEffect(() => {
+2 -2
src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
··· 9 9 import {HITSLOP_30} from '#/lib/constants' 10 10 import {useAppState} from '#/lib/hooks/useAppState' 11 11 import {logger} from '#/logger' 12 - import {useVideoPlayer} from '#/view/com/util/post-embeds/VideoPlayerContext' 12 + import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' 13 13 import {atoms as a, useTheme} from '#/alf' 14 14 import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 15 15 import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' ··· 20 20 import {TimeIndicator} from './TimeIndicator' 21 21 22 22 export function VideoEmbedInnerNative() { 23 - const player = useVideoPlayer() 23 + const {player} = useActiveVideoNative() 24 24 const ref = useRef<VideoView>(null) 25 25 const isScreenFocused = useIsFocused() 26 26 const isAppFocused = useAppState()
-47
src/view/com/util/post-embeds/VideoPlayerContext.tsx
··· 1 - import React, {useContext} from 'react' 2 - import type {VideoPlayer} from 'expo-video' 3 - import {useVideoPlayer as useExpoVideoPlayer} from 'expo-video' 4 - 5 - import {logger} from '#/logger' 6 - import { 7 - AudioCategory, 8 - PlatformInfo, 9 - } from '../../../../../modules/expo-bluesky-swiss-army' 10 - 11 - const VideoPlayerContext = React.createContext<VideoPlayer | null>(null) 12 - 13 - export function VideoPlayerProvider({ 14 - source, 15 - children, 16 - }: { 17 - source: string 18 - children: React.ReactNode 19 - }) { 20 - // eslint-disable-next-line @typescript-eslint/no-shadow 21 - const player = useExpoVideoPlayer(source, player => { 22 - try { 23 - PlatformInfo.setAudioCategory(AudioCategory.Ambient) 24 - PlatformInfo.setAudioActive(false) 25 - 26 - player.loop = true 27 - player.muted = true 28 - player.play() 29 - } catch (err) { 30 - logger.error('Failed to init video player', {safeMessage: err}) 31 - } 32 - }) 33 - 34 - return ( 35 - <VideoPlayerContext.Provider value={player}> 36 - {children} 37 - </VideoPlayerContext.Provider> 38 - ) 39 - } 40 - 41 - export function useVideoPlayer() { 42 - const context = useContext(VideoPlayerContext) 43 - if (!context) { 44 - throw new Error('useVideoPlayer must be used within a VideoPlayerProvider') 45 - } 46 - return context 47 - }
-9
src/view/com/util/post-embeds/VideoPlayerContext.web.tsx
··· 1 - import React from 'react' 2 - 3 - export function VideoPlayerProvider({children}: {children: React.ReactNode}) { 4 - return children 5 - } 6 - 7 - export function useVideoPlayer() { 8 - throw new Error('useVideoPlayer must not be used on web') 9 - }