react-native-capture-studio 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +86 -0
  3. package/android/build.gradle +139 -0
  4. package/android/generated/java/com/capturestudio/NativeCaptureStudioSpec.java +48 -0
  5. package/android/generated/jni/CMakeLists.txt +31 -0
  6. package/android/generated/jni/RNCaptureStudioSpec-generated.cpp +44 -0
  7. package/android/generated/jni/RNCaptureStudioSpec.h +31 -0
  8. package/android/generated/jni/react/renderer/components/RNCaptureStudioSpec/RNCaptureStudioSpecJSI.h +56 -0
  9. package/android/gradle.properties +5 -0
  10. package/android/src/main/AndroidManifest.xml +13 -0
  11. package/android/src/main/AndroidManifestNew.xml +2 -0
  12. package/android/src/main/java/com/capturestudio/CaptureStudioModule.kt +177 -0
  13. package/android/src/main/java/com/capturestudio/CaptureStudioPackage.kt +33 -0
  14. package/android/src/main/java/com/capturestudio/data/CameraRepository.kt +43 -0
  15. package/android/src/main/java/com/capturestudio/data/processing/ImageProcessingWorker.kt +126 -0
  16. package/android/src/main/java/com/capturestudio/data/processing/ImageProcessor.kt +244 -0
  17. package/android/src/main/java/com/capturestudio/domain/CaptureOptions.kt +0 -0
  18. package/android/src/main/java/com/capturestudio/domain/model/ImageProcessingItem.kt +18 -0
  19. package/android/src/main/java/com/capturestudio/domain/model/ProcessingResult.kt +14 -0
  20. package/android/src/main/java/com/capturestudio/ui/camera/CameraActivity.kt +55 -0
  21. package/android/src/main/java/com/capturestudio/ui/camera/CameraUiState.kt +7 -0
  22. package/android/src/main/java/com/capturestudio/ui/camera/CameraViewModel.kt +34 -0
  23. package/android/src/main/java/com/capturestudio/ui/camera/CameraViewModelFactory.kt +17 -0
  24. package/android/src/main/res/layout/activity_camera.xml +10 -0
  25. package/ios/CaptureStudio.h +7 -0
  26. package/ios/CaptureStudio.mm +186 -0
  27. package/ios/ImageProcessor.h +22 -0
  28. package/ios/ImageProcessor.mm +383 -0
  29. package/ios/generated/Package.swift +59 -0
  30. package/ios/generated/ReactAppDependencyProvider/RCTAppDependencyProvider.h +25 -0
  31. package/ios/generated/ReactAppDependencyProvider/RCTAppDependencyProvider.mm +40 -0
  32. package/ios/generated/ReactAppDependencyProvider/ReactAppDependencyProvider.podspec +34 -0
  33. package/ios/generated/ReactCodegen/RCTModuleProviders.h +16 -0
  34. package/ios/generated/ReactCodegen/RCTModuleProviders.mm +51 -0
  35. package/ios/generated/ReactCodegen/RCTModulesConformingToProtocolsProvider.h +18 -0
  36. package/ios/generated/ReactCodegen/RCTModulesConformingToProtocolsProvider.mm +54 -0
  37. package/ios/generated/ReactCodegen/RCTThirdPartyComponentsProvider.h +16 -0
  38. package/ios/generated/ReactCodegen/RCTThirdPartyComponentsProvider.mm +30 -0
  39. package/ios/generated/ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.h +14 -0
  40. package/ios/generated/ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.mm +19 -0
  41. package/ios/generated/ReactCodegen/RNCaptureStudioSpec/RNCaptureStudioSpec-generated.mm +53 -0
  42. package/ios/generated/ReactCodegen/RNCaptureStudioSpec/RNCaptureStudioSpec.h +70 -0
  43. package/ios/generated/ReactCodegen/RNCaptureStudioSpecJSI.h +56 -0
  44. package/ios/generated/ReactCodegen/ReactCodegen.podspec +110 -0
  45. package/lib/commonjs/NativeCaptureStudio.js +9 -0
  46. package/lib/commonjs/NativeCaptureStudio.js.map +1 -0
  47. package/lib/commonjs/index.js +20 -0
  48. package/lib/commonjs/index.js.map +1 -0
  49. package/lib/module/NativeCaptureStudio.js +5 -0
  50. package/lib/module/NativeCaptureStudio.js.map +1 -0
  51. package/lib/module/index.js +13 -0
  52. package/lib/module/index.js.map +1 -0
  53. package/lib/typescript/commonjs/package.json +1 -0
  54. package/lib/typescript/commonjs/src/NativeCaptureStudio.d.ts +9 -0
  55. package/lib/typescript/commonjs/src/NativeCaptureStudio.d.ts.map +1 -0
  56. package/lib/typescript/commonjs/src/index.d.ts +19 -0
  57. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  58. package/lib/typescript/module/package.json +1 -0
  59. package/lib/typescript/module/src/NativeCaptureStudio.d.ts +9 -0
  60. package/lib/typescript/module/src/NativeCaptureStudio.d.ts.map +1 -0
  61. package/lib/typescript/module/src/index.d.ts +19 -0
  62. package/lib/typescript/module/src/index.d.ts.map +1 -0
  63. package/package.json +202 -0
  64. package/react-native-capture-studio.podspec +48 -0
  65. package/react-native.config.js +12 -0
  66. package/src/NativeCaptureStudio.ts +11 -0
  67. package/src/index.tsx +30 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 varun-gandhi-digiqc
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # react-native-capture-studio
2
+
3
+ Cross-platform native image compression and watermarking for React Native.
4
+
5
+ ## Features
6
+
7
+ - **Background Processing** - Images processed off the main thread
8
+ - **WebP Compression** - Better quality at smaller file sizes than JPEG
9
+ - **Auto Watermark** - Adds timestamp text at bottom-right corner
10
+ - **Target Size** - Compresses to 300-500KB with highest possible quality
11
+ - **In-place Replace** - Overwrites original file (path stays the same)
12
+ - **Cross Platform** - Works on both iOS and Android
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ yarn add git+https://github.com/gandhi120/react-native-capture-studio.git
18
+ ```
19
+
20
+ ### iOS Setup
21
+
22
+ ```bash
23
+ cd ios && pod install && cd ..
24
+ ```
25
+
26
+ **Requires iOS 15.1+**
27
+
28
+ ### Android Setup
29
+
30
+ No additional setup required.
31
+
32
+ **Requires Android 11+ (API 30)**
33
+
34
+ ## Usage
35
+
36
+ ### Basic Usage
37
+
38
+ ```typescript
39
+ import { processImages, fetchProcessingResult } from 'react-native-capture-studio';
40
+
41
+ const compressImage = async (imagePath: string) => {
42
+ const operationId = await processImages([
43
+ {
44
+ localPath: imagePath,
45
+ timeStamp: new Date().toLocaleString(),
46
+ isForOnlyWatermark: false,
47
+ compressJpegImage: false,
48
+ replaceOriginal: true,
49
+ }
50
+ ]);
51
+
52
+ const poll = async () => {
53
+ const result = JSON.parse(await fetchProcessingResult(operationId));
54
+
55
+ if (result.status === 'completed') {
56
+ console.log('Done!', result.processedImages);
57
+ } else {
58
+ setTimeout(poll, 500);
59
+ }
60
+ };
61
+
62
+ poll();
63
+ };
64
+ ```
65
+
66
+ ### Multiple Images
67
+
68
+ ```typescript
69
+ const compressMultipleImages = async (imagePaths: string[]) => {
70
+ const images = imagePaths.map(path => ({
71
+ localPath: path,
72
+ timeStamp: new Date().toLocaleString(),
73
+ isForOnlyWatermark: false,
74
+ compressJpegImage: false,
75
+ replaceOriginal: true,
76
+ }));
77
+
78
+ const operationId = await processImages(images);
79
+
80
+ // Poll for result...
81
+ };
82
+ ```
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,139 @@
1
+ buildscript {
2
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["CaptureStudio_kotlinVersion"]
4
+
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+
10
+ dependencies {
11
+ classpath "com.android.tools.build:gradle:8.7.3"
12
+ // noinspection DifferentKotlinGradleVersion
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
+ }
15
+ }
16
+
17
+ def reactNativeArchitectures() {
18
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
19
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
20
+ }
21
+
22
+ def isNewArchitectureEnabled() {
23
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
24
+ }
25
+
26
+ apply plugin: "com.android.library"
27
+ apply plugin: "kotlin-android"
28
+
29
+ if (isNewArchitectureEnabled()) {
30
+ apply plugin: "com.facebook.react"
31
+ }
32
+
33
+ def getExtOrDefault(name) {
34
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["CaptureStudio_" + name]
35
+ }
36
+
37
+ def getExtOrIntegerDefault(name) {
38
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["CaptureStudio_" + name]).toInteger()
39
+ }
40
+
41
+ def supportsNamespace() {
42
+ def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
43
+ def major = parsed[0].toInteger()
44
+ def minor = parsed[1].toInteger()
45
+
46
+ // Namespace support was added in 7.3.0
47
+ return (major == 7 && minor >= 3) || major >= 8
48
+ }
49
+
50
+ android {
51
+ if (supportsNamespace()) {
52
+ namespace "com.capturestudio"
53
+
54
+ sourceSets {
55
+ main {
56
+ manifest.srcFile "src/main/AndroidManifestNew.xml"
57
+ }
58
+ }
59
+ }
60
+
61
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
62
+
63
+ defaultConfig {
64
+ minSdkVersion 29
65
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
66
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
67
+
68
+ }
69
+
70
+ buildFeatures {
71
+ buildConfig true
72
+ }
73
+
74
+ buildTypes {
75
+ release {
76
+ minifyEnabled false
77
+ }
78
+ }
79
+
80
+ lintOptions {
81
+ disable "GradleCompatible"
82
+ }
83
+
84
+ compileOptions {
85
+ sourceCompatibility JavaVersion.VERSION_1_8
86
+ targetCompatibility JavaVersion.VERSION_1_8
87
+ }
88
+
89
+ sourceSets {
90
+ main {
91
+ if (isNewArchitectureEnabled()) {
92
+ java.srcDirs += [
93
+ "generated/java",
94
+ "generated/jni"
95
+ ]
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ repositories {
102
+ mavenCentral()
103
+ google()
104
+ }
105
+
106
+ def kotlin_version = getExtOrDefault("kotlinVersion")
107
+
108
+ dependencies {
109
+ // For < 0.71, this will be from the local maven repo
110
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
111
+ //noinspection GradleDynamicVersion
112
+ implementation "com.facebook.react:react-native:+"
113
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
114
+
115
+ // Lifecycle and viewModel
116
+ implementation "androidx.activity:activity-ktx:1.9.0"
117
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4"
118
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.4"
119
+
120
+ // CameraX
121
+ implementation "androidx.camera:camera-core:1.3.4"
122
+ implementation "androidx.camera:camera-camera2:1.3.4"
123
+ implementation "androidx.camera:camera-lifecycle:1.3.4"
124
+ implementation "androidx.camera:camera-view:1.3.4"
125
+
126
+ // WorkManager for background image processing
127
+ implementation "androidx.work:work-runtime-ktx:2.9.0"
128
+
129
+ // ExifInterface for image orientation
130
+ implementation "androidx.exifinterface:exifinterface:1.3.7"
131
+ }
132
+
133
+ if (isNewArchitectureEnabled()) {
134
+ react {
135
+ jsRootDir = file("../src/")
136
+ libraryName = "CaptureStudio"
137
+ codegenJavaPackageName = "com.capturestudio"
138
+ }
139
+ }
@@ -0,0 +1,48 @@
1
+
2
+ /**
3
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
4
+ *
5
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
6
+ * once the code is regenerated.
7
+ *
8
+ * @generated by codegen project: GenerateModuleJavaSpec.js
9
+ *
10
+ * @nolint
11
+ */
12
+
13
+ package com.capturestudio;
14
+
15
+ import com.facebook.proguard.annotations.DoNotStrip;
16
+ import com.facebook.react.bridge.Promise;
17
+ import com.facebook.react.bridge.ReactApplicationContext;
18
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
19
+ import com.facebook.react.bridge.ReactMethod;
20
+ import com.facebook.react.bridge.ReadableArray;
21
+ import com.facebook.react.bridge.ReadableMap;
22
+ import com.facebook.react.turbomodule.core.interfaces.TurboModule;
23
+ import javax.annotation.Nonnull;
24
+
25
+ public abstract class NativeCaptureStudioSpec extends ReactContextBaseJavaModule implements TurboModule {
26
+ public static final String NAME = "CaptureStudio";
27
+
28
+ public NativeCaptureStudioSpec(ReactApplicationContext reactContext) {
29
+ super(reactContext);
30
+ }
31
+
32
+ @Override
33
+ public @Nonnull String getName() {
34
+ return NAME;
35
+ }
36
+
37
+ @ReactMethod
38
+ @DoNotStrip
39
+ public abstract void openCaptureStudio(ReadableMap options, Promise promise);
40
+
41
+ @ReactMethod
42
+ @DoNotStrip
43
+ public abstract void processImages(ReadableArray images, Promise promise);
44
+
45
+ @ReactMethod
46
+ @DoNotStrip
47
+ public abstract void fetchProcessingResult(String operationId, Promise promise);
48
+ }
@@ -0,0 +1,31 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ cmake_minimum_required(VERSION 3.13)
7
+ set(CMAKE_VERBOSE_MAKEFILE on)
8
+
9
+ file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/RNCaptureStudioSpec/*.cpp)
10
+
11
+ add_library(
12
+ react_codegen_RNCaptureStudioSpec
13
+ OBJECT
14
+ ${react_codegen_SRCS}
15
+ )
16
+
17
+ target_include_directories(react_codegen_RNCaptureStudioSpec PUBLIC . react/renderer/components/RNCaptureStudioSpec)
18
+
19
+ target_link_libraries(
20
+ react_codegen_RNCaptureStudioSpec
21
+ fbjni
22
+ jsi
23
+ # We need to link different libraries based on whether we are building rncore or not, that's necessary
24
+ # because we want to break a circular dependency between react_codegen_rncore and reactnative
25
+ reactnative
26
+ )
27
+
28
+ # Only call if the function exists (added in RN 0.79+)
29
+ if(COMMAND target_compile_reactnative_options)
30
+ target_compile_reactnative_options(react_codegen_RNCaptureStudioSpec PRIVATE)
31
+ endif()
@@ -0,0 +1,44 @@
1
+
2
+ /**
3
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
4
+ *
5
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
6
+ * once the code is regenerated.
7
+ *
8
+ * @generated by codegen project: GenerateModuleJniCpp.js
9
+ */
10
+
11
+ #include "RNCaptureStudioSpec.h"
12
+
13
+ namespace facebook::react {
14
+
15
+ static facebook::jsi::Value __hostFunction_NativeCaptureStudioSpecJSI_openCaptureStudio(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
16
+ static jmethodID cachedMethodId = nullptr;
17
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "openCaptureStudio", "(Lcom/facebook/react/bridge/ReadableMap;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
18
+ }
19
+
20
+ static facebook::jsi::Value __hostFunction_NativeCaptureStudioSpecJSI_processImages(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
21
+ static jmethodID cachedMethodId = nullptr;
22
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "processImages", "(Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
23
+ }
24
+
25
+ static facebook::jsi::Value __hostFunction_NativeCaptureStudioSpecJSI_fetchProcessingResult(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
26
+ static jmethodID cachedMethodId = nullptr;
27
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "fetchProcessingResult", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
28
+ }
29
+
30
+ NativeCaptureStudioSpecJSI::NativeCaptureStudioSpecJSI(const JavaTurboModule::InitParams &params)
31
+ : JavaTurboModule(params) {
32
+ methodMap_["openCaptureStudio"] = MethodMetadata {1, __hostFunction_NativeCaptureStudioSpecJSI_openCaptureStudio};
33
+ methodMap_["processImages"] = MethodMetadata {1, __hostFunction_NativeCaptureStudioSpecJSI_processImages};
34
+ methodMap_["fetchProcessingResult"] = MethodMetadata {1, __hostFunction_NativeCaptureStudioSpecJSI_fetchProcessingResult};
35
+ }
36
+
37
+ std::shared_ptr<TurboModule> RNCaptureStudioSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
38
+ if (moduleName == "CaptureStudio") {
39
+ return std::make_shared<NativeCaptureStudioSpecJSI>(params);
40
+ }
41
+ return nullptr;
42
+ }
43
+
44
+ } // namespace facebook::react
@@ -0,0 +1,31 @@
1
+
2
+ /**
3
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
4
+ *
5
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
6
+ * once the code is regenerated.
7
+ *
8
+ * @generated by codegen project: GenerateModuleJniH.js
9
+ */
10
+
11
+ #pragma once
12
+
13
+ #include <ReactCommon/JavaTurboModule.h>
14
+ #include <ReactCommon/TurboModule.h>
15
+ #include <jsi/jsi.h>
16
+
17
+ namespace facebook::react {
18
+
19
+ /**
20
+ * JNI C++ class for module 'NativeCaptureStudio'
21
+ */
22
+ class JSI_EXPORT NativeCaptureStudioSpecJSI : public JavaTurboModule {
23
+ public:
24
+ NativeCaptureStudioSpecJSI(const JavaTurboModule::InitParams &params);
25
+ };
26
+
27
+
28
+ JSI_EXPORT
29
+ std::shared_ptr<TurboModule> RNCaptureStudioSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
30
+
31
+ } // namespace facebook::react
@@ -0,0 +1,56 @@
1
+ /**
2
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
3
+ *
4
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
5
+ * once the code is regenerated.
6
+ *
7
+ * @generated by codegen project: GenerateModuleH.js
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include <ReactCommon/TurboModule.h>
13
+ #include <react/bridging/Bridging.h>
14
+
15
+ namespace facebook::react {
16
+
17
+
18
+ template <typename T>
19
+ class JSI_EXPORT NativeCaptureStudioCxxSpec : public TurboModule {
20
+ public:
21
+ static constexpr std::string_view kModuleName = "CaptureStudio";
22
+
23
+ protected:
24
+ NativeCaptureStudioCxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{NativeCaptureStudioCxxSpec::kModuleName}, jsInvoker) {
25
+ methodMap_["openCaptureStudio"] = MethodMetadata {.argCount = 1, .invoker = __openCaptureStudio};
26
+ methodMap_["processImages"] = MethodMetadata {.argCount = 1, .invoker = __processImages};
27
+ methodMap_["fetchProcessingResult"] = MethodMetadata {.argCount = 1, .invoker = __fetchProcessingResult};
28
+ }
29
+
30
+ private:
31
+ static jsi::Value __openCaptureStudio(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
32
+ static_assert(
33
+ bridging::getParameterCount(&T::openCaptureStudio) == 2,
34
+ "Expected openCaptureStudio(...) to have 2 parameters");
35
+ return bridging::callFromJs<jsi::Value>(rt, &T::openCaptureStudio, static_cast<NativeCaptureStudioCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
36
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt));
37
+ }
38
+
39
+ static jsi::Value __processImages(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
40
+ static_assert(
41
+ bridging::getParameterCount(&T::processImages) == 2,
42
+ "Expected processImages(...) to have 2 parameters");
43
+ return bridging::callFromJs<jsi::Value>(rt, &T::processImages, static_cast<NativeCaptureStudioCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
44
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt).asArray(rt));
45
+ }
46
+
47
+ static jsi::Value __fetchProcessingResult(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
48
+ static_assert(
49
+ bridging::getParameterCount(&T::fetchProcessingResult) == 2,
50
+ "Expected fetchProcessingResult(...) to have 2 parameters");
51
+ return bridging::callFromJs<jsi::Value>(rt, &T::fetchProcessingResult, static_cast<NativeCaptureStudioCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
52
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt));
53
+ }
54
+ };
55
+
56
+ } // namespace facebook::react
@@ -0,0 +1,5 @@
1
+ CaptureStudio_kotlinVersion=1.7.0
2
+ CaptureStudio_minSdkVersion=21
3
+ CaptureStudio_targetSdkVersion=31
4
+ CaptureStudio_compileSdkVersion=31
5
+ CaptureStudio_ndkversion=21.4.7075529
@@ -0,0 +1,13 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.capturestudio">
3
+
4
+ <uses-permission android:name="android.permission.CAMERA" />
5
+
6
+ <application>
7
+ <activity
8
+ android:name="com.capturestudio.ui.camera.CameraActivity"
9
+ android:exported="false"
10
+ android:theme="@style/Theme.AppCompat.NoActionBar" />
11
+ </application>
12
+
13
+ </manifest>
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,177 @@
1
+ package com.capturestudio
2
+
3
+ import android.content.Intent
4
+ import android.util.Log
5
+ import androidx.work.Data
6
+ import androidx.work.OneTimeWorkRequestBuilder
7
+ import androidx.work.WorkInfo
8
+ import androidx.work.WorkManager
9
+ import com.capturestudio.data.processing.ImageProcessingWorker
10
+ import com.capturestudio.ui.camera.CameraActivity
11
+ import com.facebook.react.bridge.Promise
12
+ import com.facebook.react.bridge.ReactApplicationContext
13
+ import com.facebook.react.bridge.ReadableArray
14
+ import com.facebook.react.bridge.ReadableMap
15
+ import com.facebook.react.module.annotations.ReactModule
16
+ import org.json.JSONArray
17
+ import org.json.JSONObject
18
+ import java.util.UUID
19
+ import java.util.concurrent.ExecutionException
20
+
21
+ @ReactModule(name = CaptureStudioModule.NAME)
22
+ class CaptureStudioModule(reactContext: ReactApplicationContext) :
23
+ NativeCaptureStudioSpec(reactContext) {
24
+
25
+ companion object {
26
+ const val NAME = "CaptureStudio"
27
+ private const val TAG = "CaptureStudioModule"
28
+ }
29
+
30
+ override fun getName(): String = NAME
31
+
32
+ /**
33
+ * Open the native camera capture UI.
34
+ */
35
+ override fun openCaptureStudio(options: ReadableMap, promise: Promise) {
36
+ val intent = Intent(reactApplicationContext, CameraActivity::class.java)
37
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
38
+ reactApplicationContext.startActivity(intent)
39
+ promise.resolve(null)
40
+ }
41
+
42
+ /**
43
+ * Process images with compression and watermarking.
44
+ * Returns immediately with an operation ID to poll for results.
45
+ *
46
+ * @param images Array of image processing items
47
+ * @param promise Resolves with operation ID (UUID string)
48
+ */
49
+ override fun processImages(images: ReadableArray, promise: Promise) {
50
+ try {
51
+ // Convert ReadableArray to JSON string
52
+ val imagesJson = convertArrayToJson(images)
53
+ Log.d(TAG, "Processing ${images.size()} images")
54
+
55
+ // Create input data for worker
56
+ val inputData = Data.Builder()
57
+ .putString(ImageProcessingWorker.KEY_IMAGES, imagesJson)
58
+ .build()
59
+
60
+ // Create and enqueue work request
61
+ val workRequest = OneTimeWorkRequestBuilder<ImageProcessingWorker>()
62
+ .setInputData(inputData)
63
+ .build()
64
+
65
+ WorkManager.getInstance(reactApplicationContext)
66
+ .enqueue(workRequest)
67
+
68
+ // Return the work ID for polling
69
+ val operationId = workRequest.id.toString()
70
+ Log.d(TAG, "Enqueued work with ID: $operationId")
71
+ promise.resolve(operationId)
72
+
73
+ } catch (e: Exception) {
74
+ Log.e(TAG, "Failed to enqueue image processing", e)
75
+ promise.reject("PROCESS_ERROR", e.message, e)
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Fetch the result of an image processing operation.
81
+ *
82
+ * @param operationId The operation ID returned by processImages
83
+ * @param promise Resolves with JSON string containing status and results
84
+ */
85
+ override fun fetchProcessingResult(operationId: String, promise: Promise) {
86
+ try {
87
+ val workId = UUID.fromString(operationId)
88
+ val workManager = WorkManager.getInstance(reactApplicationContext)
89
+
90
+ // Get work info synchronously
91
+ val workInfo = workManager.getWorkInfoById(workId).get()
92
+
93
+ if (workInfo == null) {
94
+ promise.reject("NOT_FOUND", "Operation not found: $operationId")
95
+ return
96
+ }
97
+
98
+ when (workInfo.state) {
99
+ WorkInfo.State.SUCCEEDED -> {
100
+ val results = workInfo.outputData.getString(ImageProcessingWorker.KEY_RESULTS)
101
+ val response = JSONObject().apply {
102
+ put("status", "completed")
103
+ put("processedImages", JSONArray(results ?: "[]"))
104
+ }
105
+ Log.d(TAG, "Operation $operationId completed successfully")
106
+
107
+ // Clean up completed work
108
+ workManager.cancelWorkById(workId)
109
+
110
+ promise.resolve(response.toString())
111
+ }
112
+
113
+ WorkInfo.State.FAILED -> {
114
+ Log.e(TAG, "Operation $operationId failed")
115
+ promise.reject("PROCESSING_FAILED", "Image processing failed")
116
+ }
117
+
118
+ WorkInfo.State.CANCELLED -> {
119
+ Log.w(TAG, "Operation $operationId was cancelled")
120
+ promise.reject("CANCELLED", "Operation was cancelled")
121
+ }
122
+
123
+ WorkInfo.State.RUNNING,
124
+ WorkInfo.State.ENQUEUED -> {
125
+ val response = JSONObject().apply {
126
+ put("status", "processing")
127
+ }
128
+ promise.resolve(response.toString())
129
+ }
130
+
131
+ WorkInfo.State.BLOCKED -> {
132
+ val response = JSONObject().apply {
133
+ put("status", "blocked")
134
+ }
135
+ promise.resolve(response.toString())
136
+ }
137
+ }
138
+
139
+ } catch (e: IllegalArgumentException) {
140
+ Log.e(TAG, "Invalid operation ID: $operationId", e)
141
+ promise.reject("INVALID_ID", "Invalid operation ID: $operationId", e)
142
+ } catch (e: ExecutionException) {
143
+ Log.e(TAG, "Error fetching work info", e)
144
+ promise.reject("FETCH_ERROR", e.message, e)
145
+ } catch (e: InterruptedException) {
146
+ Log.e(TAG, "Interrupted while fetching work info", e)
147
+ promise.reject("INTERRUPTED", e.message, e)
148
+ } catch (e: Exception) {
149
+ Log.e(TAG, "Unexpected error", e)
150
+ promise.reject("ERROR", e.message, e)
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Convert a ReadableArray to a JSON string.
156
+ */
157
+ private fun convertArrayToJson(array: ReadableArray): String {
158
+ val jsonArray = JSONArray()
159
+
160
+ for (i in 0 until array.size()) {
161
+ val map = array.getMap(i)
162
+ val jsonObject = JSONObject()
163
+
164
+ map?.let {
165
+ jsonObject.put("localPath", it.getString("localPath") ?: "")
166
+ jsonObject.put("timeStamp", it.getString("timeStamp") ?: "")
167
+ jsonObject.put("isForOnlyWatermark", it.getBoolean("isForOnlyWatermark"))
168
+ jsonObject.put("compressJpegImage", it.getBoolean("compressJpegImage"))
169
+ jsonObject.put("replaceOriginal", it.getBoolean("replaceOriginal"))
170
+ }
171
+
172
+ jsonArray.put(jsonObject)
173
+ }
174
+
175
+ return jsonArray.toString()
176
+ }
177
+ }