about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/components/hooks/useFullscreen.ts53
-rw-r--r--src/view/com/util/post-embeds/VideoEmbed.web.tsx13
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx31
3 files changed, 67 insertions, 30 deletions
diff --git a/src/components/hooks/useFullscreen.ts b/src/components/hooks/useFullscreen.ts
new file mode 100644
index 000000000..498f22223
--- /dev/null
+++ b/src/components/hooks/useFullscreen.ts
@@ -0,0 +1,53 @@
+import {
+  useCallback,
+  useEffect,
+  useRef,
+  useState,
+  useSyncExternalStore,
+} from 'react'
+
+import {isFirefox, isSafari} from '#/lib/browser'
+import {isWeb} from '#/platform/detection'
+
+function fullscreenSubscribe(onChange: () => void) {
+  document.addEventListener('fullscreenchange', onChange)
+  return () => document.removeEventListener('fullscreenchange', onChange)
+}
+
+export function useFullscreen(ref?: React.RefObject<HTMLElement>) {
+  if (!isWeb) throw new Error("'useFullscreen' is a web-only hook")
+  const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
+    Boolean(document.fullscreenElement),
+  )
+  const scrollYRef = useRef<null | number>(null)
+  const [prevIsFullscreen, setPrevIsFullscreen] = useState(isFullscreen)
+
+  const toggleFullscreen = useCallback(() => {
+    if (isFullscreen) {
+      document.exitFullscreen()
+    } else {
+      if (!ref) throw new Error('No ref provided')
+      if (!ref.current) return
+      scrollYRef.current = window.scrollY
+      ref.current.requestFullscreen()
+    }
+  }, [isFullscreen, ref])
+
+  useEffect(() => {
+    if (prevIsFullscreen === isFullscreen) return
+    setPrevIsFullscreen(isFullscreen)
+
+    // Chrome has an issue where it doesn't scroll back to the top after exiting fullscreen
+    // Let's play it safe and do it if not FF or Safari, since anything else will probably be chromium
+    if (prevIsFullscreen && !isFirefox && !isSafari) {
+      setTimeout(() => {
+        if (scrollYRef.current !== null) {
+          window.scrollTo(0, scrollYRef.current)
+          scrollYRef.current = null
+        }
+      }, 100)
+    }
+  }, [isFullscreen, prevIsFullscreen])
+
+  return [isFullscreen, toggleFullscreen] as const
+}
diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
index a25f94641..e96b75926 100644
--- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
@@ -12,6 +12,7 @@ import {
   VideoNotFoundError,
 } from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
 import {atoms as a} from '#/alf'
+import {useFullscreen} from '#/components/hooks/useFullscreen'
 import {ErrorBoundary} from '../ErrorBoundary'
 import {useActiveVideoWeb} from './ActiveVideoWebContext'
 import * as VideoFallback from './VideoEmbedInner/VideoFallback'
@@ -106,6 +107,8 @@ function ViewportObserver({
 }) {
   const ref = useRef<HTMLDivElement>(null)
   const [nearScreen, setNearScreen] = useState(false)
+  const [isFullscreen] = useFullscreen()
+  const [nearScreenOrFullscreen, setNearScreenOrFullscreen] = useState(false)
 
   // Send position when scrolling. This is done with an IntersectionObserver
   // observing a div of 100vh height
@@ -135,9 +138,17 @@ function ViewportObserver({
     }
   }, [isAnyViewActive, sendPosition])
 
+  // disguesting effect - it should be `nearScreen` except when fullscreen
+  // when it should be whatever it was before fullscreen changed
+  useEffect(() => {
+    if (!isFullscreen) {
+      setNearScreenOrFullscreen(nearScreen)
+    }
+  }, [isFullscreen, nearScreen])
+
   return (
     <View style={[a.flex_1, a.flex_row]}>
-      {nearScreen && children}
+      {nearScreenOrFullscreen && children}
       <div
         ref={ref}
         style={{
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
index 555e4298d..a12d04db6 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
@@ -1,10 +1,4 @@
-import React, {
-  useCallback,
-  useEffect,
-  useRef,
-  useState,
-  useSyncExternalStore,
-} from 'react'
+import React, {useCallback, useEffect, useRef, useState} from 'react'
 import {Pressable, View} from 'react-native'
 import {SvgProps} from 'react-native-svg'
 import {msg, Trans} from '@lingui/macro'
@@ -21,6 +15,7 @@ import {
 } from '#/state/preferences'
 import {atoms as a, useTheme, web} from '#/alf'
 import {Button} from '#/components/Button'
+import {useFullscreen} from '#/components/hooks/useFullscreen'
 import {useInteractionState} from '#/components/hooks/useInteractionState'
 import {
   ArrowsDiagonalIn_Stroke2_Corner0_Rounded as ArrowsInIcon,
@@ -850,25 +845,3 @@ function useVideoUtils(ref: React.RefObject<HTMLVideoElement>) {
     canPlay,
   }
 }
-
-function fullscreenSubscribe(onChange: () => void) {
-  document.addEventListener('fullscreenchange', onChange)
-  return () => document.removeEventListener('fullscreenchange', onChange)
-}
-
-function useFullscreen(ref: React.RefObject<HTMLElement>) {
-  const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () =>
-    Boolean(document.fullscreenElement),
-  )
-
-  const toggleFullscreen = useCallback(() => {
-    if (isFullscreen) {
-      document.exitFullscreen()
-    } else {
-      if (!ref.current) return
-      ref.current.requestFullscreen()
-    }
-  }, [isFullscreen, ref])
-
-  return [isFullscreen, toggleFullscreen] as const
-}