react-native-frame-capture 1.0.1
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/FrameCapture.podspec +21 -0
- package/LICENSE +20 -0
- package/README.md +158 -0
- package/android/build.gradle +77 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +20 -0
- package/android/src/main/java/com/framecapture/CaptureManager.kt +831 -0
- package/android/src/main/java/com/framecapture/Constants.kt +196 -0
- package/android/src/main/java/com/framecapture/ErrorHandler.kt +165 -0
- package/android/src/main/java/com/framecapture/FrameCaptureModule.kt +653 -0
- package/android/src/main/java/com/framecapture/FrameCapturePackage.kt +32 -0
- package/android/src/main/java/com/framecapture/OverlayRenderer.kt +423 -0
- package/android/src/main/java/com/framecapture/PermissionHandler.kt +150 -0
- package/android/src/main/java/com/framecapture/ScreenCaptureService.kt +366 -0
- package/android/src/main/java/com/framecapture/StorageManager.kt +221 -0
- package/android/src/main/java/com/framecapture/capture/BitmapProcessor.kt +157 -0
- package/android/src/main/java/com/framecapture/capture/CaptureEventEmitter.kt +120 -0
- package/android/src/main/java/com/framecapture/models/CaptureModels.kt +302 -0
- package/android/src/main/java/com/framecapture/models/EnumsAndExtensions.kt +60 -0
- package/android/src/main/java/com/framecapture/models/OverlayModels.kt +154 -0
- package/android/src/main/java/com/framecapture/service/CaptureNotificationManager.kt +286 -0
- package/android/src/main/java/com/framecapture/storage/StorageStrategies.kt +317 -0
- package/android/src/main/java/com/framecapture/utils/ValidationUtils.kt +379 -0
- package/app.plugin.js +1 -0
- package/ios/FrameCapture.h +5 -0
- package/ios/FrameCapture.mm +21 -0
- package/lib/module/NativeFrameCapture.js +24 -0
- package/lib/module/NativeFrameCapture.js.map +1 -0
- package/lib/module/api.js +146 -0
- package/lib/module/api.js.map +1 -0
- package/lib/module/constants.js +67 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/errors.js +19 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/events.js +58 -0
- package/lib/module/events.js.map +1 -0
- package/lib/module/index.js +24 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalize.js +51 -0
- package/lib/module/normalize.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +165 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/validation.js +190 -0
- package/lib/module/validation.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/plugin/src/index.d.ts +4 -0
- package/lib/typescript/plugin/src/index.d.ts.map +1 -0
- package/lib/typescript/src/NativeFrameCapture.d.ts +75 -0
- package/lib/typescript/src/NativeFrameCapture.d.ts.map +1 -0
- package/lib/typescript/src/api.d.ts +66 -0
- package/lib/typescript/src/api.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +41 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +14 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/events.d.ts +30 -0
- package/lib/typescript/src/events.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +12 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalize.d.ts +43 -0
- package/lib/typescript/src/normalize.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +247 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/validation.d.ts +15 -0
- package/lib/typescript/src/validation.d.ts.map +1 -0
- package/package.json +196 -0
- package/plugin/build/index.js +48 -0
- package/src/NativeFrameCapture.ts +86 -0
- package/src/api.ts +189 -0
- package/src/constants.ts +69 -0
- package/src/errors.ts +21 -0
- package/src/events.ts +61 -0
- package/src/index.tsx +31 -0
- package/src/normalize.ts +81 -0
- package/src/types.ts +327 -0
- package/src/validation.ts +321 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
package com.framecapture.capture
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.media.Image
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.framecapture.models.CaptureOptions
|
|
7
|
+
import com.framecapture.models.CaptureRegion
|
|
8
|
+
import java.nio.ByteBuffer
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Handles bitmap conversion and processing operations
|
|
12
|
+
*
|
|
13
|
+
* Converts Android Image objects to Bitmap and applies transformations:
|
|
14
|
+
* - Pixel buffer conversion from RGBA_8888 format
|
|
15
|
+
* - Row padding removal
|
|
16
|
+
* - Status bar cropping (if excludeStatusBar is enabled)
|
|
17
|
+
* - Custom region cropping (if captureRegion is specified)
|
|
18
|
+
*
|
|
19
|
+
* Properly manages bitmap memory by recycling intermediate bitmaps.
|
|
20
|
+
*/
|
|
21
|
+
class BitmapProcessor(
|
|
22
|
+
private val statusBarHeight: Int
|
|
23
|
+
) {
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Converts an Image to a Bitmap with optional cropping
|
|
27
|
+
*
|
|
28
|
+
* Performs the following operations in order:
|
|
29
|
+
* 1. Converts RGBA_8888 pixel buffer to Bitmap
|
|
30
|
+
* 2. Removes row padding if present
|
|
31
|
+
* 3. Crops status bar if excludeStatusBar is enabled
|
|
32
|
+
* 4. Crops to custom region if captureRegion is specified
|
|
33
|
+
*
|
|
34
|
+
* @param image The Image from ImageReader
|
|
35
|
+
* @param captureOptions Capture options (for cropping configuration)
|
|
36
|
+
* @return Processed Bitmap ready for overlay rendering and storage
|
|
37
|
+
*/
|
|
38
|
+
fun imageToBitmap(image: Image, captureOptions: CaptureOptions?): Bitmap {
|
|
39
|
+
try {
|
|
40
|
+
val planes = image.planes
|
|
41
|
+
val buffer: ByteBuffer = planes[0].buffer
|
|
42
|
+
val pixelStride = planes[0].pixelStride
|
|
43
|
+
val rowStride = planes[0].rowStride
|
|
44
|
+
val rowPadding = rowStride - pixelStride * image.width
|
|
45
|
+
|
|
46
|
+
// Create bitmap with the correct dimensions
|
|
47
|
+
var bitmap = Bitmap.createBitmap(
|
|
48
|
+
image.width + rowPadding / pixelStride,
|
|
49
|
+
image.height,
|
|
50
|
+
Bitmap.Config.ARGB_8888
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// Copy pixel data to bitmap
|
|
54
|
+
bitmap.copyPixelsFromBuffer(buffer)
|
|
55
|
+
|
|
56
|
+
// Crop row padding if present
|
|
57
|
+
if (rowPadding > 0) {
|
|
58
|
+
val croppedBitmap = Bitmap.createBitmap(
|
|
59
|
+
bitmap,
|
|
60
|
+
0,
|
|
61
|
+
0,
|
|
62
|
+
image.width,
|
|
63
|
+
image.height
|
|
64
|
+
)
|
|
65
|
+
bitmap.recycle()
|
|
66
|
+
bitmap = croppedBitmap
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Crop status bar if requested
|
|
70
|
+
val excludeStatusBar = captureOptions?.excludeStatusBar ?: false
|
|
71
|
+
if (excludeStatusBar && statusBarHeight > 0 && statusBarHeight < bitmap.height) {
|
|
72
|
+
val croppedBitmap = Bitmap.createBitmap(
|
|
73
|
+
bitmap,
|
|
74
|
+
0,
|
|
75
|
+
statusBarHeight, // Start below status bar
|
|
76
|
+
bitmap.width,
|
|
77
|
+
bitmap.height - statusBarHeight // Reduced height
|
|
78
|
+
)
|
|
79
|
+
bitmap.recycle()
|
|
80
|
+
bitmap = croppedBitmap
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Crop custom region if specified
|
|
84
|
+
val captureRegion = captureOptions?.captureRegion
|
|
85
|
+
if (captureRegion != null) {
|
|
86
|
+
bitmap = cropToRegion(bitmap, captureRegion)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return bitmap
|
|
90
|
+
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
Log.e(TAG, "Failed to convert image to bitmap", e)
|
|
93
|
+
throw e
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Crops bitmap to the specified custom region
|
|
99
|
+
*
|
|
100
|
+
* Supports both coordinate systems:
|
|
101
|
+
* - Pixel-based: Absolute pixel coordinates
|
|
102
|
+
* - Percentage-based: Relative to bitmap dimensions (0.0 to 1.0)
|
|
103
|
+
*
|
|
104
|
+
* Validates and clamps coordinates to prevent out-of-bounds errors.
|
|
105
|
+
*
|
|
106
|
+
* @param bitmap The bitmap to crop
|
|
107
|
+
* @param region Custom region specification
|
|
108
|
+
* @return Cropped bitmap (original is recycled)
|
|
109
|
+
*/
|
|
110
|
+
private fun cropToRegion(bitmap: Bitmap, region: CaptureRegion): Bitmap {
|
|
111
|
+
try {
|
|
112
|
+
// Calculate actual pixel coordinates
|
|
113
|
+
val (x, y, width, height) = if (region.unit == com.framecapture.Constants.POSITION_UNIT_PERCENTAGE) {
|
|
114
|
+
// Convert percentages to pixels
|
|
115
|
+
val cropX = (region.x * bitmap.width).toInt()
|
|
116
|
+
val cropY = (region.y * bitmap.height).toInt()
|
|
117
|
+
val cropWidth = (region.width * bitmap.width).toInt()
|
|
118
|
+
val cropHeight = (region.height * bitmap.height).toInt()
|
|
119
|
+
listOf(cropX, cropY, cropWidth, cropHeight)
|
|
120
|
+
} else {
|
|
121
|
+
// Use pixel values directly
|
|
122
|
+
listOf(region.x.toInt(), region.y.toInt(), region.width.toInt(), region.height.toInt())
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validate bounds
|
|
126
|
+
val safeX = maxOf(0, minOf(x, bitmap.width - 1))
|
|
127
|
+
val safeY = maxOf(0, minOf(y, bitmap.height - 1))
|
|
128
|
+
val safeWidth = minOf(width, bitmap.width - safeX)
|
|
129
|
+
val safeHeight = minOf(height, bitmap.height - safeY)
|
|
130
|
+
|
|
131
|
+
if (safeWidth <= 0 || safeHeight <= 0) {
|
|
132
|
+
return bitmap
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Crop the bitmap
|
|
136
|
+
val croppedBitmap = Bitmap.createBitmap(
|
|
137
|
+
bitmap,
|
|
138
|
+
safeX,
|
|
139
|
+
safeY,
|
|
140
|
+
safeWidth,
|
|
141
|
+
safeHeight
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// Recycle original bitmap
|
|
145
|
+
bitmap.recycle()
|
|
146
|
+
|
|
147
|
+
return croppedBitmap
|
|
148
|
+
|
|
149
|
+
} catch (e: Exception) {
|
|
150
|
+
return bitmap
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
companion object {
|
|
155
|
+
private const val TAG = "BitmapProcessor"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
package com.framecapture.capture
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.framecapture.Constants
|
|
6
|
+
import com.framecapture.models.ErrorCode
|
|
7
|
+
import com.framecapture.models.FrameInfo
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles event emission for capture operations
|
|
11
|
+
*
|
|
12
|
+
* Formats and emits capture-related events to the JavaScript layer.
|
|
13
|
+
* Provides type-safe event emission with proper error handling.
|
|
14
|
+
*
|
|
15
|
+
* Events emitted:
|
|
16
|
+
* - onFrameCaptured: When a frame is captured and saved
|
|
17
|
+
* - onCaptureStart: When capture session starts
|
|
18
|
+
* - onCaptureStop: When capture session stops (with statistics)
|
|
19
|
+
* - onCaptureError: When an error occurs during capture
|
|
20
|
+
*/
|
|
21
|
+
class CaptureEventEmitter(
|
|
22
|
+
private val eventEmitter: (String, WritableMap?) -> Unit
|
|
23
|
+
) {
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Emits onFrameCaptured event to JavaScript
|
|
27
|
+
*
|
|
28
|
+
* Contains frame information: file path, size, timestamp, and frame number.
|
|
29
|
+
*
|
|
30
|
+
* @param frameInfo Frame metadata
|
|
31
|
+
* @param frameNumber Zero-based frame number in the session
|
|
32
|
+
*/
|
|
33
|
+
fun emitFrameCaptured(frameInfo: FrameInfo, frameNumber: Int) {
|
|
34
|
+
try {
|
|
35
|
+
val params = frameInfo.toWritableMap(frameNumber)
|
|
36
|
+
eventEmitter(Constants.EVENT_FRAME_CAPTURED, params)
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
// Silently fail - event emission is not critical
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Emits onCaptureStart event to JavaScript
|
|
44
|
+
*
|
|
45
|
+
* Contains session ID and capture options.
|
|
46
|
+
*
|
|
47
|
+
* @param sessionId Unique session identifier
|
|
48
|
+
* @param options Capture configuration options
|
|
49
|
+
*/
|
|
50
|
+
fun emitCaptureStart(sessionId: String, options: com.framecapture.models.CaptureOptions) {
|
|
51
|
+
try {
|
|
52
|
+
val params = Arguments.createMap().apply {
|
|
53
|
+
putString("sessionId", sessionId)
|
|
54
|
+
putMap("options", options.toWritableMap())
|
|
55
|
+
}
|
|
56
|
+
eventEmitter(Constants.EVENT_CAPTURE_START, params)
|
|
57
|
+
} catch (e: Exception) {
|
|
58
|
+
// Silently fail - event emission is not critical
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Emits onCaptureStop event to JavaScript with session statistics
|
|
64
|
+
*
|
|
65
|
+
* Contains session ID, total frames captured, and session duration.
|
|
66
|
+
*
|
|
67
|
+
* @param sessionId Unique session identifier
|
|
68
|
+
* @param totalFrames Total number of frames captured
|
|
69
|
+
* @param duration Session duration in milliseconds
|
|
70
|
+
*/
|
|
71
|
+
fun emitCaptureStop(sessionId: String, totalFrames: Int, duration: Long) {
|
|
72
|
+
try {
|
|
73
|
+
val params = Arguments.createMap().apply {
|
|
74
|
+
putString("sessionId", sessionId)
|
|
75
|
+
putInt("totalFrames", totalFrames)
|
|
76
|
+
putDouble("duration", duration.toDouble())
|
|
77
|
+
}
|
|
78
|
+
eventEmitter(Constants.EVENT_CAPTURE_STOP, params)
|
|
79
|
+
} catch (e: Exception) {
|
|
80
|
+
// Silently fail - event emission is not critical
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Emits onCaptureError event to JavaScript
|
|
86
|
+
*
|
|
87
|
+
* Contains error code, message, and optional details for debugging.
|
|
88
|
+
*
|
|
89
|
+
* @param errorCode Error code enum value
|
|
90
|
+
* @param message Human-readable error message
|
|
91
|
+
* @param details Optional map of additional error context
|
|
92
|
+
*/
|
|
93
|
+
fun emitError(errorCode: ErrorCode, message: String, details: Map<String, Any>?) {
|
|
94
|
+
try {
|
|
95
|
+
val params = Arguments.createMap().apply {
|
|
96
|
+
putString("code", errorCode.value)
|
|
97
|
+
putString("message", message)
|
|
98
|
+
|
|
99
|
+
details?.let { detailsMap ->
|
|
100
|
+
val detailsWritableMap = Arguments.createMap()
|
|
101
|
+
detailsMap.forEach { (key, value) ->
|
|
102
|
+
when (value) {
|
|
103
|
+
is String -> detailsWritableMap.putString(key, value)
|
|
104
|
+
is Int -> detailsWritableMap.putInt(key, value)
|
|
105
|
+
is Long -> detailsWritableMap.putDouble(key, value.toDouble())
|
|
106
|
+
is Double -> detailsWritableMap.putDouble(key, value)
|
|
107
|
+
is Boolean -> detailsWritableMap.putBoolean(key, value)
|
|
108
|
+
else -> detailsWritableMap.putString(key, value.toString())
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
putMap("details", detailsWritableMap)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
eventEmitter(Constants.EVENT_CAPTURE_ERROR, params)
|
|
116
|
+
} catch (e: Exception) {
|
|
117
|
+
// Silently fail - event emission is not critical
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
package com.framecapture.models
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom capture region specification
|
|
9
|
+
*/
|
|
10
|
+
data class CaptureRegion(
|
|
11
|
+
val x: Float,
|
|
12
|
+
val y: Float,
|
|
13
|
+
val width: Float,
|
|
14
|
+
val height: Float,
|
|
15
|
+
val unit: String = com.framecapture.Constants.POSITION_UNIT_PERCENTAGE // "pixels" or "percentage"
|
|
16
|
+
) {
|
|
17
|
+
companion object {
|
|
18
|
+
fun fromReadableMap(map: ReadableMap?): CaptureRegion? {
|
|
19
|
+
if (map == null) return null
|
|
20
|
+
|
|
21
|
+
return CaptureRegion(
|
|
22
|
+
x = if (map.hasKey("x")) map.getDouble("x").toFloat() else 0f,
|
|
23
|
+
y = if (map.hasKey("y")) map.getDouble("y").toFloat() else 0f,
|
|
24
|
+
width = if (map.hasKey("width")) map.getDouble("width").toFloat() else 1f,
|
|
25
|
+
height = if (map.hasKey("height")) map.getDouble("height").toFloat() else 1f,
|
|
26
|
+
unit = if (map.hasKey("unit")) map.getString("unit") ?: com.framecapture.Constants.POSITION_UNIT_PERCENTAGE else com.framecapture.Constants.POSITION_UNIT_PERCENTAGE
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Storage configuration
|
|
34
|
+
*/
|
|
35
|
+
data class StorageConfig(
|
|
36
|
+
val warningThreshold: Long = com.framecapture.Constants.DEFAULT_STORAGE_WARNING_THRESHOLD
|
|
37
|
+
) {
|
|
38
|
+
companion object {
|
|
39
|
+
fun fromReadableMap(map: ReadableMap?): StorageConfig {
|
|
40
|
+
if (map == null) return StorageConfig()
|
|
41
|
+
|
|
42
|
+
return StorageConfig(
|
|
43
|
+
warningThreshold = if (map.hasKey("warningThreshold")) map.getDouble("warningThreshold").toLong() else com.framecapture.Constants.DEFAULT_STORAGE_WARNING_THRESHOLD
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* File naming configuration
|
|
53
|
+
*/
|
|
54
|
+
data class FileNamingConfig(
|
|
55
|
+
val prefix: String = com.framecapture.Constants.DEFAULT_FILENAME_PREFIX,
|
|
56
|
+
val dateFormat: String = com.framecapture.Constants.DEFAULT_FILENAME_DATE_FORMAT,
|
|
57
|
+
val framePadding: Int = com.framecapture.Constants.DEFAULT_FILENAME_FRAME_PADDING
|
|
58
|
+
) {
|
|
59
|
+
companion object {
|
|
60
|
+
fun fromReadableMap(map: ReadableMap?): FileNamingConfig {
|
|
61
|
+
if (map == null) return FileNamingConfig()
|
|
62
|
+
|
|
63
|
+
return FileNamingConfig(
|
|
64
|
+
prefix = map.getStringOrDefault("prefix", com.framecapture.Constants.DEFAULT_FILENAME_PREFIX),
|
|
65
|
+
dateFormat = map.getStringOrDefault("dateFormat", com.framecapture.Constants.DEFAULT_FILENAME_DATE_FORMAT),
|
|
66
|
+
framePadding = map.getIntOrDefault("framePadding", com.framecapture.Constants.DEFAULT_FILENAME_FRAME_PADDING)
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Performance configuration
|
|
74
|
+
*/
|
|
75
|
+
data class PerformanceConfig(
|
|
76
|
+
val overlayCacheSize: Int = com.framecapture.Constants.DEFAULT_OVERLAY_IMAGE_CACHE_SIZE,
|
|
77
|
+
val imageReaderBuffers: Int = com.framecapture.Constants.DEFAULT_IMAGE_READER_MAX_IMAGES,
|
|
78
|
+
val executorShutdownTimeout: Long = com.framecapture.Constants.DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT,
|
|
79
|
+
val executorForcedShutdownTimeout: Long = com.framecapture.Constants.DEFAULT_EXECUTOR_FORCED_SHUTDOWN_TIMEOUT
|
|
80
|
+
) {
|
|
81
|
+
companion object {
|
|
82
|
+
fun fromReadableMap(map: ReadableMap?): PerformanceConfig {
|
|
83
|
+
if (map == null) return PerformanceConfig()
|
|
84
|
+
|
|
85
|
+
return PerformanceConfig(
|
|
86
|
+
overlayCacheSize = if (map.hasKey("overlayCacheSize")) map.getDouble("overlayCacheSize").toInt() else com.framecapture.Constants.DEFAULT_OVERLAY_IMAGE_CACHE_SIZE,
|
|
87
|
+
imageReaderBuffers = map.getIntOrDefault("imageReaderBuffers", com.framecapture.Constants.DEFAULT_IMAGE_READER_MAX_IMAGES),
|
|
88
|
+
executorShutdownTimeout = if (map.hasKey("executorShutdownTimeout")) map.getDouble("executorShutdownTimeout").toLong() else com.framecapture.Constants.DEFAULT_EXECUTOR_SHUTDOWN_TIMEOUT,
|
|
89
|
+
executorForcedShutdownTimeout = if (map.hasKey("executorForcedShutdownTimeout")) map.getDouble("executorForcedShutdownTimeout").toLong() else com.framecapture.Constants.DEFAULT_EXECUTOR_FORCED_SHUTDOWN_TIMEOUT
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Advanced configuration options
|
|
97
|
+
*/
|
|
98
|
+
data class AdvancedConfig(
|
|
99
|
+
val storage: StorageConfig = StorageConfig(),
|
|
100
|
+
val fileNaming: FileNamingConfig = FileNamingConfig(),
|
|
101
|
+
val performance: PerformanceConfig = PerformanceConfig()
|
|
102
|
+
) {
|
|
103
|
+
companion object {
|
|
104
|
+
fun fromReadableMap(map: ReadableMap?): AdvancedConfig {
|
|
105
|
+
if (map == null) return AdvancedConfig()
|
|
106
|
+
|
|
107
|
+
return AdvancedConfig(
|
|
108
|
+
storage = StorageConfig.fromReadableMap(map.getMap("storage")),
|
|
109
|
+
fileNaming = FileNamingConfig.fromReadableMap(map.getMap("fileNaming")),
|
|
110
|
+
performance = PerformanceConfig.fromReadableMap(map.getMap("performance"))
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Configuration options for notification customization
|
|
118
|
+
*/
|
|
119
|
+
data class NotificationOptions(
|
|
120
|
+
val title: String = com.framecapture.Constants.NOTIFICATION_DEFAULT_TITLE,
|
|
121
|
+
val description: String = com.framecapture.Constants.NOTIFICATION_DEFAULT_DESCRIPTION,
|
|
122
|
+
val icon: String? = null,
|
|
123
|
+
val smallIcon: String? = null,
|
|
124
|
+
val color: String? = null,
|
|
125
|
+
val channelName: String = com.framecapture.Constants.CHANNEL_NAME,
|
|
126
|
+
val channelDescription: String = com.framecapture.Constants.CHANNEL_DESCRIPTION,
|
|
127
|
+
val priority: String = com.framecapture.Constants.NOTIFICATION_PRIORITY_LOW,
|
|
128
|
+
val showFrameCount: Boolean = true,
|
|
129
|
+
val updateInterval: Int = com.framecapture.Constants.NOTIFICATION_DEFAULT_UPDATE_INTERVAL,
|
|
130
|
+
val pausedTitle: String = com.framecapture.Constants.NOTIFICATION_DEFAULT_PAUSED_TITLE,
|
|
131
|
+
val pausedDescription: String = com.framecapture.Constants.NOTIFICATION_DEFAULT_PAUSED_DESCRIPTION,
|
|
132
|
+
val showStopAction: Boolean = true,
|
|
133
|
+
val showPauseAction: Boolean = false,
|
|
134
|
+
val showResumeAction: Boolean = true
|
|
135
|
+
) {
|
|
136
|
+
companion object {
|
|
137
|
+
fun fromReadableMap(map: ReadableMap?): NotificationOptions {
|
|
138
|
+
if (map == null) return NotificationOptions()
|
|
139
|
+
|
|
140
|
+
return NotificationOptions(
|
|
141
|
+
title = map.getStringOrDefault("title", com.framecapture.Constants.NOTIFICATION_DEFAULT_TITLE),
|
|
142
|
+
description = map.getStringOrDefault("description", com.framecapture.Constants.NOTIFICATION_DEFAULT_DESCRIPTION),
|
|
143
|
+
icon = map.getStringOrNull("icon"),
|
|
144
|
+
smallIcon = map.getStringOrNull("smallIcon"),
|
|
145
|
+
color = map.getStringOrNull("color"),
|
|
146
|
+
channelName = map.getStringOrDefault("channelName", com.framecapture.Constants.CHANNEL_NAME),
|
|
147
|
+
channelDescription = map.getStringOrDefault("channelDescription", com.framecapture.Constants.CHANNEL_DESCRIPTION),
|
|
148
|
+
priority = map.getStringOrDefault("priority", com.framecapture.Constants.NOTIFICATION_PRIORITY_LOW),
|
|
149
|
+
showFrameCount = map.getBooleanOrDefault("showFrameCount", true),
|
|
150
|
+
updateInterval = map.getIntOrDefault("updateInterval", com.framecapture.Constants.NOTIFICATION_DEFAULT_UPDATE_INTERVAL),
|
|
151
|
+
pausedTitle = map.getStringOrDefault("pausedTitle", com.framecapture.Constants.NOTIFICATION_DEFAULT_PAUSED_TITLE),
|
|
152
|
+
pausedDescription = map.getStringOrDefault("pausedDescription", com.framecapture.Constants.NOTIFICATION_DEFAULT_PAUSED_DESCRIPTION),
|
|
153
|
+
showStopAction = map.getBooleanOrDefault("showStopAction", true),
|
|
154
|
+
showPauseAction = map.getBooleanOrDefault("showPauseAction", false),
|
|
155
|
+
showResumeAction = map.getBooleanOrDefault("showResumeAction", true)
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Configuration options for screen capture
|
|
163
|
+
*/
|
|
164
|
+
data class CaptureOptions(
|
|
165
|
+
val interval: Long = com.framecapture.Constants.DEFAULT_INTERVAL,
|
|
166
|
+
val quality: Int = com.framecapture.Constants.DEFAULT_QUALITY,
|
|
167
|
+
val format: String = com.framecapture.Constants.DEFAULT_FORMAT,
|
|
168
|
+
val saveFrames: Boolean = false,
|
|
169
|
+
val storageLocation: String = com.framecapture.Constants.STORAGE_LOCATION_PRIVATE,
|
|
170
|
+
val outputDirectory: String? = null,
|
|
171
|
+
val scaleResolution: Float? = null,
|
|
172
|
+
val captureRegion: CaptureRegion? = null,
|
|
173
|
+
val excludeStatusBar: Boolean = false,
|
|
174
|
+
val notification: NotificationOptions? = null,
|
|
175
|
+
val overlays: List<OverlayConfig>? = null,
|
|
176
|
+
val advanced: AdvancedConfig = AdvancedConfig()
|
|
177
|
+
) {
|
|
178
|
+
companion object {
|
|
179
|
+
fun fromReadableMap(map: ReadableMap): CaptureOptions {
|
|
180
|
+
val notificationOptions = if (map.hasKey("notification")) {
|
|
181
|
+
NotificationOptions.fromReadableMap(map.getMap("notification"))
|
|
182
|
+
} else {
|
|
183
|
+
null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
val captureRegion = if (map.hasKey("captureRegion")) {
|
|
187
|
+
CaptureRegion.fromReadableMap(map.getMap("captureRegion"))
|
|
188
|
+
} else {
|
|
189
|
+
null
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
val overlays = if (map.hasKey("overlays")) {
|
|
193
|
+
val overlaysArray = map.getArray("overlays")
|
|
194
|
+
if (overlaysArray != null) {
|
|
195
|
+
val overlayList = mutableListOf<OverlayConfig>()
|
|
196
|
+
for (i in 0 until overlaysArray.size()) {
|
|
197
|
+
val overlayMap = overlaysArray.getMap(i)
|
|
198
|
+
if (overlayMap != null) {
|
|
199
|
+
val overlay = OverlayConfig.fromReadableMap(overlayMap)
|
|
200
|
+
if (overlay != null) {
|
|
201
|
+
overlayList.add(overlay)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
overlayList.ifEmpty { null }
|
|
206
|
+
} else {
|
|
207
|
+
null
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
val advancedConfig = if (map.hasKey("advanced")) {
|
|
214
|
+
AdvancedConfig.fromReadableMap(map.getMap("advanced"))
|
|
215
|
+
} else {
|
|
216
|
+
AdvancedConfig()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return CaptureOptions(
|
|
220
|
+
interval = if (map.hasKey("interval")) map.getDouble("interval").toLong() else com.framecapture.Constants.DEFAULT_INTERVAL,
|
|
221
|
+
quality = if (map.hasKey("quality")) map.getInt("quality") else com.framecapture.Constants.DEFAULT_QUALITY,
|
|
222
|
+
format = if (map.hasKey("format")) map.getString("format") ?: com.framecapture.Constants.DEFAULT_FORMAT else com.framecapture.Constants.DEFAULT_FORMAT,
|
|
223
|
+
saveFrames = if (map.hasKey("saveFrames")) map.getBoolean("saveFrames") else false,
|
|
224
|
+
storageLocation = if (map.hasKey("storageLocation")) map.getString("storageLocation") ?: com.framecapture.Constants.STORAGE_LOCATION_PRIVATE else com.framecapture.Constants.STORAGE_LOCATION_PRIVATE,
|
|
225
|
+
outputDirectory = if (map.hasKey("outputDirectory")) map.getString("outputDirectory") else null,
|
|
226
|
+
scaleResolution = if (map.hasKey("scaleResolution")) map.getDouble("scaleResolution").toFloat() else null,
|
|
227
|
+
captureRegion = captureRegion,
|
|
228
|
+
excludeStatusBar = if (map.hasKey("excludeStatusBar")) map.getBoolean("excludeStatusBar") else false,
|
|
229
|
+
notification = notificationOptions,
|
|
230
|
+
overlays = overlays,
|
|
231
|
+
advanced = advancedConfig
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fun toWritableMap(): WritableMap {
|
|
237
|
+
return Arguments.createMap().apply {
|
|
238
|
+
putDouble("interval", interval.toDouble())
|
|
239
|
+
putInt("quality", quality)
|
|
240
|
+
putString("format", format)
|
|
241
|
+
putBoolean("saveFrames", saveFrames)
|
|
242
|
+
putString("storageLocation", storageLocation)
|
|
243
|
+
outputDirectory?.let { putString("outputDirectory", it) }
|
|
244
|
+
scaleResolution?.let { putDouble("scaleResolution", it.toDouble()) }
|
|
245
|
+
putBoolean("excludeStatusBar", excludeStatusBar)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Represents an active capture session
|
|
252
|
+
*/
|
|
253
|
+
data class CaptureSession(
|
|
254
|
+
val id: String,
|
|
255
|
+
val startTime: Long,
|
|
256
|
+
var frameCount: Int,
|
|
257
|
+
val options: CaptureOptions
|
|
258
|
+
) {
|
|
259
|
+
fun toWritableMap(): WritableMap {
|
|
260
|
+
return Arguments.createMap().apply {
|
|
261
|
+
putString("id", id)
|
|
262
|
+
putDouble("startTime", startTime.toDouble())
|
|
263
|
+
putInt("frameCount", frameCount)
|
|
264
|
+
putMap("options", options.toWritableMap())
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Current status of the capture system
|
|
271
|
+
*/
|
|
272
|
+
data class CaptureStatus(
|
|
273
|
+
val state: CaptureState,
|
|
274
|
+
val session: CaptureSession?,
|
|
275
|
+
val isPaused: Boolean
|
|
276
|
+
) {
|
|
277
|
+
fun toWritableMap(): WritableMap {
|
|
278
|
+
return Arguments.createMap().apply {
|
|
279
|
+
putString("state", state.value)
|
|
280
|
+
session?.let { putMap("session", it.toWritableMap()) }
|
|
281
|
+
putBoolean("isPaused", isPaused)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Information about a captured frame
|
|
288
|
+
*/
|
|
289
|
+
data class FrameInfo(
|
|
290
|
+
val filePath: String,
|
|
291
|
+
val fileSize: Long,
|
|
292
|
+
val timestamp: Long
|
|
293
|
+
) {
|
|
294
|
+
fun toWritableMap(frameNumber: Int): WritableMap {
|
|
295
|
+
return Arguments.createMap().apply {
|
|
296
|
+
putString("filePath", filePath)
|
|
297
|
+
putDouble("fileSize", fileSize.toDouble())
|
|
298
|
+
putDouble("timestamp", timestamp.toDouble())
|
|
299
|
+
putInt("frameNumber", frameNumber)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.framecapture.models
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper extension functions for safe ReadableMap access
|
|
7
|
+
*/
|
|
8
|
+
fun ReadableMap.getStringOrDefault(key: String, default: String): String {
|
|
9
|
+
return if (hasKey(key)) getString(key) ?: default else default
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fun ReadableMap.getStringOrNull(key: String): String? {
|
|
13
|
+
return if (hasKey(key)) getString(key) else null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fun ReadableMap.getBooleanOrDefault(key: String, default: Boolean): Boolean {
|
|
17
|
+
return if (hasKey(key)) getBoolean(key) else default
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fun ReadableMap.getIntOrDefault(key: String, default: Int): Int {
|
|
21
|
+
return if (hasKey(key)) getInt(key) else default
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Capture mode determines when frames are captured
|
|
26
|
+
*/
|
|
27
|
+
enum class CaptureMode(val value: String) {
|
|
28
|
+
INTERVAL("interval") // Fixed interval capture
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Capture state enum
|
|
33
|
+
*/
|
|
34
|
+
enum class CaptureState(val value: String) {
|
|
35
|
+
IDLE("idle"),
|
|
36
|
+
CAPTURING("capturing"),
|
|
37
|
+
PAUSED("paused"),
|
|
38
|
+
STOPPING("stopping")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Error codes for capture operations
|
|
43
|
+
*/
|
|
44
|
+
enum class ErrorCode(val value: String) {
|
|
45
|
+
PERMISSION_DENIED("PERMISSION_DENIED"),
|
|
46
|
+
ALREADY_CAPTURING("ALREADY_CAPTURING"),
|
|
47
|
+
INVALID_OPTIONS("INVALID_OPTIONS"),
|
|
48
|
+
STORAGE_ERROR("STORAGE_ERROR"),
|
|
49
|
+
SYSTEM_ERROR("SYSTEM_ERROR"),
|
|
50
|
+
NOT_SUPPORTED("NOT_SUPPORTED")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Permission status enum
|
|
55
|
+
*/
|
|
56
|
+
enum class PermissionStatus(val value: String) {
|
|
57
|
+
GRANTED("granted"),
|
|
58
|
+
DENIED("denied"),
|
|
59
|
+
NOT_DETERMINED("not_determined")
|
|
60
|
+
}
|