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.
- package/LICENSE +20 -0
- package/README.md +86 -0
- package/android/build.gradle +139 -0
- package/android/generated/java/com/capturestudio/NativeCaptureStudioSpec.java +48 -0
- package/android/generated/jni/CMakeLists.txt +31 -0
- package/android/generated/jni/RNCaptureStudioSpec-generated.cpp +44 -0
- package/android/generated/jni/RNCaptureStudioSpec.h +31 -0
- package/android/generated/jni/react/renderer/components/RNCaptureStudioSpec/RNCaptureStudioSpecJSI.h +56 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/capturestudio/CaptureStudioModule.kt +177 -0
- package/android/src/main/java/com/capturestudio/CaptureStudioPackage.kt +33 -0
- package/android/src/main/java/com/capturestudio/data/CameraRepository.kt +43 -0
- package/android/src/main/java/com/capturestudio/data/processing/ImageProcessingWorker.kt +126 -0
- package/android/src/main/java/com/capturestudio/data/processing/ImageProcessor.kt +244 -0
- package/android/src/main/java/com/capturestudio/domain/CaptureOptions.kt +0 -0
- package/android/src/main/java/com/capturestudio/domain/model/ImageProcessingItem.kt +18 -0
- package/android/src/main/java/com/capturestudio/domain/model/ProcessingResult.kt +14 -0
- package/android/src/main/java/com/capturestudio/ui/camera/CameraActivity.kt +55 -0
- package/android/src/main/java/com/capturestudio/ui/camera/CameraUiState.kt +7 -0
- package/android/src/main/java/com/capturestudio/ui/camera/CameraViewModel.kt +34 -0
- package/android/src/main/java/com/capturestudio/ui/camera/CameraViewModelFactory.kt +17 -0
- package/android/src/main/res/layout/activity_camera.xml +10 -0
- package/ios/CaptureStudio.h +7 -0
- package/ios/CaptureStudio.mm +186 -0
- package/ios/ImageProcessor.h +22 -0
- package/ios/ImageProcessor.mm +383 -0
- package/ios/generated/Package.swift +59 -0
- package/ios/generated/ReactAppDependencyProvider/RCTAppDependencyProvider.h +25 -0
- package/ios/generated/ReactAppDependencyProvider/RCTAppDependencyProvider.mm +40 -0
- package/ios/generated/ReactAppDependencyProvider/ReactAppDependencyProvider.podspec +34 -0
- package/ios/generated/ReactCodegen/RCTModuleProviders.h +16 -0
- package/ios/generated/ReactCodegen/RCTModuleProviders.mm +51 -0
- package/ios/generated/ReactCodegen/RCTModulesConformingToProtocolsProvider.h +18 -0
- package/ios/generated/ReactCodegen/RCTModulesConformingToProtocolsProvider.mm +54 -0
- package/ios/generated/ReactCodegen/RCTThirdPartyComponentsProvider.h +16 -0
- package/ios/generated/ReactCodegen/RCTThirdPartyComponentsProvider.mm +30 -0
- package/ios/generated/ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.h +14 -0
- package/ios/generated/ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.mm +19 -0
- package/ios/generated/ReactCodegen/RNCaptureStudioSpec/RNCaptureStudioSpec-generated.mm +53 -0
- package/ios/generated/ReactCodegen/RNCaptureStudioSpec/RNCaptureStudioSpec.h +70 -0
- package/ios/generated/ReactCodegen/RNCaptureStudioSpecJSI.h +56 -0
- package/ios/generated/ReactCodegen/ReactCodegen.podspec +110 -0
- package/lib/commonjs/NativeCaptureStudio.js +9 -0
- package/lib/commonjs/NativeCaptureStudio.js.map +1 -0
- package/lib/commonjs/index.js +20 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/NativeCaptureStudio.js +5 -0
- package/lib/module/NativeCaptureStudio.js.map +1 -0
- package/lib/module/index.js +13 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/NativeCaptureStudio.d.ts +9 -0
- package/lib/typescript/commonjs/src/NativeCaptureStudio.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +19 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/NativeCaptureStudio.d.ts +9 -0
- package/lib/typescript/module/src/NativeCaptureStudio.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +19 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/package.json +202 -0
- package/react-native-capture-studio.podspec +48 -0
- package/react-native.config.js +12 -0
- package/src/NativeCaptureStudio.ts +11 -0
- 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 ¶ms)
|
|
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 ¶ms) {
|
|
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 ¶ms);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
JSI_EXPORT
|
|
29
|
+
std::shared_ptr<TurboModule> RNCaptureStudioSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms);
|
|
30
|
+
|
|
31
|
+
} // namespace facebook::react
|
package/android/generated/jni/react/renderer/components/RNCaptureStudioSpec/RNCaptureStudioSpecJSI.h
ADDED
|
@@ -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,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,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
|
+
}
|