diff options
author | Hailey <me@haileyok.com> | 2024-08-07 14:45:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-07 14:45:06 -0700 |
commit | 1b02f81cb85333462e3a9a42accc05d09aca4f2c (patch) | |
tree | 766e80438c1f109a1a7d751e9f04b7f6242f9766 /modules/expo-bluesky-swiss-army/ios | |
parent | fff2c079c2554861764974aaeeb56f79a25ba82a (diff) | |
download | voidsky-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')
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 + } +} |