From ca6ec598d472a85f620cd040f5aee01417e5aef1 Mon Sep 17 00:00:00 2001 From: Utku <74243531+utkubakir@users.noreply.github.com> Date: Fri, 17 Mar 2023 05:00:02 +0300 Subject: [PATCH] Onboarding, Spacedrop & Location Settings Screen & Styled API (#596) * fix wrong current lib logic * add delete lib dialog to LibraryGeneralSettings * add delete lib to mobile LibraryGeneralSettings too * onboarding screens * move zxcvbn to @sd/client * get started screen and bloom * merge fix * move generatePassword back to interface * add useZodForm to mobile and match react-hook-form versions * new lib screen * Implement styled api * create lib screen and some tweaks * password input * fix password meter comp * new library style tweaks * Fix remove password bug (interface) * master password screen * privacy screen * creating lib screen * hexagons are cool * Expo 48 * keyboard handling * fix P2P on IOS * fix types * asset script * new icons * Spacedrop screen * Fix mobile asset imports * fix import cycle warning * Edit Location Settings screen and style changes on other setting screens * fix library creating bug? hopefully lol * move PasswordMeter to interface --------- Co-authored-by: Oscar Beaumont --- .lintstagedrc.json | 3 - .vscode/settings.json | 12 +- CONTRIBUTING.md | 6 +- Cargo.lock | Bin 228922 -> 228919 bytes Cargo.toml | 4 +- apps/mobile/.gitignore | 9 +- apps/mobile/android/.gitignore | 8 +- apps/mobile/android/app/BUCK | 55 -- apps/mobile/android/app/build.gradle | 332 +++----- apps/mobile/android/app/build_defs.bzl | 19 - apps/mobile/android/app/proguard-rules.pro | 2 +- .../spacedrive/app/ReactNativeFlipper.java | 14 +- .../java/com/spacedrive/app/MainActivity.java | 43 +- .../com/spacedrive/app/MainApplication.java | 106 +-- .../MainApplicationReactNativeHost.java | 117 --- .../components/MainComponentsRegistry.java | 36 - ...ApplicationTurboModuleManagerDelegate.java | 48 -- .../jni/MainApplicationModuleProvider.cpp | 24 - .../main/jni/MainApplicationModuleProvider.h | 16 - ...nApplicationTurboModuleManagerDelegate.cpp | 45 -- ...ainApplicationTurboModuleManagerDelegate.h | 38 - .../src/main/jni/MainComponentsRegistry.cpp | 61 -- .../app/src/main/jni/MainComponentsRegistry.h | 32 - .../android/app/src/main/jni/OnLoad.cpp | 11 - .../spacedrive/app/ReactNativeFlipper.java | 20 + apps/mobile/android/build.gradle | 31 +- apps/mobile/android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- apps/mobile/android/gradlew | 8 +- apps/mobile/android/gradlew.bat | 16 +- apps/mobile/android/settings.gradle | 9 +- apps/mobile/app.json | 1 + apps/mobile/ios/Podfile | 38 +- apps/mobile/ios/Podfile.lock | 750 ++++++++++-------- .../ios/Spacedrive.xcodeproj/project.pbxproj | 6 +- apps/mobile/ios/Spacedrive/AppDelegate.h | 8 +- apps/mobile/ios/Spacedrive/AppDelegate.mm | 131 +-- .../ios/Spacedrive/Supporting/Expo.plist | 2 +- apps/mobile/ios/Spacedrive/main.m | 3 +- apps/mobile/ios/build-rust.sh | 2 + apps/mobile/metro.config.js | 2 + apps/mobile/package.json | 48 +- apps/mobile/scripts/postinstall.js | 1 + .../src/components/animation/layout.tsx | 41 +- .../src/components/animation/lottie.tsx | 23 +- .../drawer/DrawerLibraryManager.tsx | 4 +- .../src/components/explorer/FileItem.tsx | 16 +- .../src/components/explorer/FileRow.tsx | 12 +- .../src/components/explorer/FileThumb.tsx | 128 ++- apps/mobile/src/components/form/Input.tsx | 54 +- .../src/components/icons/FolderIcon.tsx | 3 +- .../src/components/key/PasswordMeter.tsx | 51 ++ apps/mobile/src/components/layout/Card.tsx | 2 +- apps/mobile/src/components/layout/Dialog.tsx | 4 +- .../src/components/modal/ImportModal.tsx | 6 +- .../modal/inspector/FileInfoModal.tsx | 2 +- .../components/modal/tag/CreateTagModal.tsx | 1 - .../components/modal/tag/UpdateTagModal.tsx | 1 - .../src/components/primitive/Button.tsx | 27 +- .../src/components/primitive/Divider.tsx | 14 +- .../components/settings/SettingsContainer.tsx | 5 +- .../src/components/settings/SettingsItem.tsx | 2 +- apps/mobile/src/hooks/useZodForm.ts | 21 + apps/mobile/src/lib/tailwind.ts | 18 +- apps/mobile/src/main.tsx | 1 + .../src/navigation/OnboardingNavigator.tsx | 30 +- .../src/navigation/SettingsNavigator.tsx | 10 + apps/mobile/src/navigation/TabNavigator.tsx | 36 +- .../{NodesStack.tsx => SpacedropStack.tsx} | 23 +- apps/mobile/src/screens/Nodes.tsx | 11 - apps/mobile/src/screens/Spacedrop.tsx | 141 ++++ .../src/screens/onboarding/CreateLibrary.tsx | 24 - .../screens/onboarding/CreatingLibrary.tsx | 87 ++ .../src/screens/onboarding/GetStarted.tsx | 82 ++ .../src/screens/onboarding/MasterPassword.tsx | 151 ++++ .../src/screens/onboarding/NewLibrary.tsx | 77 ++ .../src/screens/onboarding/Onboarding.tsx | 41 - .../mobile/src/screens/onboarding/Privacy.tsx | 82 ++ apps/mobile/src/screens/settings/Settings.tsx | 9 +- .../settings/client/GeneralSettings.tsx | 10 +- .../settings/client/LibrarySettings.tsx | 4 +- .../settings/library/EditLocationSettings.tsx | 218 +++++ .../library/LibraryGeneralSettings.tsx | 73 +- .../settings/library/LocationSettings.tsx | 36 +- .../screens/settings/library/TagsSettings.tsx | 4 +- apps/mobile/tailwind.config.js | 2 + core/src/lib.rs | 4 +- .../prerequisites/environment-setup.md | 4 +- .../app/$libraryId/KeyManager/Mounter.tsx | 9 +- .../$libraryId/settings/library/general.tsx | 14 +- .../library/keys/MasterPasswordDialog.tsx | 14 +- .../settings/node/libraries/CreateDialog.tsx | 4 +- interface/app/onboarding/creating-library.tsx | 5 +- interface/app/onboarding/master-password.tsx | 7 +- interface/components/PasswordMeter.tsx | 45 ++ interface/package.json | 6 +- interface/util/index.tsx | 5 + package.json | 2 +- packages/client/package.json | 4 +- packages/client/src/index.ts | 1 + packages/client/src/lib/index.ts | 1 + packages/client/src/lib/passwordStrength.ts | 20 + packages/client/src/stores/onboardingStore.ts | 3 +- packages/client/src/stores/telemetryState.tsx | 2 +- packages/client/src/utils/keys.ts | 4 - packages/ui/src/PasswordMeter.tsx | 61 -- packages/ui/src/index.ts | 1 - pnpm-lock.yaml | Bin 784028 -> 824858 bytes 108 files changed, 2077 insertions(+), 1849 deletions(-) delete mode 100644 .lintstagedrc.json delete mode 100644 apps/mobile/android/app/BUCK delete mode 100644 apps/mobile/android/app/build_defs.bzl delete mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java delete mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java delete mode 100644 apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java delete mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp delete mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h delete mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp delete mode 100644 apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h delete mode 100644 apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp delete mode 100644 apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h delete mode 100644 apps/mobile/android/app/src/main/jni/OnLoad.cpp create mode 100644 apps/mobile/android/app/src/release/java/com/spacedrive/app/ReactNativeFlipper.java create mode 100644 apps/mobile/src/components/key/PasswordMeter.tsx create mode 100644 apps/mobile/src/hooks/useZodForm.ts rename apps/mobile/src/navigation/tabs/{NodesStack.tsx => SpacedropStack.tsx} (55%) delete mode 100644 apps/mobile/src/screens/Nodes.tsx create mode 100644 apps/mobile/src/screens/Spacedrop.tsx delete mode 100644 apps/mobile/src/screens/onboarding/CreateLibrary.tsx create mode 100644 apps/mobile/src/screens/onboarding/CreatingLibrary.tsx create mode 100644 apps/mobile/src/screens/onboarding/GetStarted.tsx create mode 100644 apps/mobile/src/screens/onboarding/MasterPassword.tsx create mode 100644 apps/mobile/src/screens/onboarding/NewLibrary.tsx delete mode 100644 apps/mobile/src/screens/onboarding/Onboarding.tsx create mode 100644 apps/mobile/src/screens/onboarding/Privacy.tsx create mode 100644 apps/mobile/src/screens/settings/library/EditLocationSettings.tsx create mode 100644 interface/components/PasswordMeter.tsx create mode 100644 interface/util/index.tsx create mode 100644 packages/client/src/lib/index.ts create mode 100644 packages/client/src/lib/passwordStrength.ts delete mode 100644 packages/ui/src/PasswordMeter.tsx diff --git a/.lintstagedrc.json b/.lintstagedrc.json deleted file mode 100644 index 59c8ccb43..000000000 --- a/.lintstagedrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "*.{js,jsx,ts,tsx,html,json,yml,yaml,css,scss,md}": ["prettier --write"] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index e62e8f133..ee5885290 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,12 +38,12 @@ "rust-analyzer.procMacro.enable": true, "rust-analyzer.diagnostics.experimental.enable": false, "tailwindCSS.experimental.classRegex": [ - ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], - "tw`([^`]*)", - "tw\\.[^`]+`([^`]*)`", - "tw\\(.*?\\).*?`([^`]*)", - "tw\\.style\\(([^)]*)\\)", - "twStyle\\(([^)]*)\\)" + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], // cva(....)`...` + "tw\\.[^`]+`([^`]*)`", // tw.xxx`...` + "tw\\(.*?\\).*?`([^`]*)", // tw(....)`...` + "tw`([^`]*)", // tw`...` (mobile) + "twStyle\\(([^)]*)\\)", // twStyle(....) (mobile) + ["styled\\([^,)]+,([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] // styled(....)`...` (mobile) ], "search.exclude": { "**/node_modules": true, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9b04cd13..de7f0726b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,12 +69,12 @@ To run mobile app - Install [Android Studio](https://developer.android.com/studio) for Android and [Xcode](https://apps.apple.com/au/app/xcode/id497799835) for IOS development - `./.github/scripts/setup-system.sh mobile` + - The should setup most of the dependencies for the mobile app to build. -- You must also ensure [you must have NDK 24.0.8215888 and CMake](https://developer.android.com/studio/projects/install-ndk#default-version) in Android Studio -- `cd apps/mobile && pnpm i` - This is a separate workspace, you need to do this! + +- You must also ensure [you must have NDK 23.1.7779620 and CMake](https://developer.android.com/studio/projects/install-ndk#default-version) in Android Studio - `pnpm android` - runs on Android Emulator - `pnpm ios` - runs on iOS Emulator -- `pnpm start` - For already bundled app ### Pull Request diff --git a/Cargo.lock b/Cargo.lock index 01b48e41ef35f2fe1f8f17578e16b464eed592d7..f84a45a79628df96ef62ffb6a4f294782a323b17 100644 GIT binary patch delta 141 zcmdn>hj;rQ-i9rVk5#5O{AU!OK5qsSe_UpoZh2x!a)y0TYMHHxp+Txes$q&zih;SY zL7Jh7iD62bVX~2>k)=smYO+bPak7Dlp{21hu?nXbhB8WRZ%}2l3U1$5!3e}m+xJy4 H*RTQrPAe^W delta 112 zcmdn~hj-T>-i9rVk5wisM7Y|TCnlvNC0UrInpl_`8kwdfrWlwd8<|-erW&RiTN)>& z87CQ7B%3Fhn;0jV7$+tgni(Y;7^I~bTN;=dO&9cG5}W=&f{AT=lPaTSFpFM#W=Z?* P3PvDi+P=GjxrP-0CIKVV diff --git a/Cargo.toml b/Cargo.toml index d0a34a322..d1b462eca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,10 @@ tokio = { version = "1.25.0" } [patch.crates-io] # We use this patch so we can compile for the IOS simulator on M1 openssl-sys = { git = "https://github.com/spacedriveapp/rust-openssl", rev = "92c3dec225a9e984884d5b30a517e5d44a24d03b" } +# We patch this so that it can be built for IOS - The `main` branch uses macOS specific APIs +if-watch = { git = "https://github.com/oscartbeaumont/if-watch", rev = "410e8e1d2d0730f1441df1c29294fec4c3c04193" } -mdns-sd = { git = "https://github.com/oscartbeaumont/mdns-sd.git", rev = "45515a98e9e408c102871abaa5a9bff3bee0cbe8" } # TODO: Do upstream PR +mdns-sd = { git = "https://github.com/oscartbeaumont/mdns-sd", rev = "45515a98e9e408c102871abaa5a9bff3bee0cbe8" } # TODO: Do upstream PR rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "c03872c0ba29d2429e9c059dfb235cdd03e15e8c" } # TODO: Move back to crates.io when new jsonrpc executor + `tokio::spawn` in the Tauri IPC plugin + upgraded Tauri version is released specta = { git = "https://github.com/oscartbeaumont/rspc", rev = "c03872c0ba29d2429e9c059dfb235cdd03e15e8c" } diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore index c8eb0f9a6..6b0b6caab 100644 --- a/apps/mobile/.gitignore +++ b/apps/mobile/.gitignore @@ -37,12 +37,6 @@ node_modules/ npm-debug.log yarn-error.log -# BUCK -buck-out/ -\.buckd/ -*.keystore -!debug.keystore - # Bundle artifacts *.jsbundle @@ -53,3 +47,6 @@ buck-out/ .expo/ web-build/ dist/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* \ No newline at end of file diff --git a/apps/mobile/android/.gitignore b/apps/mobile/android/.gitignore index 64436baaf..ab9e6d140 100644 --- a/apps/mobile/android/.gitignore +++ b/apps/mobile/android/.gitignore @@ -11,11 +11,5 @@ local.properties *.iml *.hprof -# BUCK -buck-out/ -\.buckd/ -*.keystore -!debug.keystore - # Bundle artifacts -*.jsbundle +*.jsbundle \ No newline at end of file diff --git a/apps/mobile/android/app/BUCK b/apps/mobile/android/app/BUCK deleted file mode 100644 index cabced2a6..000000000 --- a/apps/mobile/android/app/BUCK +++ /dev/null @@ -1,55 +0,0 @@ -# To learn about Buck see [Docs](https://buckbuild.com/). -# To run your application with Buck: -# - install Buck -# - `npm start` - to start the packager -# - `cd android` -# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` -# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck -# - `buck install -r android/app` - compile, install and run application -# - -load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") - -lib_deps = [] - -create_aar_targets(glob(["libs/*.aar"])) - -create_jar_targets(glob(["libs/*.jar"])) - -android_library( - name = "all-libs", - exported_deps = lib_deps, -) - -android_library( - name = "app-code", - srcs = glob([ - "src/main/java/**/*.java", - ]), - deps = [ - ":all-libs", - ":build_config", - ":res", - ], -) - -android_build_config( - name = "build_config", - package = "com.spacedrive.app", -) - -android_resource( - name = "res", - package = "com.spacedrive.app", - res = "src/main/res", -) - -android_binary( - name = "app", - keystore = "//android/keystores:debug", - manifest = "src/main/AndroidManifest.xml", - package_type = "debug", - deps = [ - ":app-code", - ], -) diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle index cecfb1815..c12552698 100644 --- a/apps/mobile/android/app/build.gradle +++ b/apps/mobile/android/app/build.gradle @@ -1,8 +1,10 @@ apply plugin: "com.android.application" +apply plugin: "com.facebook.react" import com.android.build.OutputFile -import org.apache.tools.ant.taskdefs.condition.Os + +// SPACEDRIVE CODE apply plugin: 'org.mozilla.rust-android-gradle.rust-android' cargo { @@ -23,134 +25,108 @@ tasks.whenTaskAdded { task -> } } -/** - * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets - * and bundleReleaseJsAndAssets). - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "../../node_modules/react-native/react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation. If none specified and - * // "index.android.js" exists, it will be used. Otherwise "index.js" is - * // default. Can be overridden with ENTRY_FILE environment variable. - * entryFile: "index.android.js", - * - * // https://reactnative.dev/docs/performance#enable-the-ram-format - * bundleCommand: "ram-bundle", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // whether to bundle JS and assets in another build variant (if configured). - * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants - * // The configuration property can be in the following formats - * // 'bundleIn${productFlavor}${buildType}' - * // 'bundleIn${buildType}' - * // bundleInFreeDebug: true, - * // bundleInPaidRelease: true, - * // bundleInBeta: true, - * - * // whether to disable dev mode in custom build variants (by default only disabled in release) - * // for example: to disable dev mode in the staging build type (if configured) - * devDisabledInStaging: true, - * // The configuration property can be in the following formats - * // 'devDisabledIn${productFlavor}${buildType}' - * // 'devDisabledIn${buildType}' - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"], - * - * // override which node gets called and with what additional arguments - * nodeExecutableAndArgs: ["node"], - * - * // supply additional arguments to the packager - * extraPackagerArgs: [] - * ] - */ +// SPACEDRIVE CODE END def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() +def expoDebuggableVariants = ['debug'] +// Override `debuggableVariants` for expo-updates debugging +if (System.getenv('EX_UPDATES_NATIVE_DEBUG') == "1") { + react { + expoDebuggableVariants = [] + } +} -def reactNativeRoot = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() - -project.ext.react = [ - entryFile: ["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android"].execute(null, rootDir).text.trim(), - enableHermes: (findProperty('expo.jsEngine') ?: "jsc") == "hermes", - hermesCommand: new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc", - cliPath: "${reactNativeRoot}/cli.js", - composeSourceMapsPath: "${reactNativeRoot}/scripts/compose-source-maps.js", -] - -apply from: new File(reactNativeRoot, "react.gradle") /** - * Set this to true to create two separate APKs instead of one: - * - An APK that only works on ARM devices - * - An APK that only works on x86 devices - * The advantage is the size of the APK is reduced by about 4MB. - * Upload all the APKs to the Play Store and people will download - * the correct one based on the CPU architecture of their device. + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) + reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() + hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" + debuggableVariants = expoDebuggableVariants + + // Use Expo CLI to bundle the app, this ensures the Metro config + // works correctly with Expo projects. + cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim()) + bundleCommand = "export:embed" + + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '..' + // root = file("../") + // The folder where the react-native NPM package is. Default is ../node_modules/react-native + // reactNativeDir = file("../node_modules/react-native") + // The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen + // codegenDir = file("../node_modules/react-native-codegen") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + // nodeExecutableAndArgs = ["node"] + + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + // bundleAssetName = "MyApplication.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + // entryFile = file("../js/MyApplication.android.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + // hermesFlags = ["-O", "-output-source-map"] +} + +// Override `hermesEnabled` by `expo.jsEngine` +ext { + hermesEnabled = (findProperty('expo.jsEngine') ?: "hermes") == "hermes" +} + +/** + * Set this to true to create four separate APKs instead of one, + * one for each native architecture. This is useful if you don't + * use App Bundles (https://developer.android.com/guide/app-bundle/) + * and want to have separate APKs to upload to the Play Store. */ def enableSeparateBuildPerCPUArchitecture = false /** - * Run Proguard to shrink the Java bytecode in release builds. + * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() /** - * The preferred build flavor of JavaScriptCore. + * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that + * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' /** - * Whether to enable the Hermes VM. - * - * This should be set on project.ext.react and that value will be read here. If it is not set - * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode - * and the benefits of using Hermes will therefore be sharply reduced. - */ -def enableHermes = project.ext.react.get("enableHermes", false); - -/** - * Architectures to build native code for. + * Private function to get the list of Native Architectures you want to build. + * This reads the value from reactNativeArchitectures in your gradle.properties + * file and works together with the --active-arch-only flag of react-native run-android. */ def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") @@ -163,84 +139,11 @@ android { compileSdkVersion rootProject.ext.compileSdkVersion defaultConfig { - applicationId 'com.spacedrive.app' + applicationId "com.spacedrive.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "0.0.1" - buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() - - if (isNewArchitectureEnabled()) { - // We configure the NDK build only if you decide to opt-in for the New Architecture. - externalNativeBuild { - ndkBuild { - arguments "APP_PLATFORM=android-21", - "APP_STL=c++_shared", - "NDK_TOOLCHAIN_VERSION=clang", - "GENERATED_SRC_DIR=$buildDir/generated/source", - "PROJECT_BUILD_DIR=$buildDir", - "REACT_ANDROID_DIR=${reactNativeRoot}/ReactAndroid", - "REACT_ANDROID_BUILD_DIR=${reactNativeRoot}/ReactAndroid/build", - "NODE_MODULES_DIR=$rootDir/../node_modules" - cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1" - cppFlags "-std=c++17" - // Make sure this target name is the same you specify inside the - // src/main/jni/Android.mk file for the `LOCAL_MODULE` variable. - targets "spacedrive_appmodules" - - // Fix for windows limit on number of character in file paths and in command lines - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - arguments "NDK_APP_SHORT_COMMANDS=true" - } - } - } - if (!enableSeparateBuildPerCPUArchitecture) { - ndk { - abiFilters (*reactNativeArchitectures()) - } - } - } - } - - if (isNewArchitectureEnabled()) { - // We configure the NDK build only if you decide to opt-in for the New Architecture. - externalNativeBuild { - ndkBuild { - path "$projectDir/src/main/jni/Android.mk" - } - } - def reactAndroidProjectDir = project(':ReactAndroid').projectDir - def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { - dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") - from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") - into("$buildDir/react-ndk/exported") - } - def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { - dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") - from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib") - into("$buildDir/react-ndk/exported") - } - afterEvaluate { - // If you wish to add a custom TurboModule or component locally, - // you should uncomment this line. - // preBuild.dependsOn("generateCodegenArtifactsFromSchema") - preDebugBuild.dependsOn(packageReactNdkDebugLibs) - preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) - - // Due to a bug inside AGP, we have to explicitly set a dependency - // between configureNdkBuild* tasks and the preBuild tasks. - // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732 - configureNdkBuildRelease.dependsOn(preReleaseBuild) - configureNdkBuildDebug.dependsOn(preDebugBuild) - reactNativeArchitectures().each { architecture -> - tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure { - dependsOn("preDebugBuild") - } - tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure { - dependsOn("preReleaseBuild") - } - } - } } splits { @@ -277,11 +180,12 @@ android { variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html + // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = - versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + defaultConfig.versionCode * 1000 + versionCodes.get(abi) } } @@ -309,10 +213,8 @@ android { } dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - - //noinspection GradleDynamicVersion - implementation "com.facebook.react:react-native:+" // From node_modules + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; @@ -321,76 +223,38 @@ dependencies { // If your app supports Android versions before Ice Cream Sandwich (API level 14) if (isGifEnabled || isWebpEnabled) { - implementation "com.facebook.fresco:fresco:${frescoVersion}" - implementation "com.facebook.fresco:imagepipeline-okhttp3:${frescoVersion}" + implementation("com.facebook.fresco:fresco:${frescoVersion}") + implementation("com.facebook.fresco:imagepipeline-okhttp3:${frescoVersion}") } if (isGifEnabled) { // For animated gif support - implementation "com.facebook.fresco:animated-gif:${frescoVersion}" + implementation("com.facebook.fresco:animated-gif:${frescoVersion}") } if (isWebpEnabled) { // For webp support - implementation "com.facebook.fresco:webpsupport:${frescoVersion}" + implementation("com.facebook.fresco:webpsupport:${frescoVersion}") if (isWebpAnimatedEnabled) { // Animated webp support - implementation "com.facebook.fresco:animated-webp:${frescoVersion}" + implementation("com.facebook.fresco:animated-webp:${frescoVersion}") } } - implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" - debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { - exclude group:'com.facebook.fbjni' - } + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0") + + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { - exclude group:'com.facebook.flipper' exclude group:'com.squareup.okhttp3', module:'okhttp' } - debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { - exclude group:'com.facebook.flipper' - } + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") - if (enableHermes) { - //noinspection GradleDynamicVersion - implementation("com.facebook.react:hermes-engine:+") { // From node_modules - exclude group:'com.facebook.fbjni' - } + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } } -if (isNewArchitectureEnabled()) { - // If new architecture is enabled, we let you build RN from source - // Otherwise we fallback to a prebuilt .aar bundled in the NPM package. - // This will be applied to all the imported transtitive dependency. - configurations.all { - resolutionStrategy.dependencySubstitution { - substitute(module("com.facebook.react:react-native")) - .using(project(":ReactAndroid")) - .because("On New Architecture we're building React Native from source") - substitute(module("com.facebook.react:hermes-engine")) - .using(project(":ReactAndroid:hermes-engine")) - .because("On New Architecture we're building Hermes from source") - } - } -} - -// Run this once to be able to run the application with BUCK -// puts all compile dependencies into folder libs for BUCK to use -task copyDownloadableDepsToLibs(type: Copy) { - from configurations.implementation - into 'libs' -} - apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); -applyNativeModulesAppBuildGradle(project) - -def isNewArchitectureEnabled() { - // To opt-in for the New Architecture, you can either: - // - Set `newArchEnabled` to true inside the `gradle.properties` file - // - Invoke gradle with `-newArchEnabled=true` - // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` - return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" -} +applyNativeModulesAppBuildGradle(project) \ No newline at end of file diff --git a/apps/mobile/android/app/build_defs.bzl b/apps/mobile/android/app/build_defs.bzl deleted file mode 100644 index fff270f8d..000000000 --- a/apps/mobile/android/app/build_defs.bzl +++ /dev/null @@ -1,19 +0,0 @@ -"""Helper definitions to glob .aar and .jar targets""" - -def create_aar_targets(aarfiles): - for aarfile in aarfiles: - name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] - lib_deps.append(":" + name) - android_prebuilt_aar( - name = name, - aar = aarfile, - ) - -def create_jar_targets(jarfiles): - for jarfile in jarfiles: - name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] - lib_deps.append(":" + name) - prebuilt_jar( - name = name, - binary_jar = jarfile, - ) diff --git a/apps/mobile/android/app/proguard-rules.pro b/apps/mobile/android/app/proguard-rules.pro index 551eb41da..78d6c0389 100644 --- a/apps/mobile/android/app/proguard-rules.pro +++ b/apps/mobile/android/app/proguard-rules.pro @@ -11,4 +11,4 @@ -keep class com.swmansion.reanimated.** { *; } -keep class com.facebook.react.turbomodule.** { *; } -# Add any project specific keep options here: +# Add any project specific keep options here: \ No newline at end of file diff --git a/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java b/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java index 00c9f2231..37ca9c904 100644 --- a/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java +++ b/apps/mobile/android/app/src/debug/java/com/spacedrive/app/ReactNativeFlipper.java @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) Meta Platforms, Inc. and affiliates. * *

This source code is licensed under the MIT license found in the LICENSE file in the root * directory of this source tree. @@ -17,22 +17,27 @@ import com.facebook.flipper.plugins.inspector.DescriptorMapping; import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; -import com.facebook.flipper.plugins.react.ReactFlipperPlugin; import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceEventListener; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.NetworkingModule; import okhttp3.OkHttpClient; +/** + * Class responsible of loading Flipper inside your React Native application. This is the debug + * flavor of it. Here you can add your own plugins and customize the Flipper setup. + */ public class ReactNativeFlipper { public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { if (FlipperUtils.shouldEnableFlipper(context)) { final FlipperClient client = AndroidFlipperClient.getInstance(context); + client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); - client.addPlugin(new ReactFlipperPlugin()); client.addPlugin(new DatabasesFlipperPlugin(context)); client.addPlugin(new SharedPreferencesFlipperPlugin(context)); client.addPlugin(CrashReporterPlugin.getInstance()); + NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); NetworkingModule.setCustomClientBuilder( new NetworkingModule.CustomClientBuilder() { @@ -43,12 +48,13 @@ public class ReactNativeFlipper { }); client.addPlugin(networkFlipperPlugin); client.start(); + // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized // Hence we run if after all native modules have been initialized ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); if (reactContext == null) { reactInstanceManager.addReactInstanceEventListener( - new ReactInstanceManager.ReactInstanceEventListener() { + new ReactInstanceEventListener() { @Override public void onReactContextInitialized(ReactContext reactContext) { reactInstanceManager.removeReactInstanceEventListener(this); diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java index 94ac57750..1309a4b94 100644 --- a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainActivity.java @@ -5,7 +5,8 @@ import android.os.Bundle; import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; +import com.facebook.react.defaults.DefaultReactActivityDelegate; import expo.modules.ReactActivityDelegateWrapper; @@ -29,15 +30,20 @@ public class MainActivity extends ReactActivity { } /** - * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and - * you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer - * (Paper). + * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link + * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React + * (aka React 18) with two boolean flags. */ @Override protected ReactActivityDelegate createReactActivityDelegate() { - return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, - new MainActivityDelegate(this, getMainComponentName()) - ); + return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate( + this, + getMainComponentName(), + // If you opted-in for the New Architecture, we enable the Fabric Renderer. + DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled + // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18). + DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled + )); } /** @@ -59,25 +65,4 @@ public class MainActivity extends ReactActivity { // because it's doing more than {@link Activity#moveTaskToBack} in fact. super.invokeDefaultOnBackPressed(); } - - public static class MainActivityDelegate extends ReactActivityDelegate { - public MainActivityDelegate(ReactActivity activity, String mainComponentName) { - super(activity, mainComponentName); - } - - @Override - protected ReactRootView createRootView() { - ReactRootView reactRootView = new ReactRootView(getContext()); - // If you opted-in for the New Architecture, we enable the Fabric Renderer. - reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); - return reactRootView; - } - - @Override - protected boolean isConcurrentRootEnabled() { - // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). - // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - } -} +} \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java index 1d47d6150..ad2962a9e 100644 --- a/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java +++ b/apps/mobile/android/app/src/main/java/com/spacedrive/app/MainApplication.java @@ -1,69 +1,70 @@ package com.spacedrive.app; import android.app.Application; -import android.content.Context; import android.content.res.Configuration; import androidx.annotation.NonNull; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; -import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; -import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; +import com.facebook.react.defaults.DefaultReactNativeHost; import com.facebook.soloader.SoLoader; -import com.spacedrive.app.newarchitecture.MainApplicationReactNativeHost; import expo.modules.ApplicationLifecycleDispatcher; import expo.modules.ReactNativeHostWrapper; -import java.lang.reflect.InvocationTargetException; import java.util.List; public class MainApplication extends Application implements ReactApplication { - private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper( - this, - new ReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - packages.add(new com.spacedrive.app.SpacedrivePackage()); - return packages; - } + private final ReactNativeHost mReactNativeHost = + new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } - @Override - protected String getJSMainModuleName() { - return "index"; - } + @Override + protected List getPackages() { + @SuppressWarnings("UnnecessaryLocalVariable") + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + packages.add(new com.spacedrive.app.SpacedrivePackage()); + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "index"; + } + + @Override + protected boolean isNewArchEnabled() { + return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + } + + @Override + protected Boolean isHermesEnabled() { + return BuildConfig.IS_HERMES_ENABLED; + } }); - private final ReactNativeHost mNewArchitectureNativeHost = - new ReactNativeHostWrapper(this, new MainApplicationReactNativeHost(this)); - @Override public ReactNativeHost getReactNativeHost() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - return mNewArchitectureNativeHost; - } else { - return mReactNativeHost; - } + return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); - // If you opted-in for the New Architecture, we enable the TurboModule system - ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; SoLoader.init(this, /* native exopackage */ false); - - initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + DefaultNewArchitectureEntryPoint.load(); + } + ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); ApplicationLifecycleDispatcher.onApplicationCreate(this); } @@ -72,35 +73,4 @@ public class MainApplication extends Application implements ReactApplication { super.onConfigurationChanged(newConfig); ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); } - - /** - * Loads Flipper in React Native templates. Call this in the onCreate method with something like - * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); - * - * @param context - * @param reactInstanceManager - */ - private static void initializeFlipper( - Context context, ReactInstanceManager reactInstanceManager) { - if (BuildConfig.DEBUG) { - try { - /* - We use reflection here to pick up the class that initializes Flipper, - since Flipper library is not available in release mode - */ - Class aClass = Class.forName("com.spacedrive.app.ReactNativeFlipper"); - aClass - .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) - .invoke(null, context, reactInstanceManager); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - } -} +} \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java deleted file mode 100644 index 1a3e3aa1d..000000000 --- a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/MainApplicationReactNativeHost.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.spacedrive.app.newarchitecture; - -import android.app.Application; -import androidx.annotation.NonNull; -import com.facebook.react.PackageList; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.ReactPackageTurboModuleManagerDelegate; -import com.facebook.react.bridge.JSIModulePackage; -import com.facebook.react.bridge.JSIModuleProvider; -import com.facebook.react.bridge.JSIModuleSpec; -import com.facebook.react.bridge.JSIModuleType; -import com.facebook.react.bridge.JavaScriptContextHolder; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.react.fabric.CoreComponentsRegistry; -import com.facebook.react.fabric.EmptyReactNativeConfig; -import com.facebook.react.fabric.FabricJSIModuleProvider; -import com.facebook.react.fabric.ReactNativeConfig; -import com.facebook.react.uimanager.ViewManagerRegistry; -import com.spacedrive.app.BuildConfig; -import com.spacedrive.app.newarchitecture.components.MainComponentsRegistry; -import com.spacedrive.app.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate; -import java.util.ArrayList; -import java.util.List; - -/** - * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both - * TurboModule delegates and the Fabric Renderer. - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. - */ -public class MainApplicationReactNativeHost extends ReactNativeHost { - public MainApplicationReactNativeHost(Application application) { - super(application); - } - - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - // TurboModules must also be loaded here providing a valid TurboReactPackage implementation: - // packages.add(new TurboReactPackage() { ... }); - // If you have custom Fabric Components, their ViewManagers should also be loaded here - // inside a ReactPackage. - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @NonNull - @Override - protected ReactPackageTurboModuleManagerDelegate.Builder - getReactPackageTurboModuleManagerDelegateBuilder() { - // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary - // for the new architecture and to use TurboModules correctly. - return new MainApplicationTurboModuleManagerDelegate.Builder(); - } - - @Override - protected JSIModulePackage getJSIModulePackage() { - return new JSIModulePackage() { - @Override - public List getJSIModules( - final ReactApplicationContext reactApplicationContext, - final JavaScriptContextHolder jsContext) { - final List specs = new ArrayList<>(); - - // Here we provide a new JSIModuleSpec that will be responsible of providing the - // custom Fabric Components. - specs.add( - new JSIModuleSpec() { - @Override - public JSIModuleType getJSIModuleType() { - return JSIModuleType.UIManager; - } - - @Override - public JSIModuleProvider getJSIModuleProvider() { - final ComponentFactory componentFactory = new ComponentFactory(); - CoreComponentsRegistry.register(componentFactory); - - // Here we register a Components Registry. - // The one that is generated with the template contains no components - // and just provides you the one from React Native core. - MainComponentsRegistry.register(componentFactory); - - final ReactInstanceManager reactInstanceManager = getReactInstanceManager(); - - ViewManagerRegistry viewManagerRegistry = - new ViewManagerRegistry( - reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)); - - return new FabricJSIModuleProvider( - reactApplicationContext, - componentFactory, - ReactNativeConfig.DEFAULT_CONFIG, - viewManagerRegistry); - } - }); - return specs; - } - }; - } -} diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java deleted file mode 100644 index 0c5bce1e3..000000000 --- a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/components/MainComponentsRegistry.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.spacedrive.app.newarchitecture.components; - -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.soloader.SoLoader; - -/** - * Class responsible to load the custom Fabric Components. This class has native methods and needs a - * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ - * folder for you). - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. - */ -@DoNotStrip -public class MainComponentsRegistry { - static { - SoLoader.loadLibrary("fabricjni"); - } - - @DoNotStrip private final HybridData mHybridData; - - @DoNotStrip - private native HybridData initHybrid(ComponentFactory componentFactory); - - @DoNotStrip - private MainComponentsRegistry(ComponentFactory componentFactory) { - mHybridData = initHybrid(componentFactory); - } - - @DoNotStrip - public static MainComponentsRegistry register(ComponentFactory componentFactory) { - return new MainComponentsRegistry(componentFactory); - } -} diff --git a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java b/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java deleted file mode 100644 index a2cd752da..000000000 --- a/apps/mobile/android/app/src/main/java/com/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.spacedrive.app.newarchitecture.modules; - -import com.facebook.jni.HybridData; -import com.facebook.react.ReactPackage; -import com.facebook.react.ReactPackageTurboModuleManagerDelegate; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.soloader.SoLoader; -import java.util.List; - -/** - * Class responsible to load the TurboModules. This class has native methods and needs a - * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ - * folder for you). - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. - */ -public class MainApplicationTurboModuleManagerDelegate - extends ReactPackageTurboModuleManagerDelegate { - - private static volatile boolean sIsSoLibraryLoaded; - - protected MainApplicationTurboModuleManagerDelegate( - ReactApplicationContext reactApplicationContext, List packages) { - super(reactApplicationContext, packages); - } - - protected native HybridData initHybrid(); - - native boolean canCreateTurboModule(String moduleName); - - public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder { - protected MainApplicationTurboModuleManagerDelegate build( - ReactApplicationContext context, List packages) { - return new MainApplicationTurboModuleManagerDelegate(context, packages); - } - } - - @Override - protected synchronized void maybeLoadOtherSoLibraries() { - if (!sIsSoLibraryLoaded) { - // If you change the name of your application .so file in the Android.mk file, - // make sure you update the name here as well. - SoLoader.loadLibrary("spacedrive_appmodules"); - sIsSoLibraryLoaded = true; - } - } -} diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp b/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp deleted file mode 100644 index 0ac23cc62..000000000 --- a/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "MainApplicationModuleProvider.h" - -#include - -namespace facebook { -namespace react { - -std::shared_ptr MainApplicationModuleProvider( - const std::string moduleName, - const JavaTurboModule::InitParams ¶ms) { - // Here you can provide your own module provider for TurboModules coming from - // either your application or from external libraries. The approach to follow - // is similar to the following (for a library called `samplelibrary`: - // - // auto module = samplelibrary_ModuleProvider(moduleName, params); - // if (module != nullptr) { - // return module; - // } - // return rncore_ModuleProvider(moduleName, params); - return rncore_ModuleProvider(moduleName, params); -} - -} // namespace react -} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h b/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h deleted file mode 100644 index 0fa43fa69..000000000 --- a/apps/mobile/android/app/src/main/jni/MainApplicationModuleProvider.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace facebook { -namespace react { - -std::shared_ptr MainApplicationModuleProvider( - const std::string moduleName, - const JavaTurboModule::InitParams ¶ms); - -} // namespace react -} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp b/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp deleted file mode 100644 index dbbdc3d13..000000000 --- a/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "MainApplicationTurboModuleManagerDelegate.h" -#include "MainApplicationModuleProvider.h" - -namespace facebook { -namespace react { - -jni::local_ref -MainApplicationTurboModuleManagerDelegate::initHybrid( - jni::alias_ref) { - return makeCxxInstance(); -} - -void MainApplicationTurboModuleManagerDelegate::registerNatives() { - registerHybrid({ - makeNativeMethod( - "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid), - makeNativeMethod( - "canCreateTurboModule", - MainApplicationTurboModuleManagerDelegate::canCreateTurboModule), - }); -} - -std::shared_ptr -MainApplicationTurboModuleManagerDelegate::getTurboModule( - const std::string name, - const std::shared_ptr jsInvoker) { - // Not implemented yet: provide pure-C++ NativeModules here. - return nullptr; -} - -std::shared_ptr -MainApplicationTurboModuleManagerDelegate::getTurboModule( - const std::string name, - const JavaTurboModule::InitParams ¶ms) { - return MainApplicationModuleProvider(name, params); -} - -bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule( - std::string name) { - return getTurboModule(name, nullptr) != nullptr || - getTurboModule(name, {.moduleName = name}) != nullptr; -} - -} // namespace react -} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h b/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h deleted file mode 100644 index f5670d9f8..000000000 --- a/apps/mobile/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include - -#include -#include - -namespace facebook { -namespace react { - -class MainApplicationTurboModuleManagerDelegate - : public jni::HybridClass< - MainApplicationTurboModuleManagerDelegate, - TurboModuleManagerDelegate> { - public: - // Adapt it to the package you used for your Java class. - static constexpr auto kJavaDescriptor = - "Lcom/spacedrive/app/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;"; - - static jni::local_ref initHybrid(jni::alias_ref); - - static void registerNatives(); - - std::shared_ptr getTurboModule( - const std::string name, - const std::shared_ptr jsInvoker) override; - std::shared_ptr getTurboModule( - const std::string name, - const JavaTurboModule::InitParams ¶ms) override; - - /** - * Test-only method. Allows user to verify whether a TurboModule can be - * created by instances of this class. - */ - bool canCreateTurboModule(std::string name); -}; - -} // namespace react -} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp b/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp deleted file mode 100644 index 8f7edffd6..000000000 --- a/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "MainComponentsRegistry.h" - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {} - -std::shared_ptr -MainComponentsRegistry::sharedProviderRegistry() { - auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); - - // Custom Fabric Components go here. You can register custom - // components coming from your App or from 3rd party libraries here. - // - // providerRegistry->add(concreteComponentDescriptorProvider< - // AocViewerComponentDescriptor>()); - return providerRegistry; -} - -jni::local_ref -MainComponentsRegistry::initHybrid( - jni::alias_ref, - ComponentFactory *delegate) { - auto instance = makeCxxInstance(delegate); - - auto buildRegistryFunction = - [](EventDispatcher::Weak const &eventDispatcher, - ContextContainer::Shared const &contextContainer) - -> ComponentDescriptorRegistry::Shared { - auto registry = MainComponentsRegistry::sharedProviderRegistry() - ->createComponentDescriptorRegistry( - {eventDispatcher, contextContainer}); - - auto mutableRegistry = - std::const_pointer_cast(registry); - - mutableRegistry->setFallbackComponentDescriptor( - std::make_shared( - ComponentDescriptorParameters{ - eventDispatcher, contextContainer, nullptr})); - - return registry; - }; - - delegate->buildRegistryFunction = buildRegistryFunction; - return instance; -} - -void MainComponentsRegistry::registerNatives() { - registerHybrid({ - makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid), - }); -} - -} // namespace react -} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h b/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h deleted file mode 100644 index a7f222542..000000000 --- a/apps/mobile/android/app/src/main/jni/MainComponentsRegistry.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace react { - -class MainComponentsRegistry - : public facebook::jni::HybridClass { - public: - // Adapt it to the package you used for your Java class. - constexpr static auto kJavaDescriptor = - "Lcom/spacedrive/app/newarchitecture/components/MainComponentsRegistry;"; - - static void registerNatives(); - - MainComponentsRegistry(ComponentFactory *delegate); - - private: - static std::shared_ptr - sharedProviderRegistry(); - - static jni::local_ref initHybrid( - jni::alias_ref, - ComponentFactory *delegate); -}; - -} // namespace react -} // namespace facebook diff --git a/apps/mobile/android/app/src/main/jni/OnLoad.cpp b/apps/mobile/android/app/src/main/jni/OnLoad.cpp deleted file mode 100644 index c569b6e86..000000000 --- a/apps/mobile/android/app/src/main/jni/OnLoad.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "MainApplicationTurboModuleManagerDelegate.h" -#include "MainComponentsRegistry.h" - -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { - return facebook::jni::initialize(vm, [] { - facebook::react::MainApplicationTurboModuleManagerDelegate:: - registerNatives(); - facebook::react::MainComponentsRegistry::registerNatives(); - }); -} diff --git a/apps/mobile/android/app/src/release/java/com/spacedrive/app/ReactNativeFlipper.java b/apps/mobile/android/app/src/release/java/com/spacedrive/app/ReactNativeFlipper.java new file mode 100644 index 000000000..50dd8daac --- /dev/null +++ b/apps/mobile/android/app/src/release/java/com/spacedrive/app/ReactNativeFlipper.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.spacedrive.app; + +import android.content.Context; +import com.facebook.react.ReactInstanceManager; + +/** + * Class responsible of loading Flipper inside your React Native application. This is the release + * flavor of it so it's empty as we don't want to load Flipper. + */ +public class ReactNativeFlipper { + public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { + // Do nothing as we don't want to initialize Flipper on Release. + } +} \ No newline at end of file diff --git a/apps/mobile/android/build.gradle b/apps/mobile/android/build.gradle index dc4382afd..c05852a6e 100644 --- a/apps/mobile/android/build.gradle +++ b/apps/mobile/android/build.gradle @@ -4,38 +4,31 @@ import org.apache.tools.ant.taskdefs.condition.Os buildscript { ext { - buildToolsVersion = findProperty('android.buildToolsVersion') ?: '31.0.0' + buildToolsVersion = findProperty('android.buildToolsVersion') ?: '33.0.0' minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '21') - compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '31') - targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '31') - reactNativeVersion = "0.69.4" // https://github.com/expo/expo/issues/18129 + compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '33') + targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '33') if (findProperty('android.kotlinVersion')) { kotlinVersion = findProperty('android.kotlinVersion') } frescoVersion = findProperty('expo.frescoVersion') ?: '2.5.0' - if (System.properties['os.arch'] == 'aarch64') { - // For M1 Users we need to use the NDK 24 which added support for aarch64 - ndkVersion = '24.0.8215888' - } else { - // Otherwise we default to the side-by-side NDK version from AGP. - ndkVersion = '21.4.7075529' - } + // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. + ndkVersion = "23.1.7779620" } repositories { google() mavenCentral() + // Spacedrive -- Required for the Android Rust plugin. maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath('com.android.tools.build:gradle:7.1.1') + classpath('com.android.tools.build:gradle:7.4.1') classpath('com.facebook.react:react-native-gradle-plugin') - classpath('de.undercouch:gradle-download-task:5.0.1') + // Spacedrive -- Rust plugin. classpath('org.mozilla.rust-android-gradle:plugin:0.9.3') - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files } } @@ -52,13 +45,7 @@ allprojects { } google() - mavenCentral { - // We don't want to fetch react-native from Maven Central as there are - // older versions over there. - content { - excludeGroup 'com.facebook.react' - } - } + mavenCentral() maven { url 'https://www.jitpack.io' } } } diff --git a/apps/mobile/android/gradle.properties b/apps/mobile/android/gradle.properties index 9911ac4af..243667b99 100644 --- a/apps/mobile/android/gradle.properties +++ b/apps/mobile/android/gradle.properties @@ -50,4 +50,4 @@ expo.gif.enabled=true expo.webp.enabled=true # Enable animated webp support (~3.4 MB increase) # Disabled by default because iOS doesn't support animated webp -expo.webp.animated=false +expo.webp.animated=false \ No newline at end of file diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties index 669386b87..7b54e6906 100644 --- a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/apps/mobile/android/gradlew b/apps/mobile/android/gradlew index 1b6c78733..ab2855b44 100755 --- a/apps/mobile/android/gradlew +++ b/apps/mobile/android/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. @@ -231,4 +237,4 @@ eval "set -- $( tr '\n' ' ' )" '"$@"' -exec "$JAVACMD" "$@" +exec "$JAVACMD" "$@" \ No newline at end of file diff --git a/apps/mobile/android/gradlew.bat b/apps/mobile/android/gradlew.bat index ac1b06f93..a74609357 100644 --- a/apps/mobile/android/gradlew.bat +++ b/apps/mobile/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,15 +75,17 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal -:omega +:omega \ No newline at end of file diff --git a/apps/mobile/android/settings.gradle b/apps/mobile/android/settings.gradle index d52a28091..7505dd3b5 100644 --- a/apps/mobile/android/settings.gradle +++ b/apps/mobile/android/settings.gradle @@ -7,11 +7,4 @@ apply from: new File(["node", "--print", "require.resolve('@react-native-communi applyNativeModulesSettingsGradle(settings) include ':app' -includeBuild(new File(["node", "--print", "require.resolve('react-native-gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile()) - -if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") { - include(":ReactAndroid") - project(":ReactAndroid").projectDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../ReactAndroid"); - include(":ReactAndroid:hermes-engine") - project(":ReactAndroid:hermes-engine").projectDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../ReactAndroid/hermes-engine"); -} +includeBuild(new File(["node", "--print", "require.resolve('react-native-gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile()) \ No newline at end of file diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 02add5aa7..02f0b25d9 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -7,6 +7,7 @@ "orientation": "portrait", "jsEngine": "hermes", "scheme": "spacedrive", + "platforms": ["ios", "android"], "userInterfaceStyle": "automatic", "updates": { "enabled": false, diff --git a/apps/mobile/ios/Podfile b/apps/mobile/ios/Podfile index a1ba532e9..499f54880 100644 --- a/apps/mobile/ios/Podfile +++ b/apps/mobile/ios/Podfile @@ -5,34 +5,62 @@ require File.join(File.dirname(`node --print "require.resolve('@react-native-com require 'json' podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} +ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' + platform :ios, podfile_properties['ios.deploymentTarget'] || '13.0' install! 'cocoapods', :deterministic_uuids => false +prepare_react_native_project! + +# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. +# because `react-native-flipper` depends on (FlipperKit,...), which will be excluded. To fix this, +# you can also exclude `react-native-flipper` in `react-native.config.js` +# +# ```js +# module.exports = { +# dependencies: { +# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), +# } +# } +# ``` +flipper_config = FlipperConfiguration.disabled +if ENV['NO_FLIPPER'] == '1' then + # Explicitly disabled through environment variables + flipper_config = FlipperConfiguration.disabled +elsif podfile_properties.key?('ios.flipper') then + # Configure Flipper in Podfile.properties.json + if podfile_properties['ios.flipper'] == 'true' then + flipper_config = FlipperConfiguration.enabled(["Debug", "Release"]) + elsif podfile_properties['ios.flipper'] != 'false' then + flipper_config = FlipperConfiguration.enabled(["Debug", "Release"], { 'Flipper' => podfile_properties['ios.flipper'] }) + end +end + target 'Spacedrive' do use_expo_modules! config = use_native_modules! use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] + use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] # Flags change depending on the env values. flags = get_default_flags() use_react_native!( :path => config[:reactNativePath], - :hermes_enabled => podfile_properties['expo.jsEngine'] == 'hermes', + :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', :fabric_enabled => flags[:fabric_enabled], # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/..", - # - # Uncomment to opt-in to using Flipper - # Note that if you have use_frameworks! enabled, Flipper will not work - # :flipper_configuration => !ENV['CI'] ? FlipperConfiguration.enabled : FlipperConfiguration.disabled, + # Note that if you have use_frameworks! enabled, Flipper will not work if enabled + :flipper_configuration => flipper_config ) post_install do |installer| react_native_post_install( installer, + config[:reactNativePath], # Set `mac_catalyst_enabled` to `true` in order to apply patches # necessary for Mac Catalyst builds :mac_catalyst_enabled => false diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 184617f70..56e28f269 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -1,38 +1,41 @@ PODS: - boost (1.76.0) - DoubleConversion (1.1.6) - - EXApplication (5.0.1): + - EXApplication (5.1.1): - ExpoModulesCore - - EXConstants (14.0.2): + - EXConstants (14.2.1): - ExpoModulesCore - - EXFileSystem (15.1.1): + - EXFileSystem (15.2.2): - ExpoModulesCore - - EXFont (11.0.1): + - EXFont (11.1.1): - ExpoModulesCore - - EXMediaLibrary (15.0.0): + - EXMediaLibrary (15.2.2): - ExpoModulesCore - React-Core - - Expo (47.0.13): + - Expo (48.0.6): - ExpoModulesCore - - ExpoKeepAwake (11.0.1): + - ExpoKeepAwake (12.0.1): - ExpoModulesCore - - ExpoModulesCore (1.1.1): + - ExpoModulesCore (1.2.4): - React-Core + - React-RCTAppDelegate - ReactCommon/turbomodule/core - - EXSplashScreen (0.17.5): + - EXSplashScreen (0.18.1): - ExpoModulesCore - React-Core - - FBLazyVector (0.70.5) - - FBReactNativeSpec (0.70.5): + - FBLazyVector (0.71.3) + - FBReactNativeSpec (0.71.3): - RCT-Folly (= 2021.07.22.00) - - RCTRequired (= 0.70.5) - - RCTTypeSafety (= 0.70.5) - - React-Core (= 0.70.5) - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) + - RCTRequired (= 0.71.3) + - RCTTypeSafety (= 0.71.3) + - React-Core (= 0.71.3) + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) - fmt (6.2.1) - glog (0.3.5) - - hermes-engine (0.70.5) + - hermes-engine (0.71.3): + - hermes-engine/Pre-built (= 0.71.3) + - hermes-engine/Pre-built (0.71.3) - libevent (2.1.12) - lottie-ios (3.4.4) - lottie-react-native (5.1.4): @@ -55,214 +58,239 @@ PODS: - fmt (~> 6.2.1) - glog - libevent - - RCTRequired (0.70.5) - - RCTTypeSafety (0.70.5): - - FBLazyVector (= 0.70.5) - - RCTRequired (= 0.70.5) - - React-Core (= 0.70.5) - - React (0.70.5): - - React-Core (= 0.70.5) - - React-Core/DevSupport (= 0.70.5) - - React-Core/RCTWebSocket (= 0.70.5) - - React-RCTActionSheet (= 0.70.5) - - React-RCTAnimation (= 0.70.5) - - React-RCTBlob (= 0.70.5) - - React-RCTImage (= 0.70.5) - - React-RCTLinking (= 0.70.5) - - React-RCTNetwork (= 0.70.5) - - React-RCTSettings (= 0.70.5) - - React-RCTText (= 0.70.5) - - React-RCTVibration (= 0.70.5) - - React-bridging (0.70.5): - - RCT-Folly (= 2021.07.22.00) - - React-jsi (= 0.70.5) - - React-callinvoker (0.70.5) - - React-Codegen (0.70.5): - - FBReactNativeSpec (= 0.70.5) - - RCT-Folly (= 2021.07.22.00) - - RCTRequired (= 0.70.5) - - RCTTypeSafety (= 0.70.5) - - React-Core (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-Core (0.70.5): + - RCTRequired (0.71.3) + - RCTTypeSafety (0.71.3): + - FBLazyVector (= 0.71.3) + - RCTRequired (= 0.71.3) + - React-Core (= 0.71.3) + - React (0.71.3): + - React-Core (= 0.71.3) + - React-Core/DevSupport (= 0.71.3) + - React-Core/RCTWebSocket (= 0.71.3) + - React-RCTActionSheet (= 0.71.3) + - React-RCTAnimation (= 0.71.3) + - React-RCTBlob (= 0.71.3) + - React-RCTImage (= 0.71.3) + - React-RCTLinking (= 0.71.3) + - React-RCTNetwork (= 0.71.3) + - React-RCTSettings (= 0.71.3) + - React-RCTText (= 0.71.3) + - React-RCTVibration (= 0.71.3) + - React-callinvoker (0.71.3) + - React-Codegen (0.71.3): + - FBReactNativeSpec + - hermes-engine + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - React-jsi + - React-jsiexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - React-Core (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.70.5) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-Core/Default (= 0.71.3) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/CoreModulesHeaders (0.70.5): + - React-Core/CoreModulesHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/Default (0.70.5): + - React-Core/Default (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/DevSupport (0.70.5): + - React-Core/DevSupport (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.70.5) - - React-Core/RCTWebSocket (= 0.70.5) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-jsinspector (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-Core/Default (= 0.71.3) + - React-Core/RCTWebSocket (= 0.71.3) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-jsinspector (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTActionSheetHeaders (0.70.5): + - React-Core/RCTActionSheetHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTAnimationHeaders (0.70.5): + - React-Core/RCTAnimationHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTBlobHeaders (0.70.5): + - React-Core/RCTBlobHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTImageHeaders (0.70.5): + - React-Core/RCTImageHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTLinkingHeaders (0.70.5): + - React-Core/RCTLinkingHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTNetworkHeaders (0.70.5): + - React-Core/RCTNetworkHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTSettingsHeaders (0.70.5): + - React-Core/RCTSettingsHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTTextHeaders (0.70.5): + - React-Core/RCTTextHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTVibrationHeaders (0.70.5): + - React-Core/RCTVibrationHeaders (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-Core/RCTWebSocket (0.70.5): + - React-Core/RCTWebSocket (0.71.3): - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.70.5) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-Core/Default (= 0.71.3) + - React-cxxreact (= 0.71.3) + - React-hermes + - React-jsi (= 0.71.3) + - React-jsiexecutor (= 0.71.3) + - React-perflogger (= 0.71.3) - Yoga - - React-CoreModules (0.70.5): + - React-CoreModules (0.71.3): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.5) - - React-Codegen (= 0.70.5) - - React-Core/CoreModulesHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - React-RCTImage (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-cxxreact (0.70.5): + - RCTTypeSafety (= 0.71.3) + - React-Codegen (= 0.71.3) + - React-Core/CoreModulesHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - React-RCTBlob + - React-RCTImage (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-cxxreact (0.71.3): - boost (= 1.76.0) - DoubleConversion - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsinspector (= 0.70.5) - - React-logger (= 0.70.5) - - React-perflogger (= 0.70.5) - - React-runtimeexecutor (= 0.70.5) - - React-hermes (0.70.5): + - React-callinvoker (= 0.71.3) + - React-jsi (= 0.71.3) + - React-jsinspector (= 0.71.3) + - React-logger (= 0.71.3) + - React-perflogger (= 0.71.3) + - React-runtimeexecutor (= 0.71.3) + - React-hermes (0.71.3): - DoubleConversion - glog - hermes-engine - RCT-Folly (= 2021.07.22.00) - RCT-Folly/Futures (= 2021.07.22.00) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-jsiexecutor (= 0.70.5) - - React-jsinspector (= 0.70.5) - - React-perflogger (= 0.70.5) - - React-jsi (0.70.5): + - React-cxxreact (= 0.71.3) + - React-jsi + - React-jsiexecutor (= 0.71.3) + - React-jsinspector (= 0.71.3) + - React-perflogger (= 0.71.3) + - React-jsi (0.71.3): - boost (= 1.76.0) - DoubleConversion - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-jsi/Default (= 0.70.5) - - React-jsi/Default (0.70.5): - - boost (= 1.76.0) + - React-jsiexecutor (0.71.3): - DoubleConversion - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-jsiexecutor (0.70.5): - - DoubleConversion - - glog - - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-perflogger (= 0.70.5) - - React-jsinspector (0.70.5) - - React-logger (0.70.5): + - React-cxxreact (= 0.71.3) + - React-jsi (= 0.71.3) + - React-perflogger (= 0.71.3) + - React-jsinspector (0.71.3) + - React-logger (0.71.3): - glog - react-native-document-picker (8.1.3): - React-Core @@ -272,77 +300,95 @@ PODS: - RCTTypeSafety - React-Core - ReactCommon/turbomodule/core - - React-perflogger (0.70.5) - - React-RCTActionSheet (0.70.5): - - React-Core/RCTActionSheetHeaders (= 0.70.5) - - React-RCTAnimation (0.70.5): + - React-perflogger (0.71.3) + - React-RCTActionSheet (0.71.3): + - React-Core/RCTActionSheetHeaders (= 0.71.3) + - React-RCTAnimation (0.71.3): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.5) - - React-Codegen (= 0.70.5) - - React-Core/RCTAnimationHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-RCTBlob (0.70.5): + - RCTTypeSafety (= 0.71.3) + - React-Codegen (= 0.71.3) + - React-Core/RCTAnimationHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-RCTAppDelegate (0.71.3): + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - ReactCommon/turbomodule/core + - React-RCTBlob (0.71.3): + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.70.5) - - React-Core/RCTBlobHeaders (= 0.70.5) - - React-Core/RCTWebSocket (= 0.70.5) - - React-jsi (= 0.70.5) - - React-RCTNetwork (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-RCTImage (0.70.5): + - React-Codegen (= 0.71.3) + - React-Core/RCTBlobHeaders (= 0.71.3) + - React-Core/RCTWebSocket (= 0.71.3) + - React-jsi (= 0.71.3) + - React-RCTNetwork (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-RCTImage (0.71.3): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.5) - - React-Codegen (= 0.70.5) - - React-Core/RCTImageHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - React-RCTNetwork (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-RCTLinking (0.70.5): - - React-Codegen (= 0.70.5) - - React-Core/RCTLinkingHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-RCTNetwork (0.70.5): + - RCTTypeSafety (= 0.71.3) + - React-Codegen (= 0.71.3) + - React-Core/RCTImageHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - React-RCTNetwork (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-RCTLinking (0.71.3): + - React-Codegen (= 0.71.3) + - React-Core/RCTLinkingHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-RCTNetwork (0.71.3): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.5) - - React-Codegen (= 0.70.5) - - React-Core/RCTNetworkHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-RCTSettings (0.70.5): + - RCTTypeSafety (= 0.71.3) + - React-Codegen (= 0.71.3) + - React-Core/RCTNetworkHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-RCTSettings (0.71.3): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.5) - - React-Codegen (= 0.70.5) - - React-Core/RCTSettingsHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-RCTText (0.70.5): - - React-Core/RCTTextHeaders (= 0.70.5) - - React-RCTVibration (0.70.5): + - RCTTypeSafety (= 0.71.3) + - React-Codegen (= 0.71.3) + - React-Core/RCTSettingsHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-RCTText (0.71.3): + - React-Core/RCTTextHeaders (= 0.71.3) + - React-RCTVibration (0.71.3): - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.70.5) - - React-Core/RCTVibrationHeaders (= 0.70.5) - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (= 0.70.5) - - React-runtimeexecutor (0.70.5): - - React-jsi (= 0.70.5) - - ReactCommon/turbomodule/core (0.70.5): + - React-Codegen (= 0.71.3) + - React-Core/RCTVibrationHeaders (= 0.71.3) + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/core (= 0.71.3) + - React-runtimeexecutor (0.71.3): + - React-jsi (= 0.71.3) + - ReactCommon/turbomodule/bridging (0.71.3): - DoubleConversion - glog + - hermes-engine - RCT-Folly (= 2021.07.22.00) - - React-bridging (= 0.70.5) - - React-callinvoker (= 0.70.5) - - React-Core (= 0.70.5) - - React-cxxreact (= 0.70.5) - - React-jsi (= 0.70.5) - - React-logger (= 0.70.5) - - React-perflogger (= 0.70.5) + - React-callinvoker (= 0.71.3) + - React-Core (= 0.71.3) + - React-cxxreact (= 0.71.3) + - React-jsi (= 0.71.3) + - React-logger (= 0.71.3) + - React-perflogger (= 0.71.3) + - ReactCommon/turbomodule/core (0.71.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2021.07.22.00) + - React-callinvoker (= 0.71.3) + - React-Core (= 0.71.3) + - React-cxxreact (= 0.71.3) + - React-jsi (= 0.71.3) + - React-logger (= 0.71.3) + - React-perflogger (= 0.71.3) - RNCAsyncStorage (1.17.11): - React-Core - RNCMaskedView (0.2.8): - React-Core - - RNFlashList (1.4.1): + - RNFlashList (1.4.0): - React-Core - RNFS (2.20.0): - React-Core @@ -375,16 +421,16 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.19.0): + - RNScreens (3.20.0): - React-Core - React-RCTImage - - RNSVG (13.8.0): + - RNSVG (13.4.0): - React-Core - Yoga (1.14.0) DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - EXApplication (from `../../../node_modules/expo-application/ios`) - EXConstants (from `../../../node_modules/expo-constants/ios`) - EXFileSystem (from `../../../node_modules/expo-file-system/ios`) @@ -394,51 +440,51 @@ DEPENDENCIES: - ExpoKeepAwake (from `../../../node_modules/expo-keep-awake/ios`) - ExpoModulesCore (from `../../../node_modules/expo-modules-core`) - EXSplashScreen (from `../../../node_modules/expo-splash-screen/ios`) - - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - - hermes-engine (from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`) + - FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`) + - FBReactNativeSpec (from `../../../node_modules/react-native/React/FBReactNativeSpec`) + - glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libevent (~> 2.1.12) - - lottie-react-native (from `../node_modules/lottie-react-native`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../node_modules/react-native/`) - - React-bridging (from `../node_modules/react-native/ReactCommon`) - - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - lottie-react-native (from `../../../node_modules/lottie-react-native`) + - RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTRequired (from `../../../node_modules/react-native/Libraries/RCTRequired`) + - RCTTypeSafety (from `../../../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../../../node_modules/react-native/`) + - React-callinvoker (from `../../../node_modules/react-native/ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) - - React-Core (from `../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - - react-native-document-picker (from `../node_modules/react-native-document-picker`) - - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)" - - "RNFlashList (from `../node_modules/@shopify/flash-list`)" - - RNFS (from `../node_modules/react-native-fs`) - - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - - RNReanimated (from `../node_modules/react-native-reanimated`) - - RNScreens (from `../node_modules/react-native-screens`) - - RNSVG (from `../node_modules/react-native-svg`) - - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + - React-Core (from `../../../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../../../node_modules/react-native/`) + - React-CoreModules (from `../../../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../../../node_modules/react-native/ReactCommon/cxxreact`) + - React-hermes (from `../../../node_modules/react-native/ReactCommon/hermes`) + - React-jsi (from `../../../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../../../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../../../node_modules/react-native/ReactCommon/jsinspector`) + - React-logger (from `../../../node_modules/react-native/ReactCommon/logger`) + - react-native-document-picker (from `../../../node_modules/react-native-document-picker`) + - react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`) + - React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`) + - React-RCTActionSheet (from `../../../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../../../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../../../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../../../node_modules/react-native/Libraries/Blob`) + - React-RCTImage (from `../../../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../../../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../../../node_modules/react-native/Libraries/Network`) + - React-RCTSettings (from `../../../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../../../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../../../node_modules/react-native/Libraries/Vibration`) + - React-runtimeexecutor (from `../../../node_modules/react-native/ReactCommon/runtimeexecutor`) + - ReactCommon/turbomodule/core (from `../../../node_modules/react-native/ReactCommon`) + - "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)" + - "RNCMaskedView (from `../../../node_modules/@react-native-masked-view/masked-view`)" + - "RNFlashList (from `../../../node_modules/@shopify/flash-list`)" + - RNFS (from `../../../node_modules/react-native-fs`) + - RNGestureHandler (from `../../../node_modules/react-native-gesture-handler`) + - RNReanimated (from `../../../node_modules/react-native-reanimated`) + - RNScreens (from `../../../node_modules/react-native-screens`) + - RNSVG (from `../../../node_modules/react-native-svg`) + - Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: @@ -448,9 +494,9 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + :podspec: "../../../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + :podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXApplication: :path: "../../../node_modules/expo-application/ios" EXConstants: @@ -470,151 +516,151 @@ EXTERNAL SOURCES: EXSplashScreen: :path: "../../../node_modules/expo-splash-screen/ios" FBLazyVector: - :path: "../node_modules/react-native/Libraries/FBLazyVector" + :path: "../../../node_modules/react-native/Libraries/FBLazyVector" FBReactNativeSpec: - :path: "../node_modules/react-native/React/FBReactNativeSpec" + :path: "../../../node_modules/react-native/React/FBReactNativeSpec" glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + :podspec: "../../../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: - :podspec: "../node_modules/react-native/sdks/hermes/hermes-engine.podspec" + :podspec: "../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" lottie-react-native: - :path: "../node_modules/lottie-react-native" + :path: "../../../node_modules/lottie-react-native" RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :podspec: "../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTRequired: - :path: "../node_modules/react-native/Libraries/RCTRequired" + :path: "../../../node_modules/react-native/Libraries/RCTRequired" RCTTypeSafety: - :path: "../node_modules/react-native/Libraries/TypeSafety" + :path: "../../../node_modules/react-native/Libraries/TypeSafety" React: - :path: "../node_modules/react-native/" - React-bridging: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../node_modules/react-native/" React-callinvoker: - :path: "../node_modules/react-native/ReactCommon/callinvoker" + :path: "../../../node_modules/react-native/ReactCommon/callinvoker" React-Codegen: :path: build/generated/ios React-Core: - :path: "../node_modules/react-native/" + :path: "../../../node_modules/react-native/" React-CoreModules: - :path: "../node_modules/react-native/React/CoreModules" + :path: "../../../node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../node_modules/react-native/ReactCommon/cxxreact" + :path: "../../../node_modules/react-native/ReactCommon/cxxreact" React-hermes: - :path: "../node_modules/react-native/ReactCommon/hermes" + :path: "../../../node_modules/react-native/ReactCommon/hermes" React-jsi: - :path: "../node_modules/react-native/ReactCommon/jsi" + :path: "../../../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../../../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector" + :path: "../../../node_modules/react-native/ReactCommon/jsinspector" React-logger: - :path: "../node_modules/react-native/ReactCommon/logger" + :path: "../../../node_modules/react-native/ReactCommon/logger" react-native-document-picker: - :path: "../node_modules/react-native-document-picker" + :path: "../../../node_modules/react-native-document-picker" react-native-safe-area-context: - :path: "../node_modules/react-native-safe-area-context" + :path: "../../../node_modules/react-native-safe-area-context" React-perflogger: - :path: "../node_modules/react-native/ReactCommon/reactperflogger" + :path: "../../../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: - :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../../../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../node_modules/react-native/Libraries/NativeAnimation" + :path: "../../../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../../../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../node_modules/react-native/Libraries/Blob" + :path: "../../../node_modules/react-native/Libraries/Blob" React-RCTImage: - :path: "../node_modules/react-native/Libraries/Image" + :path: "../../../node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../node_modules/react-native/Libraries/LinkingIOS" + :path: "../../../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../node_modules/react-native/Libraries/Network" + :path: "../../../node_modules/react-native/Libraries/Network" React-RCTSettings: - :path: "../node_modules/react-native/Libraries/Settings" + :path: "../../../node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../node_modules/react-native/Libraries/Text" + :path: "../../../node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../node_modules/react-native/Libraries/Vibration" + :path: "../../../node_modules/react-native/Libraries/Vibration" React-runtimeexecutor: - :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../../../node_modules/react-native/ReactCommon/runtimeexecutor" ReactCommon: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../node_modules/react-native/ReactCommon" RNCAsyncStorage: - :path: "../node_modules/@react-native-async-storage/async-storage" + :path: "../../../node_modules/@react-native-async-storage/async-storage" RNCMaskedView: - :path: "../node_modules/@react-native-masked-view/masked-view" + :path: "../../../node_modules/@react-native-masked-view/masked-view" RNFlashList: - :path: "../node_modules/@shopify/flash-list" + :path: "../../../node_modules/@shopify/flash-list" RNFS: - :path: "../node_modules/react-native-fs" + :path: "../../../node_modules/react-native-fs" RNGestureHandler: - :path: "../node_modules/react-native-gesture-handler" + :path: "../../../node_modules/react-native-gesture-handler" RNReanimated: - :path: "../node_modules/react-native-reanimated" + :path: "../../../node_modules/react-native-reanimated" RNScreens: - :path: "../node_modules/react-native-screens" + :path: "../../../node_modules/react-native-screens" RNSVG: - :path: "../node_modules/react-native-svg" + :path: "../../../node_modules/react-native-svg" Yoga: - :path: "../node_modules/react-native/ReactCommon/yoga" + :path: "../../../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: a7c83b31436843459a1961bfd74b96033dc77234 + boost: 57d2868c099736d80fcd648bf211b4431e51a558 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 - EXApplication: 034b1c40a8e9fe1bff76a1e511ee90dff64ad834 - EXConstants: 3c86653c422dd77e40d10cbbabb3025003977415 - EXFileSystem: 60602b6eefa6873f97172c684b7537c9760b50d6 - EXFont: 319606bfe48c33b5b5063fb0994afdc496befe80 - EXMediaLibrary: b1c4f78878e45f6a359aff3a059e1660c41b73ab - Expo: b9fa98bf260992312ee3c424400819fb9beadafe - ExpoKeepAwake: 69b59d0a8d2b24de9f82759c39b3821fec030318 - ExpoModulesCore: 65ae09e2b2d3dd8ece30a5acc83c569968125ab0 - EXSplashScreen: 3e989924f61a8dd07ee4ea584c6ba14be9b51949 - FBLazyVector: affa4ba1bfdaac110a789192f4d452b053a86624 - FBReactNativeSpec: fe8b5f1429cfe83a8d72dc8ed61dc7704cac8745 + EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 + EXConstants: f348da07e21b23d2b085e270d7b74f282df1a7d9 + EXFileSystem: 844e86ca9b5375486ecc4ef06d3838d5597d895d + EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272 + EXMediaLibrary: 792fe9b828b5bfa2c5a8b629730f175af2938285 + Expo: 04ba1ddde0be07aff4306ae636a1804810679145 + ExpoKeepAwake: 69f5f627670d62318410392d03e0b5db0f85759a + ExpoModulesCore: 1667335d4f4c9b7801990930e6f0eea42c916a21 + EXSplashScreen: cd7fb052dff5ba8311d5c2455ecbebffe1b7a8ca + FBLazyVector: 60195509584153283780abdac5569feffb8f08cc + FBReactNativeSpec: c5a5c4f1b95ae42a17cd22c8c89c482a7b327fe3 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - hermes-engine: 7fe5fc6ef707b7fdcb161b63898ec500e285653d + hermes-engine: 38bfe887e456b33b697187570a08de33969f5db7 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 lottie-ios: 8f97d3271e155c2d688875c29cd3c74908aef5f8 lottie-react-native: b702fab740cdb952a8e2354713d3beda63ff97b0 - RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda - RCTRequired: 21229f84411088e5d8538f21212de49e46cc83e2 - RCTTypeSafety: 62eed57a32924b09edaaf170a548d1fc96223086 - React: f0254ccddeeef1defe66c6b1bb9133a4f040792b - React-bridging: e46911666b7ec19538a620a221d6396cd293d687 - React-callinvoker: 66b62e2c34546546b2f21ab0b7670346410a2b53 - React-Codegen: b6999435966df3bdf82afa3f319ba0d6f9a8532a - React-Core: dabbc9d1fe0a11d884e6ee1599789cf8eb1058a5 - React-CoreModules: 5b6b7668f156f73a56420df9ec68ca2ec8f2e818 - React-cxxreact: c7ca2baee46db22a30fce9e639277add3c3f6ad1 - React-hermes: c93e1d759ad5560dfea54d233013d7d2c725c286 - React-jsi: a565dcb49130ed20877a9bb1105ffeecbb93d02d - React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b - React-jsinspector: badd81696361249893a80477983e697aab3c1a34 - React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c + RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCTRequired: bec48f07daf7bcdc2655a0cde84e07d24d2a9e2a + RCTTypeSafety: 171394eebacf71e1cfad79dbfae7ee8fc16ca80a + React: d7433ccb6a8c36e4cbed59a73c0700fc83c3e98a + React-callinvoker: 15f165009bd22ae829b2b600e50bcc98076ce4b8 + React-Codegen: b5910000eaf1e0c2f47d29be6f82f5f1264420d7 + React-Core: b6f2f78d580a90b83fd7b0d1c6911c799f6eac82 + React-CoreModules: e0cbc1a4f4f3f60e23c476fef7ab37be363ea8c1 + React-cxxreact: c87f3f124b2117d00d410b35f16c2257e25e50fa + React-hermes: c64ca6bdf16a7069773103c9bedaf30ec90ab38f + React-jsi: 39729361645568e238081b3b3180fbad803f25a4 + React-jsiexecutor: 515b703d23ffadeac7687bc2d12fb08b90f0aaa1 + React-jsinspector: 9f7c9137605e72ca0343db4cea88006cb94856dd + React-logger: 957e5dc96d9dbffc6e0f15e0ee4d2b42829ff207 react-native-document-picker: 958e2bc82e128be69055be261aeac8d872c8d34c react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc - React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 - React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 - React-RCTAnimation: 578eebac706428e68466118e84aeacf3a282b4da - React-RCTBlob: f47a0aa61e7d1fb1a0e13da832b0da934939d71a - React-RCTImage: 60f54b66eed65d86b6dffaf4733d09161d44929d - React-RCTLinking: 91073205aeec4b29450ca79b709277319368ac9e - React-RCTNetwork: ca91f2c9465a7e335c8a5fae731fd7f10572213b - React-RCTSettings: 1a9a5d01337d55c18168c1abe0f4a589167d134a - React-RCTText: c591e8bd9347a294d8416357ca12d779afec01d5 - React-RCTVibration: 8e5c8c5d17af641f306d7380d8d0fe9b3c142c48 - React-runtimeexecutor: 7401c4a40f8728fd89df4a56104541b760876117 - ReactCommon: c9246996e73bf75a2c6c3ff15f1e16707cdc2da9 + React-perflogger: af8a3d31546077f42d729b949925cc4549f14def + React-RCTActionSheet: 57cc5adfefbaaf0aae2cf7e10bccd746f2903673 + React-RCTAnimation: 11c61e94da700c4dc915cf134513764d87fc5e2b + React-RCTAppDelegate: c3980adeaadcfd6cb495532e928b36ac6db3c14a + React-RCTBlob: ccc5049d742b41971141415ca86b83b201495695 + React-RCTImage: 7a9226b0944f1e76e8e01e35a9245c2477cdbabb + React-RCTLinking: bbe8cc582046a9c04f79c235b73c93700263e8b4 + React-RCTNetwork: fc2ca322159dc54e06508d4f5c3e934da63dc013 + React-RCTSettings: f1e9db2cdf946426d3f2b210e4ff4ce0f0d842ef + React-RCTText: 1c41dd57e5d742b1396b4eeb251851ce7ff0fca1 + React-RCTVibration: 5199a180d04873366a83855de55ac33ce60fe4d5 + React-runtimeexecutor: 7bf0dafc7b727d93c8cb94eb00a9d3753c446c3e + ReactCommon: 6f65ea5b7d84deb9e386f670dd11ce499ded7b40 RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 RNCMaskedView: bc0170f389056201c82a55e242e5d90070e18e5a - RNFlashList: 8ec7f7454721145fe84566bb9e88bcf58981c9fe + RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 - RNReanimated: 6668b0587bebd4b15dd849b99e5a9c70fc12ed95 - RNScreens: ea4cd3a853063cda19a4e3c28d2e52180c80f4eb - RNSVG: c1e76b81c76cdcd34b4e1188852892dc280eb902 - Yoga: eca980a5771bf114c41a754098cd85e6e0d90ed7 + RNReanimated: addc4900bf47882118d0a1b21747fa6705fa8cff + RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f + RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2 + Yoga: 5ed1699acbba8863755998a4245daa200ff3817b -PODFILE CHECKSUM: 4065c8b949a453403939de6e852185dbd374d972 +PODFILE CHECKSUM: 17065850599b1e955efad7a619cf825c012744d7 COCOAPODS: 1.11.3 diff --git a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj index cfcd470b9..8d11b30da 100644 --- a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj @@ -250,7 +250,7 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Spacedrive/Pods-Spacedrive-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -589,7 +589,7 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; SDKROOT = iphoneos; }; name = Debug; @@ -642,7 +642,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/react-native"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; diff --git a/apps/mobile/ios/Spacedrive/AppDelegate.h b/apps/mobile/ios/Spacedrive/AppDelegate.h index f7d297204..eaba2fa40 100644 --- a/apps/mobile/ios/Spacedrive/AppDelegate.h +++ b/apps/mobile/ios/Spacedrive/AppDelegate.h @@ -1,9 +1,7 @@ -#import -#import +#import #import - #import -@interface AppDelegate : EXAppDelegateWrapper +@interface AppDelegate : EXAppDelegateWrapper -@end +@end \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/AppDelegate.mm b/apps/mobile/ios/Spacedrive/AppDelegate.mm index a6e13e11a..a327ae802 100644 --- a/apps/mobile/ios/Spacedrive/AppDelegate.mm +++ b/apps/mobile/ios/Spacedrive/AppDelegate.mm @@ -1,89 +1,19 @@ #import "AppDelegate.h" -#import #import -#import #import -#import - -#import - -#if RCT_NEW_ARCH_ENABLED -#import -#import -#import -#import -#import -#import - -#import - -static NSString *const kRNConcurrentRoot = @"concurrentRoot"; - -@interface AppDelegate () { - RCTTurboModuleManager *_turboModuleManager; - RCTSurfacePresenterBridgeAdapter *_bridgeAdapter; - std::shared_ptr _reactNativeConfig; - facebook::react::ContextContainer::Shared _contextContainer; -} -@end -#endif @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - RCTAppSetupPrepareApp(application); + self.moduleName = @"main"; - RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions]; + // You can add your custom initial props in the dictionary below. + // They will be passed down to the ViewController used by React Native. + self.initialProps = @{}; -#if RCT_NEW_ARCH_ENABLED - _contextContainer = std::make_shared(); - _reactNativeConfig = std::make_shared(); - _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); - _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer]; - bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; -#endif - - NSDictionary *initProps = [self prepareInitialProps]; - UIView *rootView = [self.reactDelegate createRootViewWithBridge:bridge moduleName:@"main" initialProperties:initProps]; - - rootView.backgroundColor = [UIColor whiteColor]; - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [self.reactDelegate createRootViewController]; - rootViewController.view = rootView; - self.window.rootViewController = rootViewController; - [self.window makeKeyAndVisible]; - - [super application:application didFinishLaunchingWithOptions:launchOptions]; - - return YES; -} - -- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge -{ - // If you'd like to export some custom RCTBridgeModules, add them here! - return @[]; -} - -/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. -/// -/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html -/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). -/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`. -- (BOOL)concurrentRootEnabled -{ - // Switch this bool to turn on and off the concurrent root - return true; -} - -- (NSDictionary *)prepareInitialProps -{ - NSMutableDictionary *initProps = [NSMutableDictionary new]; -#if RCT_NEW_ARCH_ENABLED - initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]); -#endif - return initProps; + return [super application:application didFinishLaunchingWithOptions:launchOptions]; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge @@ -95,6 +25,16 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; #endif } +/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off. +/// +/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html +/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture). +/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`. +- (BOOL)concurrentRootEnabled +{ + return true; +} + // Linking API - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; @@ -124,43 +64,4 @@ static NSString *const kRNConcurrentRoot = @"concurrentRoot"; return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } -#if RCT_NEW_ARCH_ENABLED - -#pragma mark - RCTCxxBridgeDelegate - -- (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge -{ - _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge - delegate:self - jsInvoker:bridge.jsCallInvoker]; - return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager); -} - -#pragma mark RCTTurboModuleManagerDelegate - -- (Class)getModuleClassFromName:(const char *)name -{ - return RCTCoreModulesClassProvider(name); -} - -- (std::shared_ptr)getTurboModule:(const std::string &)name - jsInvoker:(std::shared_ptr)jsInvoker -{ - return nullptr; -} - -- (std::shared_ptr)getTurboModule:(const std::string &)name - initParams: - (const facebook::react::ObjCTurboModule::InitParams &)params -{ - return nullptr; -} - -- (id)getModuleInstanceFromClass:(Class)moduleClass -{ - return RCTAppSetupDefaultModuleFromClass(moduleClass); -} - -#endif - -@end +@end \ No newline at end of file diff --git a/apps/mobile/ios/Spacedrive/Supporting/Expo.plist b/apps/mobile/ios/Spacedrive/Supporting/Expo.plist index 883eed2cd..e5af010dd 100644 --- a/apps/mobile/ios/Spacedrive/Supporting/Expo.plist +++ b/apps/mobile/ios/Spacedrive/Supporting/Expo.plist @@ -9,7 +9,7 @@ EXUpdatesLaunchWaitMs 0 EXUpdatesSDKVersion - 46.0.0 + 48.0.0 EXUpdatesURL https://exp.host/@utkudev/spacedrive diff --git a/apps/mobile/ios/Spacedrive/main.m b/apps/mobile/ios/Spacedrive/main.m index 25181b6cc..44e059c6d 100644 --- a/apps/mobile/ios/Spacedrive/main.m +++ b/apps/mobile/ios/Spacedrive/main.m @@ -6,5 +6,4 @@ int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } -} - +} \ No newline at end of file diff --git a/apps/mobile/ios/build-rust.sh b/apps/mobile/ios/build-rust.sh index 31021c885..be81cd494 100755 --- a/apps/mobile/ios/build-rust.sh +++ b/apps/mobile/ios/build-rust.sh @@ -2,6 +2,8 @@ set -e +export PROTOC=/opt/homebrew/bin/protoc + TARGET_DIRECTORY=../../../target CARGO_FLAGS= diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index 7320fd4fb..31ff9af2b 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -12,6 +12,7 @@ const projectRoot = __dirname; const workspaceRoot = path.resolve(projectRoot, '../..'); const metroConfig = makeMetroConfig({ + ...expoDefaultConfig, projectRoot, watchFolders: [workspaceRoot], resolver: { @@ -29,6 +30,7 @@ const metroConfig = makeMetroConfig({ ] }, transformer: { + ...expoDefaultConfig.transformer, // Metro default is "uglify-es" but terser should be faster and has better defaults. minifierPath: 'metro-minify-terser', minifierConfig: { diff --git a/apps/mobile/package.json b/apps/mobile/package.json index db612c3fa..150ee03b3 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -11,62 +11,62 @@ "xcode": "open ios/spacedrive.xcworkspace", "android-studio": "open -a '/Applications/Android Studio.app' ./android", "lint": "eslint src", - "postinstall": "node scripts/postinstall.js", "typecheck": "tsc -b", "eas-build-pre-install": "npm i -g pnpm@7.18.2" }, "dependencies": { "@gorhom/bottom-sheet": "^4.4.5", + "@hookform/resolvers": "^2.9.11", "@react-native-async-storage/async-storage": "~1.17.11", "@react-native-masked-view/masked-view": "0.2.8", - "@react-navigation/bottom-tabs": "^6.5.4", - "@react-navigation/drawer": "^6.5.8", - "@react-navigation/native": "^6.1.3", - "@react-navigation/stack": "^6.3.12", + "@react-navigation/bottom-tabs": "^6.5.7", + "@react-navigation/drawer": "^6.6.2", + "@react-navigation/native": "^6.1.6", + "@react-navigation/stack": "^6.3.16", "@rspc/client": "^0.0.0-main-7c0a67c1", "@rspc/react": "^0.0.0-main-7c0a67c1", "@sd/assets": "workspace:*", "@sd/client": "workspace:*", - "@shopify/flash-list": "1.4.1", - "@tanstack/react-query": "^4.24.4", + "@shopify/flash-list": "1.4.0", + "@tanstack/react-query": "^4.26.1", "byte-size": "^8.1.0", "class-variance-authority": "^0.4.0", "dayjs": "^1.11.5", - "expo": "^47.0.13", - "expo-linking": "~3.3.0", - "expo-media-library": "~15.0.0", - "expo-splash-screen": "~0.17.5", - "expo-status-bar": "~1.4.2", + "expo": "^48.0.6", + "expo-linking": "~4.0.1", + "expo-media-library": "~15.2.2", + "expo-splash-screen": "~0.18.1", + "expo-status-bar": "~1.4.4", "intl": "^1.2.5", "lottie-react-native": "5.1.4", - "moti": "^0.22.0", + "moti": "^0.24.2", "phosphor-react-native": "^1.1.2", - "react": "18.1.0", - "react-hook-form": "^7.43.0", - "react-native": "0.70.5", + "react": "18.2.0", + "react-hook-form": "^7.43.5", + "react-native": "0.71.3", "react-native-document-picker": "^8.1.1", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "~2.9.0", "react-native-popup-menu": "^0.16.1", "react-native-reanimated": "~2.14.4", "react-native-safe-area-context": "4.5.0", - "react-native-screens": "~3.19.0", - "react-native-svg": "13.8.0", + "react-native-screens": "~3.20.0", + "react-native-svg": "13.4.0", "react-native-wheel-color-picker": "^1.2.0", - "twrnc": "^3.5.0", + "twrnc": "^3.6.0", "use-count-up": "^3.0.1", "use-debounce": "^9.0.2", - "valtio": "^1.8.0" + "valtio": "^1.10.3", + "zod": "^3.21.4" }, "devDependencies": { "@rnx-kit/metro-config": "^1.3.5", "@sd/config": "workspace:*", - "@types/react": "~18.0.26", - "@types/react-native": "~0.70.8", + "@types/react": "~18.0.27", "babel-plugin-module-resolver": "^5.0.0", "eslint-plugin-react-native": "^4.0.0", - "metro-minify-terser": "^0.74.1", + "metro-minify-terser": "0.76.0", "react-native-svg-transformer": "^1.0.0", - "typescript": "^4.9.4" + "typescript": "^4.9.5" } } diff --git a/apps/mobile/scripts/postinstall.js b/apps/mobile/scripts/postinstall.js index c79431e6f..cca9a10cc 100644 --- a/apps/mobile/scripts/postinstall.js +++ b/apps/mobile/scripts/postinstall.js @@ -2,6 +2,7 @@ let fs = require('fs-extra'); let path = require('path'); +// Not used atm, keeping it here in case we need it in the future async function copyReactNativeCodegen() { const paths = [ ['../../../node_modules/react-native-codegen', '../node_modules/react-native-codegen'], diff --git a/apps/mobile/src/components/animation/layout.tsx b/apps/mobile/src/components/animation/layout.tsx index 03ad295eb..011739d6b 100644 --- a/apps/mobile/src/components/animation/layout.tsx +++ b/apps/mobile/src/components/animation/layout.tsx @@ -1,32 +1,49 @@ import { MotiView, useDynamicAnimation } from 'moti'; import { PropsWithChildren, ReactNode } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, View, ViewProps } from 'react-native'; import { useDerivedValue, useSharedValue } from 'react-native-reanimated'; import Layout from '~/constants/Layout'; import { tw } from '~/lib/tailwind'; -// Anything wrapped with FadeIn will fade in on mount. -export const FadeInAnimation = ({ children, delay }: PropsWithChildren<{ delay?: number }>) => ( - - {children} - -); +type MotiViewProps = PropsWithChildren; -export const FadeInUpAnimation = ({ children, delay }: PropsWithChildren<{ delay?: number }>) => ( +// Anything wrapped with FadeIn will fade in on mount. +export const FadeInAnimation = ({ + children, + delay, + ...props +}: MotiViewProps & { delay?: number }) => ( {children} ); -export const LogoAnimation = ({ children }: PropsWithChildren) => ( +export const FadeInUpAnimation = ({ + children, + delay, + ...props +}: MotiViewProps & { delay?: number }) => ( + {children} + +); + +export const LogoAnimation = ({ children, ...props }: MotiViewProps) => ( + {children} diff --git a/apps/mobile/src/components/animation/lottie.tsx b/apps/mobile/src/components/animation/lottie.tsx index a599cf322..e2e7ffb7c 100644 --- a/apps/mobile/src/components/animation/lottie.tsx +++ b/apps/mobile/src/components/animation/lottie.tsx @@ -1,19 +1,14 @@ -import AnimatedLottieView from 'lottie-react-native'; -import { StyleProp, View, ViewStyle } from 'react-native'; +import AnimatedLottieView, { AnimatedLottieViewProps } from 'lottie-react-native'; -type Props = { - style?: StyleProp; -}; +type AnimationProps = Omit; -export const PulseAnimation = ({ style }: Props) => { +export const PulseAnimation = ({ style }: AnimationProps) => { return ( - - - + ); }; diff --git a/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx b/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx index 66bea4093..94df55e5d 100644 --- a/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx +++ b/apps/mobile/src/components/drawer/DrawerLibraryManager.tsx @@ -9,7 +9,7 @@ import { tw, twStyle } from '~/lib/tailwind'; import { currentLibraryStore } from '~/utils/nav'; import { AnimatedHeight } from '../animation/layout'; import CreateLibraryDialog from '../dialog/CreateLibraryDialog'; -import Divider from '../primitive/Divider'; +import { Divider } from '../primitive/Divider'; const DrawerLibraryManager = () => { const [dropdownClosed, setDropdownClosed] = useState(true); @@ -51,7 +51,7 @@ const DrawerLibraryManager = () => { {/* Libraries */} {libraries.data?.map((library) => { - console.log('library', library); + // console.log('library', library); return ( (currentLibraryStore.id = library.uuid)}> { const { item } = data; - // temp fix (will handle this on mobile-inspector branch) - const objectData = data ? (isObject(data) ? data.item : data.item.object) : null; - const isVid = ObjectKind[objectData?.kind || 0] === 'Video'; - const gridItemSize = Layout.window.width / getExplorerStore().gridNumColumns; return ( @@ -25,15 +21,7 @@ const FileItem = ({ data }: FileItemProps) => { height: gridItemSize })} > - - {item.extension && isVid && ( - - {item.extension} - - )} + {item?.name} diff --git a/apps/mobile/src/components/explorer/FileRow.tsx b/apps/mobile/src/components/explorer/FileRow.tsx index 7bb32e821..a42d92a52 100644 --- a/apps/mobile/src/components/explorer/FileRow.tsx +++ b/apps/mobile/src/components/explorer/FileRow.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Text, View } from 'react-native'; -import { ExplorerItem, ObjectKind, isObject } from '@sd/client'; +import { ExplorerItem } from '@sd/client'; import { tw, twStyle } from '~/lib/tailwind'; import { getExplorerStore } from '~/stores/explorerStore'; import FileThumb from './FileThumb'; @@ -12,21 +12,13 @@ type FileRowProps = { const FileRow = ({ data }: FileRowProps) => { const { item } = data; - // temp fix (will handle this on mobile-inspector branch) - const objectData = data ? (isObject(data) ? data.item : data.item.object) : null; - const isVid = ObjectKind[objectData?.kind || 0] === 'Video'; - return ( - + {item?.name} diff --git a/apps/mobile/src/components/explorer/FileThumb.tsx b/apps/mobile/src/components/explorer/FileThumb.tsx index 10fca052c..7dae1a7bd 100644 --- a/apps/mobile/src/components/explorer/FileThumb.tsx +++ b/apps/mobile/src/components/explorer/FileThumb.tsx @@ -1,8 +1,8 @@ +import * as icons from '@sd/assets/icons'; import { PropsWithChildren } from 'react'; import { Image, View } from 'react-native'; import { DocumentDirectoryPath } from 'react-native-fs'; -import { ExplorerItem, isObject, isPath } from '@sd/client'; -// import icons from '../../assets/icons/file'; +import { ExplorerItem, ObjectKind, isObject, isPath } from '@sd/client'; import { tw } from '../../lib/tailwind'; import FolderIcon from '../icons/FolderIcon'; @@ -13,112 +13,82 @@ type FileThumbProps = { * default: `1` */ size?: number; - kind?: string; }; export const getThumbnailUrlById = (casId: string) => `${DocumentDirectoryPath}/thumbnails/${encodeURIComponent(casId)}.webp`; +type KindType = keyof typeof icons | 'Unknown'; + +function getExplorerItemData(data: ExplorerItem) { + const objectData = data ? (isObject(data) ? data.item : data.item.object) : null; + + return { + casId: (isObject(data) ? data.item.file_paths[0]?.cas_id : data.item.cas_id) || null, + isDir: isPath(data) && data.item.is_dir, + kind: ObjectKind[objectData?.kind || 0] as KindType, + hasThumbnail: data.has_thumbnail, + extension: data.item.extension + }; +} + const FileThumbWrapper = ({ children, size = 1 }: PropsWithChildren<{ size: number }>) => ( {children} ); -export default function FileThumb({ data, size = 1, kind }: FileThumbProps) { - // const Icon = useMemo(() => { - // const Icon = icons[data.extension]; - // return Icon; - // }, [data.extension]); +export default function FileThumb({ data, size = 1 }: FileThumbProps) { + const { casId, isDir, kind, hasThumbnail, extension } = getExplorerItemData(data); - if (isPath(data) && data.item.is_dir) + if (isPath(data) && data.item.is_dir) { return ( ); + } - const casId = isObject(data) ? data.item.file_paths[0]?.cas_id : data.item.cas_id; - if (!casId) return null; - - // Icon - let icon = undefined; - if (kind === 'Archive') icon = require('@sd/assets/images/Archive.png'); - else if (kind === 'Video') icon = require('@sd/assets/images/Video.png'); - else if (kind === 'Document' && data.item.extension === 'pdf') - icon = require('@sd/assets/images/Document_pdf.png'); - else if (kind === 'Executable') icon = require('@sd/assets/images/Executable.png'); - - if (icon) { + if (hasThumbnail && casId) { + // TODO: Handle Image checkers bg? return ( - + ); } - const url = getThumbnailUrlById(casId); + // Default icon + let icon = icons['Document']; - if (data.has_thumbnail && url) { - return ( - - - - ); + if (isDir) { + icon = icons['Folder']; + } else if ( + kind && + extension && + icons[`${kind}_${extension.toLowerCase()}` as keyof typeof icons] + ) { + // e.g. Document_pdf + icon = icons[`${kind}_${extension.toLowerCase()}` as keyof typeof icons]; + } else if (kind !== 'Unknown' && kind && icons[kind]) { + icon = icons[kind]; } + // TODO: Handle video thumbnails + + // // 10 percent of the size + // const videoBarsHeight = Math.floor(size / 10); + + // // calculate 16:9 ratio for height from size + // const videoHeight = Math.floor((size * 9) / 16) + videoBarsHeight * 2; + return ( - + ); - - // Default file icon - // return ( - // - // - // - // - // - // - // - // - // {/* File Icon & Extension */} - // - // {Icon && ( - // }> - // - // - // )} - // - // {data.extension} - // - // - // - // - // ); } diff --git a/apps/mobile/src/components/form/Input.tsx b/apps/mobile/src/components/form/Input.tsx index 7f6512873..151f3b2e3 100644 --- a/apps/mobile/src/components/form/Input.tsx +++ b/apps/mobile/src/components/form/Input.tsx @@ -1,15 +1,17 @@ import { VariantProps, cva } from 'class-variance-authority'; -import { FC } from 'react'; -import { TextInputProps as RNTextInputProps, TextInput } from 'react-native'; +import { Eye, EyeSlash } from 'phosphor-react-native'; +import { useState } from 'react'; +import { Pressable, TextInputProps as RNTextInputProps, TextInput, View } from 'react-native'; import { tw, twStyle } from '~/lib/tailwind'; const input = cva(['rounded-md border text-sm leading-tight shadow-sm'], { variants: { variant: { - default: 'border-app-line bg-app text-ink' + default: 'border-app-line bg-app-input text-ink' }, size: { - default: ['py-2', 'px-3'] + default: ['py-2', 'px-3'], + md: ['py-2.5', 'px-3.5'] } }, defaultVariants: { @@ -20,13 +22,51 @@ const input = cva(['rounded-md border text-sm leading-tight shadow-sm'], { type InputProps = VariantProps & RNTextInputProps; -export const Input: FC = ({ variant, ...props }) => { +export const Input = ({ variant, size, ...props }: InputProps) => { const { style, ...otherProps } = props; return ( ); }; + +// Same as above but configured with password props & show/hide password button + +type PasswordInputProps = InputProps & { + isNewPassword?: boolean; +}; + +export const PasswordInput = ({ variant, size, ...props }: PasswordInputProps) => { + const { style, isNewPassword = false, ...otherProps } = props; + + const [showPassword, setShowPassword] = useState(false); + + const Icon = showPassword ? EyeSlash : Eye; + + return ( + + + setShowPassword((v) => !v)} + > + + + + ); +}; diff --git a/apps/mobile/src/components/icons/FolderIcon.tsx b/apps/mobile/src/components/icons/FolderIcon.tsx index 3c898e88e..77cc3236b 100644 --- a/apps/mobile/src/components/icons/FolderIcon.tsx +++ b/apps/mobile/src/components/icons/FolderIcon.tsx @@ -1,3 +1,4 @@ +import { Folder } from '@sd/assets/icons'; import FolderWhite from '@sd/assets/svgs/folder-white.svg'; import { Image } from 'react-native'; @@ -17,7 +18,7 @@ const FolderIcon: React.FC = ({ size = 24, isWhite }) => { return isWhite ? ( ) : ( - + ); }; diff --git a/apps/mobile/src/components/key/PasswordMeter.tsx b/apps/mobile/src/components/key/PasswordMeter.tsx new file mode 100644 index 000000000..aa656f160 --- /dev/null +++ b/apps/mobile/src/components/key/PasswordMeter.tsx @@ -0,0 +1,51 @@ +import { Text, View, ViewStyle } from 'react-native'; +import { getPasswordStrength } from '@sd/client'; +import { tw, twStyle } from '~/lib/tailwind'; + +// NOTE: Lazy load this component. + +type PasswordMeterProps = { + password: string; + containerStyle?: ViewStyle; +}; + +const PasswordMeter = (props: PasswordMeterProps) => { + const { score, scoreText } = getPasswordStrength(props.password); + + return ( + + + Password strength + + {scoreText} + + + + + + + ); +}; + +export default PasswordMeter; diff --git a/apps/mobile/src/components/layout/Card.tsx b/apps/mobile/src/components/layout/Card.tsx index abc9b617b..1bdb764e2 100644 --- a/apps/mobile/src/components/layout/Card.tsx +++ b/apps/mobile/src/components/layout/Card.tsx @@ -11,7 +11,7 @@ const Card = ({ children, ...props }: CardProps) => { return ( {children} diff --git a/apps/mobile/src/components/layout/Dialog.tsx b/apps/mobile/src/components/layout/Dialog.tsx index cfab38e66..487c1685d 100644 --- a/apps/mobile/src/components/layout/Dialog.tsx +++ b/apps/mobile/src/components/layout/Dialog.tsx @@ -95,8 +95,7 @@ const Dialog = (props: DialogProps) => { {props.loading && } */} - - TODO diff --git a/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx b/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx index 9283d44fd..73d1e43bb 100644 --- a/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx +++ b/apps/mobile/src/components/modal/inspector/FileInfoModal.tsx @@ -14,7 +14,7 @@ import { ExplorerItem, formatBytes, isObject, useLibraryQuery } from '@sd/client import FileThumb from '~/components/explorer/FileThumb'; import InfoTagPills from '~/components/explorer/sections/InfoTagPills'; import { Modal, ModalRef, ModalScrollView } from '~/components/layout/Modal'; -import Divider from '~/components/primitive/Divider'; +import { Divider } from '~/components/primitive/Divider'; import useForwardedRef from '~/hooks/useForwardedRef'; import { tw } from '~/lib/tailwind'; diff --git a/apps/mobile/src/components/modal/tag/CreateTagModal.tsx b/apps/mobile/src/components/modal/tag/CreateTagModal.tsx index f3bd34659..cea85ad79 100644 --- a/apps/mobile/src/components/modal/tag/CreateTagModal.tsx +++ b/apps/mobile/src/components/modal/tag/CreateTagModal.tsx @@ -78,7 +78,6 @@ const CreateTagModal = forwardRef((_, ref) => { )} + )} + + {obStore.passwordSetToken ? ( + + ) : ( + + )} + + + + + ); +}; + +export default MasterPasswordScreen; diff --git a/apps/mobile/src/screens/onboarding/NewLibrary.tsx b/apps/mobile/src/screens/onboarding/NewLibrary.tsx new file mode 100644 index 000000000..bd1a96445 --- /dev/null +++ b/apps/mobile/src/screens/onboarding/NewLibrary.tsx @@ -0,0 +1,77 @@ +import { Database } from '@sd/assets/icons'; +import { Controller } from 'react-hook-form'; +import { Alert, Image, Text, View } from 'react-native'; +import { getOnboardingStore, useOnboardingStore } from '@sd/client'; +import { Input } from '~/components/form/Input'; +import { Button } from '~/components/primitive/Button'; +import { useZodForm, z } from '~/hooks/useZodForm'; +import { tw } from '~/lib/tailwind'; +import { OnboardingStackScreenProps } from '~/navigation/OnboardingNavigator'; +import { OnboardingContainer, OnboardingDescription, OnboardingTitle } from './GetStarted'; + +const schema = z.object({ + name: z.string().min(1, { message: 'Library name is required' }) +}); + +const NewLibraryScreen = ({ navigation }: OnboardingStackScreenProps<'NewLibrary'>) => { + const obStore = useOnboardingStore(); + + const form = useZodForm({ + schema, + defaultValues: { + name: obStore.newLibraryName + } + }); + + const handleNewLibrary = form.handleSubmit(async (data) => { + getOnboardingStore().newLibraryName = data.name; + navigation.navigate('MasterPassword'); + }); + + const handleImport = () => { + Alert.alert('TODO'); + }; + + return ( + + + Create a Library + + + Libraries are a secure, on-device database. Your files remain where they are, the Library + catalogs them and stores all Spacedrive related data. + + ( + + )} + /> + + + {form.formState.errors.name && ( + + {form.formState.errors.name.message} + + )} + + + OR + + + + ); +}; + +export default NewLibraryScreen; diff --git a/apps/mobile/src/screens/onboarding/Onboarding.tsx b/apps/mobile/src/screens/onboarding/Onboarding.tsx deleted file mode 100644 index e8e9eee51..000000000 --- a/apps/mobile/src/screens/onboarding/Onboarding.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { AppLogo } from '@sd/assets/images'; -import { Image, Text, View } from 'react-native'; -import { FadeInUpAnimation, LogoAnimation } from '~/components/animation/layout'; -import { AnimatedButton } from '~/components/primitive/Button'; -import { tw } from '~/lib/tailwind'; -import { OnboardingStackScreenProps } from '~/navigation/OnboardingNavigator'; - -const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding'>) => { - return ( - - {/* Logo */} - - - - - - {/* Text */} - - - - A file explorer from the future. - - - - - Combine your drives and clouds into one database that you can organize and explore from - any device. - - - - {/* Get Started Button */} - - navigation.navigate('CreateLibrary')}> - Get Started - - - - ); -}; - -export default OnboardingScreen; diff --git a/apps/mobile/src/screens/onboarding/Privacy.tsx b/apps/mobile/src/screens/onboarding/Privacy.tsx new file mode 100644 index 000000000..972d781d5 --- /dev/null +++ b/apps/mobile/src/screens/onboarding/Privacy.tsx @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import { Pressable, Text, View, ViewStyle } from 'react-native'; +import { getOnboardingStore } from '@sd/client'; +import { Button } from '~/components/primitive/Button'; +import { tw, twStyle } from '~/lib/tailwind'; +import { OnboardingStackScreenProps } from '~/navigation/OnboardingNavigator'; +import { OnboardingContainer, OnboardingDescription, OnboardingTitle } from './GetStarted'; + +type RadioButtonProps = { + title: string; + description: string; + isSelected: boolean; + style?: ViewStyle; +}; + +// Make this a component? +const RadioButton = ({ title, description, isSelected, style }: RadioButtonProps) => { + return ( + + + {isSelected && } + + + {title} + {description} + + + ); +}; + +const PrivacyScreen = ({ navigation }: OnboardingStackScreenProps<'Privacy'>) => { + const [shareTelemetry, setShareTelemetry] = useState<'share-telemetry' | 'no-share-telemetry'>( + 'share-telemetry' + ); + + const onPress = () => { + getOnboardingStore().shareTelemetry = shareTelemetry === 'share-telemetry'; + navigation.navigate('CreatingLibrary'); + }; + + return ( + + Your Privacy + + Spacedrive is built for privacy, that's why we're open source and local first. So we'll make + it very clear what data is shared with us. + + + setShareTelemetry('share-telemetry')}> + + + setShareTelemetry('no-share-telemetry')}> + + + + + + ); +}; + +export default PrivacyScreen; diff --git a/apps/mobile/src/screens/settings/Settings.tsx b/apps/mobile/src/screens/settings/Settings.tsx index 313821c32..6fc0800de 100644 --- a/apps/mobile/src/screens/settings/Settings.tsx +++ b/apps/mobile/src/screens/settings/Settings.tsx @@ -109,7 +109,7 @@ function renderSectionHeader({ section }: { section: { title: string } }) { return ( @@ -129,14 +129,15 @@ export default function SettingsScreen({ navigation }: SettingsStackScreenProps< navigation.navigate(item.navigateTo)} + onPress={() => navigation.navigate(item.navigateTo as any)} /> )} renderSectionHeader={renderSectionHeader} ListFooterComponent={ - Spacedrive - v0.1.0 + Spacedrive + {/* TODO: Get this automatically (expo-device have this?) */} + v0.1.0 } showsVerticalScrollIndicator={false} diff --git a/apps/mobile/src/screens/settings/client/GeneralSettings.tsx b/apps/mobile/src/screens/settings/client/GeneralSettings.tsx index c38437d42..f62861c86 100644 --- a/apps/mobile/src/screens/settings/client/GeneralSettings.tsx +++ b/apps/mobile/src/screens/settings/client/GeneralSettings.tsx @@ -1,9 +1,9 @@ -import React from 'react'; import { Text, View } from 'react-native'; import { useBridgeQuery } from '@sd/client'; import { Input } from '~/components/form/Input'; import Card from '~/components/layout/Card'; -import Divider from '~/components/primitive/Divider'; +import { Divider } from '~/components/primitive/Divider'; +import { SettingsInputTitle } from '~/components/settings/SettingsContainer'; import { tw } from '~/lib/tailwind'; import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; @@ -14,7 +14,7 @@ const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'General return ( - + {/* Card Header */} Connected Node @@ -32,9 +32,9 @@ const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'General {/* Divider */} {/* Node Name and Port */} - Node Name + Node Name - Node Port + Node Port diff --git a/apps/mobile/src/screens/settings/client/LibrarySettings.tsx b/apps/mobile/src/screens/settings/client/LibrarySettings.tsx index 2deb8017d..d82c346ec 100644 --- a/apps/mobile/src/screens/settings/client/LibrarySettings.tsx +++ b/apps/mobile/src/screens/settings/client/LibrarySettings.tsx @@ -31,13 +31,13 @@ function LibraryItem({ - navigation.replace('LibraryGeneralSettings')}> + navigation.replace('LibraryGeneralSettings')}> + } diff --git a/apps/mobile/src/screens/settings/library/EditLocationSettings.tsx b/apps/mobile/src/screens/settings/library/EditLocationSettings.tsx new file mode 100644 index 000000000..9ecbb131f --- /dev/null +++ b/apps/mobile/src/screens/settings/library/EditLocationSettings.tsx @@ -0,0 +1,218 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { Archive, ArrowsClockwise, Trash } from 'phosphor-react-native'; +import React from 'react'; +import { Controller } from 'react-hook-form'; +import { Alert, ScrollView, Text, View } from 'react-native'; +import { useLibraryMutation, useLibraryQuery } from '@sd/client'; +import { Input } from '~/components/form/Input'; +import { Switch } from '~/components/form/Switch'; +import DeleteLocationModal from '~/components/modal/confirm-modals/DeleteLocationModal'; +import { AnimatedButton, Button, FakeButton } from '~/components/primitive/Button'; +import { Divider } from '~/components/primitive/Divider'; +import { + SettingsContainer, + SettingsInputInfo, + SettingsInputTitle +} from '~/components/settings/SettingsContainer'; +import { SettingsItem } from '~/components/settings/SettingsItem'; +import { useZodForm, z } from '~/hooks/useZodForm'; +import { tw } from '~/lib/tailwind'; +import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; + +const schema = z.object({ + displayName: z.string(), + localPath: z.string(), + indexer_rules_ids: z.array(z.string()), + generatePreviewMedia: z.boolean(), + syncPreviewMedia: z.boolean(), + hidden: z.boolean() +}); + +const EditLocationSettingsScreen = ({ + route, + navigation +}: SettingsStackScreenProps<'EditLocationSettings'>) => { + const { id } = route.params; + + const queryClient = useQueryClient(); + + const form = useZodForm({ schema }); + + const updateLocation = useLibraryMutation('locations.update', { + onError: (e) => console.log({ e }), + onSuccess: () => { + form.reset(form.getValues()); + queryClient.invalidateQueries(['locations.list']); + // TODO: Show toast & navigate back & reset input focus! + } + }); + + const onSubmit = form.handleSubmit((data) => + updateLocation.mutateAsync({ + id: Number(id), + name: data.displayName, + sync_preview_media: data.syncPreviewMedia, + generate_preview_media: data.generatePreviewMedia, + hidden: data.hidden, + indexer_rules_ids: [] + }) + ); + + navigation.setOptions({ + headerRight: () => ( + + {form.formState.isDirty && ( + form.reset()} + disabled={!form.formState.isDirty} + > + Reset + + )} + + Save + + + ) + }); + + useLibraryQuery(['locations.getById', id], { + onSuccess: (data) => { + if (data && !form.formState.isDirty) + form.reset({ + displayName: data.name, + localPath: data.path, + indexer_rules_ids: data.indexer_rules.map((i) => i.indexer_rule.id.toString()), + generatePreviewMedia: data.generate_preview_media, + syncPreviewMedia: data.sync_preview_media, + hidden: data.hidden + }); + } + }); + + const fullRescan = useLibraryMutation('locations.fullRescan'); + + return ( + + {/* Inputs */} + + Display Name + ( + + )} + /> + + The name of this Location, this is what will be displayed in the sidebar. Will not rename + the actual folder on disk. + + + Local Path + ( + + )} + /> + + The path to this Location, this is where the files will be stored on disk. + + + + {/* Switches */} + + + ( + + )} + /> + } + /> + ( + + )} + /> + } + /> + ( + + )} + /> + } + /> + + + {/* Indexer Rules */} + TODO: Indexer Rules + {/* Buttons */} + + + fullRescan.mutate(id)}> + + + } + /> + + + Alert.alert('Archiving locations is coming soon...')} + > + + + } + /> + + + + + + } + /> + } + /> + + + + ); +}; + +export default EditLocationSettingsScreen; diff --git a/apps/mobile/src/screens/settings/library/LibraryGeneralSettings.tsx b/apps/mobile/src/screens/settings/library/LibraryGeneralSettings.tsx index a4cb55c8a..a67dca83d 100644 --- a/apps/mobile/src/screens/settings/library/LibraryGeneralSettings.tsx +++ b/apps/mobile/src/screens/settings/library/LibraryGeneralSettings.tsx @@ -1,23 +1,29 @@ import { Trash } from 'phosphor-react-native'; import React from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import { Alert, Text, View } from 'react-native'; +import { Controller } from 'react-hook-form'; +import { Alert, View } from 'react-native'; import { useBridgeMutation, useLibraryContext } from '@sd/client'; import { Input } from '~/components/form/Input'; import { Switch } from '~/components/form/Switch'; -import { Button } from '~/components/primitive/Button'; -import { SettingsContainer } from '~/components/settings/SettingsContainer'; +import DeleteLibraryModal from '~/components/modal/confirm-modals/DeleteLibraryModal'; +import { AnimatedButton } from '~/components/primitive/Button'; +import { Divider } from '~/components/primitive/Divider'; +import { SettingsContainer, SettingsInputTitle } from '~/components/settings/SettingsContainer'; import { SettingsItem } from '~/components/settings/SettingsItem'; import { useAutoForm } from '~/hooks/useAutoForm'; +import { useZodForm, z } from '~/hooks/useZodForm'; import { tw } from '~/lib/tailwind'; import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; +const schema = z.object({ name: z.string(), description: z.string() }); + const LibraryGeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'LibraryGeneralSettings'>) => { const { library } = useLibraryContext(); - const form = useForm({ + const form = useZodForm({ + schema, defaultValues: { name: library.config.name, description: library.config.description } }); @@ -25,15 +31,14 @@ const LibraryGeneralSettingsScreen = ({ useAutoForm(form, (value) => { editLibrary({ description: value.description, name: value.name, id: library.uuid }); - console.log('Updated', value); + // console.log('Updated', value); // TODO: Show toast }); return ( - - {/* This looks bad... */} - - Name + + + Name {/* Description */} - Description + Description - {/* Encrypt */} - - - } /> - - - {/* Export */} - Alert.alert('TODO')} /> - - {/* Delete Library */} - {/* TODO: Open delete library dialog here, but do handle library switching and what happens if there is no library set ? */} - - Alert.alert('TODO')}> - - - } - /> - + + + {/* Encrypt */} + + } /> + + {/* Export */} + Alert.alert('TODO')} /> + {/* Delete Library */} + + + + + } + /> + } + /> + + ); }; diff --git a/apps/mobile/src/screens/settings/library/LocationSettings.tsx b/apps/mobile/src/screens/settings/library/LocationSettings.tsx index 2025097a3..80377ada6 100644 --- a/apps/mobile/src/screens/settings/library/LocationSettings.tsx +++ b/apps/mobile/src/screens/settings/library/LocationSettings.tsx @@ -1,4 +1,4 @@ -import { CaretRight, Repeat, Trash } from 'phosphor-react-native'; +import { CaretRight, Pen, Repeat, Trash } from 'phosphor-react-native'; import { Animated, FlatList, Pressable, Text, View } from 'react-native'; import { Swipeable } from 'react-native-gesture-handler'; import { @@ -14,7 +14,13 @@ import DeleteLocationModal from '~/components/modal/confirm-modals/DeleteLocatio import { tw, twStyle } from '~/lib/tailwind'; import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator'; -function LocationItem({ location, index }: { location: Location & { node: Node }; index: number }) { +type LocationItemProps = { + location: Location & { node: Node }; + index: number; + navigation: SettingsStackScreenProps<'LocationSettings'>['navigation']; +}; + +function LocationItem({ location, index, navigation }: LocationItemProps) { const fullRescan = useLibraryMutation('locations.fullRescan', { onMutate: () => { // TODO: Show Toast @@ -23,7 +29,11 @@ function LocationItem({ location, index }: { location: Location & { node: Node } const onlineLocations = useOnlineLocations(); - const renderRightActions = (progress: Animated.AnimatedInterpolation) => { + const renderRightActions = ( + progress: Animated.AnimatedInterpolation, + _: any, + swipeable: Swipeable + ) => { const translate = progress.interpolate({ inputRange: [0, 1], outputRange: [100, 0], @@ -32,8 +42,20 @@ function LocationItem({ location, index }: { location: Location & { node: Node } return ( + { + navigation.navigate('EditLocationSettings', { id: location.id }); + swipeable.close(); + }} + > + + {/* Full Re-scan IS too much here */} fullRescan.mutate(location.id)} > @@ -106,7 +128,9 @@ const LocationSettingsScreen = ({ navigation }: SettingsStackScreenProps<'Locati item.id.toString()} - renderItem={({ item, index }) => } + renderItem={({ item, index }) => ( + + )} /> ); diff --git a/apps/mobile/src/screens/settings/library/TagsSettings.tsx b/apps/mobile/src/screens/settings/library/TagsSettings.tsx index bc243ef70..c836cea57 100644 --- a/apps/mobile/src/screens/settings/library/TagsSettings.tsx +++ b/apps/mobile/src/screens/settings/library/TagsSettings.tsx @@ -29,13 +29,13 @@ function TagItem({ tag, index }: { tag: Tag; index: number }) { style={[tw`flex flex-row items-center`, { transform: [{ translateX: translate }] }]} > swipeable.close()} /> - updateTagModalRef.current?.present()}> + updateTagModalRef.current?.present()}> + } diff --git a/apps/mobile/tailwind.config.js b/apps/mobile/tailwind.config.js index ad795fedd..a5a9dd329 100644 --- a/apps/mobile/tailwind.config.js +++ b/apps/mobile/tailwind.config.js @@ -36,6 +36,8 @@ module.exports = { button: 'hsla(230, 15%, 23%, 1)', // menu menu: 'hsla(230, 25%, 5%, 1)', + // input + input: 'hsla(230, 15%, 20%, 1)', 50: 'hsla(230, 15%, 5%, 1)', 100: 'hsla(230, 15%, 10%, 1)', 150: 'hsla(230, 15%, 15%, 1)', diff --git a/core/src/lib.rs b/core/src/lib.rs index ab4c98faf..480a648a6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -112,8 +112,8 @@ impl Node { ); #[cfg(not(target_os = "android"))] let subscriber = subscriber.with(tracing_subscriber::fmt::layer().with_filter(CONSOLE_LOG_FILTER)); - #[cfg(target_os = "android")] - let subscriber = subscriber.with(tracing_android::layer("com.spacedrive.app").unwrap()); // TODO: This is not working + // #[cfg(target_os = "android")] + // let subscriber = subscriber.with(tracing_android::layer("com.spacedrive.app").unwrap()); // TODO: This is not working subscriber // .with( // Layer::default() diff --git a/docs/developers/prerequisites/environment-setup.md b/docs/developers/prerequisites/environment-setup.md index ade22b054..9f12c9ff1 100644 --- a/docs/developers/prerequisites/environment-setup.md +++ b/docs/developers/prerequisites/environment-setup.md @@ -59,7 +59,7 @@ To run mobile app 1. Install [Android Studio](https://developer.android.com/studio) for Android and [Xcode](https://apps.apple.com/au/app/xcode/id497799835) for IOS development 2. `./.github/scripts/setup-system.sh mobile` _The should setup most of the dependencies for the mobile app to build._ -3. You must also ensure you have [NDK 24.0.8215888 and CMake](https://developer.android.com/studio/projects/install-ndk#default-version) in Android Studio +3. You must also ensure you have [NDK 23.1.7779620 and CMake](https://developer.android.com/studio/projects/install-ndk#default-version) in Android Studio 4. `pnpm mobile android` - runs on Android Emulator 5. `pnpm mobile ios` - runs on iOS Emulator @@ -68,4 +68,4 @@ To run mobile app If you are having issues ensure you are using the following versions of Rust and Node: - Rust version: **1.67.0** -- Node version: **17** +- Node version: **18** diff --git a/interface/app/$libraryId/KeyManager/Mounter.tsx b/interface/app/$libraryId/KeyManager/Mounter.tsx index ba928a0a2..216ca2b44 100644 --- a/interface/app/$libraryId/KeyManager/Mounter.tsx +++ b/interface/app/$libraryId/KeyManager/Mounter.tsx @@ -1,14 +1,9 @@ import { Eye, EyeSlash, Info } from 'phosphor-react'; import { useEffect, useRef, useState } from 'react'; -import { - Algorithm, - HASHING_ALGOS, - HashingAlgoSlug, - generatePassword, - useLibraryMutation -} from '@sd/client'; +import { Algorithm, HASHING_ALGOS, HashingAlgoSlug, useLibraryMutation } from '@sd/client'; import { Button, CategoryHeading, Input, Select, SelectOption, Slider, Switch, tw } from '@sd/ui'; import { Tooltip } from '@sd/ui'; +import { generatePassword } from '~/util'; const KeyHeading = tw(CategoryHeading)`mb-1`; diff --git a/interface/app/$libraryId/settings/library/general.tsx b/interface/app/$libraryId/settings/library/general.tsx index cd7314928..3a31d16e0 100644 --- a/interface/app/$libraryId/settings/library/general.tsx +++ b/interface/app/$libraryId/settings/library/general.tsx @@ -1,9 +1,10 @@ import { useForm } from 'react-hook-form'; import { useBridgeMutation, useLibraryContext } from '@sd/client'; -import { Button, Input, Switch } from '@sd/ui'; +import { Button, Input, Switch, dialogManager } from '@sd/ui'; import { useDebouncedFormWatch } from '~/hooks/useDebouncedForm'; import { Heading } from '../Layout'; import Setting from '../Setting'; +import DeleteLibraryDialog from '../node/libraries/DeleteDialog'; export const Component = () => { const { library } = useLibraryContext(); @@ -64,7 +65,16 @@ export const Component = () => { description="This is permanent, your files will not be deleted, only the Spacedrive library." >

-
diff --git a/interface/app/$libraryId/settings/library/keys/MasterPasswordDialog.tsx b/interface/app/$libraryId/settings/library/keys/MasterPasswordDialog.tsx index a722a4275..b8620c731 100644 --- a/interface/app/$libraryId/settings/library/keys/MasterPasswordDialog.tsx +++ b/interface/app/$libraryId/settings/library/keys/MasterPasswordDialog.tsx @@ -4,22 +4,14 @@ import { Algorithm, HASHING_ALGOS, HashingAlgoSlug, - generatePassword, hashingAlgoSlugSchema, useLibraryMutation } from '@sd/client'; -import { - Button, - Dialog, - Input, - PasswordMeter, - Select, - SelectOption, - UseDialogProps, - useDialog -} from '@sd/ui'; +import { Button, Dialog, Input, Select, SelectOption, UseDialogProps, useDialog } from '@sd/ui'; import { useZodForm, z } from '@sd/ui/src/forms'; import { showAlertDialog } from '~/components/AlertDialog'; +import { PasswordMeter } from '~/components/PasswordMeter'; +import { generatePassword } from '~/util'; const schema = z.object({ masterPassword: z.string(), diff --git a/interface/app/$libraryId/settings/node/libraries/CreateDialog.tsx b/interface/app/$libraryId/settings/node/libraries/CreateDialog.tsx index c1cf8d552..b51565dc7 100644 --- a/interface/app/$libraryId/settings/node/libraries/CreateDialog.tsx +++ b/interface/app/$libraryId/settings/node/libraries/CreateDialog.tsx @@ -5,7 +5,6 @@ import { Algorithm, HASHING_ALGOS, HashingAlgoSlug, - generatePassword, hashingAlgoSlugSchema, useBridgeMutation, usePlausibleEvent @@ -14,7 +13,6 @@ import { Button, CheckBox, Dialog, - PasswordMeter, Select, SelectOption, Tooltip, @@ -22,6 +20,8 @@ import { useDialog } from '@sd/ui'; import { forms } from '@sd/ui'; +import { PasswordMeter } from '~/components/PasswordMeter'; +import { generatePassword } from '~/util'; import { usePlatform } from '~/util/Platform'; const { Input, z, useZodForm } = forms; diff --git a/interface/app/onboarding/creating-library.tsx b/interface/app/onboarding/creating-library.tsx index aa4d2cbdc..cd82759a6 100644 --- a/interface/app/onboarding/creating-library.tsx +++ b/interface/app/onboarding/creating-library.tsx @@ -1,9 +1,7 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import { useQueryClient } from '@tanstack/react-query'; import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router'; import { - Algorithm, HASHING_ALGOS, resetOnboardingStore, telemetryStore, @@ -59,7 +57,7 @@ export default function OnboardingCreatingLibrary() { type: 'TokenizedPassword', value: obStore.passwordSetToken || '' }, - algorithm: obStore.algorithm as Algorithm, + algorithm: obStore.algorithm, hashing_algorithm: HASHING_ALGOS[obStore.hashingAlgorithm] }); @@ -84,6 +82,7 @@ export default function OnboardingCreatingLibrary() { clearTimeout(timer); clearTimeout(timer2); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/interface/app/onboarding/master-password.tsx b/interface/app/onboarding/master-password.tsx index 6cf6449dc..efbd3921e 100644 --- a/interface/app/onboarding/master-password.tsx +++ b/interface/app/onboarding/master-password.tsx @@ -1,8 +1,9 @@ import { useState } from 'react'; import { useNavigate } from 'react-router'; import { getOnboardingStore, useBridgeMutation, useOnboardingStore } from '@sd/client'; -import { Button, Card, PasswordMeter } from '@sd/ui'; +import { Button, Card } from '@sd/ui'; import { Form, PasswordInput, useZodForm, z } from '@sd/ui/src/forms'; +import { PasswordMeter } from '~/components/PasswordMeter'; import { OnboardingContainer, OnboardingDescription, OnboardingTitle } from './Layout'; import { useUnlockOnboardingScreen } from './Progress'; @@ -118,7 +119,9 @@ export default function OnboardingNewLibrary() { disabled={form.formState.isSubmitting} variant="outline" size="sm" - onClick={() => { + onClick={(event: any) => { + // Without this, form is submitted before token gets removed + event.preventDefault(); getOnboardingStore().passwordSetToken = null; form.reset(); }} diff --git a/interface/components/PasswordMeter.tsx b/interface/components/PasswordMeter.tsx new file mode 100644 index 000000000..f7ba676f9 --- /dev/null +++ b/interface/components/PasswordMeter.tsx @@ -0,0 +1,45 @@ +import clsx from 'clsx'; +import { getPasswordStrength } from '@sd/client'; + +export interface PasswordMeterProps { + password: string; +} + +export const PasswordMeter = (props: PasswordMeterProps) => { + const { score, scoreText } = getPasswordStrength(props.password); + + return ( +
+

Password strength

+ + {scoreText} + +
+
+
+
+
+
+ ); +}; diff --git a/interface/package.json b/interface/package.json index 649bf6ddb..6e8879a45 100644 --- a/interface/package.json +++ b/interface/package.json @@ -19,6 +19,7 @@ "@fontsource/inter": "^4.5.13", "@headlessui/react": "^1.7.3", "@hookform/resolvers": "^2.9.10", + "crypto-random-string": "^5.0.0", "@radix-ui/react-progress": "^1.0.1", "@radix-ui/react-slider": "^1.1.0", "@radix-ui/react-toast": "^1.1.2", @@ -35,9 +36,6 @@ "@tanstack/react-query-devtools": "^4.22.0", "@tanstack/react-virtual": "3.0.0-beta.18", "@vitejs/plugin-react": "^2.1.0", - "@zxcvbn-ts/core": "^2.1.0", - "@zxcvbn-ts/language-common": "^2.0.1", - "@zxcvbn-ts/language-en": "^2.1.0", "autoprefixer": "^10.4.12", "byte-size": "^8.1.0", "class-variance-authority": "^0.4.0", @@ -48,7 +46,7 @@ "react-colorful": "^5.6.1", "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", - "react-hook-form": "^7.36.1", + "react-hook-form": "^7.43.5", "react-json-view": "^1.21.3", "react-loading-skeleton": "^3.1.0", "react-qr-code": "^2.0.11", diff --git a/interface/util/index.tsx b/interface/util/index.tsx new file mode 100644 index 000000000..cf4eb11fb --- /dev/null +++ b/interface/util/index.tsx @@ -0,0 +1,5 @@ +import cryptoRandomString from 'crypto-random-string'; + +// NOTE: `crypto` module is not available in RN so this can't be in client +export const generatePassword = (length: number) => + cryptoRandomString({ length, type: 'ascii-printable' }); diff --git a/package.json b/package.json index 129f49527..4ff07ecdc 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "codegen": "cargo test -p sd-core api::tests::test_and_export_rspc_bindings -- --exact", "typecheck": "turbo run typecheck", "lint": "turbo run lint", - "lint:fix": "turbo run lint -- lint:fix", + "lint:fix": "turbo run lint -- --fix", "clean": "rimraf node_modules/ **/node_modules/ target/ **/.build/ **/.next/ **/dist/**" }, "pnpm": { diff --git a/packages/client/package.json b/packages/client/package.json index d257018e5..dc255f288 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -18,7 +18,9 @@ "@rspc/react": "^0.0.0-main-7c0a67c1", "@sd/config": "workspace:*", "@tanstack/react-query": "^4.12.0", - "crypto-random-string": "^5.0.0", + "@zxcvbn-ts/core": "^2.1.0", + "@zxcvbn-ts/language-common": "^2.0.1", + "@zxcvbn-ts/language-en": "^2.1.0", "plausible-tracker": "^0.3.8", "valtio": "^1.7.4" }, diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 1f1b0fc64..a15e3fb0f 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -11,3 +11,4 @@ export * from './stores'; export * from './rspc'; export * from './core'; export * from './utils'; +export * from './lib'; diff --git a/packages/client/src/lib/index.ts b/packages/client/src/lib/index.ts new file mode 100644 index 000000000..35dc0c351 --- /dev/null +++ b/packages/client/src/lib/index.ts @@ -0,0 +1 @@ +export * from './passwordStrength'; diff --git a/packages/client/src/lib/passwordStrength.ts b/packages/client/src/lib/passwordStrength.ts new file mode 100644 index 000000000..85ca53f49 --- /dev/null +++ b/packages/client/src/lib/passwordStrength.ts @@ -0,0 +1,20 @@ +import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'; +import zxcvbnCommonPackage from '@zxcvbn-ts/language-common'; +import zxcvbnEnPackage from '@zxcvbn-ts/language-en'; + +const options = { + dictionary: { + ...zxcvbnCommonPackage.dictionary, + ...zxcvbnEnPackage.dictionary + }, + graps: zxcvbnCommonPackage.adjacencyGraphs, + translations: zxcvbnEnPackage.translations +}; + +const ratings = ['Poor', 'Weak', 'Good', 'Strong', 'Perfect']; + +export function getPasswordStrength(password: string): { scoreText: string; score: number } { + zxcvbnOptions.setOptions(options); + const result = zxcvbn(password); + return { scoreText: ratings[result.score]!, score: result.score }; +} diff --git a/packages/client/src/stores/onboardingStore.ts b/packages/client/src/stores/onboardingStore.ts index eca025f4c..e594e512f 100644 --- a/packages/client/src/stores/onboardingStore.ts +++ b/packages/client/src/stores/onboardingStore.ts @@ -1,4 +1,5 @@ import { useSnapshot } from 'valtio'; +import { Algorithm } from '../core'; import { HashingAlgoSlug } from '../utils'; import { valtioPersist } from './util'; @@ -15,7 +16,7 @@ const onboardingStoreDefaults = { unlockedScreens: ['start'], lastActiveScreen: null as string | null, shouldEncryptLibrary: false, - algorithm: 'XChaCha20Poly1305', + algorithm: 'XChaCha20Poly1305' as Algorithm, hashingAlgorithm: 'Argon2id-s' as HashingAlgoSlug, passwordSetToken: null as string | null, shareTelemetry: true, diff --git a/packages/client/src/stores/telemetryState.tsx b/packages/client/src/stores/telemetryState.tsx index c337f9302..602b650a7 100644 --- a/packages/client/src/stores/telemetryState.tsx +++ b/packages/client/src/stores/telemetryState.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { valtioPersist } from '.'; +import { valtioPersist } from './util'; export const telemetryStore = valtioPersist('sd-telemetryStore', { shareTelemetry: false // false by default, so functions cannot accidentally send data if the user has not decided diff --git a/packages/client/src/utils/keys.ts b/packages/client/src/utils/keys.ts index a680f9019..644032727 100644 --- a/packages/client/src/utils/keys.ts +++ b/packages/client/src/utils/keys.ts @@ -1,10 +1,6 @@ -import cryptoRandomString from 'crypto-random-string'; import { z } from 'zod'; import { HashingAlgorithm } from '../core'; -export const generatePassword = (length: number) => - cryptoRandomString({ length, type: 'ascii-printable' }); - export const hashingAlgoSlugSchema = z.union([ z.literal("Argon2id-s"), z.literal("Argon2id-h"), diff --git a/packages/ui/src/PasswordMeter.tsx b/packages/ui/src/PasswordMeter.tsx deleted file mode 100644 index 20393f691..000000000 --- a/packages/ui/src/PasswordMeter.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core'; -import zxcvbnCommonPackage from '@zxcvbn-ts/language-common'; -import zxcvbnEnPackage from '@zxcvbn-ts/language-en'; -import clsx from 'clsx'; - -const options = { - dictionary: { - ...zxcvbnCommonPackage.dictionary, - ...zxcvbnEnPackage.dictionary - }, - graps: zxcvbnCommonPackage.adjacencyGraphs, - translations: zxcvbnEnPackage.translations -}; -zxcvbnOptions.setOptions(options); - -const ratings = ['Poor', 'Weak', 'Good', 'Strong', 'Perfect']; - -export interface PasswordMeterProps { - password: string; -} - -export const PasswordMeter = (props: PasswordMeterProps) => { - const zx = zxcvbn(props.password); - - const widthCalcStyle = { - width: `${zx.score !== 0 ? zx.score * 25 : 12.5}%` - }; - - return ( -
-

Password strength

- - {ratings[zx.score]} - -
-
-
-
-
-
- ); -}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0f3188969..953bde36f 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -18,7 +18,6 @@ export * from './utils'; export * from './Tooltip'; export * from './Slider'; export * from './Divider'; -export * from './PasswordMeter'; export * from './Shortcut'; export * from './ProgressBar'; export * from './Folder'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 382ef84da12a39911018cc6f6c05909f78278786..7ae53aa8d9ef11efafe87c445716890dfb339d9c 100644 GIT binary patch delta 30793 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 MhG~4uf4V;TzZmxGX8-^I delta 20238 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 PyXQ_J;7#wDyYc@4|L&-w