Skip to content

Commit c284ef7

Browse files
committed
feat: implement
1 parent 8eb422d commit c284ef7

File tree

22 files changed

+1510
-1864
lines changed

22 files changed

+1510
-1864
lines changed

README.md

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,89 @@
11
# react-native-secure-view
22

3-
React Native Module for Secure View
3+
A React Native component that protects specific content from screenshots and screen recordings on both iOS and Android.
44

55
## Installation
66

7-
87
```sh
98
npm install react-native-secure-view
109
```
1110

12-
1311
## Usage
1412

13+
Wrap the content you want to protect from screenshots and screen recordings:
14+
15+
```jsx
16+
import { SecureView } from 'react-native-secure-view';
17+
18+
function App() {
19+
return (
20+
<SecureView enable={true}>
21+
<Text>This content is protected from screenshots</Text>
22+
<Image source={require('./sensitive-image.png')} />
23+
</SecureView>
24+
);
25+
}
26+
```
27+
28+
### Props
29+
30+
| Prop | Type | Default | Description |
31+
| -------- | --------- | ------- | --------------------------------------- |
32+
| `enable` | `boolean` | `true` | Enable or disable screenshot protection |
33+
34+
### Example
1535

16-
```js
17-
import { SecureViewView } from "react-native-secure-view";
36+
```jsx
37+
import React, { useState } from 'react';
38+
import { View, Text, Button } from 'react-native';
39+
import { SecureView } from 'react-native-secure-view';
1840

19-
// ...
41+
function App() {
42+
const [secure, setSecure] = useState(true);
2043

21-
<SecureViewView color="tomato" />
44+
return (
45+
<View style={{ flex: 1 }}>
46+
<Button
47+
title={`Protection: ${secure ? 'ON' : 'OFF'}`}
48+
onPress={() => setSecure(!secure)}
49+
/>
50+
51+
<SecureView enable={secure}>
52+
<Text>Protected Content</Text>
53+
</SecureView>
54+
</View>
55+
);
56+
}
2257
```
2358

59+
## How It Works
60+
61+
### Android
62+
63+
When `enable={true}`, the module applies `WindowManager.LayoutParams.FLAG_SECURE` to the activity window:
64+
65+
- **Screenshots**: Completely blocked
66+
- **Screen recording**: Entire screen appears black during recording
67+
- Recent apps switcher shows a black screen
68+
69+
### iOS
70+
71+
Uses `UITextField` with `secureTextEntry` property:
72+
73+
- **Screenshots**: The `SecureView` component appears blank/hidden in the captured image
74+
- **Screen recording**: Only the `SecureView` component appears blank during recording (rest of the screen is visible)
75+
- Works by leveraging iOS's built-in secure text field behavior that hides content during capture
76+
77+
## Limitations
78+
79+
### Android
80+
81+
- **Entire screen protection**: When enabled, the entire app screen becomes secure, not just the `SecureView` component
82+
- **Rooted devices**: May bypass protection on rooted/modified devices
83+
84+
### iOS
85+
86+
- **Component-level protection**: Only hides the specific `SecureView` component, not the entire screen
2487

2588
## Contributing
2689

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def getExtOrIntegerDefault(name) {
2626
}
2727

2828
android {
29-
namespace "com.secureview"
29+
namespace "com.effx.secureview"
3030

3131
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
3232

@@ -79,5 +79,5 @@ dependencies {
7979
react {
8080
jsRootDir = file("../src/")
8181
libraryName = "SecureViewView"
82-
codegenJavaPackageName = "com.secureview"
82+
codegenJavaPackageName = "com.effx.secureview"
8383
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.effx.secureview
2+
3+
import android.content.Context
4+
import android.view.WindowManager
5+
import android.widget.FrameLayout
6+
import com.facebook.react.uimanager.ThemedReactContext
7+
8+
class SecureView(context: Context) : FrameLayout(context) {
9+
private var _isPreventingCapture: Boolean = false
10+
11+
var isSecureEnabled: Boolean
12+
get() = _isPreventingCapture
13+
set(value) {
14+
if (_isPreventingCapture == value) return
15+
_isPreventingCapture = value
16+
17+
try {
18+
val activity = (context as? ThemedReactContext)?.currentActivity
19+
activity?.window?.let { window ->
20+
if (value) {
21+
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
22+
} else {
23+
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
24+
}
25+
}
26+
} catch (e: Exception) {
27+
e.printStackTrace()
28+
}
29+
}
30+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.effx.secureview
2+
3+
import com.facebook.react.module.annotations.ReactModule
4+
import com.facebook.react.uimanager.ThemedReactContext
5+
import com.facebook.react.uimanager.ViewGroupManager
6+
import com.facebook.react.uimanager.ViewManagerDelegate
7+
import com.facebook.react.uimanager.annotations.ReactProp
8+
import com.facebook.react.viewmanagers.SecureViewManagerDelegate
9+
import com.facebook.react.viewmanagers.SecureViewManagerInterface
10+
11+
@ReactModule(name = SecureViewManager.NAME)
12+
class SecureViewManager : ViewGroupManager<SecureView>(), SecureViewManagerInterface<SecureView> {
13+
private val mDelegate: ViewManagerDelegate<SecureView> = SecureViewManagerDelegate(this)
14+
15+
override fun getDelegate(): ViewManagerDelegate<SecureView> {
16+
return mDelegate
17+
}
18+
19+
override fun getName(): String {
20+
return NAME
21+
}
22+
23+
override fun createViewInstance(context: ThemedReactContext): SecureView {
24+
return SecureView(context)
25+
}
26+
27+
@ReactProp(name = "enable", defaultBoolean = true)
28+
override fun setEnable(view: SecureView, enable: Boolean) {
29+
view.isSecureEnabled = enable
30+
}
31+
32+
companion object {
33+
const val NAME = "SecureView"
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
package com.secureview
1+
package com.effx.secureview
22

33
import com.facebook.react.ReactPackage
44
import com.facebook.react.bridge.NativeModule
55
import com.facebook.react.bridge.ReactApplicationContext
66
import com.facebook.react.uimanager.ViewManager
77
import java.util.ArrayList
88

9-
class SecureViewViewPackage : ReactPackage {
9+
class SecureViewPackage : ReactPackage {
1010
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
1111
val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
12-
viewManagers.add(SecureViewViewManager())
12+
viewManagers.add(SecureViewManager())
1313
return viewManagers
1414
}
15-
16-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
17-
return emptyList()
18-
}
1915
}

android/src/main/java/com/secureview/SecureViewView.kt

Lines changed: 0 additions & 15 deletions
This file was deleted.

android/src/main/java/com/secureview/SecureViewViewManager.kt

Lines changed: 0 additions & 41 deletions
This file was deleted.

example/android/app/src/debug/AndroidManifest.xml

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
22

3-
<uses-permission android:name="android.permission.INTERNET" />
3+
<uses-permission android:name="android.permission.INTERNET" />
44

5-
<application
6-
android:name=".MainApplication"
5+
<application
6+
android:name=".MainApplication"
7+
android:label="@string/app_name"
8+
android:icon="@mipmap/ic_launcher"
9+
android:roundIcon="@mipmap/ic_launcher_round"
10+
android:allowBackup="false"
11+
android:theme="@style/AppTheme"
12+
android:usesCleartextTraffic="${usesCleartextTraffic}"
13+
android:supportsRtl="true">
14+
<activity
15+
android:name=".MainActivity"
716
android:label="@string/app_name"
8-
android:icon="@mipmap/ic_launcher"
9-
android:roundIcon="@mipmap/ic_launcher_round"
10-
android:allowBackup="false"
11-
android:theme="@style/AppTheme"
12-
android:supportsRtl="true">
13-
<activity
14-
android:name=".MainActivity"
15-
android:label="@string/app_name"
16-
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
17-
android:launchMode="singleTask"
18-
android:windowSoftInputMode="adjustResize"
19-
android:exported="true">
20-
<intent-filter>
21-
<action android:name="android.intent.action.MAIN" />
22-
<category android:name="android.intent.category.LAUNCHER" />
23-
</intent-filter>
24-
</activity>
25-
</application>
17+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
18+
android:launchMode="singleTask"
19+
android:windowSoftInputMode="adjustResize"
20+
android:exported="true">
21+
<intent-filter>
22+
<action android:name="android.intent.action.MAIN" />
23+
<category android:name="android.intent.category.LAUNCHER" />
24+
</intent-filter>
25+
</activity>
26+
</application>
2627
</manifest>

example/android/app/src/main/java/secureview/example/MainApplication.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ import android.app.Application
44
import com.facebook.react.PackageList
55
import com.facebook.react.ReactApplication
66
import com.facebook.react.ReactHost
7+
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
78
import com.facebook.react.ReactNativeHost
89
import com.facebook.react.ReactPackage
9-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
1010
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
1111
import com.facebook.react.defaults.DefaultReactNativeHost
12-
import com.facebook.react.soloader.OpenSourceMergedSoMapping
13-
import com.facebook.soloader.SoLoader
1412

1513
class MainApplication : Application(), ReactApplication {
1614

@@ -35,10 +33,6 @@ class MainApplication : Application(), ReactApplication {
3533

3634
override fun onCreate() {
3735
super.onCreate()
38-
SoLoader.init(this, OpenSourceMergedSoMapping)
39-
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
40-
// If you opted-in for the New Architecture, we load the native entry point for this app.
41-
load()
42-
}
36+
loadReactNative(this)
4337
}
4438
}

0 commit comments

Comments
 (0)