├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── RNScreenshotPrevent.podspec ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── killserver │ └── screenshotprev │ ├── RNScreenshotPreventModule.java │ └── RNScreenshotPreventPackage.java ├── ios ├── RNScreenshotPrevent.h ├── RNScreenshotPrevent.m ├── RNScreenshotPrevent.xcodeproj │ └── project.pbxproj ├── UIImage+ImageEffects.h └── UIImage+ImageEffects.m ├── lefthook.yml ├── package.json ├── src ├── index.d.ts └── index.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | lib/ 10 | npm-debug.log 11 | yarn-error.log 12 | 13 | 14 | # Xcode 15 | # 16 | build/ 17 | *.pbxuser 18 | !default.pbxuser 19 | *.mode1v3 20 | !default.mode1v3 21 | *.mode2v3 22 | !default.mode2v3 23 | *.perspectivev3 24 | !default.perspectivev3 25 | xcuserdata 26 | *.xccheckout 27 | *.moved-aside 28 | DerivedData 29 | *.hmap 30 | *.ipa 31 | *.xcuserstate 32 | project.xcworkspace 33 | 34 | 35 | # Android/IntelliJ 36 | # 37 | build/ 38 | .idea 39 | .gradle 40 | local.properties 41 | *.iml 42 | 43 | # BUCK 44 | buck-out/ 45 | \.buckd/ 46 | *.keystore 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 killer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) 2 | 3 | # react-native-screenshot-prevent 4 | 5 | ### This fork contains fully working blank screenshot on IOS13+ including screen recording 6 | ### This fork contains fully working image screenshot cover on IOS13+ including screen recording 7 | ### App layout is white / or black in dark theme 8 | 9 | #### For now you might disable RNPreventScreenshot.enableSecureView() in development mode (check __DEV__ variable) 10 | #### because disableSecureView() is not working yet correctly 11 | 12 | 13 | ## Getting started 14 | 15 | `$ npm install react-native-screenshot-prevent --save` 16 | 17 | ### Mostly automatic installation 18 | 19 | ### React-Native version 0.59.X and higher: on IOS you might use only `pod install` in your ios folder 20 | 21 | `$ react-native link react-native-screenshot-prevent` 22 | 23 | ### Manual installation 24 | 25 | 26 | #### iOS 27 | 28 | 1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` 29 | 2. Go to `node_modules` ➜ `react-native-screenshot-prevent` and add `RNScreenshotPrevent.xcodeproj` 30 | 3. In XCode, in the project navigator, select your project. Add `libRNScreenshotPrevent.a` to your project's `Build Phases` ➜ `Link Binary With Libraries` 31 | 4. Run your project (`Cmd+R`)< 32 | 33 | #### Android 34 | 35 | 1. Open up `android/app/src/main/java/[...]/MainApplication.java` 36 | - Add `import com.killserver.screenshotprev.RNScreenshotPreventPackage;` to the imports at the top of the file 37 | - Add `new RNScreenshotPreventPackage()` to the list returned by the `getPackages()` method 38 | 2. Append the following lines to `android/settings.gradle`: 39 | ``` 40 | include ':react-native-screenshot-prevent' 41 | project(':react-native-screenshot-prevent').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-screenshot-prevent/android') 42 | ``` 43 | 3. Insert the following lines inside the dependencies block in `android/app/build.gradle`: 44 | ``` 45 | implementation project(':react-native-screenshot-prevent') 46 | ``` 47 | 48 | 49 | 50 | ## Usage 51 | ```javascript 52 | 53 | // sample code 54 | 55 | import RNScreenshotPrevent, { addListener } from 'react-native-screenshot-prevent'; 56 | 57 | /* (IOS, Android) for android might be the only step to get secureView 58 | * on IOS enables blurry view when app goes into inactive state 59 | */ 60 | RNScreenshotPrevent.enabled(true/false); 61 | 62 | /* (IOS) enableSecureView for IOS13+ 63 | * creates a hidden secureTextField which prevents Application UI capture on screenshots 64 | */ 65 | if(!__DEV__) RNScreenshotPrevent.enableSecureView(); 66 | 67 | /* (IOS) enableSecureView for IOS13+ 68 | * creates a hidden secureTextField which prevents Application UI capture on screenshots 69 | * and uses imgUri as the source of the background image (can be both https://, file:///) 70 | */ 71 | if(!__DEV__) RNPreventScreenshot.enableSecureView(imgUri); 72 | 73 | /* (IOS) disableSecureView for IOS13+ 74 | * remove a hidden secureTextField which prevents Application UI capture on screenshots 75 | */ 76 | if(!__DEV__) RNScreenshotPrevent.disableSecureView(); 77 | 78 | /* (IOS) notification handler 79 | * notifies when user has taken screenshot (yes, after taking) - you can show alert or do some actions 80 | * 81 | * @param {function} callback fn 82 | * @returns object with .remove() method 83 | */ 84 | addListener(fn); 85 | 86 | /** example using the listener */ 87 | useEffect(() => { 88 | const subscription = RNScreenshotPrevent.addListener(() => { 89 | console.log('Screenshot taken'); 90 | showAlert({ 91 | title: 'Warning', 92 | message: 'You have taken a screenshot of the app. This is prohibited due to security reasons.', 93 | confirmText: 'I understand' 94 | }); 95 | }) 96 | 97 | return () => { 98 | subscription.remove(); 99 | } 100 | }, []); 101 | 102 | ``` 103 | 104 | 105 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/killeserver) 106 | -------------------------------------------------------------------------------- /RNScreenshotPrevent.podspec: -------------------------------------------------------------------------------- 1 | # RNScreenshotPrevent 2 | 3 | require 'json' 4 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 5 | 6 | Pod::Spec.new do |s| 7 | s.name = "RNScreenshotPrevent" 8 | s.version = package['version'] 9 | s.summary = package['description'] 10 | s.license = package['license'] 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 14 | s.platform = :ios, "9.0" 15 | s.source = { :git => "https://github.com/killserver/react-native-screenshot-prevent.git", :tag => "master" } 16 | s.source_files = "ios/**/*.{h,m}" 17 | s.requires_arc = true 18 | 19 | 20 | s.dependency "React" 21 | #s.dependency "others" 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:1.3.1' 10 | } 11 | } 12 | 13 | def safeExtGet(prop, fallback) { 14 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 15 | } 16 | 17 | def DEFAULT_COMPILE_SDK_VERSION = 23 18 | def DEFAULT_BUILD_TOOLS_VERSION = "23.0.1" 19 | def DEFAULT_MIN_SDK_VERSION = 16 20 | def DEFAULT_TARGET_SDK_VERSION = 22 21 | 22 | 23 | android { 24 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 25 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) 26 | 27 | defaultConfig { 28 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 29 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 30 | versionCode 1 31 | versionName "1.0" 32 | } 33 | lintOptions { 34 | abortOnError false 35 | } 36 | } 37 | 38 | repositories { 39 | mavenCentral() 40 | } 41 | 42 | dependencies { 43 | implementation 'com.facebook.react:react-native:+' 44 | } 45 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/killserver/screenshotprev/RNScreenshotPreventModule.java: -------------------------------------------------------------------------------- 1 | package com.killserver.screenshotprev; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Color; 8 | import android.util.Log; 9 | import android.view.ViewTreeObserver; 10 | import android.view.Window; 11 | import android.view.WindowManager; 12 | import android.widget.RelativeLayout; 13 | import android.widget.ImageView; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.Nullable; 17 | import androidx.fragment.app.DialogFragment; 18 | import androidx.fragment.app.Fragment; 19 | import androidx.fragment.app.FragmentActivity; 20 | 21 | import java.net.MalformedURLException; 22 | import java.util.List; 23 | 24 | import com.facebook.react.bridge.ReactApplicationContext; 25 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 26 | import com.facebook.react.bridge.ReactMethod; 27 | import com.facebook.react.modules.dialog.AlertFragment; 28 | 29 | import java.io.IOException; 30 | import java.net.URL; 31 | import java.util.Objects; 32 | 33 | public class RNScreenshotPreventModule extends ReactContextBaseJavaModule implements ViewTreeObserver.OnWindowFocusChangeListener { 34 | private Dialog securedDialog; 35 | 36 | public RNScreenshotPreventModule(ReactApplicationContext reactContext) { 37 | super(reactContext); 38 | } 39 | 40 | @NonNull 41 | @Override 42 | public String getName() { 43 | return "RNScreenshotPrevent"; 44 | } 45 | 46 | @ReactMethod 47 | public void enabled(boolean enable) { 48 | Log.i("RNScreenshotPreventModule", "secured called " + enable); 49 | 50 | final Activity activity = this.getReactApplicationContext().getCurrentActivity(); 51 | if (!this.getReactApplicationContext().hasCurrentActivity() || activity == null) { 52 | return; 53 | } 54 | 55 | if (enable) { 56 | activity.runOnUiThread(() -> { 57 | activity.getWindow().setFlags( 58 | WindowManager.LayoutParams.FLAG_SECURE, 59 | WindowManager.LayoutParams.FLAG_SECURE 60 | ); 61 | }); 62 | } else { 63 | activity.runOnUiThread(() -> { 64 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); 65 | }); 66 | } 67 | } 68 | 69 | @ReactMethod 70 | public void enableSecureView(String imagePath) { 71 | Log.i("RNScreenshotPreventModule", "enable secured view called " + imagePath); 72 | 73 | final Activity activity = this.getReactApplicationContext().getCurrentActivity(); 74 | if (!this.getReactApplicationContext().hasCurrentActivity() || activity == null) { 75 | return; 76 | } 77 | 78 | activity.runOnUiThread(() -> { 79 | this.createSecuredDialog(imagePath, activity); 80 | enabled(true); 81 | activity.getWindow().getDecorView().getRootView().getViewTreeObserver().addOnWindowFocusChangeListener(this); 82 | }); 83 | } 84 | 85 | @ReactMethod 86 | public void disableSecureView() { 87 | Log.i("RNScreenshotPreventModule", "disable secured view called"); 88 | 89 | final Activity activity = this.getReactApplicationContext().getCurrentActivity(); 90 | if (!this.getReactApplicationContext().hasCurrentActivity() || activity == null) { 91 | return; 92 | } 93 | 94 | activity.runOnUiThread(() -> { 95 | this.securedDialog = null; 96 | enabled(false); 97 | activity.getWindow().getDecorView().getRootView().getViewTreeObserver().removeOnWindowFocusChangeListener(this); 98 | }); 99 | } 100 | 101 | private Bitmap decodeImageUrl(String imagePath) { 102 | try { 103 | URL imageUrl = new URL(imagePath); 104 | return BitmapFactory.decodeStream(imageUrl.openConnection().getInputStream()); 105 | } catch (IOException e) { 106 | if (e instanceof MalformedURLException) { 107 | try { 108 | int resourceId = this.getReactApplicationContext().getResources().getIdentifier(imagePath, "drawable", this.getReactApplicationContext().getPackageName()); 109 | return BitmapFactory.decodeResource(this.getReactApplicationContext().getResources(), resourceId); 110 | } catch (Exception ee) { 111 | Log.e("RNScreenshotPreventModule", "exception", ee); 112 | return null; 113 | } 114 | } 115 | Log.e("RNScreenshotPreventModule", "exception", e); 116 | return null; 117 | } 118 | } 119 | 120 | private Dialog getSecuredDialog() { 121 | final Activity activity = this.getReactApplicationContext().getCurrentActivity(); 122 | if (this.securedDialog == null && activity != null) { 123 | this.createSecuredDialog(null, activity); 124 | } 125 | 126 | return securedDialog; 127 | } 128 | 129 | private void createSecuredDialog(@Nullable String imagePath, @NonNull Activity activity) { 130 | this.securedDialog = new Dialog(activity, android.R.style.Theme_Light); 131 | this.securedDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 132 | Objects.requireNonNull(this.securedDialog.getWindow()).setFlags( 133 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 134 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 135 | ); 136 | 137 | RelativeLayout layout = new RelativeLayout(activity); 138 | layout.setBackgroundColor(Color.parseColor("#FFFFFF")); 139 | 140 | ImageView imageView = new ImageView(activity); 141 | RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams( 142 | RelativeLayout.LayoutParams.MATCH_PARENT, 143 | RelativeLayout.LayoutParams.WRAP_CONTENT); 144 | imageParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 145 | 146 | imageView.setLayoutParams(imageParams); 147 | 148 | // Set image resource 149 | if (imagePath != null) { 150 | Bitmap bitmap = decodeImageUrl(imagePath); 151 | 152 | if (bitmap != null) { 153 | int imageHeight = (int)(bitmap.getHeight() * ((float) activity.getResources().getDisplayMetrics().widthPixels / bitmap.getWidth())); 154 | Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, activity.getResources().getDisplayMetrics().widthPixels, imageHeight, true); 155 | imageView.setImageBitmap(scaledBitmap); 156 | } 157 | 158 | layout.addView(imageView); 159 | } 160 | 161 | this.securedDialog.setContentView(layout); 162 | } 163 | 164 | @Override 165 | public void onWindowFocusChanged(boolean hasFocus) { 166 | Log.i("RNScreenshotPreventModule", "on window focus changed " + hasFocus); 167 | 168 | final Activity activity = this.getReactApplicationContext().getCurrentActivity(); 169 | if (activity == null) { 170 | Log.i("RNScreenshotPreventModule", "activity is null" ); 171 | return; 172 | } 173 | 174 | boolean hasAlert = false; 175 | if (activity instanceof FragmentActivity) { 176 | List fragments = ((FragmentActivity) activity).getSupportFragmentManager().getFragments(); 177 | for (Fragment fragment : fragments) { 178 | boolean isDiablog = fragment instanceof DialogFragment; 179 | boolean isAlert = fragment instanceof AlertFragment; 180 | if (isDiablog || isAlert) { 181 | hasAlert = true; 182 | break; 183 | } 184 | } 185 | } 186 | 187 | if (!hasFocus && !hasAlert){ 188 | Log.i("RNScreenshotPreventModule", "call to hide content"); 189 | this.getSecuredDialog().show(); 190 | activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); 191 | } else { 192 | Log.i("RNScreenshotPreventModule", "call to show content"); 193 | this.getSecuredDialog().hide(); 194 | activity.getWindow().setFlags( 195 | WindowManager.LayoutParams.FLAG_SECURE, 196 | WindowManager.LayoutParams.FLAG_SECURE 197 | ); 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /android/src/main/java/com/killserver/screenshotprev/RNScreenshotPreventPackage.java: -------------------------------------------------------------------------------- 1 | 2 | package com.killserver.screenshotprev; 3 | 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.uimanager.ViewManager; 12 | import com.facebook.react.bridge.JavaScriptModule; 13 | 14 | public class RNScreenshotPreventPackage implements ReactPackage { 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | return Arrays.asList(new RNScreenshotPreventModule(reactContext)); 18 | } 19 | 20 | // Deprecated from RN 0.47 21 | public List> createJSModules() { 22 | return Collections.emptyList(); 23 | } 24 | 25 | @Override 26 | public List createViewManagers(ReactApplicationContext reactContext) { 27 | return Collections.emptyList(); 28 | } 29 | } -------------------------------------------------------------------------------- /ios/RNScreenshotPrevent.h: -------------------------------------------------------------------------------- 1 | 2 | #if __has_include("RCTBridgeModule.h") 3 | #import "RCTBridgeModule.h" 4 | #import "RCTConvert.h" 5 | #import "RCTEventEmitter.h" 6 | #else 7 | #import 8 | #import 9 | #import 10 | #endif 11 | 12 | @interface RNScreenshotPrevent : RCTEventEmitter 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ios/RNScreenshotPrevent.m: -------------------------------------------------------------------------------- 1 | #import "RNScreenshotPrevent.h" 2 | #import "UIImage+ImageEffects.h" 3 | 4 | @implementation RNScreenshotPrevent { 5 | BOOL hasListeners; 6 | BOOL enabled; 7 | UIImageView *obfuscatingView; 8 | UITextField *secureField; 9 | 10 | } 11 | 12 | RCT_EXPORT_MODULE(); 13 | - (NSArray *)supportedEvents { 14 | return @[@"userDidTakeScreenshot"]; 15 | } 16 | 17 | - (dispatch_queue_t)methodQueue 18 | { 19 | return dispatch_get_main_queue(); 20 | } 21 | 22 | #pragma mark - Lifecycle 23 | 24 | - (void) startObserving { 25 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 26 | // handle inactive event 27 | [center addObserver:self selector:@selector(handleAppStateResignActive) 28 | name:UIApplicationWillResignActiveNotification 29 | object:nil]; 30 | // handle active event 31 | [center addObserver:self selector:@selector(handleAppStateActive) 32 | name:UIApplicationDidBecomeActiveNotification 33 | object:nil]; 34 | // handle screenshot taken event 35 | [center addObserver:self selector:@selector(handleAppScreenshotNotification) 36 | name:UIApplicationUserDidTakeScreenshotNotification 37 | object:nil]; 38 | 39 | hasListeners = TRUE; 40 | } 41 | 42 | - (void) stopObserving { 43 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 44 | 45 | hasListeners = FALSE; 46 | } 47 | 48 | #pragma mark - App Notification Methods 49 | 50 | /** displays blurry view when app becomes inactive */ 51 | - (void)handleAppStateResignActive { 52 | if (self->enabled) { 53 | UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; 54 | UIImageView *blurredScreenImageView = [[UIImageView alloc] initWithFrame:keyWindow.bounds]; 55 | 56 | UIGraphicsBeginImageContext(keyWindow.bounds.size); 57 | [keyWindow drawViewHierarchyInRect:keyWindow.frame afterScreenUpdates:NO]; 58 | UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext(); 59 | UIGraphicsEndImageContext(); 60 | 61 | blurredScreenImageView.image = [viewImage applyLightEffect]; 62 | 63 | self->obfuscatingView = blurredScreenImageView; 64 | [keyWindow addSubview:self->obfuscatingView]; 65 | } 66 | } 67 | 68 | /** removes blurry view when app becomes active */ 69 | - (void)handleAppStateActive { 70 | if (self->obfuscatingView) { 71 | [UIView animateWithDuration: 0.3 72 | animations: ^ { 73 | self->obfuscatingView.alpha = 0; 74 | } 75 | completion: ^(BOOL finished) { 76 | [self->obfuscatingView removeFromSuperview]; 77 | self->obfuscatingView = nil; 78 | } 79 | ]; 80 | } 81 | } 82 | 83 | /** sends screenshot taken event into app */ 84 | - (void) handleAppScreenshotNotification { 85 | // only send events when we have some listeners 86 | if(hasListeners) { 87 | [self sendEventWithName:@"userDidTakeScreenshot" body:nil]; 88 | } 89 | } 90 | 91 | +(BOOL) requiresMainQueueSetup 92 | { 93 | return YES; 94 | } 95 | 96 | CGSize CGSizeAspectFit(const CGSize aspectRatio, const CGSize boundingSize) 97 | { 98 | CGSize aspectFitSize = CGSizeMake(boundingSize.width, boundingSize.height); 99 | float mW = boundingSize.width / aspectRatio.width; 100 | float mH = boundingSize.height / aspectRatio.height; 101 | if( mH < mW ) 102 | aspectFitSize.width = mH * aspectRatio.width; 103 | else if( mW < mH ) 104 | aspectFitSize.height = mW * aspectRatio.height; 105 | return aspectFitSize; 106 | } 107 | 108 | CGSize CGSizeAspectFill(const CGSize aspectRatio, const CGSize minimumSize) 109 | { 110 | CGSize aspectFillSize = CGSizeMake(minimumSize.width, minimumSize.height); 111 | float mW = minimumSize.width / aspectRatio.width; 112 | float mH = minimumSize.height / aspectRatio.height; 113 | if( mH > mW ) 114 | aspectFillSize.width = mH * aspectRatio.width; 115 | else if( mW > mH ) 116 | aspectFillSize.height = mW * aspectRatio.height; 117 | return aspectFillSize; 118 | } 119 | 120 | 121 | /** 122 | * creates secure text field inside rootView of the app 123 | * taken from https://stackoverflow.com/questions/18680028/prevent-screen-capture-in-an-ios-app 124 | * 125 | * converted to ObjC and modified to get it working with RCT 126 | */ 127 | -(void) addSecureTextFieldToView:(UIView *) view :(NSString *) imagePath { 128 | 129 | UIView *rootView = [UIApplication sharedApplication].keyWindow.rootViewController.view; 130 | 131 | 132 | // fixes safe-area 133 | secureField = [[UITextField alloc] initWithFrame:rootView.frame]; 134 | secureField.secureTextEntry = TRUE; 135 | secureField.userInteractionEnabled = FALSE; 136 | 137 | if (imagePath && ![imagePath isEqualToString:@""]) { 138 | UIView * imgView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, rootView.frame.size.width, rootView.frame.size.height)]; 139 | NSURL *url = [NSURL URLWithString:imagePath]; 140 | NSData *data = [NSData dataWithContentsOfURL:url]; 141 | UIImage *img = [[UIImage alloc] initWithData:data]; 142 | 143 | CGSize sizeBeingScaledTo = CGSizeAspectFill(img.size, imgView.frame.size); 144 | 145 | // redraw the image to fit the screen size 146 | UIGraphicsBeginImageContextWithOptions(imgView.frame.size, NO, 0.f); 147 | 148 | float offsetX = (imgView.frame.size.width - sizeBeingScaledTo.width) / 2; 149 | float offsety = (imgView.frame.size.height - sizeBeingScaledTo.height) / 2; 150 | 151 | 152 | [img drawInRect:CGRectMake(offsetX, offsety, sizeBeingScaledTo.width, sizeBeingScaledTo.height)]; 153 | UIImage * resultImage = UIGraphicsGetImageFromCurrentImageContext(); 154 | UIGraphicsEndImageContext(); 155 | 156 | secureField.backgroundColor = [UIColor colorWithPatternImage:resultImage]; 157 | } 158 | 159 | [view sendSubviewToBack:secureField]; 160 | [view addSubview:secureField]; 161 | [view.layer.superlayer addSublayer:secureField.layer]; 162 | [[secureField.layer.sublayers lastObject] addSublayer:view.layer]; 163 | } 164 | 165 | // TODO: not working now, fix crash on _UITextFieldCanvasView contenttViewInvalidated: unrecognized selector sent to instance 166 | -(void) removeSecureTextFieldFromView:(UIView *) view { 167 | for(UITextField *subview in view.subviews){ 168 | if([subview isMemberOfClass:[UITextField class]]) { 169 | if(subview.secureTextEntry == TRUE) { 170 | [subview removeFromSuperview]; 171 | subview.secureTextEntry = FALSE; 172 | secureField.userInteractionEnabled = TRUE; 173 | } 174 | } 175 | } 176 | } 177 | 178 | #pragma mark - Public API 179 | 180 | RCT_EXPORT_METHOD(enabled:(BOOL) _enable) { 181 | self->enabled = _enable; 182 | } 183 | 184 | /** adds secure textfield view */ 185 | RCT_EXPORT_METHOD(enableSecureView: (NSString *)imagePath) { 186 | [self enabled:YES]; 187 | if(secureField.secureTextEntry == false) { 188 | UIView *view = [UIApplication sharedApplication].keyWindow.rootViewController.view; 189 | for(UIView *subview in view.subviews) { 190 | [self addSecureTextFieldToView:subview :imagePath]; 191 | } 192 | } 193 | } 194 | 195 | /** removes secure textfield from the view */ 196 | RCT_EXPORT_METHOD(disableSecureView) { 197 | [self enabled:NO]; 198 | secureField.secureTextEntry = false; 199 | UIView *view = [UIApplication sharedApplication].keyWindow.rootViewController.view; 200 | for(UIView *subview in view.subviews) { 201 | [self removeSecureTextFieldFromView:subview]; 202 | } 203 | } 204 | 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /ios/RNScreenshotPrevent.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B3E7B58A1CC2AC0600A0062D /* RNScreenshotPrevent.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNScreenshotPrevent.m */; }; 11 | F7D96EE223047BB300BC2484 /* UIImage+ImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = F7D96EE123047BB300BC2484 /* UIImage+ImageEffects.m */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = "include/$(PRODUCT_NAME)"; 19 | dstSubfolderSpec = 16; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 0; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 134814201AA4EA6300B7C361 /* libRNScreenshotPrevent.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = libRNScreenshotPrevent.a; path = libRNScreenshotPrevent.a; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | B3E7B5881CC2AC0600A0062D /* RNScreenshotPrevent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNScreenshotPrevent.h; sourceTree = ""; }; 29 | B3E7B5891CC2AC0600A0062D /* RNScreenshotPrevent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNScreenshotPrevent.m; sourceTree = ""; }; 30 | F7D96EE023047BB300BC2484 /* UIImage+ImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ImageEffects.h"; sourceTree = ""; }; 31 | F7D96EE123047BB300BC2484 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ImageEffects.m"; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 134814211AA4EA7D00B7C361 /* Products */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | 134814201AA4EA6300B7C361 /* libRNScreenshotPrevent.a */, 49 | ); 50 | name = Products; 51 | sourceTree = ""; 52 | }; 53 | 58B511D21A9E6C8500147676 = { 54 | isa = PBXGroup; 55 | children = ( 56 | F7D96EE023047BB300BC2484 /* UIImage+ImageEffects.h */, 57 | F7D96EE123047BB300BC2484 /* UIImage+ImageEffects.m */, 58 | B3E7B5881CC2AC0600A0062D /* RNScreenshotPrevent.h */, 59 | B3E7B5891CC2AC0600A0062D /* RNScreenshotPrevent.m */, 60 | 134814211AA4EA7D00B7C361 /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | /* End PBXGroup section */ 65 | 66 | /* Begin PBXNativeTarget section */ 67 | 58B511DA1A9E6C8500147676 /* RNScreenshotPrevent */ = { 68 | isa = PBXNativeTarget; 69 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNScreenshotPrevent" */; 70 | buildPhases = ( 71 | 58B511D71A9E6C8500147676 /* Sources */, 72 | 58B511D81A9E6C8500147676 /* Frameworks */, 73 | 58B511D91A9E6C8500147676 /* CopyFiles */, 74 | ); 75 | buildRules = ( 76 | ); 77 | dependencies = ( 78 | ); 79 | name = RNScreenshotPrevent; 80 | productName = RCTDataManager; 81 | productReference = 134814201AA4EA6300B7C361 /* libRNScreenshotPrevent.a */; 82 | productType = "com.apple.product-type.library.static"; 83 | }; 84 | /* End PBXNativeTarget section */ 85 | 86 | /* Begin PBXProject section */ 87 | 58B511D31A9E6C8500147676 /* Project object */ = { 88 | isa = PBXProject; 89 | attributes = { 90 | LastUpgradeCheck = 0830; 91 | ORGANIZATIONNAME = Facebook; 92 | TargetAttributes = { 93 | 58B511DA1A9E6C8500147676 = { 94 | CreatedOnToolsVersion = 6.1.1; 95 | }; 96 | }; 97 | }; 98 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNScreenshotPrevent" */; 99 | compatibilityVersion = "Xcode 3.2"; 100 | developmentRegion = English; 101 | hasScannedForEncodings = 0; 102 | knownRegions = ( 103 | English, 104 | en, 105 | ); 106 | mainGroup = 58B511D21A9E6C8500147676; 107 | productRefGroup = 58B511D21A9E6C8500147676; 108 | projectDirPath = ""; 109 | projectRoot = ""; 110 | targets = ( 111 | 58B511DA1A9E6C8500147676 /* RNScreenshotPrevent */, 112 | ); 113 | }; 114 | /* End PBXProject section */ 115 | 116 | /* Begin PBXSourcesBuildPhase section */ 117 | 58B511D71A9E6C8500147676 /* Sources */ = { 118 | isa = PBXSourcesBuildPhase; 119 | buildActionMask = 2147483647; 120 | files = ( 121 | F7D96EE223047BB300BC2484 /* UIImage+ImageEffects.m in Sources */, 122 | B3E7B58A1CC2AC0600A0062D /* RNScreenshotPrevent.m in Sources */, 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXSourcesBuildPhase section */ 127 | 128 | /* Begin XCBuildConfiguration section */ 129 | 58B511ED1A9E6C8500147676 /* Debug */ = { 130 | isa = XCBuildConfiguration; 131 | buildSettings = { 132 | ALWAYS_SEARCH_USER_PATHS = NO; 133 | CLANG_ANALYZER_NONNULL = YES; 134 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 135 | CLANG_CXX_LIBRARY = "libc++"; 136 | CLANG_ENABLE_MODULES = YES; 137 | CLANG_ENABLE_OBJC_ARC = YES; 138 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 139 | CLANG_WARN_BOOL_CONVERSION = YES; 140 | CLANG_WARN_COMMA = YES; 141 | CLANG_WARN_CONSTANT_CONVERSION = YES; 142 | CLANG_WARN_DEPRICATED_OBJC_IMPLEMENTATIONS = YES; 143 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 144 | CLANG_WARN_EMPTY_BODY = YES; 145 | CLANG_WARN_ENUM_CONVERSION = YES; 146 | CLANG_WARN_INFINITE_RECURSION = YES; 147 | CLANG_WARN_INT_CONVERSION = YES; 148 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 149 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 150 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 151 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 152 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 153 | CLANG_WARN_STRICT_PROTOTYPES = YES; 154 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 155 | CLANG_WARN_UNREACHABLE_CODE = YES; 156 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 157 | COPY_PHASE_STRIP = NO; 158 | ENABLE_STRICT_OBJC_MSGSEND = YES; 159 | ENABLE_TESTABILITY = YES; 160 | GCC_C_LANGUAGE_STANDARD = gnu99; 161 | GCC_DYNAMIC_NO_PIC = NO; 162 | GCC_NO_COMMON_BLOCKS = YES; 163 | GCC_OPTIMIZATION_LEVEL = 0; 164 | GCC_PREPROCESSOR_DEFINITIONS = ( 165 | "DEBUG=1", 166 | "$(inherited)", 167 | ); 168 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 169 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 170 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 171 | GCC_WARN_UNDECLARED_SELECTOR = YES; 172 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 173 | GCC_WARN_UNUSED_FUNCTION = YES; 174 | GCC_WARN_UNUSED_VARIABLE = YES; 175 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 176 | MTL_ENABLE_DEBUG_INFO = YES; 177 | ONLY_ACTIVE_ARCH = YES; 178 | SDKROOT = iphoneos; 179 | }; 180 | name = Debug; 181 | }; 182 | 58B511EE1A9E6C8500147676 /* Release */ = { 183 | isa = XCBuildConfiguration; 184 | buildSettings = { 185 | ALWAYS_SEARCH_USER_PATHS = NO; 186 | CLANG_ANALYZER_NONNULL = YES; 187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 188 | CLANG_CXX_LIBRARY = "libc++"; 189 | CLANG_ENABLE_MODULES = YES; 190 | CLANG_ENABLE_OBJC_ARC = YES; 191 | CLANG_WARN_BOOL_CONVERSION = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 194 | CLANG_WARN_EMPTY_BODY = YES; 195 | CLANG_WARN_ENUM_CONVERSION = YES; 196 | CLANG_WARN_INFINITE_RECURSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 200 | CLANG_WARN_UNREACHABLE_CODE = YES; 201 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 202 | COPY_PHASE_STRIP = YES; 203 | ENABLE_NS_ASSERTIONS = NO; 204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 205 | GCC_C_LANGUAGE_STANDARD = gnu99; 206 | GCC_NO_COMMON_BLOCKS = YES; 207 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 208 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 209 | GCC_WARN_UNDECLARED_SELECTOR = YES; 210 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 211 | GCC_WARN_UNUSED_FUNCTION = YES; 212 | GCC_WARN_UNUSED_VARIABLE = YES; 213 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 214 | MTL_ENABLE_DEBUG_INFO = NO; 215 | SDKROOT = iphoneos; 216 | VALIDATE_PRODUCT = YES; 217 | }; 218 | name = Release; 219 | }; 220 | 58B511F01A9E6C8500147676 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | HEADER_SEARCH_PATHS = ( 224 | "$(inherited)", 225 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 226 | "$(SRCROOT)/../../../React/", 227 | "$(SRCROOT)/../../react-native/React/", 228 | ); 229 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 230 | OTHER_LDFLAGS = "-ObjC"; 231 | PRODUCT_NAME = RNScreenshotPrevent; 232 | SKIP_INSTALL = YES; 233 | }; 234 | name = Debug; 235 | }; 236 | 58B511F11A9E6C8500147676 /* Release */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | HEADER_SEARCH_PATHS = ( 240 | "$(inherited)", 241 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 242 | "$(SRCROOT)/../../../React/", 243 | "$(SRCROOT)/../../react-native/React/", 244 | ); 245 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 246 | OTHER_LDFLAGS = "-ObjC"; 247 | PRODUCT_NAME = RNScreenshotPrevent; 248 | SKIP_INSTALL = YES; 249 | }; 250 | name = Release; 251 | }; 252 | /* End XCBuildConfiguration section */ 253 | 254 | /* Begin XCConfigurationList section */ 255 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNScreenshotPrevent" */ = { 256 | isa = XCConfigurationList; 257 | buildConfigurations = ( 258 | 58B511ED1A9E6C8500147676 /* Debug */, 259 | 58B511EE1A9E6C8500147676 /* Release */, 260 | ); 261 | defaultConfigurationIsVisible = 0; 262 | defaultConfigurationName = Release; 263 | }; 264 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNScreenshotPrevent" */ = { 265 | isa = XCConfigurationList; 266 | buildConfigurations = ( 267 | 58B511F01A9E6C8500147676 /* Debug */, 268 | 58B511F11A9E6C8500147676 /* Release */, 269 | ); 270 | defaultConfigurationIsVisible = 0; 271 | defaultConfigurationName = Release; 272 | }; 273 | /* End XCConfigurationList section */ 274 | }; 275 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 276 | } 277 | -------------------------------------------------------------------------------- /ios/UIImage+ImageEffects.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImage+ImageEffects.h 3 | Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code you’ll want to look out to find out how to use vImage to efficiently calculate a blur. 4 | Version: 1.0 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2013 Apple Inc. All Rights Reserved. 45 | 46 | 47 | Copyright © 2013 Apple Inc. All rights reserved. 48 | WWDC 2013 License 49 | 50 | NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013 51 | Session. Please refer to the applicable WWDC 2013 Session for further 52 | information. 53 | 54 | IMPORTANT: This Apple software is supplied to you by Apple Inc. 55 | ("Apple") in consideration of your agreement to the following terms, and 56 | your use, installation, modification or redistribution of this Apple 57 | software constitutes acceptance of these terms. If you do not agree with 58 | these terms, please do not use, install, modify or redistribute this 59 | Apple software. 60 | 61 | In consideration of your agreement to abide by the following terms, and 62 | subject to these terms, Apple grants you a non-exclusive license, under 63 | Apple's copyrights in this original Apple software (the "Apple 64 | Software"), to use, reproduce, modify and redistribute the Apple 65 | Software, with or without modifications, in source and/or binary forms; 66 | provided that if you redistribute the Apple Software in its entirety and 67 | without modifications, you must retain this notice and the following 68 | text and disclaimers in all such redistributions of the Apple Software. 69 | Neither the name, trademarks, service marks or logos of Apple Inc. may 70 | be used to endorse or promote products derived from the Apple Software 71 | without specific prior written permission from Apple. Except as 72 | expressly stated in this notice, no other rights or licenses, express or 73 | implied, are granted by Apple herein, including but not limited to any 74 | patent rights that may be infringed by your derivative works or by other 75 | works in which the Apple Software may be incorporated. 76 | 77 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES 78 | NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE 79 | IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR 80 | A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 81 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 82 | 83 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 84 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 85 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 86 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 87 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 88 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 89 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 90 | POSSIBILITY OF SUCH DAMAGE. 91 | 92 | EA1002 93 | 5/3/2013 94 | */ 95 | 96 | @import UIKit; 97 | 98 | @interface UIImage (ImageEffects) 99 | 100 | - (UIImage *)applyLightEffect; 101 | - (UIImage *)applyExtraLightEffect; 102 | - (UIImage *)applyDarkEffect; 103 | - (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor; 104 | 105 | - (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage; 106 | 107 | - (UIImage *)rotateByDegrees:(CGFloat)degrees; 108 | 109 | + (UIImage *)imagePixelFromColor:(UIColor *)color; 110 | + (UIImage *)imageFromColor:(UIColor *)color withSize:(CGSize)size; 111 | 112 | @end -------------------------------------------------------------------------------- /ios/UIImage+ImageEffects.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImage+ImageEffects.m 3 | Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code you’ll want to look out to find out how to use vImage to efficiently calculate a blur. 4 | Version: 1.0 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2013 Apple Inc. All Rights Reserved. 45 | 46 | 47 | Copyright © 2013 Apple Inc. All rights reserved. 48 | WWDC 2013 License 49 | 50 | NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013 51 | Session. Please refer to the applicable WWDC 2013 Session for further 52 | information. 53 | 54 | IMPORTANT: This Apple software is supplied to you by Apple Inc. 55 | ("Apple") in consideration of your agreement to the following terms, and 56 | your use, installation, modification or redistribution of this Apple 57 | software constitutes acceptance of these terms. If you do not agree with 58 | these terms, please do not use, install, modify or redistribute this 59 | Apple software. 60 | 61 | In consideration of your agreement to abide by the following terms, and 62 | subject to these terms, Apple grants you a non-exclusive license, under 63 | Apple's copyrights in this original Apple software (the "Apple 64 | Software"), to use, reproduce, modify and redistribute the Apple 65 | Software, with or without modifications, in source and/or binary forms; 66 | provided that if you redistribute the Apple Software in its entirety and 67 | without modifications, you must retain this notice and the following 68 | text and disclaimers in all such redistributions of the Apple Software. 69 | Neither the name, trademarks, service marks or logos of Apple Inc. may 70 | be used to endorse or promote products derived from the Apple Software 71 | without specific prior written permission from Apple. Except as 72 | expressly stated in this notice, no other rights or licenses, express or 73 | implied, are granted by Apple herein, including but not limited to any 74 | patent rights that may be infringed by your derivative works or by other 75 | works in which the Apple Software may be incorporated. 76 | 77 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES 78 | NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE 79 | IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR 80 | A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 81 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 82 | 83 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 84 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 85 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 86 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 87 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 88 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 89 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 90 | POSSIBILITY OF SUCH DAMAGE. 91 | 92 | EA1002 93 | 5/3/2013 94 | */ 95 | 96 | #import "UIImage+ImageEffects.h" 97 | 98 | @import Accelerate; 99 | #import 100 | 101 | 102 | @implementation UIImage (ImageEffects) 103 | 104 | 105 | - (UIImage *)applyLightEffect 106 | { 107 | UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; 108 | return [self applyBlurWithRadius:30 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 109 | } 110 | 111 | 112 | - (UIImage *)applyExtraLightEffect 113 | { 114 | UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; 115 | return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 116 | } 117 | 118 | 119 | - (UIImage *)applyDarkEffect 120 | { 121 | UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; 122 | return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 123 | } 124 | 125 | 126 | - (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor 127 | { 128 | const CGFloat EffectColorAlpha = 0.6; 129 | UIColor *effectColor = tintColor; 130 | int componentCount = CGColorGetNumberOfComponents(tintColor.CGColor); 131 | if (componentCount == 2) { 132 | CGFloat b; 133 | if ([tintColor getWhite:&b alpha:NULL]) { 134 | effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha]; 135 | } 136 | } 137 | else { 138 | CGFloat r, g, b; 139 | if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) { 140 | effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha]; 141 | } 142 | } 143 | return [self applyBlurWithRadius:10 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil]; 144 | } 145 | 146 | 147 | - (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage 148 | { 149 | // Check pre-conditions. 150 | if (self.size.width < 1 || self.size.height < 1) { 151 | NSLog (@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self); 152 | return nil; 153 | } 154 | if (!self.CGImage) { 155 | NSLog (@"*** error: image must be backed by a CGImage: %@", self); 156 | return nil; 157 | } 158 | if (maskImage && !maskImage.CGImage) { 159 | NSLog (@"*** error: maskImage must be backed by a CGImage: %@", maskImage); 160 | return nil; 161 | } 162 | 163 | CGRect imageRect = { CGPointZero, self.size }; 164 | UIImage *effectImage = self; 165 | 166 | BOOL hasBlur = blurRadius > __FLT_EPSILON__; 167 | BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; 168 | if (hasBlur || hasSaturationChange) { 169 | UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); 170 | CGContextRef effectInContext = UIGraphicsGetCurrentContext(); 171 | CGContextScaleCTM(effectInContext, 1.0, -1.0); 172 | CGContextTranslateCTM(effectInContext, 0, -self.size.height); 173 | CGContextDrawImage(effectInContext, imageRect, self.CGImage); 174 | 175 | vImage_Buffer effectInBuffer; 176 | effectInBuffer.data = CGBitmapContextGetData(effectInContext); 177 | effectInBuffer.width = CGBitmapContextGetWidth(effectInContext); 178 | effectInBuffer.height = CGBitmapContextGetHeight(effectInContext); 179 | effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext); 180 | 181 | UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); 182 | CGContextRef effectOutContext = UIGraphicsGetCurrentContext(); 183 | vImage_Buffer effectOutBuffer; 184 | effectOutBuffer.data = CGBitmapContextGetData(effectOutContext); 185 | effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext); 186 | effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext); 187 | effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext); 188 | 189 | if (hasBlur) { 190 | // A description of how to compute the box kernel width from the Gaussian 191 | // radius (aka standard deviation) appears in the SVG spec: 192 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 193 | // 194 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 195 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 196 | // approximates the Gaussian kernel to within roughly 3%. 197 | // 198 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 199 | // 200 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 201 | // 202 | CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale]; 203 | NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); 204 | if (radius % 2 != 1) { 205 | radius += 1; // force radius to be odd so that the three box-blur methodology works. 206 | } 207 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend); 208 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend); 209 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend); 210 | } 211 | BOOL effectImageBuffersAreSwapped = NO; 212 | if (hasSaturationChange) { 213 | CGFloat s = saturationDeltaFactor; 214 | CGFloat floatingPointSaturationMatrix[] = { 215 | 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, 216 | 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, 217 | 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, 218 | 0, 0, 0, 1, 219 | }; 220 | const int32_t divisor = 256; 221 | NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]); 222 | int16_t saturationMatrix[matrixSize]; 223 | for (NSUInteger i = 0; i < matrixSize; ++i) { 224 | saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); 225 | } 226 | if (hasBlur) { 227 | vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); 228 | effectImageBuffersAreSwapped = YES; 229 | } 230 | else { 231 | vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); 232 | } 233 | } 234 | if (!effectImageBuffersAreSwapped) 235 | effectImage = UIGraphicsGetImageFromCurrentImageContext(); 236 | UIGraphicsEndImageContext(); 237 | 238 | if (effectImageBuffersAreSwapped) 239 | effectImage = UIGraphicsGetImageFromCurrentImageContext(); 240 | UIGraphicsEndImageContext(); 241 | } 242 | 243 | // Set up output context. 244 | UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]); 245 | CGContextRef outputContext = UIGraphicsGetCurrentContext(); 246 | CGContextScaleCTM(outputContext, 1.0, -1.0); 247 | CGContextTranslateCTM(outputContext, 0, -self.size.height); 248 | 249 | // Draw base image. 250 | CGContextDrawImage(outputContext, imageRect, self.CGImage); 251 | 252 | // Draw effect image. 253 | if (hasBlur) { 254 | CGContextSaveGState(outputContext); 255 | if (maskImage) { 256 | CGContextClipToMask(outputContext, imageRect, maskImage.CGImage); 257 | } 258 | CGContextDrawImage(outputContext, imageRect, effectImage.CGImage); 259 | CGContextRestoreGState(outputContext); 260 | } 261 | 262 | // Add in color tint. 263 | if (tintColor) { 264 | CGContextSaveGState(outputContext); 265 | CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); 266 | CGContextFillRect(outputContext, imageRect); 267 | CGContextRestoreGState(outputContext); 268 | } 269 | 270 | // Output image is ready. 271 | UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext(); 272 | UIGraphicsEndImageContext(); 273 | 274 | return outputImage; 275 | } 276 | 277 | - (UIImage *)rotateByDegrees:(CGFloat)degrees { 278 | UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.size.width, self.size.height)]; 279 | CGAffineTransform t = CGAffineTransformMakeRotation(degrees * M_PI / 180.0f); 280 | rotatedViewBox.transform = t; 281 | CGSize rotatedSize = rotatedViewBox.frame.size; 282 | 283 | UIGraphicsBeginImageContext(rotatedSize); 284 | CGContextRef bitmap = UIGraphicsGetCurrentContext(); 285 | CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2); 286 | CGContextRotateCTM(bitmap, degrees * M_PI / 180.0f); 287 | CGContextScaleCTM(bitmap, 1.0, -1.0); 288 | CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), self.CGImage); 289 | 290 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 291 | UIGraphicsEndImageContext(); 292 | return newImage; 293 | } 294 | 295 | + (UIImage *)imagePixelFromColor:(UIColor *)color { 296 | return [UIImage imageFromColor:color withSize:CGSizeMake(1.0f, 1.0f)]; 297 | } 298 | 299 | + (UIImage *)imageFromColor:(UIColor *)color withSize:(CGSize)size { 300 | CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height); 301 | UIGraphicsBeginImageContext(rect.size); 302 | CGContextRef context = UIGraphicsGetCurrentContext(); 303 | CGContextSetFillColorWithColor(context, [color CGColor]); 304 | CGContextFillRect(context, rect); 305 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 306 | UIGraphicsEndImageContext(); 307 | return image; 308 | } 309 | 310 | @end -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # EXAMPLE USAGE: 2 | # 3 | # Refer for explanation to following link: 4 | # https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md 5 | # 6 | # pre-push: 7 | # commands: 8 | # packages-audit: 9 | # tags: frontend security 10 | # run: yarn audit 11 | # gems-audit: 12 | # tags: backend security 13 | # run: bundle audit 14 | # 15 | # pre-commit: 16 | # parallel: true 17 | # commands: 18 | # eslint: 19 | # glob: "*.{js,ts,jsx,tsx}" 20 | # run: yarn eslint {staged_files} 21 | # rubocop: 22 | # tags: backend style 23 | # glob: "*.rb" 24 | # exclude: "application.rb|routes.rb" 25 | # run: bundle exec rubocop --force-exclusion {all_files} 26 | # govet: 27 | # tags: backend style 28 | # files: git ls-files -m 29 | # glob: "*.go" 30 | # run: go vet {files} 31 | # scripts: 32 | # "hello.js": 33 | # runner: node 34 | # "any.go": 35 | # runner: go run 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-screenshot-prevent", 3 | "version": "1.2.1", 4 | "description": "This fork contains fully working blank screenshot on IOS13+ including screen recording", 5 | "main": "lib/module/index", 6 | "module": "lib/module/index", 7 | "types": "./src/index.d.ts", 8 | "react-native": "lib/module/index", 9 | "source": "./src/index", 10 | "files": [ 11 | "src", 12 | "lib", 13 | "android", 14 | "ios", 15 | "cpp", 16 | "*.podspec", 17 | "!ios/build", 18 | "!android/build", 19 | "!android/gradle", 20 | "!android/gradlew", 21 | "!android/gradlew.bat", 22 | "!android/local.properties", 23 | "!**/__tests__", 24 | "!**/__fixtures__", 25 | "!**/__mocks__", 26 | "!**/.*" 27 | ], 28 | "scripts": { 29 | "test": "jest", 30 | "typecheck": "tsc --noEmit", 31 | "prepare": "bob build", 32 | "lint": "eslint \"**/*.{js,ts,tsx}\"" 33 | }, 34 | "keywords": [ 35 | "react-native", 36 | "ios", 37 | "android" 38 | ], 39 | "repository": "https://github.com/killserver/react-native-screenshot-prevent", 40 | "author": "killserver", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/killserver/react-native-screenshot-prevent/issues" 44 | }, 45 | "homepage": "https://github.com/killserver/react-native-screenshot-prevent#readme", 46 | "devDependencies": { 47 | "@react-native-community/bob": "^0.10.1", 48 | "@commitlint/config-conventional": "^17.0.2", 49 | "@evilmartians/lefthook": "^1.2.2", 50 | "@react-native-community/eslint-config": "^3.0.2", 51 | "@release-it/conventional-changelog": "^5.0.0", 52 | "@types/jest": "^28.1.2", 53 | "@types/node": "^20.3.1", 54 | "@types/react": "~17.0.21", 55 | "@types/react-native": "0.70.0", 56 | "del-cli": "^5.0.0", 57 | "eslint": "^8.4.1", 58 | "eslint-config-prettier": "^8.5.0", 59 | "eslint-plugin-prettier": "^4.0.0", 60 | "jest": "^28.1.1", 61 | "pod-install": "^0.1.0", 62 | "prettier": "^2.0.5", 63 | "react": "18.2.0", 64 | "react-native": "0.71.7", 65 | "typescript": "^4.5.2" 66 | }, 67 | "resolutions": { 68 | "@types/react": "17.0.21" 69 | }, 70 | "peerDependencies": { 71 | "react": "*", 72 | "react-native": "*" 73 | }, 74 | "engines": { 75 | "node": ">= 16.0.0" 76 | }, 77 | "packageManager": "yarn@1.22.22+sha256.c17d3797fb9a9115bf375e31bfd30058cac6bc9c3b8807a3d8cb2094794b51ca", 78 | "jest": { 79 | "preset": "react-native", 80 | "modulePathIgnorePatterns": [ 81 | "/lib/" 82 | ] 83 | }, 84 | "commitlint": { 85 | "extends": [ 86 | "@commitlint/config-conventional" 87 | ] 88 | }, 89 | "eslintConfig": { 90 | "root": true, 91 | "extends": [ 92 | "@react-native-community", 93 | "prettier" 94 | ], 95 | "rules": { 96 | "prettier/prettier": [ 97 | "error", 98 | { 99 | "quoteProps": "consistent", 100 | "singleQuote": true, 101 | "tabWidth": 4, 102 | "trailingComma": "es5", 103 | "useTabs": true 104 | } 105 | ] 106 | } 107 | }, 108 | "eslintIgnore": [ 109 | "node_modules/", 110 | "lib/" 111 | ], 112 | "prettier": { 113 | "quoteProps": "consistent", 114 | "singleQuote": true, 115 | "tabWidth": 4, 116 | "trailingComma": "es5", 117 | "useTabs": true 118 | }, 119 | "@react-native-community/bob": { 120 | "source": "src", 121 | "output": "lib", 122 | "targets": [ 123 | "module", 124 | "typescript" 125 | ] 126 | }, 127 | "codegenConfig": { 128 | "name": "RNScreenshotPreventSpec", 129 | "type": "modules", 130 | "jsSrcsDir": "src" 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-screenshot-prevent' { 2 | export function enabled(value: boolean): void 3 | export function enableSecureView(imagePath?: string): void; 4 | export function disableSecureView(): void; 5 | export function usePreventScreenshot(): void; 6 | export function useDisableSecureView(): void; 7 | export function addListener(fn: Function): void; 8 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; 2 | import { useEffect } from 'react'; 3 | 4 | type FN = (resp: any) => void 5 | type Return = { 6 | readonly remove: () => void 7 | } 8 | let addListen: any, RNScreenshotPrevent: any; 9 | if (Platform.OS !== "web") { 10 | const { RNScreenshotPrevent: RNScreenshotPreventNative } = NativeModules; 11 | RNScreenshotPrevent = { 12 | ...RNScreenshotPreventNative, 13 | enableSecureView: function enableSecureView(imagePath: string = "") { 14 | RNScreenshotPreventNative.enableSecureView(imagePath) 15 | } 16 | } 17 | const eventEmitter = new NativeEventEmitter(RNScreenshotPrevent); 18 | 19 | /** 20 | * subscribes to userDidTakeScreenshot event 21 | * @param {function} callback handler 22 | * @returns {function} unsubscribe fn 23 | */ 24 | addListen = (fn: FN): Return => { 25 | if (typeof (fn) !== 'function') { 26 | console.error('RNScreenshotPrevent: addListener requires valid callback function'); 27 | return { 28 | remove: (): void => { 29 | console.error("RNScreenshotPrevent: remove not work because addListener requires valid callback function"); 30 | } 31 | }; 32 | } 33 | 34 | return eventEmitter.addListener("userDidTakeScreenshot", fn); 35 | } 36 | } else { 37 | RNScreenshotPrevent = { 38 | enabled: (enabled: boolean): void => { 39 | console.warn("RNScreenshotPrevent: enabled not work in web. value: " + enabled); 40 | }, 41 | enableSecureView: (imagePath: string = ""): void => { 42 | console.warn("RNScreenshotPrevent: enableSecureView not work in web."+(!!imagePath ? " send: "+imagePath : "")); 43 | }, 44 | disableSecureView: (): void => { 45 | console.warn("RNScreenshotPrevent: disableSecureView not work in web"); 46 | } 47 | } 48 | addListen = (fn: FN): Return => { 49 | if (typeof (fn) !== 'function') { 50 | console.error('RNScreenshotPrevent: addListener requires valid callback function'); 51 | return { 52 | remove: (): void => { 53 | console.error("RNScreenshotPrevent: remove not work because addListener requires valid callback function"); 54 | } 55 | }; 56 | } 57 | console.warn("RNScreenshotPrevent: addListener not work in web"); 58 | return { 59 | remove: (): void => { 60 | console.warn("RNScreenshotPrevent: remove addListener not work in web"); 61 | } 62 | } 63 | } 64 | } 65 | 66 | export const usePreventScreenshot = () => { 67 | useEffect(() => { 68 | RNScreenshotPrevent.enabled(true); 69 | return () => { 70 | RNScreenshotPrevent.enabled(false); 71 | }; 72 | }, []); 73 | } 74 | 75 | export const useDisableSecureView = (imagePath: string = "") => { 76 | useEffect(() => { 77 | RNScreenshotPrevent.enableSecureView(imagePath); 78 | return () => { 79 | RNScreenshotPrevent.disableSecureView(); 80 | }; 81 | }, []); 82 | } 83 | 84 | export const enabled: (enabled: boolean) => void = RNScreenshotPrevent.enabled 85 | export const enableSecureView: (imagePath?: string) => void = RNScreenshotPrevent.enableSecureView 86 | export const disableSecureView: () => void = RNScreenshotPrevent.disableSecureView 87 | export const addListener: (fn: FN) => void = addListen 88 | export default RNScreenshotPrevent; 89 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig", 4 | "exclude": ["example"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "rootDir": "./src", 5 | "paths": { 6 | "react-native-screenshot-prevent": ["./src/index"] 7 | }, 8 | "ignoreDeprecations": "5.0", 9 | "allowUnreachableCode": false, 10 | "allowUnusedLabels": false, 11 | "allowJs": true, 12 | "esModuleInterop": true, 13 | "importsNotUsedAsValues": "error", 14 | "forceConsistentCasingInFileNames": true, 15 | "jsx": "react", 16 | "lib": ["esnext","ES6","DOM"], 17 | "types": ["react"], 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "noFallthroughCasesInSwitch": true, 21 | "noImplicitReturns": true, 22 | "noImplicitUseStrict": false, 23 | "noStrictGenericChecks": false, 24 | "noUncheckedIndexedAccess": true, 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | "resolveJsonModule": true, 28 | "skipLibCheck": true, 29 | "strict": true, 30 | "target": "esnext" 31 | }, 32 | "include": ["src/**/*"], 33 | "exclude": ["lib"] 34 | } 35 | --------------------------------------------------------------------------------