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.
Files changed (77) hide show
  1. package/FrameCapture.podspec +21 -0
  2. package/LICENSE +20 -0
  3. package/README.md +158 -0
  4. package/android/build.gradle +77 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +20 -0
  7. package/android/src/main/java/com/framecapture/CaptureManager.kt +831 -0
  8. package/android/src/main/java/com/framecapture/Constants.kt +196 -0
  9. package/android/src/main/java/com/framecapture/ErrorHandler.kt +165 -0
  10. package/android/src/main/java/com/framecapture/FrameCaptureModule.kt +653 -0
  11. package/android/src/main/java/com/framecapture/FrameCapturePackage.kt +32 -0
  12. package/android/src/main/java/com/framecapture/OverlayRenderer.kt +423 -0
  13. package/android/src/main/java/com/framecapture/PermissionHandler.kt +150 -0
  14. package/android/src/main/java/com/framecapture/ScreenCaptureService.kt +366 -0
  15. package/android/src/main/java/com/framecapture/StorageManager.kt +221 -0
  16. package/android/src/main/java/com/framecapture/capture/BitmapProcessor.kt +157 -0
  17. package/android/src/main/java/com/framecapture/capture/CaptureEventEmitter.kt +120 -0
  18. package/android/src/main/java/com/framecapture/models/CaptureModels.kt +302 -0
  19. package/android/src/main/java/com/framecapture/models/EnumsAndExtensions.kt +60 -0
  20. package/android/src/main/java/com/framecapture/models/OverlayModels.kt +154 -0
  21. package/android/src/main/java/com/framecapture/service/CaptureNotificationManager.kt +286 -0
  22. package/android/src/main/java/com/framecapture/storage/StorageStrategies.kt +317 -0
  23. package/android/src/main/java/com/framecapture/utils/ValidationUtils.kt +379 -0
  24. package/app.plugin.js +1 -0
  25. package/ios/FrameCapture.h +5 -0
  26. package/ios/FrameCapture.mm +21 -0
  27. package/lib/module/NativeFrameCapture.js +24 -0
  28. package/lib/module/NativeFrameCapture.js.map +1 -0
  29. package/lib/module/api.js +146 -0
  30. package/lib/module/api.js.map +1 -0
  31. package/lib/module/constants.js +67 -0
  32. package/lib/module/constants.js.map +1 -0
  33. package/lib/module/errors.js +19 -0
  34. package/lib/module/errors.js.map +1 -0
  35. package/lib/module/events.js +58 -0
  36. package/lib/module/events.js.map +1 -0
  37. package/lib/module/index.js +24 -0
  38. package/lib/module/index.js.map +1 -0
  39. package/lib/module/normalize.js +51 -0
  40. package/lib/module/normalize.js.map +1 -0
  41. package/lib/module/package.json +1 -0
  42. package/lib/module/types.js +165 -0
  43. package/lib/module/types.js.map +1 -0
  44. package/lib/module/validation.js +190 -0
  45. package/lib/module/validation.js.map +1 -0
  46. package/lib/typescript/package.json +1 -0
  47. package/lib/typescript/plugin/src/index.d.ts +4 -0
  48. package/lib/typescript/plugin/src/index.d.ts.map +1 -0
  49. package/lib/typescript/src/NativeFrameCapture.d.ts +75 -0
  50. package/lib/typescript/src/NativeFrameCapture.d.ts.map +1 -0
  51. package/lib/typescript/src/api.d.ts +66 -0
  52. package/lib/typescript/src/api.d.ts.map +1 -0
  53. package/lib/typescript/src/constants.d.ts +41 -0
  54. package/lib/typescript/src/constants.d.ts.map +1 -0
  55. package/lib/typescript/src/errors.d.ts +14 -0
  56. package/lib/typescript/src/errors.d.ts.map +1 -0
  57. package/lib/typescript/src/events.d.ts +30 -0
  58. package/lib/typescript/src/events.d.ts.map +1 -0
  59. package/lib/typescript/src/index.d.ts +12 -0
  60. package/lib/typescript/src/index.d.ts.map +1 -0
  61. package/lib/typescript/src/normalize.d.ts +43 -0
  62. package/lib/typescript/src/normalize.d.ts.map +1 -0
  63. package/lib/typescript/src/types.d.ts +247 -0
  64. package/lib/typescript/src/types.d.ts.map +1 -0
  65. package/lib/typescript/src/validation.d.ts +15 -0
  66. package/lib/typescript/src/validation.d.ts.map +1 -0
  67. package/package.json +196 -0
  68. package/plugin/build/index.js +48 -0
  69. package/src/NativeFrameCapture.ts +86 -0
  70. package/src/api.ts +189 -0
  71. package/src/constants.ts +69 -0
  72. package/src/errors.ts +21 -0
  73. package/src/events.ts +61 -0
  74. package/src/index.tsx +31 -0
  75. package/src/normalize.ts +81 -0
  76. package/src/types.ts +327 -0
  77. 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
+ }