@developer_tribe/react-native-comnyx 0.13.11 → 0.14.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/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/AndroidManifestNew.xml +4 -0
- package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +301 -0
- package/android/src/main/java/com/comnyx/ComnyxPackage.kt +1 -1
- package/android/src/main/java/com/comnyx/VideoPlayerActivity.kt +91 -0
- package/android/src/main/java/com/comnyx/src/messaging/notifications/NotificationsService.kt +12 -1
- package/ios/ComnyxMediaPicker.m +23 -0
- package/ios/ComnyxMediaPicker.swift +377 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js +62 -0
- package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -0
- package/lib/commonjs/api/index.js +19 -0
- package/lib/commonjs/api/index.js.map +1 -1
- package/lib/commonjs/api/media.js +76 -0
- package/lib/commonjs/api/media.js.map +1 -0
- package/lib/commonjs/assets/attachment-01.png +0 -0
- package/lib/commonjs/assets/x-circle.png +0 -0
- package/lib/commonjs/components/ChatList.js +48 -22
- package/lib/commonjs/components/ChatList.js.map +1 -1
- package/lib/commonjs/components/LinkifyText.js +5 -1
- package/lib/commonjs/components/LinkifyText.js.map +1 -1
- package/lib/commonjs/components/MediaMessageItem.js +333 -0
- package/lib/commonjs/components/MediaMessageItem.js.map +1 -0
- package/lib/commonjs/components/MediaPickerButton.js +47 -0
- package/lib/commonjs/components/MediaPickerButton.js.map +1 -0
- package/lib/commonjs/components/MediaViewerModal.js +157 -0
- package/lib/commonjs/components/MediaViewerModal.js.map +1 -0
- package/lib/commonjs/components/MessageInput.js +344 -73
- package/lib/commonjs/components/MessageInput.js.map +1 -1
- package/lib/commonjs/components/MessageItem.js +17 -7
- package/lib/commonjs/components/MessageItem.js.map +1 -1
- package/lib/commonjs/constants/translations.js +203 -29
- package/lib/commonjs/constants/translations.js.map +1 -1
- package/lib/commonjs/data/fake/media.js +105 -0
- package/lib/commonjs/data/fake/media.js.map +1 -0
- package/lib/commonjs/notifications/initializeNotifications.js +1 -0
- package/lib/commonjs/notifications/initializeNotifications.js.map +1 -1
- package/lib/commonjs/types/MediaTypes.js +2 -0
- package/lib/commonjs/types/MediaTypes.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/commonjs/version.js.map +1 -1
- package/lib/module/NativeComnyxMediaPicker.js +54 -0
- package/lib/module/NativeComnyxMediaPicker.js.map +1 -0
- package/lib/module/api/index.js +1 -0
- package/lib/module/api/index.js.map +1 -1
- package/lib/module/api/media.js +70 -0
- package/lib/module/api/media.js.map +1 -0
- package/lib/module/assets/attachment-01.png +0 -0
- package/lib/module/assets/x-circle.png +0 -0
- package/lib/module/components/ChatList.js +48 -22
- package/lib/module/components/ChatList.js.map +1 -1
- package/lib/module/components/LinkifyText.js +5 -1
- package/lib/module/components/LinkifyText.js.map +1 -1
- package/lib/module/components/MediaMessageItem.js +330 -0
- package/lib/module/components/MediaMessageItem.js.map +1 -0
- package/lib/module/components/MediaPickerButton.js +43 -0
- package/lib/module/components/MediaPickerButton.js.map +1 -0
- package/lib/module/components/MediaViewerModal.js +153 -0
- package/lib/module/components/MediaViewerModal.js.map +1 -0
- package/lib/module/components/MessageInput.js +347 -75
- package/lib/module/components/MessageInput.js.map +1 -1
- package/lib/module/components/MessageItem.js +17 -7
- package/lib/module/components/MessageItem.js.map +1 -1
- package/lib/module/constants/translations.js +203 -29
- package/lib/module/constants/translations.js.map +1 -1
- package/lib/module/data/fake/media.js +99 -0
- package/lib/module/data/fake/media.js.map +1 -0
- package/lib/module/notifications/initializeNotifications.js +1 -0
- package/lib/module/notifications/initializeNotifications.js.map +1 -1
- package/lib/module/types/MediaTypes.js +2 -0
- package/lib/module/types/MediaTypes.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/module/version.js.map +1 -1
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +7 -0
- package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -0
- package/lib/typescript/src/api/index.d.ts +1 -0
- package/lib/typescript/src/api/index.d.ts.map +1 -1
- package/lib/typescript/src/api/media.d.ts +7 -0
- package/lib/typescript/src/api/media.d.ts.map +1 -0
- package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
- package/lib/typescript/src/components/LinkifyText.d.ts.map +1 -1
- package/lib/typescript/src/components/MediaMessageItem.d.ts +6 -0
- package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -0
- package/lib/typescript/src/components/MediaPickerButton.d.ts +5 -0
- package/lib/typescript/src/components/MediaPickerButton.d.ts.map +1 -0
- package/lib/typescript/src/components/MediaViewerModal.d.ts +8 -0
- package/lib/typescript/src/components/MediaViewerModal.d.ts.map +1 -0
- package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
- package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
- package/lib/typescript/src/constants/translations.d.ts.map +1 -1
- package/lib/typescript/src/data/fake/media.d.ts +6 -0
- package/lib/typescript/src/data/fake/media.d.ts.map +1 -0
- package/lib/typescript/src/notifications/initializeNotifications.d.ts.map +1 -1
- package/lib/typescript/src/types/Conversation.d.ts +19 -0
- package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
- package/lib/typescript/src/types/LocalizationKeys.d.ts +6 -0
- package/lib/typescript/src/types/LocalizationKeys.d.ts.map +1 -1
- package/lib/typescript/src/types/MediaTypes.d.ts +26 -0
- package/lib/typescript/src/types/MediaTypes.d.ts.map +1 -0
- package/lib/typescript/src/version.d.ts +1 -1
- package/lib/typescript/src/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeComnyxMediaPicker.ts +62 -0
- package/src/api/index.ts +1 -0
- package/src/api/media.ts +116 -0
- package/src/assets/attachment-01.png +0 -0
- package/src/assets/x-circle.png +0 -0
- package/src/components/ChatList.tsx +81 -24
- package/src/components/CustomerForm.tsx +1 -1
- package/src/components/LinkifyText.tsx +3 -2
- package/src/components/MediaMessageItem.tsx +390 -0
- package/src/components/MediaPickerButton.tsx +48 -0
- package/src/components/MediaViewerModal.tsx +161 -0
- package/src/components/MessageInput.tsx +396 -84
- package/src/components/MessageItem.tsx +19 -4
- package/src/constants/translations.ts +174 -0
- package/src/data/fake/media.ts +110 -0
- package/src/notifications/initializeNotifications.ts +1 -0
- package/src/types/Conversation.ts +20 -0
- package/src/types/LocalizationKeys.ts +6 -0
- package/src/types/MediaTypes.ts +27 -0
- package/src/version.ts +1 -1
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
2
|
package="com.comnyx">
|
|
3
|
+
|
|
4
|
+
<application>
|
|
5
|
+
<service
|
|
6
|
+
android:name="com.comnyx.messaging.ComnyxFirebaseMessagingService"
|
|
7
|
+
android:priority="-1500"
|
|
8
|
+
android:exported="true">
|
|
9
|
+
<intent-filter>
|
|
10
|
+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
11
|
+
</intent-filter>
|
|
12
|
+
</service>
|
|
13
|
+
<activity
|
|
14
|
+
android:name="com.comnyx.VideoPlayerActivity"
|
|
15
|
+
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
|
16
|
+
android:exported="false" />
|
|
17
|
+
</application>
|
|
3
18
|
</manifest>
|
|
@@ -8,5 +8,9 @@
|
|
|
8
8
|
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
|
9
9
|
</intent-filter>
|
|
10
10
|
</service>
|
|
11
|
+
<activity
|
|
12
|
+
android:name="com.comnyx.VideoPlayerActivity"
|
|
13
|
+
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
|
14
|
+
android:exported="false" />
|
|
11
15
|
</application>
|
|
12
16
|
</manifest>
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
package com.comnyx
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.graphics.Bitmap
|
|
6
|
+
import android.graphics.BitmapFactory
|
|
7
|
+
import android.net.Uri
|
|
8
|
+
import android.provider.OpenableColumns
|
|
9
|
+
import android.webkit.MimeTypeMap
|
|
10
|
+
import com.facebook.react.bridge.*
|
|
11
|
+
import java.io.File
|
|
12
|
+
import java.io.FileOutputStream
|
|
13
|
+
import java.io.InputStream
|
|
14
|
+
import android.media.MediaMetadataRetriever
|
|
15
|
+
|
|
16
|
+
class ComnyxMediaPickerModule(reactContext: ReactApplicationContext) :
|
|
17
|
+
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
18
|
+
|
|
19
|
+
companion object {
|
|
20
|
+
const val NAME = "ComnyxMediaPicker"
|
|
21
|
+
private const val REQUEST_MEDIA = 2003
|
|
22
|
+
private const val COMPRESS_THRESHOLD = 500 * 1024 // 500KB
|
|
23
|
+
private const val COMPRESS_QUALITY = 80
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private var promise: Promise? = null
|
|
27
|
+
|
|
28
|
+
init {
|
|
29
|
+
reactContext.addActivityEventListener(this)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun getName(): String = NAME
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// MARK: - Pick Media (both images and videos)
|
|
37
|
+
|
|
38
|
+
@ReactMethod
|
|
39
|
+
fun pickMedia(promise: Promise) {
|
|
40
|
+
this.promise = promise
|
|
41
|
+
val activity = reactApplicationContext.currentActivity
|
|
42
|
+
if (activity == null) {
|
|
43
|
+
promise.reject("NO_ACTIVITY", "Activity is not available")
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
47
|
+
type = "*/*"
|
|
48
|
+
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
|
|
49
|
+
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
50
|
+
addCategory(Intent.CATEGORY_OPENABLE)
|
|
51
|
+
}
|
|
52
|
+
activity.startActivityForResult(intent, REQUEST_MEDIA)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MARK: - Generate Thumbnail from Video URL
|
|
56
|
+
|
|
57
|
+
@ReactMethod
|
|
58
|
+
fun generateThumbnail(videoUrl: String, promise: Promise) {
|
|
59
|
+
android.util.Log.d("ComnyxMedia", "generateThumbnail called with: $videoUrl")
|
|
60
|
+
Thread {
|
|
61
|
+
try {
|
|
62
|
+
val retriever = MediaMetadataRetriever()
|
|
63
|
+
if (videoUrl.startsWith("http://") || videoUrl.startsWith("https://")) {
|
|
64
|
+
android.util.Log.d("ComnyxMedia", "Setting remote data source")
|
|
65
|
+
retriever.setDataSource(videoUrl, HashMap<String, String>())
|
|
66
|
+
} else {
|
|
67
|
+
android.util.Log.d("ComnyxMedia", "Setting local data source")
|
|
68
|
+
retriever.setDataSource(reactApplicationContext, Uri.parse(videoUrl))
|
|
69
|
+
}
|
|
70
|
+
val bitmap = retriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
|
|
71
|
+
retriever.release()
|
|
72
|
+
|
|
73
|
+
if (bitmap != null) {
|
|
74
|
+
android.util.Log.d("ComnyxMedia", "Thumbnail bitmap generated: ${bitmap.width}x${bitmap.height}")
|
|
75
|
+
val thumbFile = File(reactApplicationContext.cacheDir, "thumb_${System.currentTimeMillis()}.jpg")
|
|
76
|
+
FileOutputStream(thumbFile).use { fos ->
|
|
77
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos)
|
|
78
|
+
}
|
|
79
|
+
bitmap.recycle()
|
|
80
|
+
promise.resolve("file://" + thumbFile.absolutePath)
|
|
81
|
+
} else {
|
|
82
|
+
android.util.Log.d("ComnyxMedia", "Thumbnail bitmap is null")
|
|
83
|
+
promise.resolve(null)
|
|
84
|
+
}
|
|
85
|
+
} catch (e: Exception) {
|
|
86
|
+
android.util.Log.e("ComnyxMedia", "generateThumbnail error: ${e.message}", e)
|
|
87
|
+
promise.resolve(null)
|
|
88
|
+
}
|
|
89
|
+
}.start()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// MARK: - Open Video
|
|
93
|
+
|
|
94
|
+
@ReactMethod
|
|
95
|
+
fun openVideo(uri: String, promise: Promise) {
|
|
96
|
+
try {
|
|
97
|
+
val activity = reactApplicationContext.currentActivity
|
|
98
|
+
if (activity == null) {
|
|
99
|
+
promise.reject("NO_ACTIVITY", "Activity is not available")
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (uri.startsWith("http://") || uri.startsWith("https://")) {
|
|
104
|
+
// Remote URL — use system player for faster streaming
|
|
105
|
+
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
106
|
+
setDataAndType(Uri.parse(uri), "video/*")
|
|
107
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
108
|
+
}
|
|
109
|
+
activity.startActivity(intent)
|
|
110
|
+
promise.resolve(null)
|
|
111
|
+
} else {
|
|
112
|
+
// Local file — use custom VideoPlayerActivity
|
|
113
|
+
val intent = Intent(activity, VideoPlayerActivity::class.java).apply {
|
|
114
|
+
putExtra("video_uri", uri)
|
|
115
|
+
}
|
|
116
|
+
activity.startActivity(intent)
|
|
117
|
+
promise.resolve(null)
|
|
118
|
+
}
|
|
119
|
+
} catch (e: Exception) {
|
|
120
|
+
promise.reject("OPEN_ERROR", e.message, e)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// MARK: - Activity Result
|
|
125
|
+
|
|
126
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
127
|
+
if (requestCode != REQUEST_MEDIA) return
|
|
128
|
+
|
|
129
|
+
if (resultCode != Activity.RESULT_OK || data == null) {
|
|
130
|
+
promise?.resolve(null)
|
|
131
|
+
promise = null
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Collect all selected URIs
|
|
136
|
+
val uris = mutableListOf<Uri>()
|
|
137
|
+
val clipData = data.clipData
|
|
138
|
+
if (clipData != null) {
|
|
139
|
+
for (i in 0 until clipData.itemCount) {
|
|
140
|
+
uris.add(clipData.getItemAt(i).uri)
|
|
141
|
+
}
|
|
142
|
+
} else if (data.data != null) {
|
|
143
|
+
uris.add(data.data!!)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (uris.isEmpty()) {
|
|
147
|
+
promise?.resolve(null)
|
|
148
|
+
promise = null
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Capture and clear promise to prevent double-resolve races
|
|
153
|
+
val currentPromise = promise
|
|
154
|
+
promise = null
|
|
155
|
+
|
|
156
|
+
// Move all heavy I/O to a background thread to prevent ANR
|
|
157
|
+
Thread {
|
|
158
|
+
try {
|
|
159
|
+
val context = reactApplicationContext
|
|
160
|
+
val contentResolver = context.contentResolver
|
|
161
|
+
val resultsArray = Arguments.createArray()
|
|
162
|
+
|
|
163
|
+
for (uri in uris) {
|
|
164
|
+
val mimeTypeStr = contentResolver.getType(uri) ?: ""
|
|
165
|
+
val isImage = mimeTypeStr.startsWith("image")
|
|
166
|
+
val mimeType = contentResolver.getType(uri) ?: if (isImage) "image/jpeg" else "video/mp4"
|
|
167
|
+
|
|
168
|
+
// Get file name
|
|
169
|
+
var fileName = "file_${System.currentTimeMillis()}"
|
|
170
|
+
val cursor = contentResolver.query(uri, null, null, null, null)
|
|
171
|
+
cursor?.use {
|
|
172
|
+
if (it.moveToFirst()) {
|
|
173
|
+
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
174
|
+
if (nameIndex >= 0) {
|
|
175
|
+
fileName = it.getString(nameIndex) ?: fileName
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Copy to temp file
|
|
181
|
+
val tempFile = File(context.cacheDir, "${System.currentTimeMillis()}_$fileName")
|
|
182
|
+
contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
183
|
+
FileOutputStream(tempFile).use { outputStream ->
|
|
184
|
+
inputStream.copyTo(outputStream)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
var finalFile = tempFile
|
|
189
|
+
var finalMimeType = mimeType
|
|
190
|
+
var finalFileSize = tempFile.length()
|
|
191
|
+
|
|
192
|
+
// Compress image if >500KB
|
|
193
|
+
if (isImage && finalFileSize > COMPRESS_THRESHOLD) {
|
|
194
|
+
val compressed = compressImage(tempFile)
|
|
195
|
+
if (compressed != null) {
|
|
196
|
+
tempFile.delete()
|
|
197
|
+
finalFile = compressed
|
|
198
|
+
finalMimeType = "image/jpeg"
|
|
199
|
+
finalFileSize = compressed.length()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
val result = Arguments.createMap().apply {
|
|
204
|
+
putString("uri", Uri.fromFile(finalFile).toString())
|
|
205
|
+
putString("type", if (isImage) "image" else "video")
|
|
206
|
+
putString("fileName", finalFile.name)
|
|
207
|
+
putDouble("fileSize", finalFileSize.toDouble())
|
|
208
|
+
putString("mimeType", finalMimeType)
|
|
209
|
+
if (!isImage) {
|
|
210
|
+
val thumbnailUri = generateVideoThumbnail(finalFile)
|
|
211
|
+
if (thumbnailUri != null) {
|
|
212
|
+
putString("thumbnailUri", thumbnailUri)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
resultsArray.pushMap(result)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
currentPromise?.resolve(resultsArray)
|
|
220
|
+
} catch (e: Exception) {
|
|
221
|
+
currentPromise?.reject("PICK_ERROR", e.message, e)
|
|
222
|
+
}
|
|
223
|
+
}.start()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override fun onNewIntent(intent: Intent) {
|
|
227
|
+
// Not needed
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// MARK: - Image Compression
|
|
231
|
+
|
|
232
|
+
private fun compressImage(file: File): File? {
|
|
233
|
+
return try {
|
|
234
|
+
val bitmap = BitmapFactory.decodeFile(file.absolutePath) ?: return null
|
|
235
|
+
val compressedFile = File(reactApplicationContext.cacheDir, "compressed_${System.currentTimeMillis()}.jpg")
|
|
236
|
+
FileOutputStream(compressedFile).use { outputStream ->
|
|
237
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, COMPRESS_QUALITY, outputStream)
|
|
238
|
+
}
|
|
239
|
+
bitmap.recycle()
|
|
240
|
+
compressedFile
|
|
241
|
+
} catch (e: Exception) {
|
|
242
|
+
null
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private fun generateVideoThumbnail(videoFile: File): String? {
|
|
247
|
+
return try {
|
|
248
|
+
val retriever = MediaMetadataRetriever()
|
|
249
|
+
retriever.setDataSource(videoFile.absolutePath)
|
|
250
|
+
val bitmap = retriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
|
|
251
|
+
retriever.release()
|
|
252
|
+
if (bitmap != null) {
|
|
253
|
+
val thumbFile = File(reactApplicationContext.cacheDir, "thumb_${System.currentTimeMillis()}.jpg")
|
|
254
|
+
FileOutputStream(thumbFile).use { outputStream ->
|
|
255
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, outputStream)
|
|
256
|
+
}
|
|
257
|
+
bitmap.recycle()
|
|
258
|
+
Uri.fromFile(thumbFile).toString()
|
|
259
|
+
} else null
|
|
260
|
+
} catch (e: Exception) {
|
|
261
|
+
null
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@ReactMethod
|
|
266
|
+
fun deleteTempFile(uri: String, promise: Promise) {
|
|
267
|
+
Thread {
|
|
268
|
+
try {
|
|
269
|
+
val path = uri.removePrefix("file://")
|
|
270
|
+
val file = File(path)
|
|
271
|
+
if (file.exists()) {
|
|
272
|
+
file.delete()
|
|
273
|
+
}
|
|
274
|
+
promise.resolve(null)
|
|
275
|
+
} catch (e: Exception) {
|
|
276
|
+
promise.resolve(null)
|
|
277
|
+
}
|
|
278
|
+
}.start()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@ReactMethod
|
|
282
|
+
fun cleanupTempFiles(promise: Promise) {
|
|
283
|
+
Thread {
|
|
284
|
+
try {
|
|
285
|
+
val cacheDir = reactApplicationContext.cacheDir
|
|
286
|
+
val files = cacheDir.listFiles() ?: emptyArray()
|
|
287
|
+
for (file in files) {
|
|
288
|
+
val name = file.name.lowercase()
|
|
289
|
+
if (name.startsWith("thumb_") || name.startsWith("compressed_") ||
|
|
290
|
+
(name.contains("_") && (name.endsWith(".jpg") || name.endsWith(".jpeg") ||
|
|
291
|
+
name.endsWith(".png") || name.endsWith(".mp4") || name.endsWith(".mov")))) {
|
|
292
|
+
file.delete()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
promise.resolve(null)
|
|
296
|
+
} catch (e: Exception) {
|
|
297
|
+
promise.resolve(null)
|
|
298
|
+
}
|
|
299
|
+
}.start()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -7,7 +7,7 @@ import com.facebook.react.uimanager.ViewManager
|
|
|
7
7
|
|
|
8
8
|
class ComnyxPackage : ReactPackage {
|
|
9
9
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
-
return listOf(ComnyxModule(reactContext))
|
|
10
|
+
return listOf(ComnyxModule(reactContext), ComnyxMediaPickerModule(reactContext))
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package com.comnyx
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.view.Gravity
|
|
6
|
+
import android.view.Window
|
|
7
|
+
import android.view.WindowManager
|
|
8
|
+
import android.widget.FrameLayout
|
|
9
|
+
import android.widget.ImageButton
|
|
10
|
+
import android.widget.ProgressBar
|
|
11
|
+
import android.widget.VideoView
|
|
12
|
+
import android.app.Activity as AndroidActivity
|
|
13
|
+
import android.widget.MediaController
|
|
14
|
+
|
|
15
|
+
class VideoPlayerActivity : AndroidActivity() {
|
|
16
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
17
|
+
super.onCreate(savedInstanceState)
|
|
18
|
+
|
|
19
|
+
// Full screen
|
|
20
|
+
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
21
|
+
window.setFlags(
|
|
22
|
+
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
23
|
+
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
val container = FrameLayout(this)
|
|
27
|
+
container.setBackgroundColor(android.graphics.Color.BLACK)
|
|
28
|
+
|
|
29
|
+
val videoView = VideoView(this)
|
|
30
|
+
val videoParams = FrameLayout.LayoutParams(
|
|
31
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
32
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
33
|
+
Gravity.CENTER
|
|
34
|
+
)
|
|
35
|
+
container.addView(videoView, videoParams)
|
|
36
|
+
|
|
37
|
+
// Loading spinner
|
|
38
|
+
val progressBar = ProgressBar(this)
|
|
39
|
+
val progressParams = FrameLayout.LayoutParams(
|
|
40
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
41
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
42
|
+
Gravity.CENTER
|
|
43
|
+
)
|
|
44
|
+
container.addView(progressBar, progressParams)
|
|
45
|
+
|
|
46
|
+
// Close button (X) top-right
|
|
47
|
+
val closeButton = ImageButton(this)
|
|
48
|
+
closeButton.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
49
|
+
closeButton.setImageResource(android.R.drawable.ic_menu_close_clear_cancel)
|
|
50
|
+
closeButton.setColorFilter(android.graphics.Color.WHITE)
|
|
51
|
+
closeButton.setPadding(24, 24, 24, 24)
|
|
52
|
+
val closeParams = FrameLayout.LayoutParams(
|
|
53
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
54
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
55
|
+
Gravity.TOP or Gravity.END
|
|
56
|
+
)
|
|
57
|
+
closeParams.topMargin = 60
|
|
58
|
+
closeParams.rightMargin = 24
|
|
59
|
+
container.addView(closeButton, closeParams)
|
|
60
|
+
|
|
61
|
+
closeButton.setOnClickListener {
|
|
62
|
+
finish()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setContentView(container)
|
|
66
|
+
|
|
67
|
+
val videoUri = intent.getStringExtra("video_uri") ?: run {
|
|
68
|
+
finish()
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// MediaController for play/pause/seek controls
|
|
73
|
+
val mediaController = MediaController(this)
|
|
74
|
+
mediaController.setAnchorView(videoView)
|
|
75
|
+
videoView.setMediaController(mediaController)
|
|
76
|
+
|
|
77
|
+
videoView.setVideoURI(Uri.parse(videoUri))
|
|
78
|
+
videoView.setOnPreparedListener { mp ->
|
|
79
|
+
progressBar.visibility = android.view.View.GONE
|
|
80
|
+
mp.isLooping = false
|
|
81
|
+
videoView.start()
|
|
82
|
+
}
|
|
83
|
+
videoView.setOnCompletionListener {
|
|
84
|
+
finish()
|
|
85
|
+
}
|
|
86
|
+
videoView.setOnErrorListener { _, _, _ ->
|
|
87
|
+
finish()
|
|
88
|
+
true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
package/android/src/main/java/com/comnyx/src/messaging/notifications/NotificationsService.kt
CHANGED
|
@@ -15,6 +15,7 @@ import com.facebook.react.bridge.Arguments
|
|
|
15
15
|
import com.facebook.react.bridge.WritableMap
|
|
16
16
|
import com.google.firebase.messaging.RemoteMessage
|
|
17
17
|
import com.comnyx.Logger
|
|
18
|
+
import kotlin.math.abs
|
|
18
19
|
|
|
19
20
|
class NotificationsHelper(private val context: Context) {
|
|
20
21
|
|
|
@@ -149,7 +150,17 @@ class NotificationsHelper(private val context: Context) {
|
|
|
149
150
|
.setContentIntent(pendingIntent)
|
|
150
151
|
|
|
151
152
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
152
|
-
|
|
153
|
+
// Use a unique notification id per message so notifications don't overwrite each other.
|
|
154
|
+
// Prefer FCM's messageId when available; fall back to sentTime/currentTimeMillis.
|
|
155
|
+
val notificationId: Int = remoteMessage.messageId
|
|
156
|
+
?.hashCode()
|
|
157
|
+
?.let { if (it == Int.MIN_VALUE) Int.MAX_VALUE else abs(it) }
|
|
158
|
+
?: run {
|
|
159
|
+
val basis = if (remoteMessage.sentTime > 0) remoteMessage.sentTime else System.currentTimeMillis()
|
|
160
|
+
(basis % Int.MAX_VALUE).toInt()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
notificationManager.notify(notificationId, builder.build())
|
|
153
164
|
Logger.i("Notification displayed successfully", TAG)
|
|
154
165
|
|
|
155
166
|
val writableMap: WritableMap = Arguments.createMap()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(ComnyxMediaPicker, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(pickMedia:(RCTPromiseResolveBlock)resolve
|
|
6
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
7
|
+
|
|
8
|
+
RCT_EXTERN_METHOD(generateThumbnail:(NSString *)videoUrl
|
|
9
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
10
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
11
|
+
|
|
12
|
+
RCT_EXTERN_METHOD(openVideo:(NSString *)uri
|
|
13
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
14
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
15
|
+
|
|
16
|
+
RCT_EXTERN_METHOD(deleteTempFile:(NSString *)uri
|
|
17
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
19
|
+
|
|
20
|
+
RCT_EXTERN_METHOD(cleanupTempFiles:(RCTPromiseResolveBlock)resolve
|
|
21
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
22
|
+
|
|
23
|
+
@end
|