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,154 @@
|
|
|
1
|
+
package com.framecapture.models
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Overlay position - either preset or custom coordinates
|
|
7
|
+
*/
|
|
8
|
+
sealed class OverlayPosition {
|
|
9
|
+
data class Preset(val value: String) : OverlayPosition()
|
|
10
|
+
data class Coordinates(val x: Float, val y: Float, val unit: String = com.framecapture.Constants.POSITION_UNIT_PIXELS) : OverlayPosition()
|
|
11
|
+
|
|
12
|
+
companion object {
|
|
13
|
+
fun fromReadableMap(map: ReadableMap?): OverlayPosition? {
|
|
14
|
+
if (map == null) return null
|
|
15
|
+
|
|
16
|
+
return try {
|
|
17
|
+
val preset = map.getString("preset")
|
|
18
|
+
if (preset != null) Preset(preset) else null
|
|
19
|
+
} catch (e: Exception) {
|
|
20
|
+
Coordinates(
|
|
21
|
+
x = if (map.hasKey("x")) map.getDouble("x").toFloat() else 0f,
|
|
22
|
+
y = if (map.hasKey("y")) map.getDouble("y").toFloat() else 0f,
|
|
23
|
+
unit = if (map.hasKey("unit")) map.getString("unit") ?: com.framecapture.Constants.POSITION_UNIT_PIXELS else com.framecapture.Constants.POSITION_UNIT_PIXELS
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fun fromString(preset: String): OverlayPosition {
|
|
29
|
+
return Preset(preset)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Text style configuration
|
|
36
|
+
*/
|
|
37
|
+
data class TextStyle(
|
|
38
|
+
val fontSize: Int = com.framecapture.Constants.TEXT_DEFAULT_FONT_SIZE,
|
|
39
|
+
val color: String = com.framecapture.Constants.TEXT_DEFAULT_COLOR,
|
|
40
|
+
val backgroundColor: String = com.framecapture.Constants.TEXT_DEFAULT_BACKGROUND_COLOR,
|
|
41
|
+
val padding: Int = com.framecapture.Constants.TEXT_DEFAULT_PADDING,
|
|
42
|
+
val fontWeight: String = com.framecapture.Constants.TEXT_WEIGHT_NORMAL,
|
|
43
|
+
val textAlign: String = com.framecapture.Constants.TEXT_ALIGN_LEFT
|
|
44
|
+
) {
|
|
45
|
+
companion object {
|
|
46
|
+
fun fromReadableMap(map: ReadableMap?): TextStyle {
|
|
47
|
+
if (map == null) return TextStyle()
|
|
48
|
+
|
|
49
|
+
val bgColor = map.getStringOrDefault("backgroundColor", com.framecapture.Constants.TEXT_DEFAULT_BACKGROUND_COLOR)
|
|
50
|
+
|
|
51
|
+
return TextStyle(
|
|
52
|
+
fontSize = map.getIntOrDefault("fontSize", com.framecapture.Constants.TEXT_DEFAULT_FONT_SIZE),
|
|
53
|
+
color = map.getStringOrDefault("color", com.framecapture.Constants.TEXT_DEFAULT_COLOR),
|
|
54
|
+
backgroundColor = bgColor,
|
|
55
|
+
padding = map.getIntOrDefault("padding", com.framecapture.Constants.TEXT_DEFAULT_PADDING),
|
|
56
|
+
fontWeight = map.getStringOrDefault("fontWeight", com.framecapture.Constants.TEXT_WEIGHT_NORMAL),
|
|
57
|
+
textAlign = map.getStringOrDefault("textAlign", com.framecapture.Constants.TEXT_ALIGN_LEFT)
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Image size configuration
|
|
65
|
+
*/
|
|
66
|
+
data class ImageSize(
|
|
67
|
+
val width: Int,
|
|
68
|
+
val height: Int
|
|
69
|
+
) {
|
|
70
|
+
companion object {
|
|
71
|
+
fun fromReadableMap(map: ReadableMap?): ImageSize? {
|
|
72
|
+
if (map == null) return null
|
|
73
|
+
|
|
74
|
+
return ImageSize(
|
|
75
|
+
width = if (map.hasKey("width")) map.getInt("width") else return null,
|
|
76
|
+
height = if (map.hasKey("height")) map.getInt("height") else return null
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Base overlay configuration
|
|
84
|
+
*/
|
|
85
|
+
sealed class OverlayConfig {
|
|
86
|
+
abstract val type: String
|
|
87
|
+
abstract val position: OverlayPosition
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Text overlay configuration
|
|
91
|
+
*/
|
|
92
|
+
data class Text(
|
|
93
|
+
override val type: String = com.framecapture.Constants.OVERLAY_TYPE_TEXT,
|
|
94
|
+
val content: String,
|
|
95
|
+
override val position: OverlayPosition,
|
|
96
|
+
val style: TextStyle = TextStyle()
|
|
97
|
+
) : OverlayConfig()
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Image overlay configuration
|
|
101
|
+
*/
|
|
102
|
+
data class Image(
|
|
103
|
+
override val type: String = com.framecapture.Constants.OVERLAY_TYPE_IMAGE,
|
|
104
|
+
val source: String,
|
|
105
|
+
override val position: OverlayPosition,
|
|
106
|
+
val size: ImageSize? = null,
|
|
107
|
+
val opacity: Float = com.framecapture.Constants.IMAGE_DEFAULT_OPACITY
|
|
108
|
+
) : OverlayConfig()
|
|
109
|
+
|
|
110
|
+
companion object {
|
|
111
|
+
fun fromReadableMap(map: ReadableMap): OverlayConfig? {
|
|
112
|
+
val type = map.getStringOrNull("type") ?: return null
|
|
113
|
+
|
|
114
|
+
return when (type) {
|
|
115
|
+
com.framecapture.Constants.OVERLAY_TYPE_TEXT -> {
|
|
116
|
+
val content = map.getStringOrNull("content") ?: return null
|
|
117
|
+
val position = parsePositionFromMap(map) ?: return null
|
|
118
|
+
val style = TextStyle.fromReadableMap(map.getMap("style"))
|
|
119
|
+
|
|
120
|
+
Text(content = content, position = position, style = style)
|
|
121
|
+
}
|
|
122
|
+
com.framecapture.Constants.OVERLAY_TYPE_IMAGE -> {
|
|
123
|
+
val source = map.getStringOrNull("source") ?: return null
|
|
124
|
+
val position = parsePositionFromMap(map) ?: return null
|
|
125
|
+
val size = ImageSize.fromReadableMap(map.getMap("size"))
|
|
126
|
+
val opacity = if (map.hasKey("opacity")) map.getDouble("opacity").toFloat() else com.framecapture.Constants.IMAGE_DEFAULT_OPACITY
|
|
127
|
+
|
|
128
|
+
Image(source = source, position = position, size = size, opacity = opacity)
|
|
129
|
+
}
|
|
130
|
+
else -> null
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private fun parsePositionFromMap(map: ReadableMap): OverlayPosition? {
|
|
135
|
+
if (!map.hasKey("position")) return null
|
|
136
|
+
|
|
137
|
+
return try {
|
|
138
|
+
val positionString = map.getString("position")
|
|
139
|
+
if (positionString != null) {
|
|
140
|
+
OverlayPosition.fromString(positionString)
|
|
141
|
+
} else {
|
|
142
|
+
null
|
|
143
|
+
}
|
|
144
|
+
} catch (e: Exception) {
|
|
145
|
+
try {
|
|
146
|
+
val positionMap = map.getMap("position")
|
|
147
|
+
OverlayPosition.fromReadableMap(positionMap)
|
|
148
|
+
} catch (e2: Exception) {
|
|
149
|
+
null
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
package com.framecapture.service
|
|
2
|
+
|
|
3
|
+
import android.app.Notification
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.PendingIntent
|
|
7
|
+
import android.app.Service
|
|
8
|
+
import android.content.Context
|
|
9
|
+
import android.content.Intent
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import androidx.core.app.NotificationCompat
|
|
12
|
+
import com.framecapture.Constants
|
|
13
|
+
import com.framecapture.ScreenCaptureService
|
|
14
|
+
import com.framecapture.models.NotificationOptions
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Manages notification creation, updates, and styling for the capture service
|
|
18
|
+
*
|
|
19
|
+
* Handles all notification-related operations for the foreground service:
|
|
20
|
+
* - Notification channel creation (Android 8.0+)
|
|
21
|
+
* - Custom styling (colors, icons, priority)
|
|
22
|
+
* - Action buttons (pause/resume/stop)
|
|
23
|
+
* - Frame count updates with template substitution
|
|
24
|
+
* - Paused state notifications
|
|
25
|
+
*
|
|
26
|
+
* Supports full customization via NotificationOptions.
|
|
27
|
+
*/
|
|
28
|
+
class CaptureNotificationManager(
|
|
29
|
+
private val service: Service,
|
|
30
|
+
private val notificationOptions: NotificationOptions
|
|
31
|
+
) {
|
|
32
|
+
|
|
33
|
+
companion object {
|
|
34
|
+
const val NOTIFICATION_ID = Constants.NOTIFICATION_ID
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolves an icon resource name to a resource ID
|
|
39
|
+
* Falls back to default icon if resource not found or null
|
|
40
|
+
*/
|
|
41
|
+
private fun getIconResource(iconName: String?): Int {
|
|
42
|
+
if (iconName == null) {
|
|
43
|
+
return android.R.drawable.ic_menu_camera
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
val resourceId = service.applicationContext.resources.getIdentifier(
|
|
48
|
+
iconName,
|
|
49
|
+
Constants.RESOURCE_TYPE_DRAWABLE,
|
|
50
|
+
service.applicationContext.packageName
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if (resourceId == 0) {
|
|
54
|
+
return android.R.drawable.ic_menu_camera
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return resourceId
|
|
58
|
+
} catch (e: Exception) {
|
|
59
|
+
return android.R.drawable.ic_menu_camera
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parses a color hex string to a color integer
|
|
65
|
+
* Returns null if parsing fails or input is null
|
|
66
|
+
*/
|
|
67
|
+
private fun parseColor(colorString: String?): Int? {
|
|
68
|
+
if (colorString == null) return null
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
return android.graphics.Color.parseColor(colorString)
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Maps a priority string to a NotificationCompat priority constant
|
|
79
|
+
* Supports "low", "default", "high" (case-insensitive)
|
|
80
|
+
*/
|
|
81
|
+
private fun getNotificationPriority(priority: String): Int {
|
|
82
|
+
return when (priority.lowercase()) {
|
|
83
|
+
Constants.NOTIFICATION_PRIORITY_HIGH -> NotificationCompat.PRIORITY_HIGH
|
|
84
|
+
Constants.NOTIFICATION_PRIORITY_DEFAULT -> NotificationCompat.PRIORITY_DEFAULT
|
|
85
|
+
Constants.NOTIFICATION_PRIORITY_LOW -> NotificationCompat.PRIORITY_LOW
|
|
86
|
+
else -> NotificationCompat.PRIORITY_LOW
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Formats a description template by replacing {frameCount} placeholder with actual count
|
|
92
|
+
*/
|
|
93
|
+
private fun formatDescription(template: String, frameCount: Int): String {
|
|
94
|
+
return template.replace(Constants.NOTIFICATION_TEMPLATE_FRAME_COUNT, frameCount.toString())
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generates a unique channel ID based on notification options
|
|
99
|
+
* Returns default CHANNEL_ID for default settings, or hash-based ID for custom channels
|
|
100
|
+
*/
|
|
101
|
+
private fun getChannelId(): String {
|
|
102
|
+
return if (notificationOptions.channelName == Constants.CHANNEL_NAME) {
|
|
103
|
+
Constants.CHANNEL_ID
|
|
104
|
+
} else {
|
|
105
|
+
"${Constants.CUSTOM_CHANNEL_ID_PREFIX}${notificationOptions.channelName.hashCode()}"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates notification channel for API 26+
|
|
111
|
+
* Uses custom channel settings from notificationOptions
|
|
112
|
+
*/
|
|
113
|
+
fun createNotificationChannel() {
|
|
114
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
115
|
+
val channelId = getChannelId()
|
|
116
|
+
|
|
117
|
+
val channel = NotificationChannel(
|
|
118
|
+
channelId,
|
|
119
|
+
notificationOptions.channelName,
|
|
120
|
+
NotificationManager.IMPORTANCE_LOW
|
|
121
|
+
).apply {
|
|
122
|
+
description = notificationOptions.channelDescription
|
|
123
|
+
setShowBadge(false)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
127
|
+
notificationManager.createNotificationChannel(channel)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates a reusable notification builder with custom styling applied
|
|
133
|
+
*/
|
|
134
|
+
private fun createNotificationBuilder(): NotificationCompat.Builder {
|
|
135
|
+
val builder = NotificationCompat.Builder(service, getChannelId())
|
|
136
|
+
.setSmallIcon(getIconResource(notificationOptions.smallIcon))
|
|
137
|
+
.setPriority(getNotificationPriority(notificationOptions.priority))
|
|
138
|
+
.setOngoing(true)
|
|
139
|
+
|
|
140
|
+
// Apply color if provided
|
|
141
|
+
parseColor(notificationOptions.color)?.let {
|
|
142
|
+
builder.setColor(it)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Apply large icon if provided
|
|
146
|
+
notificationOptions.icon?.let { iconName ->
|
|
147
|
+
try {
|
|
148
|
+
val iconRes = getIconResource(iconName)
|
|
149
|
+
val bitmap = android.graphics.BitmapFactory.decodeResource(service.resources, iconRes)
|
|
150
|
+
if (bitmap != null) {
|
|
151
|
+
builder.setLargeIcon(bitmap)
|
|
152
|
+
}
|
|
153
|
+
} catch (e: Exception) {
|
|
154
|
+
// Silently fail - large icon is optional
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return builder
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Creates a PendingIntent for service actions
|
|
163
|
+
*/
|
|
164
|
+
private fun createActionPendingIntent(action: String, requestCode: Int): PendingIntent {
|
|
165
|
+
val intent = Intent(service, ScreenCaptureService::class.java).apply {
|
|
166
|
+
this.action = action
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
170
|
+
PendingIntent.getService(
|
|
171
|
+
service,
|
|
172
|
+
requestCode,
|
|
173
|
+
intent,
|
|
174
|
+
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
175
|
+
)
|
|
176
|
+
} else {
|
|
177
|
+
PendingIntent.getService(
|
|
178
|
+
service,
|
|
179
|
+
requestCode,
|
|
180
|
+
intent,
|
|
181
|
+
PendingIntent.FLAG_UPDATE_CURRENT
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Creates the initial foreground service notification
|
|
188
|
+
*/
|
|
189
|
+
fun createNotification(): Notification {
|
|
190
|
+
val builder = createNotificationBuilder()
|
|
191
|
+
.setContentTitle(notificationOptions.title)
|
|
192
|
+
.setContentText(notificationOptions.description)
|
|
193
|
+
|
|
194
|
+
// Add pause action if enabled
|
|
195
|
+
if (notificationOptions.showPauseAction) {
|
|
196
|
+
builder.addAction(
|
|
197
|
+
android.R.drawable.ic_media_pause,
|
|
198
|
+
Constants.NOTIFICATION_ACTION_PAUSE,
|
|
199
|
+
createActionPendingIntent(Constants.ACTION_PAUSE_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Add stop action if enabled
|
|
204
|
+
if (notificationOptions.showStopAction) {
|
|
205
|
+
builder.addAction(
|
|
206
|
+
android.R.drawable.ic_menu_close_clear_cancel,
|
|
207
|
+
Constants.NOTIFICATION_ACTION_STOP,
|
|
208
|
+
createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return builder.build()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Updates the notification with current frame count progress
|
|
217
|
+
*/
|
|
218
|
+
fun updateNotification(frameCount: Int) {
|
|
219
|
+
try {
|
|
220
|
+
val builder = createNotificationBuilder()
|
|
221
|
+
.setContentTitle(notificationOptions.title)
|
|
222
|
+
.setContentText(formatDescription(notificationOptions.description, frameCount))
|
|
223
|
+
|
|
224
|
+
// Add pause action if enabled
|
|
225
|
+
if (notificationOptions.showPauseAction) {
|
|
226
|
+
builder.addAction(
|
|
227
|
+
android.R.drawable.ic_media_pause,
|
|
228
|
+
Constants.NOTIFICATION_ACTION_PAUSE,
|
|
229
|
+
createActionPendingIntent(Constants.ACTION_PAUSE_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add stop action if enabled
|
|
234
|
+
if (notificationOptions.showStopAction) {
|
|
235
|
+
builder.addAction(
|
|
236
|
+
android.R.drawable.ic_menu_close_clear_cancel,
|
|
237
|
+
Constants.NOTIFICATION_ACTION_STOP,
|
|
238
|
+
createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
val notification = builder.build()
|
|
243
|
+
val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
244
|
+
notificationManager.notify(NOTIFICATION_ID, notification)
|
|
245
|
+
|
|
246
|
+
} catch (e: Exception) {
|
|
247
|
+
// Silently fail - notification update is not critical
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Updates the notification to show paused state
|
|
253
|
+
*/
|
|
254
|
+
fun updateNotificationPaused(frameCount: Int) {
|
|
255
|
+
try {
|
|
256
|
+
val builder = createNotificationBuilder()
|
|
257
|
+
.setContentTitle(notificationOptions.pausedTitle)
|
|
258
|
+
.setContentText(formatDescription(notificationOptions.pausedDescription, frameCount))
|
|
259
|
+
|
|
260
|
+
// Add resume action if enabled
|
|
261
|
+
if (notificationOptions.showResumeAction) {
|
|
262
|
+
builder.addAction(
|
|
263
|
+
android.R.drawable.ic_media_play,
|
|
264
|
+
Constants.NOTIFICATION_ACTION_RESUME,
|
|
265
|
+
createActionPendingIntent(Constants.ACTION_RESUME_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_PAUSE_RESUME)
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Add stop action if enabled
|
|
270
|
+
if (notificationOptions.showStopAction) {
|
|
271
|
+
builder.addAction(
|
|
272
|
+
android.R.drawable.ic_menu_close_clear_cancel,
|
|
273
|
+
Constants.NOTIFICATION_ACTION_STOP,
|
|
274
|
+
createActionPendingIntent(Constants.ACTION_STOP_CAPTURE, Constants.NOTIFICATION_REQUEST_CODE_STOP)
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
val notification = builder.build()
|
|
279
|
+
val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
280
|
+
notificationManager.notify(NOTIFICATION_ID, notification)
|
|
281
|
+
|
|
282
|
+
} catch (e: Exception) {
|
|
283
|
+
// Silently fail - notification update is not critical
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|