@sagepilot-ai/react-native-camera-addon 0.3.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.
@@ -0,0 +1,135 @@
1
+ package ai.sagepilot.reactnativecameraaddon;
2
+
3
+ import android.content.Context;
4
+ import android.util.Log;
5
+ import java.io.ByteArrayOutputStream;
6
+ import java.io.File;
7
+ import java.io.FileInputStream;
8
+ import java.io.FileOutputStream;
9
+ import java.io.IOException;
10
+ import java.nio.charset.StandardCharsets;
11
+ import java.util.UUID;
12
+ import java.util.concurrent.ConcurrentHashMap;
13
+
14
+ final class SagepilotInAppCameraResultStore {
15
+ private static final String TAG = "SagepilotInAppCamera";
16
+ private static final String RESULT_DIRECTORY_NAME = "sagepilot-camera-results";
17
+ private static final String RESULT_FILE_EXTENSION = ".b64";
18
+ private static final int BUFFER_SIZE = 8192;
19
+
20
+ private static final ConcurrentHashMap<String, PickedImage> RESULTS = new ConcurrentHashMap<>();
21
+
22
+ /** Prevents construction of the static camera result store. */
23
+ private SagepilotInAppCameraResultStore() {}
24
+
25
+ /** Stores one captured image payload and returns a small result id for Activity result transport. */
26
+ static String put(Context context, PickedImage image) {
27
+ String resultId = UUID.randomUUID().toString();
28
+ RESULTS.put(resultId, image);
29
+ writeResultFile(context, resultId, image.dataBase64);
30
+ return resultId;
31
+ }
32
+
33
+ /** Takes and removes one captured image payload by result id. */
34
+ static PickedImage take(
35
+ Context context,
36
+ String resultId,
37
+ String fallbackFileName,
38
+ String fallbackMimeType,
39
+ long fallbackSizeBytes
40
+ ) {
41
+ if (resultId == null || resultId.length() == 0) {
42
+ return null;
43
+ }
44
+ PickedImage image = RESULTS.remove(resultId);
45
+ if (image != null) {
46
+ deleteResultFile(context, resultId);
47
+ return image;
48
+ }
49
+
50
+ String dataBase64 = readResultFile(context, resultId);
51
+ deleteResultFile(context, resultId);
52
+ if (dataBase64 == null || dataBase64.length() == 0) {
53
+ return null;
54
+ }
55
+ return new PickedImage(fallbackFileName, fallbackMimeType, fallbackSizeBytes, dataBase64);
56
+ }
57
+
58
+ /** Writes a durable base64 payload copy for process-death recovery between result creation and JS delivery. */
59
+ private static void writeResultFile(Context context, String resultId, String dataBase64) {
60
+ if (context == null || dataBase64 == null || dataBase64.length() == 0) {
61
+ return;
62
+ }
63
+ File resultFile = getResultFile(context, resultId);
64
+ File directory = resultFile.getParentFile();
65
+ if (directory == null) {
66
+ return;
67
+ }
68
+ try {
69
+ if (!directory.exists() && !directory.mkdirs()) {
70
+ Log.w(TAG, "Could not create camera result directory.");
71
+ return;
72
+ }
73
+ try (FileOutputStream outputStream = new FileOutputStream(resultFile)) {
74
+ outputStream.write(dataBase64.getBytes(StandardCharsets.UTF_8));
75
+ outputStream.flush();
76
+ }
77
+ } catch (IOException | RuntimeException error) {
78
+ Log.w(TAG, "Could not persist camera result payload.", error);
79
+ }
80
+ }
81
+
82
+ /** Reads a previously-persisted base64 result payload. */
83
+ private static String readResultFile(Context context, String resultId) {
84
+ if (context == null) {
85
+ return null;
86
+ }
87
+ File resultFile = getResultFile(context, resultId);
88
+ if (!resultFile.exists() || !resultFile.isFile()) {
89
+ return null;
90
+ }
91
+ try (FileInputStream inputStream = new FileInputStream(resultFile);
92
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
93
+ byte[] buffer = new byte[BUFFER_SIZE];
94
+ int bytesRead;
95
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
96
+ outputStream.write(buffer, 0, bytesRead);
97
+ }
98
+ return outputStream.toString(StandardCharsets.UTF_8.name());
99
+ } catch (IOException | RuntimeException error) {
100
+ Log.w(TAG, "Could not read persisted camera result payload.", error);
101
+ return null;
102
+ }
103
+ }
104
+
105
+ /** Deletes a persisted result payload after it is consumed or superseded by the static store. */
106
+ private static void deleteResultFile(Context context, String resultId) {
107
+ if (context == null) {
108
+ return;
109
+ }
110
+ File resultFile = getResultFile(context, resultId);
111
+ if (resultFile.exists() && !resultFile.delete()) {
112
+ Log.w(TAG, "Could not delete camera result payload.");
113
+ }
114
+ }
115
+
116
+ /** Returns the durable result payload path for a result id. */
117
+ private static File getResultFile(Context context, String resultId) {
118
+ return new File(new File(context.getCacheDir(), RESULT_DIRECTORY_NAME), resultId + RESULT_FILE_EXTENSION);
119
+ }
120
+
121
+ static final class PickedImage {
122
+ final String fileName;
123
+ final String mimeType;
124
+ final long sizeBytes;
125
+ final String dataBase64;
126
+
127
+ /** Holds the bridge-ready camera payload without putting it into the result Intent. */
128
+ PickedImage(String fileName, String mimeType, long sizeBytes, String dataBase64) {
129
+ this.fileName = fileName;
130
+ this.mimeType = mimeType;
131
+ this.sizeBytes = sizeBytes;
132
+ this.dataBase64 = dataBase64;
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,22 @@
1
+ package ai.sagepilot.reactnativecameraaddon;
2
+
3
+ import com.facebook.react.ReactPackage;
4
+ import com.facebook.react.bridge.NativeModule;
5
+ import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.facebook.react.uimanager.ViewManager;
7
+ import java.util.Collections;
8
+ import java.util.List;
9
+
10
+ public class SagepilotReactNativeCameraAddonPackage implements ReactPackage {
11
+ /** Registers the optional CameraX native module exposed to JavaScript. */
12
+ @Override
13
+ public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
14
+ return Collections.singletonList(new SagepilotInAppCameraModule(reactContext));
15
+ }
16
+
17
+ /** The CameraX addon does not register React Native view managers. */
18
+ @Override
19
+ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
20
+ return Collections.emptyList();
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:pathData="M20,6 L9,17 L4,12"
8
+ android:strokeColor="#FF111827"
9
+ android:strokeWidth="2"
10
+ android:strokeLineCap="round"
11
+ android:strokeLineJoin="round" />
12
+ </vector>
@@ -0,0 +1,18 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:pathData="M3,12 A9,9 0,1 0,12,3 Q8.1,3 5.26,5.74 L3,8"
8
+ android:strokeColor="#FFFFFFFF"
9
+ android:strokeWidth="2"
10
+ android:strokeLineCap="round"
11
+ android:strokeLineJoin="round" />
12
+ <path
13
+ android:pathData="M3,3 L3,8 L8,8"
14
+ android:strokeColor="#FFFFFFFF"
15
+ android:strokeWidth="2"
16
+ android:strokeLineCap="round"
17
+ android:strokeLineJoin="round" />
18
+ </vector>
@@ -0,0 +1,36 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:pathData="M11,19 L4,19 Q2,19 2,17 L2,7 Q2,5 4,5 L9,5"
8
+ android:strokeColor="#FFFFFFFF"
9
+ android:strokeWidth="2"
10
+ android:strokeLineCap="round"
11
+ android:strokeLineJoin="round" />
12
+ <path
13
+ android:pathData="M13,5 L20,5 Q22,5 22,7 L22,17 Q22,19 20,19 L15,19"
14
+ android:strokeColor="#FFFFFFFF"
15
+ android:strokeWidth="2"
16
+ android:strokeLineCap="round"
17
+ android:strokeLineJoin="round" />
18
+ <path
19
+ android:pathData="M12,9 A3,3 0,1 0,12,15 A3,3 0,1 0,12,9"
20
+ android:strokeColor="#FFFFFFFF"
21
+ android:strokeWidth="2"
22
+ android:strokeLineCap="round"
23
+ android:strokeLineJoin="round" />
24
+ <path
25
+ android:pathData="M18,22 L15,19 L18,16"
26
+ android:strokeColor="#FFFFFFFF"
27
+ android:strokeWidth="2"
28
+ android:strokeLineCap="round"
29
+ android:strokeLineJoin="round" />
30
+ <path
31
+ android:pathData="M6,2 L9,5 L6,8"
32
+ android:strokeColor="#FFFFFFFF"
33
+ android:strokeWidth="2"
34
+ android:strokeLineCap="round"
35
+ android:strokeLineJoin="round" />
36
+ </vector>
@@ -0,0 +1,18 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:viewportWidth="24"
5
+ android:viewportHeight="24">
6
+ <path
7
+ android:pathData="M18,6 L6,18"
8
+ android:strokeColor="#FFFFFFFF"
9
+ android:strokeWidth="2"
10
+ android:strokeLineCap="round"
11
+ android:strokeLineJoin="round" />
12
+ <path
13
+ android:pathData="M6,6 L18,18"
14
+ android:strokeColor="#FFFFFFFF"
15
+ android:strokeWidth="2"
16
+ android:strokeLineCap="round"
17
+ android:strokeLineJoin="round" />
18
+ </vector>
@@ -0,0 +1,7 @@
1
+ <resources>
2
+ <style name="SagepilotInAppCameraTheme" parent="@android:style/Theme.Material.NoActionBar">
3
+ <item name="android:windowNoTitle">true</item>
4
+ <item name="android:windowActionBar">false</item>
5
+ <item name="android:windowFullscreen">true</item>
6
+ </style>
7
+ </resources>
@@ -0,0 +1,67 @@
1
+ import { SagepilotPickedFile, SagepilotFilePickerAdapter } from '@sagepilot-ai/react-native-sdk';
2
+
3
+ type SagepilotNativeCameraOptions = {
4
+ /** Longest image edge in pixels after native processing. */
5
+ imageMaxDimension?: number;
6
+ /** JPEG quality from 0 to 1 after native processing. */
7
+ imageQuality?: number;
8
+ /** Reject the processed image when it exceeds this size in bytes. */
9
+ imageMaxFileSizeBytes?: number;
10
+ };
11
+ type SagepilotNativeCameraModule = {
12
+ openCamera: (options: Required<SagepilotNativeCameraOptions>) => Promise<SagepilotPickedFile | null>;
13
+ openImageLibrary?: (options: {
14
+ multiple: boolean;
15
+ imageMaxFileSizeBytes: number;
16
+ }) => Promise<SagepilotPickedFile[] | null>;
17
+ openDocumentPicker?: (options: {
18
+ multiple: boolean;
19
+ documentMaxFileSizeBytes: number;
20
+ }) => Promise<SagepilotPickedFile[] | null>;
21
+ moduleCapabilitiesVersion?: number;
22
+ supportsInAppCamera?: boolean;
23
+ supportsImageLibrary?: boolean;
24
+ supportsDocumentPicker?: boolean;
25
+ addListener?: (eventName: string) => void;
26
+ removeListeners?: (count: number) => void;
27
+ };
28
+ type SagepilotNativeCameraCapabilities = {
29
+ moduleCapabilitiesVersion: number;
30
+ requiredModuleCapabilitiesVersion: number;
31
+ supportsInAppCamera: boolean;
32
+ supportsImageLibrary: boolean;
33
+ supportsDocumentPicker: boolean;
34
+ staleNativeModule: boolean;
35
+ };
36
+ /**
37
+ * Checks whether the SDK in-app camera native module is registered.
38
+ * Android registers this module in the current release; iOS can add a
39
+ * compatible module later without changing the JS contract.
40
+ */
41
+ declare function isSagepilotNativeCameraAvailable(): boolean;
42
+ /** Reads native picker capability constants from the installed Android module. */
43
+ declare function getSagepilotNativeCameraCapabilities(): SagepilotNativeCameraCapabilities;
44
+ /**
45
+ * Opens the SDK-owned native in-app camera module and returns adapter shape.
46
+ * User cancellation maps to an empty array.
47
+ */
48
+ declare function pickSagepilotCameraFiles(options?: SagepilotNativeCameraOptions): Promise<SagepilotPickedFile[]>;
49
+
50
+ type SagepilotCameraXFilePickerOptions = SagepilotNativeCameraOptions & {
51
+ /** Include the CameraX camera source when the Android native module is registered. */
52
+ includeCamera?: boolean;
53
+ /** Include Android photo-library picking from the CameraX addon native module. */
54
+ includeLibrary?: boolean;
55
+ /** Include Android document picking from the CameraX addon native module. */
56
+ includeDocuments?: boolean;
57
+ /** Reject documents larger than this size before reading them into memory. */
58
+ documentMaxFileSizeBytes?: number;
59
+ };
60
+ /**
61
+ * Builds the optional Android CameraX file-picker adapter.
62
+ * Returns undefined on non-Android platforms or when the addon native module is
63
+ * not linked into the host app.
64
+ */
65
+ declare function createSagepilotCameraXFilePicker(options?: SagepilotCameraXFilePickerOptions): SagepilotFilePickerAdapter | undefined;
66
+
67
+ export { type SagepilotCameraXFilePickerOptions, type SagepilotNativeCameraCapabilities, type SagepilotNativeCameraModule, type SagepilotNativeCameraOptions, createSagepilotCameraXFilePicker, getSagepilotNativeCameraCapabilities, isSagepilotNativeCameraAvailable, pickSagepilotCameraFiles };
@@ -0,0 +1,67 @@
1
+ import { SagepilotPickedFile, SagepilotFilePickerAdapter } from '@sagepilot-ai/react-native-sdk';
2
+
3
+ type SagepilotNativeCameraOptions = {
4
+ /** Longest image edge in pixels after native processing. */
5
+ imageMaxDimension?: number;
6
+ /** JPEG quality from 0 to 1 after native processing. */
7
+ imageQuality?: number;
8
+ /** Reject the processed image when it exceeds this size in bytes. */
9
+ imageMaxFileSizeBytes?: number;
10
+ };
11
+ type SagepilotNativeCameraModule = {
12
+ openCamera: (options: Required<SagepilotNativeCameraOptions>) => Promise<SagepilotPickedFile | null>;
13
+ openImageLibrary?: (options: {
14
+ multiple: boolean;
15
+ imageMaxFileSizeBytes: number;
16
+ }) => Promise<SagepilotPickedFile[] | null>;
17
+ openDocumentPicker?: (options: {
18
+ multiple: boolean;
19
+ documentMaxFileSizeBytes: number;
20
+ }) => Promise<SagepilotPickedFile[] | null>;
21
+ moduleCapabilitiesVersion?: number;
22
+ supportsInAppCamera?: boolean;
23
+ supportsImageLibrary?: boolean;
24
+ supportsDocumentPicker?: boolean;
25
+ addListener?: (eventName: string) => void;
26
+ removeListeners?: (count: number) => void;
27
+ };
28
+ type SagepilotNativeCameraCapabilities = {
29
+ moduleCapabilitiesVersion: number;
30
+ requiredModuleCapabilitiesVersion: number;
31
+ supportsInAppCamera: boolean;
32
+ supportsImageLibrary: boolean;
33
+ supportsDocumentPicker: boolean;
34
+ staleNativeModule: boolean;
35
+ };
36
+ /**
37
+ * Checks whether the SDK in-app camera native module is registered.
38
+ * Android registers this module in the current release; iOS can add a
39
+ * compatible module later without changing the JS contract.
40
+ */
41
+ declare function isSagepilotNativeCameraAvailable(): boolean;
42
+ /** Reads native picker capability constants from the installed Android module. */
43
+ declare function getSagepilotNativeCameraCapabilities(): SagepilotNativeCameraCapabilities;
44
+ /**
45
+ * Opens the SDK-owned native in-app camera module and returns adapter shape.
46
+ * User cancellation maps to an empty array.
47
+ */
48
+ declare function pickSagepilotCameraFiles(options?: SagepilotNativeCameraOptions): Promise<SagepilotPickedFile[]>;
49
+
50
+ type SagepilotCameraXFilePickerOptions = SagepilotNativeCameraOptions & {
51
+ /** Include the CameraX camera source when the Android native module is registered. */
52
+ includeCamera?: boolean;
53
+ /** Include Android photo-library picking from the CameraX addon native module. */
54
+ includeLibrary?: boolean;
55
+ /** Include Android document picking from the CameraX addon native module. */
56
+ includeDocuments?: boolean;
57
+ /** Reject documents larger than this size before reading them into memory. */
58
+ documentMaxFileSizeBytes?: number;
59
+ };
60
+ /**
61
+ * Builds the optional Android CameraX file-picker adapter.
62
+ * Returns undefined on non-Android platforms or when the addon native module is
63
+ * not linked into the host app.
64
+ */
65
+ declare function createSagepilotCameraXFilePicker(options?: SagepilotCameraXFilePickerOptions): SagepilotFilePickerAdapter | undefined;
66
+
67
+ export { type SagepilotCameraXFilePickerOptions, type SagepilotNativeCameraCapabilities, type SagepilotNativeCameraModule, type SagepilotNativeCameraOptions, createSagepilotCameraXFilePicker, getSagepilotNativeCameraCapabilities, isSagepilotNativeCameraAvailable, pickSagepilotCameraFiles };