about summary refs log tree commit diff
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-11-28 16:55:05 +0000
committerGitHub <noreply@github.com>2024-11-28 16:55:05 +0000
commitd08ce0d473374c0720d018e8fc8ed550ed32544a (patch)
treed564b58ea931538d23897c7b2ce8b69ac54c8580
parent0aee6390fa8faad22608bbc2b0b4352c2af0a2f9 (diff)
downloadvoidsky-d08ce0d473374c0720d018e8fc8ed550ed32544a.tar.zst
Fix stuck lightbox (#6816)
* Fix lightbox getting stuck by fixing rAF order

If you spam opening lightbox too fast, the effect that calls rAF will clean up and set up again midflight. Unfortunately, due to rAF order being unreliable, it may fire in reverse order, causing "open, open, close" instead of "open, close, open", so it would get stuck closed. This fixes the rAF order.

* Don't allow opening another lightbox while it's open
-rw-r--r--src/state/lightbox.tsx10
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx33
2 files changed, 40 insertions, 3 deletions
diff --git a/src/state/lightbox.tsx b/src/state/lightbox.tsx
index 67a450991..3e01d7ef7 100644
--- a/src/state/lightbox.tsx
+++ b/src/state/lightbox.tsx
@@ -31,7 +31,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
 
   const openLightbox = useNonReactiveCallback(
     (lightbox: Omit<Lightbox, 'id'>) => {
-      setActiveLightbox({...lightbox, id: nanoid()})
+      setActiveLightbox(prevLightbox => {
+        if (prevLightbox) {
+          // Ignore duplicate open requests. If it's already open,
+          // the user has to explicitly close the previous one first.
+          return prevLightbox
+        } else {
+          return {...lightbox, id: nanoid()}
+        }
+      })
     },
   )
 
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index e7d158d03..ce59b4b9d 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -111,14 +111,14 @@ export default function ImageViewRoot({
       )
 
     // https://github.com/software-mansion/react-native-reanimated/issues/6677
-    requestAnimationFrame(() => {
+    rAF_FIXED(() => {
       openProgress.set(() =>
         canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1,
       )
     })
     return () => {
       // https://github.com/software-mansion/react-native-reanimated/issues/6677
-      requestAnimationFrame(() => {
+      rAF_FIXED(() => {
         openProgress.set(() =>
           canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0,
         )
@@ -752,3 +752,32 @@ function withClampedSpring(value: any, config: WithSpringConfig) {
   'worklet'
   return withSpring(value, {...config, overshootClamping: true})
 }
+
+// We have to do this because we can't trust RN's rAF to fire in order.
+// https://github.com/facebook/react-native/issues/48005
+let isFrameScheduled = false
+let pendingFrameCallbacks: Array<() => void> = []
+function rAF_FIXED(callback: () => void) {
+  pendingFrameCallbacks.push(callback)
+  if (!isFrameScheduled) {
+    isFrameScheduled = true
+    requestAnimationFrame(() => {
+      const callbacks = pendingFrameCallbacks.slice()
+      isFrameScheduled = false
+      pendingFrameCallbacks = []
+      let hasError = false
+      let error
+      for (let i = 0; i < callbacks.length; i++) {
+        try {
+          callbacks[i]()
+        } catch (e) {
+          hasError = true
+          error = e
+        }
+      }
+      if (hasError) {
+        throw error
+      }
+    })
+  }
+}