diff options
-rw-r--r-- | android/settings.gradle | 4 | ||||
-rw-r--r-- | ios/Podfile | 2 | ||||
-rw-r--r-- | ios/Podfile.lock | 24 | ||||
-rw-r--r-- | ios/app.xcodeproj/project.pbxproj | 90 | ||||
-rw-r--r-- | ios/app/Info.plist | 2 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/state/models/user-local-photos.ts | 25 | ||||
-rw-r--r-- | src/view/com/composer/ComposePost.tsx | 51 | ||||
-rw-r--r-- | src/view/com/composer/PhotoCarouselPicker.tsx | 93 | ||||
-rw-r--r-- | src/view/com/composer/SelectedPhoto.tsx | 83 | ||||
-rw-r--r-- | src/view/index.ts | 6 | ||||
-rw-r--r-- | yarn.lock | 10 |
12 files changed, 341 insertions, 51 deletions
diff --git a/android/settings.gradle b/android/settings.gradle index 3c22b7a2c..e6c53dc6b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,8 @@ rootProject.name = 'app' +include ':@react-native-camera-roll_camera-roll' +project(':@react-native-camera-roll_camera-roll').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-camera-roll/camera-roll/android') +include ':@react-native-camera-roll_camera-roll' +project(':@react-native-camera-roll_camera-roll').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-camera-roll/camera-roll/android') apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' includeBuild('../node_modules/react-native-gradle-plugin') diff --git a/ios/Podfile b/ios/Podfile index f9a45704c..b4fa1f55f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -20,6 +20,8 @@ target 'app' do :app_path => "#{Pod::Config.instance.installation_root}/.." ) + pod 'react-native-cameraroll', :path => '../node_modules/@react-native-camera-roll/camera-roll' + target 'appTests' do inherit! :complete # Pods for testing diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 39b5a5ad0..37e55dacd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -220,6 +220,8 @@ PODS: - React-jsinspector (0.68.2) - React-logger (0.68.2): - glog + - react-native-cameraroll (5.1.0): + - React-Core - react-native-pager-view (6.0.2): - React-Core - react-native-safe-area-context (4.4.1): @@ -301,6 +303,15 @@ PODS: - React-Core - RNGestureHandler (2.6.1): - React-Core + - RNImageCropPicker (0.38.1): + - React-Core + - React-RCTImage + - RNImageCropPicker/QBImagePickerController (= 0.38.1) + - TOCropViewController + - RNImageCropPicker/QBImagePickerController (0.38.1): + - React-Core + - React-RCTImage + - TOCropViewController - RNInAppBrowser (3.7.0): - React-Core - RNReanimated (2.10.0): @@ -335,6 +346,7 @@ PODS: - React-RCTImage - RNSVG (12.4.4): - React-Core + - TOCropViewController (2.6.1) - Yoga (1.14.0) DEPENDENCIES: @@ -359,6 +371,7 @@ DEPENDENCIES: - 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-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - react-native-pager-view (from `../node_modules/react-native-pager-view`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - react-native-splash-screen (from `../node_modules/react-native-splash-screen`) @@ -377,6 +390,7 @@ DEPENDENCIES: - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) + - RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`) - RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`) - RNReanimated (from `../node_modules/react-native-reanimated`) - RNScreens (from `../node_modules/react-native-screens`) @@ -386,6 +400,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - fmt + - TOCropViewController EXTERNAL SOURCES: boost: @@ -426,6 +441,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-cameraroll: + :path: "../node_modules/@react-native-camera-roll/camera-roll" react-native-pager-view: :path: "../node_modules/react-native-pager-view" react-native-safe-area-context: @@ -462,6 +479,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-clipboard/clipboard" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" + RNImageCropPicker: + :path: "../node_modules/react-native-image-crop-picker" RNInAppBrowser: :path: "../node_modules/react-native-inappbrowser-reborn" RNReanimated: @@ -494,6 +513,7 @@ SPEC CHECKSUMS: React-jsiexecutor: b7b553412f2ec768fe6c8f27cd6bafdb9d8719e6 React-jsinspector: c5989c77cb89ae6a69561095a61cce56a44ae8e8 React-logger: a0833912d93b36b791b7a521672d8ee89107aff1 + react-native-cameraroll: a40b082318eb1ecd0336a2f29d9f74b7f2c8cae8 react-native-pager-view: 592421df0259bf7a7a4fe85b74c24f3f39905605 react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457 @@ -512,12 +532,14 @@ SPEC CHECKSUMS: RNCAsyncStorage: 0c357f3156fcb16c8589ede67cc036330b6698ca RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd RNGestureHandler: 28ad20bf02257791f7f137b31beef34b9549f54b + RNImageCropPicker: 648356d68fbf9911a1016b3e3723885d28373eda RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNReanimated: 5bdcbcc3a72aedeee7bb099604262403aa75a1e5 RNScreens: 0df01424e9e0ed7827200d6ed1087ddd06c493f9 RNSVG: ecd661f380a07ba690c9c5929c475a44f432d674 + TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 Yoga: 99652481fcd320aefa4a7ef90095b95acd181952 -PODFILE CHECKSUM: cf94853ebcb0d8e0d027dca9ab7a4ede886a8f20 +PODFILE CHECKSUM: 1c470a84819c5f003f8861cf58774e2fedb862b3 COCOAPODS: 1.11.3 diff --git a/ios/app.xcodeproj/project.pbxproj b/ios/app.xcodeproj/project.pbxproj index 914a272df..f964b1232 100644 --- a/ios/app.xcodeproj/project.pbxproj +++ b/ios/app.xcodeproj/project.pbxproj @@ -11,10 +11,10 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 5CEAE7B7A55582F96F1D5952 /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB672808307A6013805A3FE /* libPods-app.a */; }; + 6D37A166CEFC7E8937DE72CB /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 596ED3134AA9EF6DBAB6EB96 /* libPods-app.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; E4BBD590292C1F5200296224 /* app.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = E4437C9E28581FA7006DA9E7 /* app.entitlements */; }; - FEB90D21557517F9279AECA4 /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BAD3BC60FA05CF2D4F6F9BA2 /* libPods-app-appTests.a */; }; + F19C1FC2EC3C7162CB25E815 /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 16A4C892E71787BF5849EC83 /* libPods-app-appTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -31,21 +31,21 @@ 00E356EE1AD99517003FC87E /* appTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = appTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 00E356F21AD99517003FC87E /* appTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = appTests.m; sourceTree = "<group>"; }; - 03F7088487057F17D25D2F4B /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = "<group>"; }; + 0C0949EE4E80361188F0C002 /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = "<group>"; }; 13B07F961A680F5B00A75B9A /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = app/AppDelegate.h; sourceTree = "<group>"; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = app/AppDelegate.mm; sourceTree = "<group>"; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = app/Images.xcassets; sourceTree = "<group>"; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = app/Info.plist; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = app/main.m; sourceTree = "<group>"; }; - 19C43486502F2DE23E091FE3 /* Pods-app-appTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.debug.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.debug.xcconfig"; sourceTree = "<group>"; }; + 16A4C892E71787BF5849EC83 /* libPods-app-appTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-appTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 30A0A65C98339E6009F0927D /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = "<group>"; }; + 4DFF35494A5D1C813CE7EF11 /* Pods-app-appTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.debug.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.debug.xcconfig"; sourceTree = "<group>"; }; + 596ED3134AA9EF6DBAB6EB96 /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 685FA16DF9AC2B99AE3B5DBD /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = "<group>"; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = app/LaunchScreen.storyboard; sourceTree = "<group>"; }; - 970005155A83960D1D13380F /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = "<group>"; }; - BAD3BC60FA05CF2D4F6F9BA2 /* libPods-app-appTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-appTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E4437C9E28581FA7006DA9E7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = app.entitlements; path = app/app.entitlements; sourceTree = "<group>"; }; - ED22CAA45207BC18E75DB44B /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = "<group>"; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - FCB672808307A6013805A3FE /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,7 +53,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FEB90D21557517F9279AECA4 /* libPods-app-appTests.a in Frameworks */, + F19C1FC2EC3C7162CB25E815 /* libPods-app-appTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -61,7 +61,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5CEAE7B7A55582F96F1D5952 /* libPods-app.a in Frameworks */, + 6D37A166CEFC7E8937DE72CB /* libPods-app.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,8 +103,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - FCB672808307A6013805A3FE /* libPods-app.a */, - BAD3BC60FA05CF2D4F6F9BA2 /* libPods-app-appTests.a */, + 596ED3134AA9EF6DBAB6EB96 /* libPods-app.a */, + 16A4C892E71787BF5849EC83 /* libPods-app-appTests.a */, ); name = Frameworks; sourceTree = "<group>"; @@ -143,10 +143,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - ED22CAA45207BC18E75DB44B /* Pods-app.debug.xcconfig */, - 970005155A83960D1D13380F /* Pods-app.release.xcconfig */, - 19C43486502F2DE23E091FE3 /* Pods-app-appTests.debug.xcconfig */, - 03F7088487057F17D25D2F4B /* Pods-app-appTests.release.xcconfig */, + 0C0949EE4E80361188F0C002 /* Pods-app.debug.xcconfig */, + 30A0A65C98339E6009F0927D /* Pods-app.release.xcconfig */, + 4DFF35494A5D1C813CE7EF11 /* Pods-app-appTests.debug.xcconfig */, + 685FA16DF9AC2B99AE3B5DBD /* Pods-app-appTests.release.xcconfig */, ); path = Pods; sourceTree = "<group>"; @@ -158,11 +158,11 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "appTests" */; buildPhases = ( - F309585457BFF312EF73FD95 /* [CP] Check Pods Manifest.lock */, + D25A2C8B8B280E54CDBF5192 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - 2772CBCCE24036517E86445D /* [CP] Copy Pods Resources */, + 93C4A008E3B1F5BDF90AA282 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -178,13 +178,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "app" */; buildPhases = ( - 4D45C29570A58E7A8A8D177D /* [CP] Check Pods Manifest.lock */, + E5C10128C4EC047B9CD6EEFB /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 9CEFF2A5EA3D60C120442CA6 /* [CP] Copy Pods Resources */, + 29E5C2625DDCB23B01CF3257 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -266,63 +266,63 @@ shellPath = /bin/sh; shellScript = "set -e\n\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 2772CBCCE24036517E86445D /* [CP] Copy Pods Resources */ = { + 29E5C2625DDCB23B01CF3257 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 4D45C29570A58E7A8A8D177D /* [CP] Check Pods Manifest.lock */ = { + 93C4A008E3B1F5BDF90AA282 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-app-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app-appTests/Pods-app-appTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 9CEFF2A5EA3D60C120442CA6 /* [CP] Copy Pods Resources */ = { + D25A2C8B8B280E54CDBF5192 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-app-appTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F309585457BFF312EF73FD95 /* [CP] Check Pods Manifest.lock */ = { + E5C10128C4EC047B9CD6EEFB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -337,7 +337,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-app-appTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-app-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -396,7 +396,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 19C43486502F2DE23E091FE3 /* Pods-app-appTests.debug.xcconfig */; + baseConfigurationReference = 4DFF35494A5D1C813CE7EF11 /* Pods-app-appTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -424,7 +424,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 03F7088487057F17D25D2F4B /* Pods-app-appTests.release.xcconfig */; + baseConfigurationReference = 685FA16DF9AC2B99AE3B5DBD /* Pods-app-appTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; @@ -449,7 +449,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ED22CAA45207BC18E75DB44B /* Pods-app.debug.xcconfig */; + baseConfigurationReference = 0C0949EE4E80361188F0C002 /* Pods-app.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -478,7 +478,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 970005155A83960D1D13380F /* Pods-app.release.xcconfig */; + baseConfigurationReference = 30A0A65C98339E6009F0927D /* Pods-app.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/ios/app/Info.plist b/ios/app/Info.plist index 416d55759..5a9e7e53a 100644 --- a/ios/app/Info.plist +++ b/ios/app/Info.plist @@ -48,6 +48,8 @@ </dict> </dict> </dict> + <key>NSPhotoLibraryUsageDescription</key> + <string></string> <key>NSLocationWhenInUseUsageDescription</key> <string></string> <key>UILaunchStoryboardName</key> diff --git a/package.json b/package.json index 44b47d53c..3e0afb334 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fortawesome/react-native-fontawesome": "^0.3.0", "@gorhom/bottom-sheet": "^4", "@react-native-async-storage/async-storage": "^1.17.6", + "@react-native-camera-roll/camera-roll": "^5.1.0", "@react-native-clipboard/clipboard": "^1.10.0", "@zxing/text-encoding": "^0.9.0", "base64-js": "^1.5.1", @@ -34,6 +35,7 @@ "react-native": "0.68.2", "react-native-appstate-hook": "^1.0.6", "react-native-gesture-handler": "^2.5.0", + "react-native-image-crop-picker": "^0.38.1", "react-native-inappbrowser-reborn": "^3.6.3", "react-native-linear-gradient": "^2.6.2", "react-native-pager-view": "^6.0.2", diff --git a/src/state/models/user-local-photos.ts b/src/state/models/user-local-photos.ts new file mode 100644 index 000000000..4e01f1d94 --- /dev/null +++ b/src/state/models/user-local-photos.ts @@ -0,0 +1,25 @@ +import {PhotoIdentifier} from './../../../node_modules/@react-native-camera-roll/camera-roll/src/CameraRoll' +import {makeAutoObservable} from 'mobx' +import {CameraRoll} from '@react-native-camera-roll/camera-roll' +import {RootStoreModel} from './root-store' + +export class UserLocalPhotosModel { + // state + photos: PhotoIdentifier[] = [] + + constructor(public rootStore: RootStoreModel) { + makeAutoObservable(this, { + rootStore: false, + }) + } + + async setup() { + await this._getPhotos() + } + + private async _getPhotos() { + CameraRoll.getPhotos({first: 20}).then(r => { + this.photos = r.edges + }) + } +} diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx index a61759c24..114586f47 100644 --- a/src/view/com/composer/ComposePost.tsx +++ b/src/view/com/composer/ComposePost.tsx @@ -23,6 +23,9 @@ import * as apilib from '../../../state/lib/api' import {ComposerOpts} from '../../../state/models/shell-ui' import {s, colors, gradients} from '../../lib/styles' import {detectLinkables} from '../../../lib/strings' +import {UserLocalPhotosModel} from '../../../state/models/user-local-photos' +import {PhotoCarouselPicker} from './PhotoCarouselPicker' +import {SelectedPhoto} from './SelectedPhoto' const MAX_TEXT_LENGTH = 256 const DANGER_TEXT_LENGTH = MAX_TEXT_LENGTH @@ -41,14 +44,25 @@ export const ComposePost = observer(function ComposePost({ const [isProcessing, setIsProcessing] = useState(false) const [error, setError] = useState('') const [text, setText] = useState('') + const [selectedPhotos, setSelectedPhotos] = useState<string[]>([]) + const autocompleteView = useMemo<UserAutocompleteViewModel>( () => new UserAutocompleteViewModel(store), [], ) + const localPhotos = useMemo<UserLocalPhotosModel>( + () => new UserLocalPhotosModel(store), + [], + ) useEffect(() => { autocompleteView.setup() }) + + useEffect(() => { + localPhotos.setup() + }, [localPhotos]) + useEffect(() => { // HACK // wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view @@ -60,7 +74,9 @@ export const ComposePost = observer(function ComposePost({ }, 250) } return () => { - if (to) clearTimeout(to) + if (to) { + clearTimeout(to) + } } }, [textInput.current]) @@ -115,6 +131,10 @@ export const ComposePost = observer(function ComposePost({ const canPost = text.length <= MAX_TEXT_LENGTH const progressColor = text.length > DANGER_TEXT_LENGTH ? '#e60000' : undefined + const selectTextInputLayout = + selectedPhotos.length !== 0 + ? styles.textInputLayoutWithPhoto + : styles.textInputLayoutWithoutPhoto const textDecorated = useMemo(() => { let i = 0 @@ -192,7 +212,7 @@ export const ComposePost = observer(function ComposePost({ </View> </View> ) : undefined} - <View style={styles.textInputLayout}> + <View style={[styles.textInputLayout, selectTextInputLayout]}> <UserAvatar handle={store.me.handle || ''} displayName={store.me.displayName} @@ -208,8 +228,18 @@ export const ComposePost = observer(function ComposePost({ {textDecorated} </TextInput> </View> - <View - style={[s.flexRow, {alignItems: 'center'}, s.pt10, s.pb10, s.pr5]}> + <SelectedPhoto + selectedPhotos={selectedPhotos} + setSelectedPhotos={setSelectedPhotos} + /> + <PhotoCarouselPicker + selectedPhotos={selectedPhotos} + setSelectedPhotos={setSelectedPhotos} + localPhotos={localPhotos} + inputText={text} + /> + <View style={styles.separator} /> + <View style={[s.flexRow, s.pt10, s.pb10, s.pr5, styles.contentCenter]}> <View style={s.flex1} /> <Text style={[s.mr10, {color: progressColor}]}> {MAX_TEXT_LENGTH - text.length} @@ -282,9 +312,14 @@ const styles = StyleSheet.create({ justifyContent: 'center', marginRight: 5, }, + textInputLayoutWithPhoto: { + flexWrap: 'wrap', + }, + textInputLayoutWithoutPhoto: { + flex: 1, + }, textInputLayout: { flexDirection: 'row', - flex: 1, borderTopWidth: 1, borderTopColor: colors.gray2, paddingTop: 16, @@ -307,4 +342,10 @@ const styles = StyleSheet.create({ paddingLeft: 13, paddingRight: 8, }, + contentCenter: {alignItems: 'center'}, + separator: { + borderBottomColor: 'black', + borderBottomWidth: StyleSheet.hairlineWidth, + width: '100%', + }, }) diff --git a/src/view/com/composer/PhotoCarouselPicker.tsx b/src/view/com/composer/PhotoCarouselPicker.tsx new file mode 100644 index 000000000..17be8a5f5 --- /dev/null +++ b/src/view/com/composer/PhotoCarouselPicker.tsx @@ -0,0 +1,93 @@ +import React from 'react' +import {Image, StyleSheet, TouchableOpacity, ScrollView} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {colors} from '../../lib/styles' +import {openPicker, openCamera} from 'react-native-image-crop-picker' +import {observer} from 'mobx-react-lite' + +export const PhotoCarouselPicker = observer(function PhotoCarouselPicker({ + selectedPhotos, + setSelectedPhotos, + inputText, + localPhotos, +}: { + selectedPhotos: string[] + setSelectedPhotos: React.Dispatch<React.SetStateAction<string[]>> + inputText: string + localPhotos: any +}) { + return localPhotos.photos != null && + inputText === '' && + selectedPhotos.length === 0 ? ( + <ScrollView + horizontal + style={styles.photosContainer} + showsHorizontalScrollIndicator={false}> + <TouchableOpacity + style={[styles.galleryButton, styles.photo]} + onPress={() => { + openCamera({multiple: true, maxFiles: 4}).then() + }}> + <FontAwesomeIcon + icon="camera" + size={24} + style={{color: colors.blue3}} + /> + </TouchableOpacity> + {localPhotos.photos.map((item: any, index: number) => ( + <TouchableOpacity + key={`local-image-${index}`} + style={styles.photoButton} + onPress={() => { + setSelectedPhotos([item.node.image.uri, ...selectedPhotos]) + }}> + <Image style={styles.photo} source={{uri: item.node.image.uri}} /> + </TouchableOpacity> + ))} + <TouchableOpacity + style={[styles.galleryButton, styles.photo]} + onPress={() => { + openPicker({multiple: true, maxFiles: 4}).then(items => { + setSelectedPhotos([ + ...items.reduce( + (accum, cur) => accum.concat(cur.sourceURL!), + [] as string[], + ), + ...selectedPhotos, + ]) + }) + }}> + <FontAwesomeIcon icon="image" style={{color: colors.blue3}} size={24} /> + </TouchableOpacity> + </ScrollView> + ) : null +}) + +const styles = StyleSheet.create({ + photosContainer: { + width: '100%', + maxHeight: 96, + padding: 8, + overflow: 'hidden', + }, + galleryButton: { + borderWidth: 1, + borderColor: colors.gray3, + alignItems: 'center', + justifyContent: 'center', + }, + photoButton: { + width: 75, + height: 75, + marginRight: 8, + borderWidth: 1, + borderRadius: 16, + borderColor: colors.gray3, + }, + photo: { + width: 75, + height: 75, + marginRight: 8, + borderRadius: 16, + }, +}) diff --git a/src/view/com/composer/SelectedPhoto.tsx b/src/view/com/composer/SelectedPhoto.tsx new file mode 100644 index 000000000..1fe147483 --- /dev/null +++ b/src/view/com/composer/SelectedPhoto.tsx @@ -0,0 +1,83 @@ +import React from 'react' +import {Image, StyleSheet, TouchableOpacity, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {colors} from '../../lib/styles' +import {observer} from 'mobx-react-lite' + +export const SelectedPhoto = ({ + selectedPhotos, + setSelectedPhotos, +}: { + selectedPhotos: string[] + setSelectedPhotos: React.Dispatch<React.SetStateAction<string[]>> +}) => { + const imageStyle = + selectedPhotos.length === 1 + ? styles.image250 + : selectedPhotos.length === 2 + ? styles.image175 + : styles.image85 + + return selectedPhotos.length !== 0 ? ( + <View style={styles.imageContainer}> + {selectedPhotos.length !== 0 && + selectedPhotos.map((item, index) => ( + <View + key={`selected-image-${index}`} + style={[styles.image, imageStyle]}> + <TouchableOpacity + onPress={() => { + setSelectedPhotos( + selectedPhotos.filter(filterItem => filterItem !== item), + ) + }} + style={styles.removePhotoButton}> + <FontAwesomeIcon + icon="xmark" + size={16} + style={{color: colors.white}} + /> + </TouchableOpacity> + + <Image style={[styles.image, imageStyle]} source={{uri: item}} /> + </View> + ))} + </View> + ) : null +} + +const styles = StyleSheet.create({ + imageContainer: { + flex: 1, + flexDirection: 'row', + marginTop: 16, + }, + image: { + borderRadius: 8, + margin: 2, + }, + image250: { + width: 250, + height: 250, + }, + image175: { + width: 175, + height: 175, + }, + image85: { + width: 85, + height: 85, + }, + removePhotoButton: { + position: 'absolute', + top: 8, + right: 8, + width: 24, + height: 24, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.black, + zIndex: 1, + }, +}) diff --git a/src/view/index.ts b/src/view/index.ts index e38e1debf..bd0e33cbe 100644 --- a/src/view/index.ts +++ b/src/view/index.ts @@ -56,6 +56,9 @@ import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark' import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket' import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan' import {faX} from '@fortawesome/free-solid-svg-icons/faX' +import {faCamera} from '@fortawesome/free-solid-svg-icons/faCamera' +import {faImage} from '@fortawesome/free-solid-svg-icons/faImage' +import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark' export function setup() { library.add( @@ -115,5 +118,8 @@ export function setup() { faTicket, faTrashCan, faX, + faCamera, + faImage, + faXmark, ) } diff --git a/yarn.lock b/yarn.lock index 1160158e1..bbaaa2d6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1897,6 +1897,11 @@ dependencies: merge-options "^3.0.4" +"@react-native-camera-roll/camera-roll@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@react-native-camera-roll/camera-roll/-/camera-roll-5.1.0.tgz#5cfb3cf02d72ab03b3d6a0bdda392e2896c8d55f" + integrity sha512-74pavpt2T2U3V0r5d+pn4NChJbRNcydqakp3NVmosod35Lzxrt9My7kCLDdHXW2S6J6DhgXb/n36/heZQB4AUA== + "@react-native-clipboard/clipboard@^1.10.0": version "1.11.1" resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.11.1.tgz#d3a9e685ce2383b1e92b89a334896c5575cc103d" @@ -10191,6 +10196,11 @@ react-native-gradle-plugin@^0.0.6: resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.6.tgz#b61a9234ad2f61430937911003cddd7e15c72b45" integrity sha512-eIlgtsmDp1jLC24dRn43hB3kEcZVqx6DUQbR0N1ABXGnMEafm9I3V3dUUeD1vh+Dy5WqijSoEwLNUPLgu5zDMg== +react-native-image-crop-picker@^0.38.1: + version "0.38.1" + resolved "https://registry.yarnpkg.com/react-native-image-crop-picker/-/react-native-image-crop-picker-0.38.1.tgz#5973b4a8b55835b987e6be2064de411e849ac005" + integrity sha512-cF5UQnWplzHCeiCO+aiGS/0VomWaLmFf3nSsgTMPfY+8+99h8N/eHQvVdSF7RsGw50B8394wGeGyqHjjp8YRWw== + react-native-inappbrowser-reborn@^3.6.3: version "3.7.0" resolved "https://registry.yarnpkg.com/react-native-inappbrowser-reborn/-/react-native-inappbrowser-reborn-3.7.0.tgz#849a43c3c7da22b65147649fe596836bcb494083" |