diff --git a/android/app/build.gradle b/android/app/build.gradle index 49091072..0b059e29 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -159,6 +159,8 @@ android { } dependencies { + compile project(':react-native-file-viewer') + compile project(':react-native-fs') compile project(':react-native-gesture-handler') implementation fileTree(dir: "libs", include: ["*.jar"]) diff --git a/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf b/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf index 69404e3d..82524a0c 100644 Binary files a/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf and b/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf differ diff --git a/android/app/src/main/assets/fonts/Octicons.ttf b/android/app/src/main/assets/fonts/Octicons.ttf index 09e2b2d7..09f5a96c 100644 Binary files a/android/app/src/main/assets/fonts/Octicons.ttf and b/android/app/src/main/assets/fonts/Octicons.ttf differ diff --git a/android/app/src/main/java/com/standardnotes/MainApplication.java b/android/app/src/main/java/com/standardnotes/MainApplication.java index 4d3959fd..85cff87a 100644 --- a/android/app/src/main/java/com/standardnotes/MainApplication.java +++ b/android/app/src/main/java/com/standardnotes/MainApplication.java @@ -9,6 +9,8 @@ import android.support.annotation.Nullable; import android.view.WindowManager; import com.facebook.react.ReactApplication; +import com.vinzscam.reactnativefileviewer.RNFileViewerPackage; +import com.rnfs.RNFSPackage; import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; @@ -44,6 +46,8 @@ public class MainApplication extends Application implements ReactApplication { protected List getPackages() { return Arrays.asList( new MainReactPackage(), + new RNFileViewerPackage(), + new RNFSPackage(), new RNGestureHandlerPackage(), BugsnagReactNative.getPackage(), new KeychainPackage(), diff --git a/android/settings.gradle b/android/settings.gradle index 82525a90..7a2e98dd 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,8 @@ rootProject.name = 'StandardNotes' +include ':react-native-file-viewer' +project(':react-native-file-viewer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-file-viewer/android') +include ':react-native-fs' +project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android') include ':app' include ':react-native-gesture-handler' diff --git a/ios/StandardNotes.xcodeproj/project.pbxproj b/ios/StandardNotes.xcodeproj/project.pbxproj index 0aef7106..65a06b36 100644 --- a/ios/StandardNotes.xcodeproj/project.pbxproj +++ b/ios/StandardNotes.xcodeproj/project.pbxproj @@ -5,7 +5,6 @@ }; objectVersion = 46; objects = { - /* Begin PBXBuildFile section */ 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; @@ -13,6 +12,7 @@ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; 00E356F31AD99517003FC87E /* StandardNotesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* StandardNotesTests.m */; }; + 0A7C7EB08AA3465C8E686821 /* libRNFS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CEB6B877AE784055A8E294A8 /* libRNFS.a */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; @@ -41,6 +41,7 @@ 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; 965031D980094619B7DBA0FD /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1F569402A90047A59845394A /* Ionicons.ttf */; }; 9A2C235D0ABA4B0CB9A428CA /* libRNKeychain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CDCF33ADCFE845D588CC4E66 /* libRNKeychain.a */; }; + 9E1F5D0FE7C441D685DAEEAA /* libRNFileViewer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37A42F4068AE42DD8D2DF182 /* libRNFileViewer.a */; }; ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */; }; C00E89E3E7F949A4AA0F613E /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F30913DACE34E71895FBF91 /* libz.tbd */; }; CD17667C1F795DC100165C83 /* libSNTextView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD1766781F795AE500165C83 /* libSNTextView.a */; }; @@ -394,6 +395,27 @@ remoteGlobalIDString = 134814201AA4EA6300B7C361; remoteInfo = RNGestureHandler; }; + CDEBDD9B21E044B500333D77 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2FAF1266E8404686B7F36870 /* RNFS.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = F12AFB9B1ADAF8F800E0535D; + remoteInfo = RNFS; + }; + CDEBDD9D21E044B500333D77 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2FAF1266E8404686B7F36870 /* RNFS.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6456441F1EB8DA9100672408; + remoteInfo = "RNFS-tvOS"; + }; + CDEBDDE221E044BD00333D77 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 645E3D9167344C3E81B7223C /* RNFileViewer.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RNFileViewer; + }; CDFC05B41F79A868007EFC71 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CD17664C1F795AE500165C83 /* SNTextView.xcodeproj */; @@ -435,12 +457,15 @@ 2416263A135F439AA3C5F9D2 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; }; 2D02E47B1E0B4A5D006451C7 /* StandardNotes-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "StandardNotes-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* StandardNotes-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "StandardNotes-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2FAF1266E8404686B7F36870 /* RNFS.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFS.xcodeproj; path = "../node_modules/react-native-fs/RNFS.xcodeproj"; sourceTree = ""; }; + 37A42F4068AE42DD8D2DF182 /* libRNFileViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFileViewer.a; sourceTree = ""; }; 48127930FB1344778C168838 /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = ""; }; 54ED130E749A46A3B15B27F2 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = ""; }; 59DCF8530F2945FFAA0300FD /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; 5C13FDBBDDEE4F1285F88B41 /* RNStoreReview.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNStoreReview.xcodeproj; path = "../node_modules/react-native-store-review/ios/RNStoreReview.xcodeproj"; sourceTree = ""; }; 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; }; 6314B32C63AC4827A9329AA6 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = ""; }; + 645E3D9167344C3E81B7223C /* RNFileViewer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNFileViewer.xcodeproj; path = "../node_modules/react-native-file-viewer/ios/RNFileViewer.xcodeproj"; sourceTree = ""; }; 6F30913DACE34E71895FBF91 /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 737AF67874434967865855D8 /* BugsnagReactNative.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = BugsnagReactNative.xcodeproj; path = "../node_modules/bugsnag-react-native/cocoa/BugsnagReactNative.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; @@ -454,6 +479,7 @@ CDB58A101F6C5178009EF868 /* RNMail.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNMail.xcodeproj; path = "../vendor/react-native-mail/RNMail.xcodeproj"; sourceTree = ""; }; CDC17F4A1F6E24720037D7F9 /* StandardNotes.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = StandardNotes.entitlements; path = StandardNotes/StandardNotes.entitlements; sourceTree = ""; }; CDCF33ADCFE845D588CC4E66 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNKeychain.a; sourceTree = ""; }; + CEB6B877AE784055A8E294A8 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = ""; }; D38724A93AC141D6B51D1356 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; }; EFD3F9197A5F41C0904D7E60 /* libBugsnagReactNative.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBugsnagReactNative.a; sourceTree = ""; }; /* End PBXFileReference section */ @@ -494,6 +520,8 @@ C00E89E3E7F949A4AA0F613E /* libz.tbd in Frameworks */, 6C1915C4DE9040A9BB17CFBB /* libRNStoreReview.a in Frameworks */, 300BF5D7132F46BCAB353149 /* libRNGestureHandler.a in Frameworks */, + 0A7C7EB08AA3465C8E686821 /* libRNFS.a in Frameworks */, + 9E1F5D0FE7C441D685DAEEAA /* libRNFileViewer.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -699,6 +727,8 @@ 737AF67874434967865855D8 /* BugsnagReactNative.xcodeproj */, 5C13FDBBDDEE4F1285F88B41 /* RNStoreReview.xcodeproj */, 19A39FBB53A3465B81022E02 /* RNGestureHandler.xcodeproj */, + 2FAF1266E8404686B7F36870 /* RNFS.xcodeproj */, + 645E3D9167344C3E81B7223C /* RNFileViewer.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -821,6 +851,8 @@ EFD3F9197A5F41C0904D7E60 /* libBugsnagReactNative.a */, ADB6F6B9BC7144FCB2C08D40 /* libRNStoreReview.a */, 04047F33889C425483EB8244 /* libRNGestureHandler.a */, + CEB6B877AE784055A8E294A8 /* libRNFS.a */, + 37A42F4068AE42DD8D2DF182 /* libRNFileViewer.a */, ); name = "Recovered References"; sourceTree = ""; @@ -833,6 +865,23 @@ name = Products; sourceTree = ""; }; + CDEBDD9721E044B500333D77 /* Products */ = { + isa = PBXGroup; + children = ( + CDEBDD9C21E044B500333D77 /* libRNFS.a */, + CDEBDD9E21E044B500333D77 /* libRNFS.a */, + ); + name = Products; + sourceTree = ""; + }; + CDEBDDB721E044BD00333D77 /* Products */ = { + isa = PBXGroup; + children = ( + CDEBDDE321E044BD00333D77 /* libRNFileViewer.a */, + ); + name = Products; + sourceTree = ""; + }; D0C5C4528C8147238D73A75F /* Frameworks */ = { isa = PBXGroup; children = ( @@ -1025,6 +1074,14 @@ ProductGroup = CDB58A0B1F6C5174009EF868 /* Products */; ProjectRef = CDB58A0A1F6C5174009EF868 /* ReactNativeFingerprintScanner.xcodeproj */; }, + { + ProductGroup = CDEBDDB721E044BD00333D77 /* Products */; + ProjectRef = 645E3D9167344C3E81B7223C /* RNFileViewer.xcodeproj */; + }, + { + ProductGroup = CDEBDD9721E044B500333D77 /* Products */; + ProjectRef = 2FAF1266E8404686B7F36870 /* RNFS.xcodeproj */; + }, { ProductGroup = CDE0D5F521D4012300E093B5 /* Products */; ProjectRef = 19A39FBB53A3465B81022E02 /* RNGestureHandler.xcodeproj */; @@ -1390,6 +1447,27 @@ remoteRef = CDE0D5F821D4012300E093B5 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + CDEBDD9C21E044B500333D77 /* libRNFS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNFS.a; + remoteRef = CDEBDD9B21E044B500333D77 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + CDEBDD9E21E044B500333D77 /* libRNFS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNFS.a; + remoteRef = CDEBDD9D21E044B500333D77 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + CDEBDDE321E044BD00333D77 /* libRNFileViewer.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNFileViewer.a; + remoteRef = CDEBDDE221E044BD00333D77 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -1542,6 +1620,8 @@ "$(SRCROOT)/../node_modules/bugsnag-react-native/cocoa/**", "$(SRCROOT)/../node_modules/react-native-store-review/ios", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-file-viewer/ios", ); INFOPLIST_FILE = StandardNotesTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1553,6 +1633,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1576,6 +1658,8 @@ "$(SRCROOT)/../node_modules/bugsnag-react-native/cocoa/**", "$(SRCROOT)/../node_modules/react-native-store-review/ios", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-file-viewer/ios", ); INFOPLIST_FILE = StandardNotesTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1587,6 +1671,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1614,6 +1700,8 @@ "$(SRCROOT)/../node_modules/react-native/Libraries/Text", "$(SRCROOT)/../node_modules/react-native-store-review/ios", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-file-viewer/ios", ); INFOPLIST_FILE = StandardNotes/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1647,6 +1735,8 @@ "$(SRCROOT)/../node_modules/react-native/Libraries/Text", "$(SRCROOT)/../node_modules/react-native-store-review/ios", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-file-viewer/ios", ); INFOPLIST_FILE = StandardNotes/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1683,6 +1773,8 @@ "$(SRCROOT)/../node_modules/bugsnag-react-native/cocoa/**", "$(SRCROOT)/../node_modules/react-native-store-review/ios", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-file-viewer/ios", ); INFOPLIST_FILE = "StandardNotes-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1693,6 +1785,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1725,6 +1819,8 @@ "$(SRCROOT)/../node_modules/bugsnag-react-native/cocoa/**", "$(SRCROOT)/../node_modules/react-native-store-review/ios", "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", + "$(SRCROOT)/../node_modules/react-native-fs/**", + "$(SRCROOT)/../node_modules/react-native-file-viewer/ios", ); INFOPLIST_FILE = "StandardNotes-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1735,6 +1831,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1768,6 +1866,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.StandardNotes-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1797,6 +1897,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.StandardNotes-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/StandardNotes/Info.plist b/ios/StandardNotes/Info.plist index b0c46ede..84866757 100644 --- a/ios/StandardNotes/Info.plist +++ b/ios/StandardNotes/Info.plist @@ -40,7 +40,7 @@ NSFaceIDUsageDescription Face ID is required to unlock your notes. NSLocationWhenInUseUsageDescription - + UIAppFonts Entypo.ttf diff --git a/package-lock.json b/package-lock.json index 0fb1fcdf..13fc4aa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10442,6 +10442,20 @@ } } }, + "react-native-file-viewer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/react-native-file-viewer/-/react-native-file-viewer-1.0.10.tgz", + "integrity": "sha512-M+v/nUM1wiFSn0PkXT5dnrIxGi+UGHcNz8vydWSEbtDiyxXyUWbcpDJL+cKOarpW7lNzZlEPQHjzossyfjOEng==" + }, + "react-native-fs": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.13.3.tgz", + "integrity": "sha512-B62LSSAEYQGItg7KVTzTVVCxezOYFBYp4DMVFbdoZUd1mZVFdqR2sy1HY1mye1VI/Lf3IbxSyZEQ0GmrrdwLjg==", + "requires": { + "base-64": "0.1.0", + "utf8": "2.1.2" + } + }, "react-native-gesture-handler": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.0.12.tgz", @@ -12491,6 +12505,11 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "utf8": { + "version": "2.1.2", + "resolved": "http://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index df89d72a..3647ff34 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "moment": "^2.23.0", "react": "16.6.3", "react-native": "0.57.8", + "react-native-file-viewer": "^1.0.10", + "react-native-fs": "^2.13.3", "react-native-gesture-handler": "^1.0.12", "react-native-keychain": "^1.2.1", "react-native-store-review": "^0.1.3", diff --git a/src/containers/account/OptionsSection.js b/src/containers/account/OptionsSection.js index ba73cf50..a7c7d7a0 100644 --- a/src/containers/account/OptionsSection.js +++ b/src/containers/account/OptionsSection.js @@ -75,7 +75,7 @@ export default class OptionsSection extends Component { disabled={this.state.loadingExport} leftAligned={true} options={this.exportOptions()} - title={this.state.loadingExport ? "Preparing Data..." : "Export Data"} + title={this.state.loadingExport ? "Processing..." : "Export Data"} onPress={this.onExportPress} /> diff --git a/src/lib/BackupsManager.js b/src/lib/BackupsManager.js new file mode 100644 index 00000000..35fb0b4e --- /dev/null +++ b/src/lib/BackupsManager.js @@ -0,0 +1,123 @@ +import { Share } from 'react-native'; +import Storage from '@SFJS/storageManager' +import Auth from '@SFJS/authManager' +import KeysManager from '@Lib/keysManager' +import AlertManager from "@SFJS/alertManager"; +import UserPrefsManager from '@Lib/userPrefsManager' +import ModelManager from '@SFJS/modelManager' +import ApplicationState from "@Lib/ApplicationState" +import RNFS from 'react-native-fs'; +import FileViewer from 'react-native-file-viewer'; +const base64 = require('base-64'); + +export default class BackupsManager { + + static instance = null; + static get() { + if(this.instance == null) { + this.instance = new BackupsManager(); + } + return this.instance; + } + + /* + On iOS, we can use Share to share a file of arbitrary length. + This doesn't work on Android however. Seems to have a very low limit. + For Android, we'll use RNFS to save the file to disk, then FileViewer to + ask the user what application they would like to open the file with. + For .txt files, not many applications handle it. So, we'll want to notify the user + the path the file was saved to. + */ + + async export(encrypted) { + var auth_params = await Auth.get().getAuthParams(); + var keys = encrypted ? KeysManager.get().activeKeys() : null; + + var items = []; + + for(var item of ModelManager.get().allItems) { + var itemParams = new SFItemParams(item, keys, auth_params); + var params = await itemParams.paramsForExportFile(); + items.push(params); + } + + if(items.length == 0) { + Alert.alert('No Data', "You don't have any notes yet."); + return false; + } + + var data = {items: items} + + if(keys) { + var authParams = KeysManager.get().activeAuthParams(); + // auth params are only needed when encrypted with a standard file key + data["auth_params"] = authParams; + } + + var jsonString = JSON.stringify(data, null, 2 /* pretty print */); + let modifier = encrypted ? "Encrypted" : "Decrypted"; + let filename = `Standard Notes ${modifier} Backup - ${this._formattedDate()}.txt`; + + if(ApplicationState.isIOS) { + return this._exportIOS(filename, jsonString); + } else { + let filepath = await this._exportAndroid(filename, jsonString); + return this._showFileSavePromptAndroid(filepath); + } + } + + async _exportIOS(filename, data) { + return new Promise((resolve, reject) => { + ApplicationState.get().performActionWithoutStateChangeImpact(async () => { + Share.share({ + title: filename, + message: data, + }).then((result) => { + resolve(result != Share.dismissedAction); + }).catch((error) => { + resolve(false); + }) + }) + }) + } + + async _exportAndroid(filename, data) { + let filepath = `${RNFS.DocumentDirectoryPath}/${filename}`; + return RNFS.writeFile(filepath, data).then(() => { + return filepath; + }) + } + + async _openFileAndroid(filepath) { + return FileViewer.open(filepath).then(() => { + // success + return true; + }).catch(error => { + console.log("Error opening file", error); + return false; + }); + } + + async _showFileSavePromptAndroid(filepath) { + return AlertManager.get().confirm({ + title: "Backup Saved", + text: `Your backup file has been saved to your local disk at this location:\n\n${filepath}`, + cancelButtonText: "Done", + confirmButtonText: "Open File", + onConfirm: () => { + this._openFileAndroid(filepath); + } + }).then(() => { + return true; + }).catch(() => { + // Did Cancel, still success + return true; + }) + } + + /* Utils */ + + _formattedDate() { + return new Date().getTime(); + } +} diff --git a/src/lib/sfjs/alertManager.js b/src/lib/sfjs/alertManager.js index e63989f7..2146de4e 100644 --- a/src/lib/sfjs/alertManager.js +++ b/src/lib/sfjs/alertManager.js @@ -12,11 +12,11 @@ export default class AlertManager extends SFAlertManager { return this.instance; } - async confirm({title, text, confirmButtonText = "OK", onConfirm, onCancel} = {}) { + async confirm({title, text, confirmButtonText = "OK", cancelButtonText = "Cancel", onConfirm, onCancel} = {}) { return new Promise((resolve, reject) => { // On iOS, confirm should go first. On Android, cancel should go first. let buttons = [ - {text: 'Cancel', onPress: () => { + {text: cancelButtonText, onPress: () => { reject(); onCancel && onCancel(); }}, @@ -28,5 +28,4 @@ export default class AlertManager extends SFAlertManager { Alert.alert(title, text, buttons, { cancelable: true }) }) } - } diff --git a/src/screens/Settings.js b/src/screens/Settings.js index 2257d2a0..3c8f1fb2 100644 --- a/src/screens/Settings.js +++ b/src/screens/Settings.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import {ScrollView, View, Alert, Keyboard, Linking, Platform, Share, NativeModules} from 'react-native'; import Sync from '../lib/sfjs/syncManager' -import ModelManager from '../lib/sfjs/modelManager' +import ModelManager from '@SFJS/modelManager' import AlertManager from '../lib/sfjs/alertManager' import SF from '@SFJS/sfjs' @@ -10,6 +10,9 @@ import Auth from '../lib/sfjs/authManager' import KeysManager from '@Lib/keysManager' import UserPrefsManager from '../lib/userPrefsManager' import OptionsState from "@Lib/OptionsState" +import ApplicationState from "@Lib/ApplicationState" +import StyleKit from "@Style/StyleKit" +import BackupsManager from "@Lib/BackupsManager" import SectionHeader from "../components/SectionHeader"; import ButtonCell from "../components/ButtonCell"; @@ -25,10 +28,6 @@ import PasscodeSection from "../containers/account/PasscodeSection" import EncryptionSection from "../containers/account/EncryptionSection" import CompanySection from "../containers/account/CompanySection" import LockedView from "../containers/LockedView"; -import ApplicationState from "@Lib/ApplicationState" -import StyleKit from "../style/StyleKit" - -var base64 = require('base-64'); export default class Settings extends Abstract { @@ -269,51 +268,13 @@ export default class Settings extends Abstract { onExportPress = async (encrypted, callback) => { this.handlePrivilegedAction(true, SFPrivilegesManager.ActionManageBackups, async () => { - let customCallback = (success) => { + BackupsManager.get().export(encrypted, callback).then((success) => { if(success) { var date = new Date(); this.setState({lastExportDate: date}); UserPrefsManager.get().setLastExportDate(date); } callback(); - } - var auth_params = await Auth.get().getAuthParams(); - var keys = encrypted ? KeysManager.get().activeKeys() : null; - - var items = []; - - for(var item of ModelManager.get().allItems) { - var itemParams = new SFItemParams(item, keys, auth_params); - var params = await itemParams.paramsForExportFile(); - items.push(params); - } - - if(items.length == 0) { - Alert.alert('No Data', "You don't have any notes yet."); - customCallback(); - return; - } - - var data = {items: items} - - if(keys) { - var authParams = KeysManager.get().activeAuthParams(); - // auth params are only needed when encrypted with a standard file key - data["auth_params"] = authParams; - } - - var jsonString = JSON.stringify(data, null, 2 /* pretty print */); - - var calledCallback = false; - - ApplicationState.get().performActionWithoutStateChangeImpact(() => { - Share.share({ - title: encrypted ? "SN-Encrypted-Backup" : 'SN-Decrypted-Backup', - message: jsonString, - }).then((event) => { - console.log("Result", event); - customCallback(event != Share.dismissedAction); - }) }) }); }