@momo-kits/anti-screenshot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "AntiScreenshot"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://www.npmjs.com/.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+
20
+ # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
21
+ # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
22
+ if respond_to?(:install_modules_dependencies, true)
23
+ install_modules_dependencies(s)
24
+ else
25
+ s.dependency "React-Core"
26
+
27
+ # Don't install the dependencies when we run `pod install` in the old architecture.
28
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
29
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
30
+ s.pod_target_xcconfig = {
31
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
32
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
33
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
34
+ }
35
+ s.dependency "React-RCTFabric"
36
+ s.dependency "React-Codegen"
37
+ s.dependency "RCT-Folly"
38
+ s.dependency "RCTRequired"
39
+ s.dependency "RCTTypeSafety"
40
+ s.dependency "ReactCommon/turbomodule/core"
41
+ end
42
+ end
43
+ end
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 dung.pham2
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @momo-kits/anti-screenshot
2
+
3
+ A React Native component that protects content from screenshots and screen recordings.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @momo-kits/anti-screenshot
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { AntiScreenshotView } from '@momo-kits/anti-screenshot';
15
+
16
+ function SecureScreen() {
17
+ return (
18
+ <AntiScreenshotView style={{ flex: 1 }}>
19
+ <Text>This content is protected from screenshots</Text>
20
+ <Text>It will appear blank/black in screenshots and screen recordings</Text>
21
+ </AntiScreenshotView>
22
+ );
23
+ }
24
+ ```
25
+
26
+ ## How it works
27
+
28
+ ### Android
29
+ Uses a secure `SurfaceView` with `setSecure(true)` combined with bitmap rendering. Child views are rendered to a bitmap, which is then drawn onto the secure surface. Screenshots only capture the secure surface (which appears black).
30
+
31
+ ### iOS
32
+ Uses the secure `UITextField` trick. When `secureTextEntry` is enabled, UITextField uses an internal view that prevents screenshot capture. The component extracts this internal view and uses it as a container for the protected content.
33
+
34
+ ## Props
35
+
36
+ | Prop | Type | Description |
37
+ |------|------|-------------|
38
+ | `style` | `StyleProp<ViewStyle>` | Style for the container view |
39
+ | `children` | `React.ReactNode` | Content to be protected from screenshots |
40
+
41
+ All standard `View` props are also supported.
42
+
43
+ ## License
44
+
45
+ MIT
@@ -0,0 +1,83 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['AntiScreenshot_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ apply plugin: "com.facebook.react"
23
+
24
+ def getExtOrIntegerDefault(name) {
25
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["AntiScreenshot_" + name]).toInteger()
26
+ }
27
+
28
+ android {
29
+ namespace "com.momokits.antiscreenshot"
30
+
31
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
32
+
33
+ defaultConfig {
34
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
36
+ }
37
+
38
+ buildFeatures {
39
+ buildConfig true
40
+ }
41
+
42
+ buildTypes {
43
+ release {
44
+ minifyEnabled false
45
+ }
46
+ }
47
+
48
+ lintOptions {
49
+ disable "GradleCompatible"
50
+ }
51
+
52
+ compileOptions {
53
+ sourceCompatibility JavaVersion.VERSION_1_8
54
+ targetCompatibility JavaVersion.VERSION_1_8
55
+ }
56
+
57
+ sourceSets {
58
+ main {
59
+ java.srcDirs += [
60
+ "generated/java",
61
+ "generated/jni"
62
+ ]
63
+ }
64
+ }
65
+ }
66
+
67
+ repositories {
68
+ mavenCentral()
69
+ google()
70
+ }
71
+
72
+ def kotlin_version = getExtOrDefault("kotlinVersion")
73
+
74
+ dependencies {
75
+ implementation "com.facebook.react:react-android"
76
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
+ }
78
+
79
+ react {
80
+ jsRootDir = file("../src/")
81
+ libraryName = "AntiScreenshotView"
82
+ codegenJavaPackageName = "com.momokits.antiscreenshot"
83
+ }
@@ -0,0 +1,4 @@
1
+ AntiScreenshot_kotlinVersion=1.9.25
2
+ AntiScreenshot_minSdkVersion=24
3
+ AntiScreenshot_targetSdkVersion=35
4
+ AntiScreenshot_compileSdkVersion=35
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,16 @@
1
+ package com.momokits.antiscreenshot
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class AntiScreenshotPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return emptyList()
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return listOf(AntiScreenshotViewManager())
15
+ }
16
+ }
@@ -0,0 +1,284 @@
1
+ package com.momokits.antiscreenshot
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import android.graphics.Canvas
6
+ import android.graphics.Color
7
+ import android.graphics.PixelFormat
8
+ import android.graphics.PorterDuff
9
+ import android.os.Handler
10
+ import android.os.Looper
11
+ import android.util.AttributeSet
12
+ import android.view.SurfaceHolder
13
+ import android.view.SurfaceView
14
+ import android.view.View
15
+ import android.view.ViewGroup
16
+ import android.view.ViewTreeObserver
17
+ import android.widget.FrameLayout
18
+
19
+ /**
20
+ * AntiScreenshotView - A ViewGroup that protects its content from screenshots.
21
+ *
22
+ * Content inside this view will appear as black/blank in screenshots and screen recordings,
23
+ * while displaying normally on the device screen.
24
+ *
25
+ * How it works:
26
+ * 1. Child views are added to a hidden container
27
+ * 2. The container is rendered to a Bitmap
28
+ * 3. The Bitmap is drawn onto a secure SurfaceView
29
+ * 4. Screenshots only capture the secure surface (which appears black)
30
+ */
31
+ class AntiScreenshotView @JvmOverloads constructor(
32
+ context: Context,
33
+ attrs: AttributeSet? = null,
34
+ defStyleAttr: Int = 0
35
+ ) : FrameLayout(context, attrs, defStyleAttr) {
36
+
37
+ private var secureSurface: SurfaceView? = null
38
+ private var contentContainer: FrameLayout? = null
39
+ private var isSurfaceReady = false
40
+ private val handler = Handler(Looper.getMainLooper())
41
+ private var redrawRunnable: Runnable? = null
42
+ private var contentBitmap: Bitmap? = null
43
+ private var isFirstDraw = true
44
+
45
+ private val surfaceCallback = object : SurfaceHolder.Callback {
46
+ override fun surfaceCreated(holder: SurfaceHolder) {
47
+ isSurfaceReady = true
48
+ scheduleRedraw()
49
+ }
50
+
51
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
52
+ scheduleRedraw()
53
+ }
54
+
55
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
56
+ isSurfaceReady = false
57
+ }
58
+ }
59
+
60
+ private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
61
+ scheduleRedraw()
62
+ }
63
+
64
+ init {
65
+ initializeViews()
66
+ }
67
+
68
+ private fun initializeViews() {
69
+ // 1. Create content container - Initially VISIBLE for layout/measure
70
+ contentContainer = FrameLayout(context).apply {
71
+ layoutParams = LayoutParams(
72
+ LayoutParams.MATCH_PARENT,
73
+ LayoutParams.WRAP_CONTENT
74
+ )
75
+ visibility = View.VISIBLE
76
+ }
77
+
78
+ // 2. Create secure SurfaceView
79
+ secureSurface = SurfaceView(context).apply {
80
+ layoutParams = LayoutParams(
81
+ LayoutParams.MATCH_PARENT,
82
+ LayoutParams.MATCH_PARENT
83
+ )
84
+ setZOrderOnTop(true)
85
+ holder.setFormat(PixelFormat.TRANSLUCENT)
86
+ holder.addCallback(surfaceCallback)
87
+ setSecure(true)
88
+ }
89
+
90
+ // Add content container first (will be hidden after first render)
91
+ super.addView(contentContainer, -1, contentContainer!!.layoutParams)
92
+ // Add secure surface on top
93
+ super.addView(secureSurface, -1, secureSurface!!.layoutParams)
94
+
95
+ // Listen for layout changes
96
+ viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
97
+ }
98
+
99
+ private fun scheduleRedraw() {
100
+ redrawRunnable?.let { handler.removeCallbacks(it) }
101
+ redrawRunnable = Runnable { redrawContent() }
102
+ // Delay to ensure content is laid out
103
+ handler.postDelayed(redrawRunnable!!, if (isFirstDraw) 100 else 16)
104
+ }
105
+
106
+ /**
107
+ * Redraws the content onto the secure surface.
108
+ */
109
+ private fun redrawContent() {
110
+ if (!isSurfaceReady) return
111
+
112
+ val container = contentContainer ?: return
113
+ val surface = secureSurface ?: return
114
+ val holder = surface.holder
115
+
116
+ // Ensure content is visible and laid out before capturing
117
+ if (container.visibility != View.VISIBLE) {
118
+ container.visibility = View.VISIBLE
119
+ handler.postDelayed({ redrawContent() }, 50)
120
+ return
121
+ }
122
+
123
+ if (container.width <= 0 || container.height <= 0) {
124
+ // Wait for layout
125
+ handler.postDelayed({ redrawContent() }, 50)
126
+ return
127
+ }
128
+
129
+ try {
130
+ // Create or recreate bitmap if needed
131
+ val bitmapWidth = container.width
132
+ val bitmapHeight = container.height
133
+
134
+ if (contentBitmap == null ||
135
+ contentBitmap?.width != bitmapWidth ||
136
+ contentBitmap?.height != bitmapHeight ||
137
+ contentBitmap?.isRecycled == true
138
+ ) {
139
+ contentBitmap?.recycle()
140
+ contentBitmap = Bitmap.createBitmap(
141
+ bitmapWidth,
142
+ bitmapHeight,
143
+ Bitmap.Config.ARGB_8888
144
+ )
145
+ }
146
+
147
+ val bitmap = contentBitmap ?: return
148
+
149
+ // Draw content to bitmap
150
+ val bitmapCanvas = Canvas(bitmap)
151
+ bitmapCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
152
+ container.draw(bitmapCanvas)
153
+
154
+ // Draw bitmap to secure surface
155
+ val canvas = holder.lockCanvas()
156
+ if (canvas != null) {
157
+ try {
158
+ canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
159
+ canvas.drawBitmap(bitmap, 0f, 0f, null)
160
+ } finally {
161
+ holder.unlockCanvasAndPost(canvas)
162
+ }
163
+ }
164
+
165
+ // Hide original content after drawing to surface
166
+ container.visibility = View.INVISIBLE
167
+ isFirstDraw = false
168
+
169
+ } catch (e: Exception) {
170
+ e.printStackTrace()
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Force redraw - makes content visible briefly to capture updates.
176
+ */
177
+ fun forceRedraw() {
178
+ contentContainer?.visibility = View.VISIBLE
179
+ scheduleRedraw()
180
+ }
181
+
182
+ // Override addView to redirect children to content container
183
+ override fun addView(child: View?) {
184
+ if (contentContainer != null && child != secureSurface && child != contentContainer) {
185
+ contentContainer?.addView(child)
186
+ setupChildListener(child)
187
+ forceRedraw()
188
+ } else {
189
+ super.addView(child)
190
+ }
191
+ }
192
+
193
+ override fun addView(child: View?, index: Int) {
194
+ if (contentContainer != null && child != secureSurface && child != contentContainer) {
195
+ contentContainer?.addView(child, index)
196
+ setupChildListener(child)
197
+ forceRedraw()
198
+ } else {
199
+ super.addView(child, index)
200
+ }
201
+ }
202
+
203
+ override fun addView(child: View?, params: ViewGroup.LayoutParams?) {
204
+ if (contentContainer != null && child != secureSurface && child != contentContainer) {
205
+ contentContainer?.addView(child, params)
206
+ setupChildListener(child)
207
+ forceRedraw()
208
+ } else {
209
+ super.addView(child, params)
210
+ }
211
+ }
212
+
213
+ override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
214
+ if (contentContainer != null && child != secureSurface && child != contentContainer) {
215
+ contentContainer?.addView(child, index, params)
216
+ setupChildListener(child)
217
+ forceRedraw()
218
+ } else {
219
+ super.addView(child, index, params)
220
+ }
221
+ }
222
+
223
+ override fun addView(child: View?, width: Int, height: Int) {
224
+ if (contentContainer != null && child != secureSurface && child != contentContainer) {
225
+ contentContainer?.addView(child, width, height)
226
+ setupChildListener(child)
227
+ forceRedraw()
228
+ } else {
229
+ super.addView(child, width, height)
230
+ }
231
+ }
232
+
233
+ private fun setupChildListener(child: View?) {
234
+ child?.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
235
+ forceRedraw()
236
+ }
237
+ }
238
+
239
+ override fun removeView(view: View?) {
240
+ if (view != secureSurface && view != contentContainer) {
241
+ contentContainer?.removeView(view)
242
+ forceRedraw()
243
+ } else {
244
+ super.removeView(view)
245
+ }
246
+ }
247
+
248
+ override fun removeViewAt(index: Int) {
249
+ contentContainer?.removeViewAt(index)
250
+ forceRedraw()
251
+ }
252
+
253
+ override fun removeAllViews() {
254
+ contentContainer?.removeAllViews()
255
+ forceRedraw()
256
+ }
257
+
258
+ override fun onDetachedFromWindow() {
259
+ super.onDetachedFromWindow()
260
+ redrawRunnable?.let { handler.removeCallbacks(it) }
261
+ contentBitmap?.recycle()
262
+ contentBitmap = null
263
+ secureSurface?.holder?.removeCallback(surfaceCallback)
264
+ try {
265
+ viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
266
+ } catch (e: Exception) {
267
+ // Ignore
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Gets the actual child count (excluding internal views).
273
+ */
274
+ fun getContentChildCount(): Int {
275
+ return contentContainer?.childCount ?: 0
276
+ }
277
+
278
+ /**
279
+ * Gets a child view at the specified index.
280
+ */
281
+ fun getContentChildAt(index: Int): View? {
282
+ return contentContainer?.getChildAt(index)
283
+ }
284
+ }
@@ -0,0 +1,19 @@
1
+ package com.momokits.antiscreenshot
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.ThemedReactContext
5
+ import com.facebook.react.uimanager.ViewGroupManager
6
+
7
+ @ReactModule(name = AntiScreenshotViewManager.NAME)
8
+ class AntiScreenshotViewManager : ViewGroupManager<AntiScreenshotView>() {
9
+
10
+ override fun getName(): String = NAME
11
+
12
+ override fun createViewInstance(reactContext: ThemedReactContext): AntiScreenshotView {
13
+ return AntiScreenshotView(reactContext)
14
+ }
15
+
16
+ companion object {
17
+ const val NAME = "RNAntiScreenshot"
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ /// A UIView that protects its content from being captured in screenshots or
6
+ /// screen recordings. It uses a secure UITextField's internal view as a
7
+ /// container for the content.
8
+ @interface AntiScreenshotView : UIView
9
+
10
+ @end
11
+
12
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,84 @@
1
+ #import "AntiScreenshotView.h"
2
+
3
+ @interface AntiScreenshotView ()
4
+
5
+ @property(nonatomic, strong) UITextField *secureTextField;
6
+ @property(nonatomic, strong) UIView *secureContainerView;
7
+
8
+ @end
9
+
10
+ @implementation AntiScreenshotView
11
+
12
+ - (instancetype)initWithFrame:(CGRect)frame {
13
+ self = [super initWithFrame:frame];
14
+ if (self) {
15
+ [self setupSecureView];
16
+ }
17
+ return self;
18
+ }
19
+
20
+ - (instancetype)initWithCoder:(NSCoder *)coder {
21
+ self = [super initWithCoder:coder];
22
+ if (self) {
23
+ [self setupSecureView];
24
+ }
25
+ return self;
26
+ }
27
+
28
+ - (void)setupSecureView {
29
+ // Create a secure text field - the magic ingredient
30
+ self.secureTextField = [[UITextField alloc] init];
31
+ self.secureTextField.secureTextEntry = YES;
32
+ self.secureTextField.userInteractionEnabled = NO;
33
+
34
+ // Get the internal secure view from UITextField
35
+ // When secureTextEntry is YES, UITextField uses a special internal view
36
+ // that prevents screenshot capture
37
+ if (self.secureTextField.subviews.count > 0) {
38
+ self.secureContainerView = self.secureTextField.subviews.firstObject;
39
+
40
+ // Add the secure container to our view hierarchy
41
+ [self addSubview:self.secureContainerView];
42
+
43
+ // Setup constraints
44
+ self.secureContainerView.translatesAutoresizingMaskIntoConstraints = NO;
45
+ [NSLayoutConstraint activateConstraints:@[
46
+ [self.secureContainerView.topAnchor
47
+ constraintEqualToAnchor:self.topAnchor],
48
+ [self.secureContainerView.leadingAnchor
49
+ constraintEqualToAnchor:self.leadingAnchor],
50
+ [self.secureContainerView.trailingAnchor
51
+ constraintEqualToAnchor:self.trailingAnchor],
52
+ [self.secureContainerView.bottomAnchor
53
+ constraintEqualToAnchor:self.bottomAnchor]
54
+ ]];
55
+ }
56
+ }
57
+
58
+ - (void)addSubview:(UIView *)view {
59
+ // Redirect child views to the secure container if available
60
+ if (self.secureContainerView && view != self.secureContainerView) {
61
+ [self.secureContainerView addSubview:view];
62
+ } else {
63
+ [super addSubview:view];
64
+ }
65
+ }
66
+
67
+ - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index {
68
+ if (self.secureContainerView && view != self.secureContainerView) {
69
+ [self.secureContainerView insertSubview:view atIndex:index];
70
+ } else {
71
+ [super insertSubview:view atIndex:index];
72
+ }
73
+ }
74
+
75
+ - (void)layoutSubviews {
76
+ [super layoutSubviews];
77
+
78
+ // Ensure secure container fills the view
79
+ if (self.secureContainerView) {
80
+ self.secureContainerView.frame = self.bounds;
81
+ }
82
+ }
83
+
84
+ @end
@@ -0,0 +1,10 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface RNAntiScreenshot : RCTViewComponentView
7
+
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,59 @@
1
+ #import "RNAntiScreenshot.h"
2
+
3
+ #import <react/renderer/components/AntiScreenshotViewSpec/ComponentDescriptors.h>
4
+ #import <react/renderer/components/AntiScreenshotViewSpec/EventEmitters.h>
5
+ #import <react/renderer/components/AntiScreenshotViewSpec/Props.h>
6
+ #import <react/renderer/components/AntiScreenshotViewSpec/RCTComponentViewHelpers.h>
7
+
8
+ #import "AntiScreenshotView.h"
9
+ #import "RCTFabricComponentsPlugins.h"
10
+
11
+ using namespace facebook::react;
12
+
13
+ @interface RNAntiScreenshot () <RCTRNAntiScreenshotViewProtocol>
14
+ @end
15
+
16
+ @implementation RNAntiScreenshot {
17
+ AntiScreenshotView *_view;
18
+ }
19
+
20
+ + (ComponentDescriptorProvider)componentDescriptorProvider {
21
+ return concreteComponentDescriptorProvider<
22
+ RNAntiScreenshotComponentDescriptor>();
23
+ }
24
+
25
+ - (instancetype)initWithFrame:(CGRect)frame {
26
+ if (self = [super initWithFrame:frame]) {
27
+ static const auto defaultProps =
28
+ std::make_shared<const RNAntiScreenshotProps>();
29
+ _props = defaultProps;
30
+
31
+ _view = [[AntiScreenshotView alloc] init];
32
+ self.contentView = _view;
33
+ }
34
+
35
+ return self;
36
+ }
37
+
38
+ - (void)updateProps:(Props::Shared const &)props
39
+ oldProps:(Props::Shared const &)oldProps {
40
+ [super updateProps:props oldProps:oldProps];
41
+ }
42
+
43
+ - (void)mountChildComponentView:
44
+ (UIView<RCTComponentViewProtocol> *)childComponentView
45
+ index:(NSInteger)index {
46
+ [_view insertSubview:childComponentView atIndex:index];
47
+ }
48
+
49
+ - (void)unmountChildComponentView:
50
+ (UIView<RCTComponentViewProtocol> *)childComponentView
51
+ index:(NSInteger)index {
52
+ [childComponentView removeFromSuperview];
53
+ }
54
+
55
+ @end
56
+
57
+ Class<RCTComponentViewProtocol> RNAntiScreenshotCls(void) {
58
+ return RNAntiScreenshot.class;
59
+ }
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@momo-kits/anti-screenshot",
3
+ "version": "0.1.0",
4
+ "description": "A React Native component that protects content from screenshots and screen recordings.",
5
+ "main": "./src/index.tsx",
6
+ "files": [
7
+ "src",
8
+ "lib",
9
+ "android",
10
+ "ios",
11
+ "cpp",
12
+ "*.podspec",
13
+ "react-native.config.js",
14
+ "!ios/build",
15
+ "!android/build",
16
+ "!android/gradle",
17
+ "!android/gradlew",
18
+ "!android/gradlew.bat",
19
+ "!android/local.properties",
20
+ "!**/__tests__",
21
+ "!**/__fixtures__",
22
+ "!**/__mocks__",
23
+ "!**/.*"
24
+ ],
25
+ "scripts": {
26
+ "example": "yarn workspace @momo-kits/anti-screenshot-example",
27
+ "test": "jest",
28
+ "typecheck": "tsc",
29
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
30
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
31
+ "build": "echo",
32
+ "release": "release-it --only-version"
33
+ },
34
+ "keywords": [
35
+ "react-native",
36
+ "ios",
37
+ "android",
38
+ "screenshot",
39
+ "anti-screenshot",
40
+ "screen-capture",
41
+ "security"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://www.npmjs.com/.git"
46
+ },
47
+ "author": "MoMo Team",
48
+ "license": "MIT",
49
+ "bugs": {
50
+ "url": "https://www.npmjs.com//issues"
51
+ },
52
+ "homepage": "https://www.npmjs.com/#readme",
53
+ "publishConfig": {
54
+ "registry": "https://registry.npmjs.org/"
55
+ },
56
+ "devDependencies": {
57
+ "react": "19.0.0",
58
+ "react-native": "0.80.1"
59
+ },
60
+ "peerDependencies": {
61
+ "react": "*",
62
+ "react-native": "*"
63
+ },
64
+ "engines": {
65
+ "node": ">=18.0.0"
66
+ },
67
+ "codegenConfig": {
68
+ "name": "AntiScreenshotViewSpec",
69
+ "type": "all",
70
+ "jsSrcsDir": "src",
71
+ "android": {
72
+ "javaPackageName": "com.momokits.antiscreenshot"
73
+ },
74
+ "ios": {
75
+ "componentProvider": {
76
+ "RNAntiScreenshot": "RNAntiScreenshot"
77
+ }
78
+ }
79
+ },
80
+ "dependencies": {}
81
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import type { ViewProps, StyleProp, ViewStyle } from 'react-native';
3
+ import RNAntiScreenshotNativeComponent from './RNAntiScreenshotNativeComponent';
4
+
5
+ export interface AntiScreenshotViewProps extends ViewProps {
6
+ /**
7
+ * Style for the container view
8
+ */
9
+ style?: StyleProp<ViewStyle>;
10
+ /**
11
+ * Children to be protected from screenshots
12
+ */
13
+ children?: React.ReactNode;
14
+ }
15
+
16
+ /**
17
+ * AntiScreenshotView - A component that protects its content from screenshots.
18
+ *
19
+ * Content inside this view will appear as black/blank in screenshots and screen recordings,
20
+ * while displaying normally on the device screen.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <AntiScreenshotView style={{ flex: 1 }}>
25
+ * <Text>This content is protected from screenshots</Text>
26
+ * </AntiScreenshotView>
27
+ * ```
28
+ */
29
+ export const AntiScreenshotView: React.FC<AntiScreenshotViewProps> = ({
30
+ children,
31
+ style,
32
+ ...props
33
+ }) => {
34
+ return (
35
+ <RNAntiScreenshotNativeComponent style={style} {...props}>
36
+ {children}
37
+ </RNAntiScreenshotNativeComponent>
38
+ );
39
+ };
40
+
41
+ export default AntiScreenshotView;
@@ -0,0 +1,6 @@
1
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
2
+ import type { ViewProps } from 'react-native';
3
+
4
+ export interface NativeProps extends ViewProps {}
5
+
6
+ export default codegenNativeComponent<NativeProps>('RNAntiScreenshot');
package/src/index.tsx ADDED
@@ -0,0 +1,2 @@
1
+ export { AntiScreenshotView, type AntiScreenshotViewProps } from './AntiScreenshotView';
2
+ export { default } from './AntiScreenshotView';