diff --git a/apps/mobile/app/components/app-lock-overlay/index.tsx b/apps/mobile/app/components/app-lock-overlay/index.tsx index e4d66986e..65d75a078 100644 --- a/apps/mobile/app/components/app-lock-overlay/index.tsx +++ b/apps/mobile/app/components/app-lock-overlay/index.tsx @@ -21,7 +21,6 @@ import React, { useCallback, useEffect, useRef } from "react"; import { AppStateStatus, Platform, TextInput, View } from "react-native"; //@ts-ignore import { useThemeColors } from "@notesnook/theme"; -import { enabled } from "react-native-privacy-snapshot"; import { DatabaseLogger } from "../../common/database"; import { decrypt, @@ -121,7 +120,6 @@ const AppLockedOverlay = () => { ); if (unlocked) { lockApp(false); - enabled(false); password.current = undefined; } biometricUnlockAwaitingUserInput.current = false; @@ -148,7 +146,6 @@ const AppLockedOverlay = () => { } lockApp(false); - enabled(false); password.current = undefined; } else { ToastManager.show({ @@ -163,10 +160,6 @@ const AppLockedOverlay = () => { }; useEffect(() => { - if (appState === "active") { - enabled(false); - } - const prevState = lastAppState.current; lastAppState.current = appState; @@ -192,13 +185,8 @@ const AppLockedOverlay = () => { DatabaseLogger.info("Biometric unlock request"); onUnlockAppRequested(); } - - enabled(false); } else { SettingsService.appEnteredBackground(); - if (SettingsService.get().privacyScreen) { - enabled(true); - } } }, [appState, onUnlockAppRequested, appLocked]); diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx index 32bfa2e63..e909c100c 100644 --- a/apps/mobile/app/screens/settings/settings-data.tsx +++ b/apps/mobile/app/screens/settings/settings-data.tsx @@ -70,6 +70,7 @@ import { useDragState } from "./editor/state"; import { verifyUser, verifyUserWithApplock } from "./functions"; import { SettingSection } from "./types"; import { getTimeLeft } from "./user-section"; +import ScreenGuardModule from "react-native-screenguard"; type User = any; @@ -850,9 +851,18 @@ export const settingsGroups: SettingSection[] = [ "Hide app contents when you switch to other apps. This will also disable screenshot taking in the app.", modifer: () => { const settings = SettingsService.get(); - Platform.OS === "android" - ? NotesnookModule.setSecureMode(!settings.privacyScreen) - : enabled(true); + if (Platform.OS === "ios") { + enabled(!settings.privacyScreen); + if (settings.privacyScreen) { + ScreenGuardModule.unregister(); + } else { + ScreenGuardModule.register({ + backgroundColor: "#000000" + }); + } + } else { + NotesnookModule.setSecureMode(!settings.privacyScreen); + } SettingsService.set({ privacyScreen: !settings.privacyScreen }); }, diff --git a/apps/mobile/app/services/settings.ts b/apps/mobile/app/services/settings.ts index 73e6fa790..baee7763f 100644 --- a/apps/mobile/app/services/settings.ts +++ b/apps/mobile/app/services/settings.ts @@ -29,6 +29,7 @@ import { NotesnookModule } from "../utils/notesnook-module"; import { scale, updateSize } from "../utils/size"; import { DatabaseLogger } from "../common/database"; import { useUserStore } from "../stores/use-user-store"; +import ScreenGuardModule from "react-native-screenguard"; function reset() { const settings = get(); if (settings.reminder !== "off" && settings.reminder !== "useroff") { @@ -117,12 +118,14 @@ function setPrivacyScreen(settings: SettingStore["settings"]) { NotesnookModule.setSecureMode(true); } else { enabled(true); + ScreenGuardModule.register({ backgroundColor: "#000000" }); } } else { if (Platform.OS === "android") { NotesnookModule.setSecureMode(false); } else { enabled(false); + ScreenGuardModule.unregister(); } } } diff --git a/apps/mobile/native/ios/Notesnook.xcodeproj/project.pbxproj b/apps/mobile/native/ios/Notesnook.xcodeproj/project.pbxproj index 797e86402..8298057ff 100644 --- a/apps/mobile/native/ios/Notesnook.xcodeproj/project.pbxproj +++ b/apps/mobile/native/ios/Notesnook.xcodeproj/project.pbxproj @@ -21,9 +21,9 @@ 6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; }; 6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; }; 656835812BB29A9800144BAB /* OpenSans-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 656835802BB29A8300144BAB /* OpenSans-Italic.ttf */; }; - 656DD2AB2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; }; - 656DD2AC2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; }; - 656DD2AD2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; }; + 656DD2AB2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; }; + 656DD2AC2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; }; + 656DD2AD2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; }; 6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; }; 659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; }; 65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; }; @@ -586,9 +586,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 656DD2AB2B1891DF00A362EA /* BuildFile in Resources */, - 656DD2AC2B1891DF00A362EA /* BuildFile in Resources */, - 656DD2AD2B1891DF00A362EA /* BuildFile in Resources */, + 656DD2AB2B1891DF00A362EA /* (null) in Resources */, + 656DD2AC2B1891DF00A362EA /* (null) in Resources */, + 656DD2AD2B1891DF00A362EA /* (null) in Resources */, 65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */, 65C149872A61151B005C40F1 /* extension.bundle in Resources */, 65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */, @@ -1084,7 +1084,7 @@ "${PODS_ROOT}/Headers/Public/#{s.name}/**", ); INFOPLIST_FILE = Notesnook/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1189,7 +1189,7 @@ "${PODS_ROOT}/Headers/Public/#{s.name}/**", ); INFOPLIST_FILE = Notesnook/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/apps/mobile/native/ios/Podfile.lock b/apps/mobile/native/ios/Podfile.lock index e9bf61fd7..996fc44eb 100644 --- a/apps/mobile/native/ios/Podfile.lock +++ b/apps/mobile/native/ios/Podfile.lock @@ -343,6 +343,9 @@ PODS: - React-Core - react-native-safe-area-context (4.9.0): - React-Core + - react-native-screenguard (1.0.0): + - React-Core + - SDWebImage (~> 5.11.1) - react-native-share-extension (3.0.0): - React - react-native-sodium (1.5.4): @@ -548,6 +551,9 @@ PODS: - RNZipArchive/Core (6.0.9): - React-Core - SSZipArchive (~> 2.2) + - SDWebImage (5.11.1): + - SDWebImage/Core (= 5.11.1) + - SDWebImage/Core (5.11.1) - SexyTooltip (1.2.5): - pop (~> 1.0) - SocketRocket (0.6.0) @@ -606,6 +612,7 @@ DEPENDENCIES: - react-native-pdf (from `../../node_modules/react-native-pdf`) - react-native-quick-sqlite (from `../../node_modules/react-native-quick-sqlite`) - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) + - react-native-screenguard (from `../../node_modules/react-native-screenguard`) - "react-native-share-extension (from `../../node_modules/@ammarahmed/react-native-share-extension`)" - "react-native-sodium (from `../../node_modules/@ammarahmed/react-native-sodium`)" - react-native-theme-switch-animation (from `../../node_modules/react-native-theme-switch-animation`) @@ -664,6 +671,7 @@ SPEC REPOS: - MMKV - MMKVCore - pop + - SDWebImage - SocketRocket - SSZipArchive - SwiftyRSA @@ -754,6 +762,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native-quick-sqlite" react-native-safe-area-context: :path: "../../node_modules/react-native-safe-area-context" + react-native-screenguard: + :path: "../../node_modules/react-native-screenguard" react-native-share-extension: :path: "../../node_modules/@ammarahmed/react-native-share-extension" react-native-sodium: @@ -905,6 +915,7 @@ SPEC CHECKSUMS: react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1 react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b + react-native-screenguard: 8b36a3df84c76cd2b82c477f71c26fa1c8cc14a0 react-native-share-extension: faed334b1ddf165f1e576fcabd3dc1c9e748bfa9 react-native-sodium: 955bb0dc3ea05f8ea06d5e96cb89d1be7b5d7681 react-native-theme-switch-animation: 220f883f7be290e79f2ab022093ed1a7a5929e6d @@ -949,6 +960,7 @@ SPEC CHECKSUMS: RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105 RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f RNZipArchive: 68a0c6db4b1c103f846f1559622050df254a3ade + SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef diff --git a/apps/mobile/native/package.json b/apps/mobile/native/package.json index a3ac31448..ff403e21c 100644 --- a/apps/mobile/native/package.json +++ b/apps/mobile/native/package.json @@ -66,7 +66,8 @@ "react-native-theme-switch-animation": "^0.6.0", "@ammarahmed/react-native-background-fetch": "^4.2.2", "react-native-image-crop-picker": "^0.40.2", - "react-native-url-polyfill": "^2.0.0" + "react-native-url-polyfill": "^2.0.0", + "react-native-screenguard": "^1.0.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/apps/mobile/native/react-native.config.js b/apps/mobile/native/react-native.config.js index 51deabbc2..2d806ff31 100644 --- a/apps/mobile/native/react-native.config.js +++ b/apps/mobile/native/react-native.config.js @@ -17,6 +17,12 @@ config.dependencies['react-native-vector-icons'] = { }, } +config.dependencies['react-native-screenguard'] = { + platforms: { + android: null, + }, +} + if (isGithubRelease) { config.dependencies["react-native-iap"] = { platforms: { diff --git a/apps/mobile/package-lock.json b/apps/mobile/package-lock.json index 308950666..fcca96214 100644 --- a/apps/mobile/package-lock.json +++ b/apps/mobile/package-lock.json @@ -1,12 +1,12 @@ { "name": "@notesnook/mobile", - "version": "3.0.4", + "version": "3.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@notesnook/mobile", - "version": "3.0.4", + "version": "3.0.9", "hasInstallScript": true, "license": "GPL-3.0-or-later", "workspaces": [ @@ -28487,6 +28487,7 @@ "react-native-reanimated": "3.3.0", "react-native-safe-area-context": "^4.3.1", "react-native-scoped-storage": "^1.9.5", + "react-native-screenguard": "^1.0.0", "react-native-screens": "^3.13.1", "react-native-securerandom": "^1.0.1", "react-native-share": "^7.2.0", @@ -45253,6 +45254,18 @@ "react-native": "*" } }, + "node_modules/react-native-screenguard": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-native-screenguard/-/react-native-screenguard-1.0.0.tgz", + "integrity": "sha512-fqtoJI8TxszKqrntnHW48EMb4gNVA9VCAg5UG1FcIXEyv2W70kWoMxRRFoWuCRYlBWSAWqiXO+R6Xfk/0um0cw==", + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-screens": { "version": "3.21.1", "license": "MIT", diff --git a/apps/mobile/patches/react-native-screenguard+1.0.0.patch b/apps/mobile/patches/react-native-screenguard+1.0.0.patch new file mode 100644 index 000000000..f0c9937d4 --- /dev/null +++ b/apps/mobile/patches/react-native-screenguard+1.0.0.patch @@ -0,0 +1,54 @@ +diff --git a/node_modules/react-native-screenguard/ios/ScreenGuard.mm b/node_modules/react-native-screenguard/ios/ScreenGuard.mm +index 9fa7d33..d2e6d68 100644 +--- a/node_modules/react-native-screenguard/ios/ScreenGuard.mm ++++ b/node_modules/react-native-screenguard/ios/ScreenGuard.mm +@@ -11,7 +11,7 @@ + @implementation ScreenGuard + RCT_EXPORT_MODULE(ScreenGuard) + +-bool hasListeners; ++bool hasListeners_; + + UITextField *textField; + UIImageView *imageView; +@@ -22,11 +22,11 @@ @implementation ScreenGuard + } + + - (void)startObserving { +- hasListeners = YES; ++ hasListeners_ = YES; + } + + - (void)stopObserving { +- hasListeners = NO; ++ hasListeners_ = NO; + } + + - (void)secureViewWithBackgroundColor: (NSString *)color { +@@ -335,7 +335,7 @@ - (UIImage *)convertViewToImage:(UIView *)view { + queue:mainQueue + usingBlock:^(NSNotification *notification) { + +- if (hasListeners && getScreenShotPath) { ++ if (hasListeners_ && getScreenShotPath) { + UIViewController *presentedViewController = RCTPresentedViewController(); + + UIImage *image = [self convertViewToImage:presentedViewController.view.superview]; +@@ -359,7 +359,7 @@ - (UIImage *)convertViewToImage:(UIView *)view { + result = @{@"path": filePath, @"name": fileName, @"type": @"PNG"}; + } + [self emit:SCREENSHOT_EVT body: result]; +- } else if (hasListeners) { ++ } else if (hasListeners_) { + [self emit:SCREENSHOT_EVT body: nil]; + } + }]; +@@ -376,7 +376,7 @@ - (UIImage *)convertViewToImage:(UIView *)view { + queue:mainQueue + usingBlock:^(NSNotification *notification) { + +- if (hasListeners) { ++ if (hasListeners_) { + [self emit:SCREEN_RECORDING_EVT body:nil]; + } + }];