react-native-frame-capture 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FrameCapture.podspec +21 -21
- package/LICENSE +20 -20
- package/README.md +159 -158
- package/android/build.gradle +77 -77
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +20 -20
- package/android/src/main/java/com/framecapture/CaptureManager.kt +1013 -831
- package/android/src/main/java/com/framecapture/Constants.kt +205 -196
- package/android/src/main/java/com/framecapture/ErrorHandler.kt +165 -165
- package/android/src/main/java/com/framecapture/FrameCaptureModule.kt +653 -653
- package/android/src/main/java/com/framecapture/OverlayRenderer.kt +423 -423
- package/android/src/main/java/com/framecapture/PermissionHandler.kt +150 -150
- package/android/src/main/java/com/framecapture/ScreenCaptureService.kt +366 -366
- package/android/src/main/java/com/framecapture/StorageManager.kt +221 -221
- package/android/src/main/java/com/framecapture/capture/BitmapProcessor.kt +157 -157
- package/android/src/main/java/com/framecapture/capture/CaptureEventEmitter.kt +150 -120
- package/android/src/main/java/com/framecapture/capture/ChangeDetector.kt +191 -0
- package/android/src/main/java/com/framecapture/models/CaptureModels.kt +343 -302
- package/android/src/main/java/com/framecapture/models/EnumsAndExtensions.kt +67 -60
- package/android/src/main/java/com/framecapture/models/OverlayModels.kt +154 -154
- package/android/src/main/java/com/framecapture/service/CaptureNotificationManager.kt +286 -286
- package/android/src/main/java/com/framecapture/storage/StorageStrategies.kt +317 -317
- package/android/src/main/java/com/framecapture/utils/ValidationUtils.kt +379 -379
- package/ios/FrameCapture.h +5 -5
- package/ios/FrameCapture.mm +21 -21
- package/lib/module/NativeFrameCapture.js.map +1 -1
- package/lib/module/constants.js +45 -0
- package/lib/module/constants.js.map +1 -1
- package/lib/module/normalize.js +10 -1
- package/lib/module/normalize.js.map +1 -1
- package/lib/module/types.js +9 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/validation.js +86 -9
- package/lib/module/validation.js.map +1 -1
- package/lib/typescript/src/NativeFrameCapture.d.ts +7 -0
- package/lib/typescript/src/NativeFrameCapture.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts +33 -0
- package/lib/typescript/src/constants.d.ts.map +1 -1
- package/lib/typescript/src/normalize.d.ts +8 -2
- package/lib/typescript/src/normalize.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +29 -5
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/validation.d.ts.map +1 -1
- package/package.json +199 -196
- package/src/NativeFrameCapture.ts +8 -0
- package/src/constants.ts +45 -0
- package/src/normalize.ts +23 -3
- package/src/types.ts +30 -2
- package/src/validation.ts +132 -13
- package/plugin/build/index.js +0 -48
|
@@ -1,286 +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
|
-
}
|
|
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
|
+
}
|