mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React, {
2 useCallback,
3 useEffect,
4 useId,
5 useMemo,
6 useRef,
7 useState,
8} from 'react'
9import {useWindowDimensions} from 'react-native'
10
11import {isNative, isWeb} from '#/platform/detection'
12
13const Context = React.createContext<{
14 activeViewId: string | null
15 setActiveView: (viewId: string) => void
16 sendViewPosition: (viewId: string, y: number) => void
17} | null>(null)
18
19export function Provider({children}: {children: React.ReactNode}) {
20 if (!isWeb) {
21 throw new Error('ActiveVideoWebContext may only be used on web.')
22 }
23
24 const [activeViewId, setActiveViewId] = useState<string | null>(null)
25 const activeViewLocationRef = useRef(Infinity)
26 const {height: windowHeight} = useWindowDimensions()
27
28 // minimising re-renders by using refs
29 const manuallySetRef = useRef(false)
30 const activeViewIdRef = useRef(activeViewId)
31 useEffect(() => {
32 activeViewIdRef.current = activeViewId
33 }, [activeViewId])
34
35 const setActiveView = useCallback(
36 (viewId: string) => {
37 setActiveViewId(viewId)
38 manuallySetRef.current = true
39 // we don't know the exact position, but it's definitely on screen
40 // so just guess that it's in the middle. Any value is fine
41 // so long as it's not offscreen
42 activeViewLocationRef.current = windowHeight / 2
43 },
44 [windowHeight],
45 )
46
47 const sendViewPosition = useCallback(
48 (viewId: string, y: number) => {
49 if (isNative) return
50
51 if (viewId === activeViewIdRef.current) {
52 activeViewLocationRef.current = y
53 } else {
54 if (
55 distanceToIdealPosition(y) <
56 distanceToIdealPosition(activeViewLocationRef.current)
57 ) {
58 // if the old view was manually set, only usurp if the old view is offscreen
59 if (
60 manuallySetRef.current &&
61 withinViewport(activeViewLocationRef.current)
62 ) {
63 return
64 }
65
66 setActiveViewId(viewId)
67 activeViewLocationRef.current = y
68 manuallySetRef.current = false
69 }
70 }
71
72 function distanceToIdealPosition(yPos: number) {
73 return Math.abs(yPos - windowHeight / 2.5)
74 }
75
76 function withinViewport(yPos: number) {
77 return yPos > 0 && yPos < windowHeight
78 }
79 },
80 [windowHeight],
81 )
82
83 const value = useMemo(
84 () => ({
85 activeViewId,
86 setActiveView,
87 sendViewPosition,
88 }),
89 [activeViewId, setActiveView, sendViewPosition],
90 )
91
92 return <Context.Provider value={value}>{children}</Context.Provider>
93}
94
95export function useActiveVideoWeb() {
96 const context = React.useContext(Context)
97 if (!context) {
98 throw new Error(
99 'useActiveVideoWeb must be used within a ActiveVideoWebProvider',
100 )
101 }
102
103 const {activeViewId, setActiveView, sendViewPosition} = context
104 const id = useId()
105
106 return {
107 active: activeViewId === id,
108 setActive: () => {
109 setActiveView(id)
110 },
111 currentActiveView: activeViewId,
112 sendPosition: (y: number) => sendViewPosition(id, y),
113 }
114}