about summary refs log tree commit diff
path: root/modules/expo-bluesky-swiss-army/ios
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-08-07 14:45:06 -0700
committerGitHub <noreply@github.com>2024-08-07 14:45:06 -0700
commit1b02f81cb85333462e3a9a42accc05d09aca4f2c (patch)
tree766e80438c1f109a1a7d751e9f04b7f6242f9766 /modules/expo-bluesky-swiss-army/ios
parentfff2c079c2554861764974aaeeb56f79a25ba82a (diff)
downloadvoidsky-1b02f81cb85333462e3a9a42accc05d09aca4f2c.tar.zst
[Video] Visibility detection view (#4741)
Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com>
Diffstat (limited to 'modules/expo-bluesky-swiss-army/ios')
-rw-r--r--modules/expo-bluesky-swiss-army/ios/Visibility/ExpoBlueskyVisibilityViewModule.swift21
-rw-r--r--modules/expo-bluesky-swiss-army/ios/Visibility/VisibilityViewManager.swift86
-rw-r--r--modules/expo-bluesky-swiss-army/ios/Visibility/VisiblityView.swift69
3 files changed, 176 insertions, 0 deletions
diff --git a/modules/expo-bluesky-swiss-army/ios/Visibility/ExpoBlueskyVisibilityViewModule.swift b/modules/expo-bluesky-swiss-army/ios/Visibility/ExpoBlueskyVisibilityViewModule.swift
new file mode 100644
index 000000000..ec12a84af
--- /dev/null
+++ b/modules/expo-bluesky-swiss-army/ios/Visibility/ExpoBlueskyVisibilityViewModule.swift
@@ -0,0 +1,21 @@
+import ExpoModulesCore
+
+public class ExpoBlueskyVisibilityViewModule: Module {
+  public func definition() -> ModuleDefinition {
+    Name("ExpoBlueskyVisibilityView")
+
+    AsyncFunction("updateActiveViewAsync") {
+      VisibilityViewManager.shared.updateActiveView()
+    }
+
+    View(VisibilityView.self) {
+      Events([
+        "onChangeStatus"
+      ])
+
+      Prop("enabled") { (view: VisibilityView, prop: Bool) in
+        view.enabled = prop
+      }
+    }
+  }
+}
diff --git a/modules/expo-bluesky-swiss-army/ios/Visibility/VisibilityViewManager.swift b/modules/expo-bluesky-swiss-army/ios/Visibility/VisibilityViewManager.swift
new file mode 100644
index 000000000..ae8e16868
--- /dev/null
+++ b/modules/expo-bluesky-swiss-army/ios/Visibility/VisibilityViewManager.swift
@@ -0,0 +1,86 @@
+import Foundation
+
+class VisibilityViewManager {
+  static let shared = VisibilityViewManager()
+
+  private let views = NSHashTable<VisibilityView>(options: .weakMemory)
+  private var currentlyActiveView: VisibilityView?
+  private var screenHeight: CGFloat = UIScreen.main.bounds.height
+  private var prevCount = 0
+
+  func addView(_ view: VisibilityView) {
+    self.views.add(view)
+
+    if self.prevCount == 0 {
+      self.updateActiveView()
+    }
+    self.prevCount = self.views.count
+  }
+
+  func removeView(_ view: VisibilityView) {
+    self.views.remove(view)
+    self.prevCount = self.views.count
+  }
+
+  func updateActiveView() {
+    DispatchQueue.main.async {
+      var activeView: VisibilityView?
+
+      if self.views.count == 1 {
+        let view = self.views.allObjects[0]
+        if view.isViewableEnough() {
+          activeView = view
+        }
+      } else if self.views.count > 1 {
+        let views = self.views.allObjects
+        var mostVisibleView: VisibilityView?
+        var mostVisiblePosition: CGRect?
+
+        views.forEach { view in
+          if !view.isViewableEnough() {
+            return
+          }
+
+          guard let position = view.getPositionOnScreen() else {
+            return
+          }
+
+          if position.minY >= 150 {
+            if mostVisiblePosition == nil {
+              mostVisiblePosition = position
+            }
+
+            if let unwrapped = mostVisiblePosition,
+               position.minY <= unwrapped.minY {
+              mostVisibleView = view
+              mostVisiblePosition = position
+            }
+          }
+        }
+
+        activeView = mostVisibleView
+      }
+
+      if activeView == self.currentlyActiveView {
+        return
+      }
+
+      self.clearActiveView()
+      if let view = activeView {
+        self.setActiveView(view)
+      }
+    }
+  }
+
+  private func clearActiveView() {
+    if let currentlyActiveView = self.currentlyActiveView {
+      currentlyActiveView.setIsCurrentlyActive(isActive: false)
+      self.currentlyActiveView = nil
+    }
+  }
+
+  private func setActiveView(_ view: VisibilityView) {
+    view.setIsCurrentlyActive(isActive: true)
+    self.currentlyActiveView = view
+  }
+}
diff --git a/modules/expo-bluesky-swiss-army/ios/Visibility/VisiblityView.swift b/modules/expo-bluesky-swiss-army/ios/Visibility/VisiblityView.swift
new file mode 100644
index 000000000..fd99ee493
--- /dev/null
+++ b/modules/expo-bluesky-swiss-army/ios/Visibility/VisiblityView.swift
@@ -0,0 +1,69 @@
+import ExpoModulesCore
+
+class VisibilityView: ExpoView {
+  var enabled = false {
+    didSet {
+      if enabled {
+        VisibilityViewManager.shared.removeView(self)
+      }
+    }
+  }
+
+  private let onChangeStatus = EventDispatcher()
+  private var isCurrentlyActiveView = false
+
+  required init(appContext: AppContext? = nil) {
+    super.init(appContext: appContext)
+  }
+
+  public override func willMove(toWindow newWindow: UIWindow?) {
+    super.willMove(toWindow: newWindow)
+
+    if !self.enabled {
+      return
+    }
+
+    if newWindow == nil {
+      VisibilityViewManager.shared.removeView(self)
+    } else {
+      VisibilityViewManager.shared.addView(self)
+    }
+  }
+
+  func setIsCurrentlyActive(isActive: Bool) {
+    if isCurrentlyActiveView == isActive {
+      return
+    }
+    self.isCurrentlyActiveView = isActive
+    self.onChangeStatus([
+      "isActive": isActive
+    ])
+  }
+}
+
+// 🚨 DANGER 🚨
+// These functions need to be called from the main thread. Xcode will warn you if you call one of them
+// off the main thread, so pay attention!
+extension UIView {
+  func getPositionOnScreen() -> CGRect? {
+    if let window = self.window {
+      return self.convert(self.bounds, to: window)
+    }
+    return nil
+  }
+
+  func isViewableEnough() -> Bool {
+    guard let window = self.window else {
+      return false
+    }
+
+    let viewFrameOnScreen = self.convert(self.bounds, to: window)
+    let screenBounds = window.bounds
+    let intersection = viewFrameOnScreen.intersection(screenBounds)
+
+    let viewHeight = viewFrameOnScreen.height
+    let intersectionHeight = intersection.height
+
+    return intersectionHeight >= 0.5 * viewHeight
+  }
+}