Skip to content

feat: React Native 0.83 + New Architecture support (iOS & Android)#28

Open
enahum wants to merge 11 commits intomasterfrom
new-arch
Open

feat: React Native 0.83 + New Architecture support (iOS & Android)#28
enahum wants to merge 11 commits intomasterfrom
new-arch

Conversation

@enahum
Copy link
Copy Markdown
Contributor

@enahum enahum commented May 6, 2026

Summary

  • Adds New Architecture (Fabric/TurboModules) support on Android
  • Adds New Architecture support on iOS and removes the React Native Navigation (RNN) dependency
  • Migrates the iOS example AppDelegate from Objective-C (.mm/.h/main.m) to Swift, using the new RCTReactNativeFactory + RCTDefaultReactNativeFactoryDelegate pattern required by RN 0.83
  • Removes the legacy ObjC/Swift bridging header and placeholder File.swift

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1665f960-e5da-4ed9-897d-b2e3cc5ab8b0

📥 Commits

Reviewing files that changed from the base of the PR and between b1332d8 and c142266.

📒 Files selected for processing (1)
  • ios/ScreenCaptureManager.swift

📝 Walkthrough

Walkthrough

Reworks iOS screen-capture protection into a policy-driven ScreenCaptureManager using AnimatedBlurEffectView; removes legacy runtime swizzles/extensions. Exposes Android blur methods with non-positive radius guard and scales radius by 10. Modernizes the example app (Swift, RN/Gradle/Pods/Metro/tooling), upgrades tooling/deps, and tweaks TypeScript/ignore settings.

Changes

iOS Screen-capture & Blur Redesign

Layer / File(s) Summary
Component
ios/AnimatedBlurEffectView.swift
Adds AnimatedBlurEffectView (UIVisualEffectView) with blurIntensity, blurStyle, animator and setupBlur() to animate/apply blur.
Core Manager
ios/ScreenCaptureManager.swift
Introduces state (blurEffectView, protectionTextField, originalParent, isAuthenticating, blurOnAuthenticate); replaces getKeyWindow() with getLastKeyWindow(); adds policy methods (applyScreenCapturePolicy, removeScreenCapturePolicy, setScreenCapturePolicy), lifecycle handlers, and public APIs: conditionalApplyBlurEffect, conditionalRemoveBlurEffect, applyBlurEffect(intensity:animated:), removeBlurEffect().
Native Bridge
ios/Emm.mm, ios/Emm.swift
Bridge now calls ScreenCaptureManager APIs with intensity/animated and simple remove; Emm.swift registers additional lifecycle observers and routes removals through conditional paths.
Cleanup (removed swizzles/extensions)
ios/Extensions/*
ios/Extensions/UINavigationController+Swizzle.swift, ios/Extensions/UIView+Shield.swift, ios/Extensions/UIViewController+Shield.swift, ios/Extensions/UIViewController+Swizzle.swift, ios/Extensions/UIWindow+Swizzle.swift
Deletes legacy swizzling and shield extensions previously implementing screen-capture protection; functionality consolidated into ScreenCaptureManager/AnimatedBlurEffectView.

Android: Blur API Exposure & Behavior

Layer / File(s) Summary
Core Blur Logic
android/src/main/java/com/mattermost/emm/EmmModuleImpl.kt
Adds early-return guard: if radius <= 0 then call removeBlurEffect() and return on UI thread; when applying blur, sets blur radius via radius.toFloat() * 10f.
Module Bridge
android/src/oldarch/java/com/EmmModule.kt
Changes anonymous ActivityEventListener.onActivityResult to take non-null Activity; adds @ReactMethod applyBlurEffect(radius: Double) and removeBlurEffect() delegating to implementation.

Example App Modernization & Tooling

Layer / File(s) Summary
Dependencies / Tooling
package.json, example/package.json, example/Gemfile
Bumps React to 19.2.5 and React Native to 0.83.9; upgrades devDependencies and adjusts Gemfile/CocoaPods constraints.
Android Build & Host
example/android/build.gradle, example/android/app/build.gradle, example/android/gradle/wrapper/gradle-wrapper.properties, example/android/gradlew, example/android/gradlew.bat, example/android/app/.../MainApplication.kt
Updates buildTools/compile/target SDK to 36, NDK and Kotlin versions, Gradle wrapper to 9.0.0, switches JSC to international flavor, and migrates host setup to getDefaultReactHost/loadReactNative.
iOS Project Migration
example/ios/ReactNativeEmmExample.xcodeproj/..., example/ios/ReactNativeEmmExample/AppDelegate.swift, example/ios/ReactNativeEmmExample/Info.plist, deleted Obj‑C sources
Migrates example app to a Swift AppDelegate, removes Obj‑C AppDelegate/main/bridging header, updates PBX to use Pods frameworks and Swift build settings, and adds Info.plist keys.
Metro / Babel / Podfile
example/metro.config.js, example/babel.config.js, example/ios/Podfile
Replaces Metro blacklist-based aliasing with resolveRequest + blockList; simplifies Babel config to presets-only; adds Podfile env flags (RCT_USE_RN_DEP, RCT_USE_PREBUILT_RNCORE, USE_FRAMEWORKS='static').

Repo config & TypeScript tweaks

Layer / File(s) Summary
Ignore / Types
.gitignore, src/context.tsx
Adds .kotlin ignore entry; updates withManagedConfig generic bound from JSX.IntrinsicAttributes to React.JSX.IntrinsicAttributes.
TS Config
tsconfig.json
Sets rootDir: "./src", changes moduleResolution to "bundler", and adds exclude: ["example"].

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant RNApp as React Native App
    participant Emm as Emm Module (bridge)
    participant SCM as ScreenCaptureManager
    participant Blur as AnimatedBlurEffectView
    participant UI as Window/UI Layer

    User->>RNApp: request blur (radius)
    RNApp->>Emm: applyBlurEffect(radius)
    Emm->>SCM: applyBlurEffect(intensity/animated)
    alt intensity > 0
        SCM->>Blur: create/configure AnimatedBlurEffectView(intensity)
        Blur->>UI: insert overlay into last key window
        Blur->>Blur: setupBlur() animate to fractionComplete
    else intensity <= 0
        SCM->>SCM: removeBlurEffect()
        SCM->>UI: remove overlay
    end
    Note over SCM,RNApp: lifecycle events -> conditionalApply/conditionalRemove
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main objective of the pull request: adding React Native 0.83 support with New Architecture for both iOS and Android platforms.
Description check ✅ Passed The description is directly related to the changeset, detailing the key updates: New Architecture support, iOS AppDelegate migration to Swift, and removal of RNN dependency and legacy files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new-arch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Microsoft Presidio Analyzer (2.2.362)
ios/ScreenCaptureManager.swift

Microsoft Presidio Analyzer failed to scan this file


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/src/main/java/com/mattermost/emm/EmmModuleImpl.kt (1)

282-301: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the 10f multiplier on blur radius.

The setBlurRadius(radius.toFloat() * 10f) call at line 301 rescales the public parameter unexpectedly. Since authenticate() passes 8.0, it becomes 80f—far outside BlurView's typical range of 10–30f used in its examples and demo code. This changes the API contract silently; the radius parameter should be passed as-is to match library expectations and preserve the intended blur intensity.

Proposed fix
-                        .setBlurRadius(radius.toFloat() * 10f)
+                        .setBlurRadius(radius.toFloat())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/src/main/java/com/mattermost/emm/EmmModuleImpl.kt` around lines 282 -
301, The blur radius is being multiplied by 10 when configuring the BlurView
which unexpectedly rescales the public parameter; in EmmModuleImpl.kt locate the
BlurView setup block (where blurView is created and setupWith(rootView,
blurAlgorithm) is called) and change the setBlurRadius call to pass the radius
directly (use radius.toFloat()) instead of radius.toFloat() * 10f so the API
contract and expected intensity from authenticate() are preserved.
🧹 Nitpick comments (2)
ios/AnimatedBlurEffectView.swift (1)

22-31: 💤 Low value

Consider stopping the animator on deinit.

The UIViewPropertyAnimator should be explicitly stopped when the view is deallocated to ensure clean resource release. While the [weak self] capture prevents retain cycles, an active animator referencing a deallocated view could lead to unexpected behaviour.

🛡️ Proposed fix to add cleanup
+  deinit {
+    animator.stopAnimation(true)
+  }
+
   func setupBlur() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/AnimatedBlurEffectView.swift` around lines 22 - 31, Add a deinit to
AnimatedBlurEffectView that stops and finalizes the UIViewPropertyAnimator to
avoid stray animations when the view is deallocated: in deinit call
animator.stopAnimation(true), then animator.finishAnimation(at: .current) (or
.end if you want to finalize to end state), and then clear any strong reference
(e.g., set animator = nil) so the animator releases; update any related teardown
logic if you have a different animator property name.
example/package.json (1)

13-13: ⚡ Quick win

Move prettier to devDependencies.

prettier is a code formatting tool and should be a development dependency, not a runtime dependency.

♻️ Proposed fix
   "dependencies": {
     "@mattermost/react-native-emm": "../",
-    "prettier": "3.8.3",
     "react": "19.2.5",
     "react-native": "0.83.9"
   },
   "devDependencies": {
     "@babel/core": "7.29.0",
     "@babel/runtime": "7.29.2",
+    "prettier": "3.8.3",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/package.json` at line 13, The package.json currently lists
"prettier": "3.8.3" as a runtime dependency; remove that entry from dependencies
and add the same version under devDependencies (or run npm install --save-dev
prettier@3.8.3) so Prettier is a development-only dependency; update
package.json's dependencies/devDependencies accordingly and run the package
manager to update the lockfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@example/android/gradle/wrapper/gradle-wrapper.properties`:
- Line 3: The Gradle wrapper was bumped to 9.0.0 which is incompatible with
React Native 0.83.9's AGP 8.12.0; either revert the wrapper to a compatible 8.x
distribution (e.g., change distributionUrl in gradle-wrapper.properties back to
gradle-8.13-bin.zip) or instead update the Android Gradle Plugin to 9.0+ across
the project (adjust AGP version in the Gradle configuration / libs.versions.toml
and any RN-specific overrides) so AGP and the distributionUrl match; ensure
distributionUrl and AGP versions are consistent before merging.

In `@example/android/gradlew`:
- Line 117: The CLASSPATH variable in the POSIX gradle wrapper is assigned the
escaped-quote literal (CLASSPATH="\\\"\\\"") which propagates into the Java
args; change the assignment to an actual empty string (CLASSPATH="") or unset it
so it does not insert stray quotes into the Java command used when invoking the
wrapper (the CLASSPATH variable referenced in the wrapper invocation that builds
the Java args / -classpath or -jar gradle-wrapper.jar). Ensure any later
concatenation or use of CLASSPATH in the invocation logic treats an empty value
correctly (no extra quoting or literal backslashes).

In `@example/metro.config.js`:
- Around line 27-35: The current resolveRequest implementation may return a
non-existent base path when no extension matches; update the logic in
resolveRequest (the block handling moduleName === pak.name) to check whether the
computed filePath actually exists and, if not, either throw a clear error or log
a warning and delegate to the default resolver; specifically, after computing
base and extensions and attempting to find an existing file, if no file is found
(filePath === base or fs.existsSync(filePath) is false) throw a descriptive
Error that includes pak.name and base (or call processLogger.warn and return
context.resolveRequest(context, moduleName, platform)) so consumers don’t get an
invalid filePath.

In `@ios/Emm.swift`:
- Around line 17-23: The invalidate() method currently only removes observers
for self but captureEvents() registered observers on
ScreenCaptureManager.shared, causing leaked/duplicate observers; update
invalidate() to also remove the observers added on ScreenCaptureManager.shared
by calling
NotificationCenter.default.removeObserver(ScreenCaptureManager.shared) or
removing the specific notifications (UIApplication.willResignActiveNotification
and UIApplication.didBecomeActiveNotification) for ScreenCaptureManager.shared,
and keep the existing conditionalRemoveBlurEffect(forced: true) call; locate the
registration calls in captureEvents() and mirror their removal in invalidate().

In `@ios/ScreenCaptureManager.swift`:
- Around line 11-16: The didSet for preventScreenCapture currently only calls
setScreenCapturePolicy(), but must also re-register the capture listener so
screenCaptureStatusChange() receives UIScreen.capturedDidChangeNotification
updates; modify the preventScreenCapture.didSet to call listenForScreenCapture()
(or remove/rehook the notification observer via the same helper) whenever the
flag flips, ensuring listenForScreenCapture() subscribes/unsubscribes
appropriately and that setScreenCapturePolicy() continues to apply/remove layer
protection across view controllers.
- Around line 24-25: The selectors registered for
UIScreen.capturedDidChangeNotification and app lifecycle notifications expect
methods that accept a Notification, but conditionalApplyBlurEffect(intensity:)
and conditionalRemoveBlurEffect(forced:) have mismatched signatures; add `@objc`
wrapper methods like conditionalApplyBlurEffectNotification(_ notification:
Notification) and conditionalRemoveBlurEffectNotification(_ notification:
Notification) that extract any needed state (or call the public helpers with
default/derived arguments) and invoke conditionalApplyBlurEffect(intensity:) and
conditionalRemoveBlurEffect(forced:) respectively, then update the
NotificationCenter.addObserver calls (for
UIScreen.capturedDidChangeNotification,
UIApplication.willResignActiveNotification, and
UIApplication.didBecomeActiveNotification) to use these new wrapper selector
names so the selector signatures match NotificationCenter’s required (_
notification: Notification) form.

---

Outside diff comments:
In `@android/src/main/java/com/mattermost/emm/EmmModuleImpl.kt`:
- Around line 282-301: The blur radius is being multiplied by 10 when
configuring the BlurView which unexpectedly rescales the public parameter; in
EmmModuleImpl.kt locate the BlurView setup block (where blurView is created and
setupWith(rootView, blurAlgorithm) is called) and change the setBlurRadius call
to pass the radius directly (use radius.toFloat()) instead of radius.toFloat() *
10f so the API contract and expected intensity from authenticate() are
preserved.

---

Nitpick comments:
In `@example/package.json`:
- Line 13: The package.json currently lists "prettier": "3.8.3" as a runtime
dependency; remove that entry from dependencies and add the same version under
devDependencies (or run npm install --save-dev prettier@3.8.3) so Prettier is a
development-only dependency; update package.json's dependencies/devDependencies
accordingly and run the package manager to update the lockfile.

In `@ios/AnimatedBlurEffectView.swift`:
- Around line 22-31: Add a deinit to AnimatedBlurEffectView that stops and
finalizes the UIViewPropertyAnimator to avoid stray animations when the view is
deallocated: in deinit call animator.stopAnimation(true), then
animator.finishAnimation(at: .current) (or .end if you want to finalize to end
state), and then clear any strong reference (e.g., set animator = nil) so the
animator releases; update any related teardown logic if you have a different
animator property name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: afdeb9b0-8268-4f35-8136-c8b4c2fc6b3c

📥 Commits

Reviewing files that changed from the base of the PR and between cfabc43 and ce481b5.

⛔ Files ignored due to path filters (5)
  • example/Gemfile.lock is excluded by !**/*.lock
  • example/android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
  • example/ios/Podfile.lock is excluded by !**/*.lock
  • example/package-lock.json is excluded by !**/package-lock.json
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • .gitignore
  • android/src/main/java/com/mattermost/emm/EmmModuleImpl.kt
  • android/src/oldarch/java/com/EmmModule.kt
  • example/Gemfile
  • example/android/app/build.gradle
  • example/android/app/src/main/java/com/example/mattermostreactnativeemm/MainApplication.kt
  • example/android/build.gradle
  • example/android/gradle.properties
  • example/android/gradle/wrapper/gradle-wrapper.properties
  • example/android/gradlew
  • example/android/gradlew.bat
  • example/babel.config.js
  • example/ios/File.swift
  • example/ios/Podfile
  • example/ios/ReactNativeEmmExample-Bridging-Header.h
  • example/ios/ReactNativeEmmExample.xcodeproj/project.pbxproj
  • example/ios/ReactNativeEmmExample/AppDelegate.h
  • example/ios/ReactNativeEmmExample/AppDelegate.mm
  • example/ios/ReactNativeEmmExample/AppDelegate.swift
  • example/ios/ReactNativeEmmExample/Info.plist
  • example/ios/ReactNativeEmmExample/main.m
  • example/metro.config.js
  • example/package.json
  • ios/AnimatedBlurEffectView.swift
  • ios/Emm.mm
  • ios/Emm.swift
  • ios/Extensions/UINavigationController+Swizzle.swift
  • ios/Extensions/UIView+Shield.swift
  • ios/Extensions/UIViewController+Shield.swift
  • ios/Extensions/UIViewController+Swizzle.swift
  • ios/Extensions/UIWindow+Swizzle.swift
  • ios/ScreenCaptureManager.swift
  • package.json
  • src/context.tsx
  • tsconfig.json
💤 Files with no reviewable changes (11)
  • example/ios/ReactNativeEmmExample/main.m
  • example/ios/File.swift
  • ios/Extensions/UIWindow+Swizzle.swift
  • ios/Extensions/UIViewController+Shield.swift
  • example/babel.config.js
  • ios/Extensions/UIView+Shield.swift
  • example/ios/ReactNativeEmmExample-Bridging-Header.h
  • example/ios/ReactNativeEmmExample/AppDelegate.h
  • ios/Extensions/UINavigationController+Swizzle.swift
  • ios/Extensions/UIViewController+Swizzle.swift
  • example/ios/ReactNativeEmmExample/AppDelegate.mm

Comment thread example/android/gradle/wrapper/gradle-wrapper.properties
Comment thread example/android/gradlew
Comment thread example/metro.config.js
Comment thread ios/Emm.swift Outdated
Comment thread ios/ScreenCaptureManager.swift
Comment thread ios/ScreenCaptureManager.swift Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
ios/Emm.swift (1)

15-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against duplicate observer registration on ScreenCaptureManager.shared.

If captureEvents() is called multiple times (e.g., across JS bridge reloads), the willResignActiveNotification and didBecomeActiveNotification observers will be registered multiple times on the singleton, causing duplicate blur callbacks. Consider adding a once-only guard.

Proposed fix — add a static flag in ScreenCaptureManager

In ScreenCaptureManager.swift, add a flag and registration method:

private var hasRegisteredLifecycleObservers = false

func registerLifecycleObserversOnce() {
    guard !hasRegisteredLifecycleObservers else { return }
    hasRegisteredLifecycleObservers = true
    NotificationCenter.default.addObserver(
        self,
        selector: `#selector`(handleWillResignActive(_:)),
        name: UIApplication.willResignActiveNotification,
        object: nil
    )
    NotificationCenter.default.addObserver(
        self,
        selector: `#selector`(handleDidBecomeActive(_:)),
        name: UIApplication.didBecomeActiveNotification,
        object: nil
    )
}

Then in Emm.swift:

 `@objc` public func captureEvents() {
     NotificationCenter.default.addObserver(self, selector: `#selector`(managedConfigChaged(notification:)), name: UserDefaults.didChangeNotification, object: nil)
-    NotificationCenter.default.addObserver(ScreenCaptureManager.shared, selector: `#selector`(ScreenCaptureManager.handleWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
-    NotificationCenter.default.addObserver(ScreenCaptureManager.shared, selector: `#selector`(ScreenCaptureManager.handleDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
+    ScreenCaptureManager.shared.registerLifecycleObserversOnce()
 }

Based on learnings: "guard against multiple registrations (e.g., check or use a once-once pattern) and document this lifecycle in code comments or tests."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/Emm.swift` around lines 15 - 19, captureEvents() currently registers
ScreenCaptureManager.shared for willResignActiveNotification and
didBecomeActiveNotification each time it's called, causing duplicate observers;
fix by adding a once-only guard inside ScreenCaptureManager (e.g., a private
hasRegisteredLifecycleObservers Bool and a public
registerLifecycleObserversOnce() method) that registers those two notifications
only if not already registered, then replace the direct
NotificationCenter.addObserver calls for ScreenCaptureManager.shared in
captureEvents() with a call to
ScreenCaptureManager.shared.registerLifecycleObserversOnce(); ensure the flag
and method live on ScreenCaptureManager and document the lifecycle behavior in a
brief comment.
🧹 Nitpick comments (1)
ios/ScreenCaptureManager.swift (1)

98-108: 💤 Low value

Consider potential race on rapid preventScreenCapture toggles.

The 0.25-second asyncAfter delay means rapid toggles of preventScreenCapture could queue multiple async blocks. While the guards in applyScreenCapturePolicy and removeScreenCapturePolicy provide some protection, the timing-dependent behavior could lead to unexpected states. Consider using a cancellation pattern or debounce mechanism if rapid toggling is a realistic scenario.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/ScreenCaptureManager.swift` around lines 98 - 108, The
setScreenCapturePolicy function schedules a delayed block which can be queued
multiple times during rapid toggles; change it to keep and cancel a stored
DispatchWorkItem (or similar token) so each call cancels the previously
scheduled work before creating a new one, then dispatch the new work item to
call applyScreenCapturePolicy or removeScreenCapturePolicy on the main queue;
update class state with a private var (e.g., pendingScreenCaptureWorkItem) and
ensure you cancel and nil it when the work runs to implement a
debounce/cancellation pattern for preventScreenCapture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 119-121: The handler handleCapturedDidChange currently only calls
conditionalApplyBlurEffect() and never removes the blur when capture stops;
update it to mirror screenCaptureStatusChange() behavior by checking
UIScreen.main.isCaptured and calling removeBlurEffect() when false (or simply
call screenCaptureStatusChange() from handleCapturedDidChange) so the blur is
removed on capture stop; refer to handleCapturedDidChange,
conditionalApplyBlurEffect(), screenCaptureStatusChange(), and
removeBlurEffect() to locate and implement the change.
- Around line 173-177: The non-animated fallback uses UIBlurEffect(style:
.regular) which mismatches the AnimatedBlurEffectView initialization using
.dark; update the else branch to apply the same blur style as the animated path
(use UIBlurEffect(style: .dark) or derive a single blurStyle constant used by
both AnimatedBlurEffectView init and blurEffectView.effect) so
AnimatedBlurEffectView and blurEffectView share the identical style and visual
appearance.

---

Duplicate comments:
In `@ios/Emm.swift`:
- Around line 15-19: captureEvents() currently registers
ScreenCaptureManager.shared for willResignActiveNotification and
didBecomeActiveNotification each time it's called, causing duplicate observers;
fix by adding a once-only guard inside ScreenCaptureManager (e.g., a private
hasRegisteredLifecycleObservers Bool and a public
registerLifecycleObserversOnce() method) that registers those two notifications
only if not already registered, then replace the direct
NotificationCenter.addObserver calls for ScreenCaptureManager.shared in
captureEvents() with a call to
ScreenCaptureManager.shared.registerLifecycleObserversOnce(); ensure the flag
and method live on ScreenCaptureManager and document the lifecycle behavior in a
brief comment.

---

Nitpick comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 98-108: The setScreenCapturePolicy function schedules a delayed
block which can be queued multiple times during rapid toggles; change it to keep
and cancel a stored DispatchWorkItem (or similar token) so each call cancels the
previously scheduled work before creating a new one, then dispatch the new work
item to call applyScreenCapturePolicy or removeScreenCapturePolicy on the main
queue; update class state with a private var (e.g.,
pendingScreenCaptureWorkItem) and ensure you cancel and nil it when the work
runs to implement a debounce/cancellation pattern for preventScreenCapture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 225d0840-1b2c-46c2-9d61-2bce6bd109c2

📥 Commits

Reviewing files that changed from the base of the PR and between ce481b5 and 57f9eea.

📒 Files selected for processing (2)
  • ios/Emm.swift
  • ios/ScreenCaptureManager.swift

Comment thread ios/ScreenCaptureManager.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ios/ScreenCaptureManager.swift (1)

19-27: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prevent duplicate capture observer registrations.

When preventScreenCapture is set to true more than once, this path can register the same observer repeatedly, causing duplicate callbacks.

Proposed minimal fix
 private func listenForScreenCapture() {
     if self.preventScreenCapture {
+        NotificationCenter.default.removeObserver(
+            self,
+            name: UIScreen.capturedDidChangeNotification,
+            object: nil
+        )
         NotificationCenter.default.addObserver(
             self,
             selector: `#selector`(handleCapturedDidChange(_:)),
             name: UIScreen.capturedDidChangeNotification,
             object: nil
         )
         return
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/ScreenCaptureManager.swift` around lines 19 - 27, The
listenForScreenCapture method can add the same observer multiple times; before
calling NotificationCenter.default.addObserver for
UIScreen.capturedDidChangeNotification (in listenForScreenCapture) remove any
existing observer registration for self and that notification (or use a stored
observer token and check it) so you only register once; update
listenForScreenCapture and the observer management around
handleCapturedDidChange(_:) to either call
NotificationCenter.default.removeObserver(self, name:
UIScreen.capturedDidChangeNotification, object: nil) before adding or to store
and reuse a single observer token to prevent duplicate callbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 115-117: The handler handleDidBecomeActive currently calls
conditionalRemoveBlurEffect(forced: true) which bypasses capture/auth checks and
can remove blur during an active screen capture; change this to not force
removal—call conditionalRemoveBlurEffect(forced: false) (or the
parameterless/explicit-safe overload) so the method honors ongoing capture/auth
state, ensuring conditionalRemoveBlurEffect still performs its capture/auth
checks before removing the blur.

---

Outside diff comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 19-27: The listenForScreenCapture method can add the same observer
multiple times; before calling NotificationCenter.default.addObserver for
UIScreen.capturedDidChangeNotification (in listenForScreenCapture) remove any
existing observer registration for self and that notification (or use a stored
observer token and check it) so you only register once; update
listenForScreenCapture and the observer management around
handleCapturedDidChange(_:) to either call
NotificationCenter.default.removeObserver(self, name:
UIScreen.capturedDidChangeNotification, object: nil) before adding or to store
and reuse a single observer token to prevent duplicate callbacks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0e700231-b8b1-4950-a43e-42a5aa18d8a4

📥 Commits

Reviewing files that changed from the base of the PR and between 57f9eea and cfe54fe.

📒 Files selected for processing (1)
  • ios/ScreenCaptureManager.swift

Comment thread ios/ScreenCaptureManager.swift
@enahum
Copy link
Copy Markdown
Contributor Author

enahum commented May 6, 2026

@coderabbitai resume

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 174-178: The non-animated branch currently sets
blurEffectView.effect directly and ignores the stored intensity; instead, call
AnimatedBlurEffectView.setupBlur() and set the UIViewPropertyAnimator's
fractionComplete to the stored intensity so the configured blur intensity is
applied immediately—specifically update the else branch that now touches
blurEffectView to invoke setupBlur() on the AnimatedBlurEffectView instance and
set animator.fractionComplete = intensity (and ensure blurEffectView.alpha =
1.0) so the blur state is applied without animation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b2b0eaac-a7f4-42d8-9504-41e8e0fbf8c3

📥 Commits

Reviewing files that changed from the base of the PR and between cfe54fe and b7df0f6.

📒 Files selected for processing (1)
  • ios/ScreenCaptureManager.swift

Comment thread ios/ScreenCaptureManager.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 8-9: applyScreenCapturePolicy() currently reparents the key window
but removeScreenCapturePolicy() calls getLastKeyWindow() again which can return
a different window; update the logic to capture and keep a strong reference to
the exact window/layer you protected when applying (e.g., store the key window
or its layer into a property like originalParentWindow/originalParent) and in
removeScreenCapturePolicy() restore that same stored layer back to its
originalParent instead of looking up getLastKeyWindow(); after restoring, clear
the stored references (protectionTextField and originalParent/
originalParentWindow) to avoid retention.
- Around line 38-43: Update the screen-capture condition so the blur remains
when a biometric auth flow has set isAuthenticating and blurOnAuthenticate even
if preventScreenCapture is false: in screenCaptureStatusChange() (and the
similar check at the other occurrence) replace the existing branch that requires
self.preventScreenCapture && UIScreen.main.isCaptured && !self.isAuthenticating
with a condition that applies the blur when UIScreen.main.isCaptured AND
(self.preventScreenCapture OR (self.isAuthenticating &&
self.blurOnAuthenticate)); remove the !self.isAuthenticating gating so
auth-triggered blur is preserved and otherwise call removeBlurEffect().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40c56190-1681-446c-a4d9-27a38ee96e43

📥 Commits

Reviewing files that changed from the base of the PR and between b7df0f6 and 6392f58.

📒 Files selected for processing (1)
  • ios/ScreenCaptureManager.swift

Comment thread ios/ScreenCaptureManager.swift
Comment thread ios/ScreenCaptureManager.swift
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
ios/ScreenCaptureManager.swift (2)

8-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the same window that was originally protected.

applyScreenCapturePolicy() reparents whichever window is key at enable time, but removeScreenCapturePolicy() asks for the key window again later. If focus changes while protection is active, removal can detach the wrong layer and leave the protected window stranded.

Proposed fix
 private var protectionTextField: UITextField?
 private var originalParent: CALayer?
+private weak var protectedWindow: UIWindow?
@@
     private func applyScreenCapturePolicy() {
         guard let keyWindow = self.getLastKeyWindow(),
+              protectedWindow == nil,
               originalParent == nil,
               protectionTextField == nil else {
             return
         }
@@
         if let firstTextFieldSublayer = textField.layer.sublayers?.first {
             keyWindow.layer.removeFromSuperlayer()
             firstTextFieldSublayer.addSublayer(keyWindow.layer)
         }
         
+        protectedWindow = keyWindow
         protectionTextField = textField
     }
@@
     private func removeScreenCapturePolicy() {
         guard let textField = protectionTextField,
-              let window = self.getLastKeyWindow(),
+              let window = protectedWindow,
               let originalParentLayer = originalParent else {
             return
         }
@@
         textField.layer.removeFromSuperlayer()
         protectionTextField = nil
         originalParent = nil
+        protectedWindow = nil
     }

Also applies to: 60-97

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/ScreenCaptureManager.swift` around lines 8 - 9,
applyScreenCapturePolicy() currently reparents the current key window but
removeScreenCapturePolicy() re-queries the key window, risking detaching the
wrong layer if focus changed; fix by saving the exact original window/layer when
you apply protection (use the existing originalParent and store a reference to
the window or its layer at that time, e.g., originalParent and a new
originalWindow or originalLayer variable) and in removeScreenCapturePolicy()
always restore to that stored original reference (check nil and safely
unparent/re-add protectionTextField to originalParent/originalLayer), clearing
the stored references after restoration.

38-43: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the auth blur in place through screenCaptureStatusChange().

ios/Emm.swift:30-40 sets isAuthenticating and blurOnAuthenticate before the biometric prompt, but this fallback branch still tears the overlay down whenever preventScreenCapture is false. Because handleDidBecomeActive(_:) immediately routes through here, resuming the app mid-prompt can expose the protected UI.

Proposed fix
 func screenCaptureStatusChange() {
     if self.preventScreenCapture && UIScreen.main.isCaptured && !self.isAuthenticating {
         conditionalApplyBlurEffect() // Prevent screen recording
-    } else {
+    } else if !(self.isAuthenticating && self.blurOnAuthenticate) {
         removeBlurEffect()
     }
 }

Also applies to: 116-118

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ios/ScreenCaptureManager.swift` around lines 38 - 43, Update
screenCaptureStatusChange() so the blur is retained during authentication:
change the if-condition that currently checks (preventScreenCapture &&
UIScreen.main.isCaptured && !self.isAuthenticating) to apply the blur when
either screen-capture protection is triggered OR an authentication blur is
requested. Concretely, call conditionalApplyBlurEffect() when
((preventScreenCapture && UIScreen.main.isCaptured) || (isAuthenticating &&
blurOnAuthenticate)) and only call removeBlurEffect() when neither case applies;
also ensure any callers such as handleDidBecomeActive(_:) that route through
screenCaptureStatusChange() will therefore preserve the auth blur during
biometric prompts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 125-131: The fade-out completion currently unconditionally removes
blurEffectView, which drops any new blur requests made during the 250ms removal
because blurEffectView is non-nil; update the blur-removal completion to
re-check the current requirement ((preventScreenCapture && !isAuthenticating) ||
(isAuthenticating && blurOnAuthenticate)) before fully removing the view—if the
requirement now holds, restore or keep the blur (e.g., call
applyBlurEffect(intensity:animated:) or skip removal) instead of removing it;
apply the same re-check/update logic in the other blur-removal block referenced
around the second region (the block paired with conditionalApplyBlurEffect and
the corresponding hide/fade completion) so new requests during the animation are
honored.
- Around line 140-149: The Objective-C selector call in Emm.mm is using a
non-existent method signature; update the call site that references
applyBlurEffectWithIntensity:radius:animated: to call the exported Swift
selector applyBlurEffect:animated: on ScreenCaptureManager.shared instead, i.e.
replace the applyBlurEffectWithIntensity:... invocation with a call to
applyBlurEffect: animated: so it matches the `@objc` public func
applyBlurEffect(intensity:animated:) in ScreenCaptureManager.swift.

---

Duplicate comments:
In `@ios/ScreenCaptureManager.swift`:
- Around line 8-9: applyScreenCapturePolicy() currently reparents the current
key window but removeScreenCapturePolicy() re-queries the key window, risking
detaching the wrong layer if focus changed; fix by saving the exact original
window/layer when you apply protection (use the existing originalParent and
store a reference to the window or its layer at that time, e.g., originalParent
and a new originalWindow or originalLayer variable) and in
removeScreenCapturePolicy() always restore to that stored original reference
(check nil and safely unparent/re-add protectionTextField to
originalParent/originalLayer), clearing the stored references after restoration.
- Around line 38-43: Update screenCaptureStatusChange() so the blur is retained
during authentication: change the if-condition that currently checks
(preventScreenCapture && UIScreen.main.isCaptured && !self.isAuthenticating) to
apply the blur when either screen-capture protection is triggered OR an
authentication blur is requested. Concretely, call conditionalApplyBlurEffect()
when ((preventScreenCapture && UIScreen.main.isCaptured) || (isAuthenticating &&
blurOnAuthenticate)) and only call removeBlurEffect() when neither case applies;
also ensure any callers such as handleDidBecomeActive(_:) that route through
screenCaptureStatusChange() will therefore preserve the auth blur during
biometric prompts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d605cebe-35f2-4292-a472-489ca2aefa7a

📥 Commits

Reviewing files that changed from the base of the PR and between b7df0f6 and 6392f58.

📒 Files selected for processing (1)
  • ios/ScreenCaptureManager.swift

Comment thread ios/ScreenCaptureManager.swift
Comment thread ios/ScreenCaptureManager.swift
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/Emm.mm`:
- Around line 161-163: The Objective-C call inside Emm.mm's -applyBlurEffect: is
using the wrong selector; it calls [[ScreenCaptureManager shared]
applyBlurEffect:radius animated:true] but the Swift implementation exported as
applyBlurEffect(intensity:animated:) is available as
applyBlurEffect:intensity:animated:, so update the call to invoke the correct
selector or change the Swift method to export a simpler selector (add an
`@objc`("applyBlurEffect:animated:") on the Swift method); specifically, modify
the Emm.mm use of ScreenCaptureManager shared to call the selector matching
applyBlurEffect:intensity:animated: (passing radius as the intensity parameter)
or alternatively annotate the Swift method to `@objc`("applyBlurEffect:animated:")
so the existing ObjC call works.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed414265-3100-4daf-9f8f-751d236f1b84

📥 Commits

Reviewing files that changed from the base of the PR and between 6392f58 and fecc87e.

📒 Files selected for processing (1)
  • ios/Emm.mm

Comment thread ios/Emm.mm
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/ScreenCaptureManager.swift`:
- Line 10: protectedWindow is declared weak so it may be nil out before
removeScreenCapturePolicy() runs, leaving protectionTextField and originalParent
latched; change protectedWindow from a weak var to a strong (non-weak) var to
retain the window while protection is active, assign the window into
protectedWindow when enabling protection (same place that currently sets
originalParent/protectionTextField), and ensure removeScreenCapturePolicy()
clears protectedWindow (set to nil) as part of its cleanup along with
protectionTextField and originalParent to avoid leaks and allow future
transitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 94c80d36-b2b6-40ad-bd73-fcd3d374f6a6

📥 Commits

Reviewing files that changed from the base of the PR and between 5734647 and b1332d8.

📒 Files selected for processing (1)
  • ios/ScreenCaptureManager.swift

Comment thread ios/ScreenCaptureManager.swift
@enahum
Copy link
Copy Markdown
Contributor Author

enahum commented May 6, 2026

@coderabittai resolve

@enahum enahum requested a review from larkox May 6, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant