@developer_tribe/react-native-comnyx 0.13.13 → 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.
Files changed (126) hide show
  1. package/android/generated/RCTAppDependencyProvider.h +25 -0
  2. package/android/generated/RCTAppDependencyProvider.mm +55 -0
  3. package/android/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
  4. package/android/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
  5. package/android/generated/RCTThirdPartyComponentsProvider.h +16 -0
  6. package/android/generated/RCTThirdPartyComponentsProvider.mm +23 -0
  7. package/android/generated/ReactAppDependencyProvider.podspec +34 -0
  8. package/android/generated/jni/CMakeLists.txt +36 -0
  9. package/android/generated/jni/RNComnyxSpec-generated.cpp +22 -0
  10. package/android/generated/jni/RNComnyxSpec.h +24 -0
  11. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +17 -0
  12. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +19 -0
  13. package/android/src/main/AndroidManifest.xml +15 -0
  14. package/android/src/main/AndroidManifestNew.xml +4 -0
  15. package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +301 -0
  16. package/android/src/main/java/com/comnyx/ComnyxPackage.kt +1 -1
  17. package/android/src/main/java/com/comnyx/VideoPlayerActivity.kt +91 -0
  18. package/ios/ComnyxMediaPicker.m +23 -0
  19. package/ios/ComnyxMediaPicker.swift +377 -0
  20. package/lib/commonjs/NativeComnyxMediaPicker.js +62 -0
  21. package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -0
  22. package/lib/commonjs/api/index.js +19 -0
  23. package/lib/commonjs/api/index.js.map +1 -1
  24. package/lib/commonjs/api/media.js +76 -0
  25. package/lib/commonjs/api/media.js.map +1 -0
  26. package/lib/commonjs/assets/attachment-01.png +0 -0
  27. package/lib/commonjs/assets/x-circle.png +0 -0
  28. package/lib/commonjs/components/ChatList.js +48 -22
  29. package/lib/commonjs/components/ChatList.js.map +1 -1
  30. package/lib/commonjs/components/LinkifyText.js +5 -1
  31. package/lib/commonjs/components/LinkifyText.js.map +1 -1
  32. package/lib/commonjs/components/MediaMessageItem.js +333 -0
  33. package/lib/commonjs/components/MediaMessageItem.js.map +1 -0
  34. package/lib/commonjs/components/MediaPickerButton.js +47 -0
  35. package/lib/commonjs/components/MediaPickerButton.js.map +1 -0
  36. package/lib/commonjs/components/MediaViewerModal.js +157 -0
  37. package/lib/commonjs/components/MediaViewerModal.js.map +1 -0
  38. package/lib/commonjs/components/MessageInput.js +344 -73
  39. package/lib/commonjs/components/MessageInput.js.map +1 -1
  40. package/lib/commonjs/components/MessageItem.js +17 -7
  41. package/lib/commonjs/components/MessageItem.js.map +1 -1
  42. package/lib/commonjs/constants/translations.js +203 -29
  43. package/lib/commonjs/constants/translations.js.map +1 -1
  44. package/lib/commonjs/data/fake/media.js +105 -0
  45. package/lib/commonjs/data/fake/media.js.map +1 -0
  46. package/lib/commonjs/types/MediaTypes.js +2 -0
  47. package/lib/commonjs/types/MediaTypes.js.map +1 -0
  48. package/lib/commonjs/version.js +1 -1
  49. package/lib/commonjs/version.js.map +1 -1
  50. package/lib/module/NativeComnyxMediaPicker.js +54 -0
  51. package/lib/module/NativeComnyxMediaPicker.js.map +1 -0
  52. package/lib/module/api/index.js +1 -0
  53. package/lib/module/api/index.js.map +1 -1
  54. package/lib/module/api/media.js +70 -0
  55. package/lib/module/api/media.js.map +1 -0
  56. package/lib/module/assets/attachment-01.png +0 -0
  57. package/lib/module/assets/x-circle.png +0 -0
  58. package/lib/module/components/ChatList.js +48 -22
  59. package/lib/module/components/ChatList.js.map +1 -1
  60. package/lib/module/components/LinkifyText.js +5 -1
  61. package/lib/module/components/LinkifyText.js.map +1 -1
  62. package/lib/module/components/MediaMessageItem.js +330 -0
  63. package/lib/module/components/MediaMessageItem.js.map +1 -0
  64. package/lib/module/components/MediaPickerButton.js +43 -0
  65. package/lib/module/components/MediaPickerButton.js.map +1 -0
  66. package/lib/module/components/MediaViewerModal.js +153 -0
  67. package/lib/module/components/MediaViewerModal.js.map +1 -0
  68. package/lib/module/components/MessageInput.js +347 -75
  69. package/lib/module/components/MessageInput.js.map +1 -1
  70. package/lib/module/components/MessageItem.js +17 -7
  71. package/lib/module/components/MessageItem.js.map +1 -1
  72. package/lib/module/constants/translations.js +203 -29
  73. package/lib/module/constants/translations.js.map +1 -1
  74. package/lib/module/data/fake/media.js +99 -0
  75. package/lib/module/data/fake/media.js.map +1 -0
  76. package/lib/module/types/MediaTypes.js +2 -0
  77. package/lib/module/types/MediaTypes.js.map +1 -0
  78. package/lib/module/version.js +1 -1
  79. package/lib/module/version.js.map +1 -1
  80. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +7 -0
  81. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -0
  82. package/lib/typescript/src/api/index.d.ts +1 -0
  83. package/lib/typescript/src/api/index.d.ts.map +1 -1
  84. package/lib/typescript/src/api/media.d.ts +7 -0
  85. package/lib/typescript/src/api/media.d.ts.map +1 -0
  86. package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
  87. package/lib/typescript/src/components/LinkifyText.d.ts.map +1 -1
  88. package/lib/typescript/src/components/MediaMessageItem.d.ts +6 -0
  89. package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -0
  90. package/lib/typescript/src/components/MediaPickerButton.d.ts +5 -0
  91. package/lib/typescript/src/components/MediaPickerButton.d.ts.map +1 -0
  92. package/lib/typescript/src/components/MediaViewerModal.d.ts +8 -0
  93. package/lib/typescript/src/components/MediaViewerModal.d.ts.map +1 -0
  94. package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
  95. package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
  96. package/lib/typescript/src/constants/translations.d.ts.map +1 -1
  97. package/lib/typescript/src/data/fake/media.d.ts +6 -0
  98. package/lib/typescript/src/data/fake/media.d.ts.map +1 -0
  99. package/lib/typescript/src/types/Conversation.d.ts +19 -0
  100. package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
  101. package/lib/typescript/src/types/LocalizationKeys.d.ts +6 -0
  102. package/lib/typescript/src/types/LocalizationKeys.d.ts.map +1 -1
  103. package/lib/typescript/src/types/MediaTypes.d.ts +26 -0
  104. package/lib/typescript/src/types/MediaTypes.d.ts.map +1 -0
  105. package/lib/typescript/src/version.d.ts +1 -1
  106. package/lib/typescript/src/version.d.ts.map +1 -1
  107. package/package.json +1 -1
  108. package/src/NativeComnyxMediaPicker.ts +62 -0
  109. package/src/api/index.ts +1 -0
  110. package/src/api/media.ts +116 -0
  111. package/src/assets/attachment-01.png +0 -0
  112. package/src/assets/x-circle.png +0 -0
  113. package/src/components/ChatList.tsx +81 -24
  114. package/src/components/CustomerForm.tsx +1 -1
  115. package/src/components/LinkifyText.tsx +3 -2
  116. package/src/components/MediaMessageItem.tsx +390 -0
  117. package/src/components/MediaPickerButton.tsx +48 -0
  118. package/src/components/MediaViewerModal.tsx +161 -0
  119. package/src/components/MessageInput.tsx +396 -84
  120. package/src/components/MessageItem.tsx +19 -4
  121. package/src/constants/translations.ts +174 -0
  122. package/src/data/fake/media.ts +110 -0
  123. package/src/types/Conversation.ts +20 -0
  124. package/src/types/LocalizationKeys.ts +6 -0
  125. package/src/types/MediaTypes.ts +27 -0
  126. package/src/version.ts +1 -1
@@ -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
+ }
@@ -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
@@ -0,0 +1,377 @@
1
+ import Foundation
2
+ import UIKit
3
+ import PhotosUI
4
+ import AVFoundation
5
+ import AVKit
6
+ import React
7
+
8
+ @objc(ComnyxMediaPicker)
9
+ class ComnyxMediaPicker: NSObject, RCTBridgeModule {
10
+
11
+ static func moduleName() -> String! {
12
+ return "ComnyxMediaPicker"
13
+ }
14
+
15
+ static func requiresMainQueueSetup() -> Bool {
16
+ return true
17
+ }
18
+
19
+ private var resolveBlock: RCTPromiseResolveBlock?
20
+ private var rejectBlock: RCTPromiseRejectBlock?
21
+
22
+ private func getRootViewController() -> UIViewController? {
23
+ return UIApplication.shared.delegate?.window??.rootViewController?.presentedViewController
24
+ ?? UIApplication.shared.delegate?.window??.rootViewController
25
+ }
26
+
27
+
28
+ // MARK: - Pick Media (both images and videos)
29
+
30
+ @objc
31
+ func pickMedia(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
32
+ self.resolveBlock = resolve
33
+ self.rejectBlock = reject
34
+
35
+ DispatchQueue.main.async {
36
+ if #available(iOS 14.0, *) {
37
+ var config = PHPickerConfiguration()
38
+ config.filter = .any(of: [.images, .videos])
39
+ config.selectionLimit = 0
40
+ let picker = PHPickerViewController(configuration: config)
41
+ picker.delegate = self
42
+ self.getRootViewController()?.present(picker, animated: true)
43
+ } else {
44
+ let picker = UIImagePickerController()
45
+ picker.sourceType = .photoLibrary
46
+ picker.mediaTypes = ["public.image", "public.movie"]
47
+ picker.delegate = self
48
+ self.getRootViewController()?.present(picker, animated: true)
49
+ }
50
+ }
51
+ }
52
+
53
+ // MARK: - Generate Thumbnail from Video URL
54
+
55
+ @objc
56
+ func generateThumbnail(_ videoUrl: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
57
+ DispatchQueue.global(qos: .userInitiated).async {
58
+ guard let url = URL(string: videoUrl) else {
59
+ resolve(nil)
60
+ return
61
+ }
62
+ let asset = AVAsset(url: url)
63
+ let generator = AVAssetImageGenerator(asset: asset)
64
+ generator.appliesPreferredTrackTransform = true
65
+ let time = CMTimeMake(value: 0, timescale: 1)
66
+ do {
67
+ let cgImage = try generator.copyCGImage(at: time, actualTime: nil)
68
+ let uiImage = UIImage(cgImage: cgImage)
69
+ if let data = uiImage.jpegData(compressionQuality: 0.8) {
70
+ let fileName = "thumb_\(Int(Date().timeIntervalSince1970 * 1000)).jpg"
71
+ let tempDir = NSTemporaryDirectory()
72
+ let filePath = (tempDir as NSString).appendingPathComponent(fileName)
73
+ try data.write(to: URL(fileURLWithPath: filePath))
74
+ resolve("file://" + filePath)
75
+ } else {
76
+ resolve(nil)
77
+ }
78
+ } catch {
79
+ resolve(nil)
80
+ }
81
+ }
82
+ }
83
+
84
+ // MARK: - Open Video
85
+
86
+ @objc
87
+ func openVideo(_ uri: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
88
+ guard let url = URL(string: uri) else {
89
+ reject("INVALID_URL", "Invalid video URL", nil)
90
+ return
91
+ }
92
+ DispatchQueue.main.async { [weak self] in
93
+ guard let rootVC = self?.getRootViewController() else {
94
+ reject("NO_VC", "No view controller available", nil)
95
+ return
96
+ }
97
+ let player = AVPlayer(url: url)
98
+ let playerVC = AVPlayerViewController()
99
+ playerVC.player = player
100
+ rootVC.present(playerVC, animated: true) {
101
+ player.play()
102
+ }
103
+ resolve(nil)
104
+ }
105
+ }
106
+
107
+ // MARK: - Compress Image
108
+
109
+ private func compressImageIfNeeded(imageURL: URL, originalData: Data) -> (URL, Data, String) {
110
+ let fileSize = originalData.count
111
+ let threshold = 500 * 1024 // 500KB
112
+
113
+ if fileSize > threshold, let uiImage = UIImage(data: originalData) {
114
+ // Compress to 80% JPEG quality
115
+ if let compressedData = uiImage.jpegData(compressionQuality: 0.8) {
116
+ let tempDir = NSTemporaryDirectory()
117
+ let compressedFileName = UUID().uuidString + ".jpg"
118
+ let compressedURL = URL(fileURLWithPath: tempDir).appendingPathComponent(compressedFileName)
119
+ do {
120
+ try compressedData.write(to: compressedURL)
121
+ if imageURL != compressedURL {
122
+ try? FileManager.default.removeItem(at: imageURL)
123
+ }
124
+ return (compressedURL, compressedData, "image/jpeg")
125
+ } catch {
126
+ // Fall back to original
127
+ return (imageURL, originalData, self.mimeType(for: imageURL))
128
+ }
129
+ }
130
+ }
131
+ return (imageURL, originalData, self.mimeType(for: imageURL))
132
+ }
133
+
134
+ private func mimeType(for url: URL) -> String {
135
+ let ext = url.pathExtension.lowercased()
136
+ switch ext {
137
+ case "jpg", "jpeg": return "image/jpeg"
138
+ case "png": return "image/png"
139
+ case "gif": return "image/gif"
140
+ case "heic", "heif": return "image/heic"
141
+ case "mp4": return "video/mp4"
142
+ case "mov": return "video/quicktime"
143
+ case "m4v": return "video/x-m4v"
144
+ default: return "application/octet-stream"
145
+ }
146
+ }
147
+
148
+ private func resolveWithAsset(url: URL, type: String, mimeType: String, fileSize: Int, thumbnailUri: String? = nil) {
149
+ var result: [String: Any] = [
150
+ "uri": url.absoluteString,
151
+ "type": type,
152
+ "fileName": url.lastPathComponent,
153
+ "fileSize": fileSize,
154
+ "mimeType": mimeType,
155
+ ]
156
+ if let thumb = thumbnailUri {
157
+ result["thumbnailUri"] = thumb
158
+ }
159
+ resolveBlock?([result])
160
+ resolveBlock = nil
161
+ rejectBlock = nil
162
+ }
163
+
164
+ private func generateVideoThumbnail(for videoURL: URL) -> String? {
165
+ let asset = AVAsset(url: videoURL)
166
+ let generator = AVAssetImageGenerator(asset: asset)
167
+ generator.appliesPreferredTrackTransform = true
168
+ generator.maximumSize = CGSize(width: 500, height: 500)
169
+
170
+ do {
171
+ let cgImage = try generator.copyCGImage(at: CMTime.zero, actualTime: nil)
172
+ let uiImage = UIImage(cgImage: cgImage)
173
+ if let jpegData = uiImage.jpegData(compressionQuality: 0.7) {
174
+ let tempDir = NSTemporaryDirectory()
175
+ let thumbFileName = "thumb_" + UUID().uuidString + ".jpg"
176
+ let thumbURL = URL(fileURLWithPath: tempDir).appendingPathComponent(thumbFileName)
177
+ try jpegData.write(to: thumbURL)
178
+ return thumbURL.absoluteString
179
+ }
180
+ } catch {
181
+ // Thumbnail generation failed, return nil
182
+ }
183
+ return nil
184
+ }
185
+
186
+ @objc
187
+ func deleteTempFile(_ uri: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
188
+ DispatchQueue.global(qos: .utility).async {
189
+ let path = uri.replacingOccurrences(of: "file://", with: "")
190
+ let fileManager = FileManager.default
191
+ if fileManager.fileExists(atPath: path) {
192
+ try? fileManager.removeItem(atPath: path)
193
+ }
194
+ resolve(nil)
195
+ }
196
+ }
197
+
198
+ @objc
199
+ func cleanupTempFiles(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
200
+ DispatchQueue.global(qos: .utility).async {
201
+ let tempDir = NSTemporaryDirectory()
202
+ let fileManager = FileManager.default
203
+ do {
204
+ let files = try fileManager.contentsOfDirectory(atPath: tempDir)
205
+ for file in files {
206
+ let lower = file.lowercased()
207
+ if lower.hasPrefix("thumb_") || lower.hasPrefix("compressed_") || lower.contains("_") && (lower.hasSuffix(".jpg") || lower.hasSuffix(".jpeg") || lower.hasSuffix(".png") || lower.hasSuffix(".mp4") || lower.hasSuffix(".mov")) {
208
+ let fullPath = (tempDir as NSString).appendingPathComponent(file)
209
+ try? fileManager.removeItem(atPath: fullPath)
210
+ }
211
+ }
212
+ resolve(nil)
213
+ } catch {
214
+ resolve(nil)
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ // MARK: - PHPickerViewControllerDelegate (iOS 14+)
221
+
222
+ @available(iOS 14.0, *)
223
+ extension ComnyxMediaPicker: PHPickerViewControllerDelegate {
224
+ func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
225
+ picker.dismiss(animated: true)
226
+
227
+ guard !results.isEmpty else {
228
+ resolveBlock?(nil)
229
+ resolveBlock = nil
230
+ rejectBlock = nil
231
+ return
232
+ }
233
+
234
+ let allAssets = NSMutableArray()
235
+ let lock = NSLock()
236
+ let group = DispatchGroup()
237
+
238
+ for result in results {
239
+ let provider = result.itemProvider
240
+
241
+ // Handle Image
242
+ if provider.hasItemConformingToTypeIdentifier("public.image") {
243
+ group.enter()
244
+ provider.loadFileRepresentation(forTypeIdentifier: "public.image") { [weak self] url, error in
245
+ defer { group.leave() }
246
+ guard let self = self, let sourceURL = url, error == nil else {
247
+ print("[ComnyxMediaPicker] Image load failed: \(error?.localizedDescription ?? "no url")")
248
+ return
249
+ }
250
+
251
+ let tempDir = NSTemporaryDirectory()
252
+ let tempURL = URL(fileURLWithPath: tempDir).appendingPathComponent(UUID().uuidString + "_" + sourceURL.lastPathComponent)
253
+ do {
254
+ if FileManager.default.fileExists(atPath: tempURL.path) {
255
+ try FileManager.default.removeItem(at: tempURL)
256
+ }
257
+ try FileManager.default.copyItem(at: sourceURL, to: tempURL)
258
+ let data = try Data(contentsOf: tempURL)
259
+ let (finalURL, finalData, finalMimeType) = self.compressImageIfNeeded(imageURL: tempURL, originalData: data)
260
+ let asset: [String: Any] = [
261
+ "uri": finalURL.absoluteString,
262
+ "type": "image",
263
+ "fileName": finalURL.lastPathComponent,
264
+ "fileSize": finalData.count,
265
+ "mimeType": finalMimeType,
266
+ ]
267
+ print("[ComnyxMediaPicker] Image asset ready: \(asset)")
268
+ lock.lock()
269
+ allAssets.add(asset)
270
+ lock.unlock()
271
+ } catch {
272
+ print("[ComnyxMediaPicker] Image processing error: \(error)")
273
+ }
274
+ }
275
+ continue
276
+ }
277
+
278
+ // Handle Video
279
+ if provider.hasItemConformingToTypeIdentifier("public.movie") {
280
+ group.enter()
281
+ provider.loadFileRepresentation(forTypeIdentifier: "public.movie") { [weak self] url, error in
282
+ defer { group.leave() }
283
+ guard let self = self, let sourceURL = url, error == nil else {
284
+ print("[ComnyxMediaPicker] Video load failed: \(error?.localizedDescription ?? "no url")")
285
+ return
286
+ }
287
+
288
+ let tempDir = NSTemporaryDirectory()
289
+ let tempURL = URL(fileURLWithPath: tempDir).appendingPathComponent(UUID().uuidString + "_" + sourceURL.lastPathComponent)
290
+ do {
291
+ if FileManager.default.fileExists(atPath: tempURL.path) {
292
+ try FileManager.default.removeItem(at: tempURL)
293
+ }
294
+ try FileManager.default.copyItem(at: sourceURL, to: tempURL)
295
+ let attrs = try FileManager.default.attributesOfItem(atPath: tempURL.path)
296
+ let fileSize = attrs[.size] as? Int ?? 0
297
+ let mime = self.mimeType(for: tempURL)
298
+ let thumbnailUri = self.generateVideoThumbnail(for: tempURL)
299
+ var asset: [String: Any] = [
300
+ "uri": tempURL.absoluteString,
301
+ "type": "video",
302
+ "fileName": tempURL.lastPathComponent,
303
+ "fileSize": fileSize,
304
+ "mimeType": mime,
305
+ ]
306
+ if let thumb = thumbnailUri {
307
+ asset["thumbnailUri"] = thumb
308
+ }
309
+ print("[ComnyxMediaPicker] Video asset ready: \(asset)")
310
+ lock.lock()
311
+ allAssets.add(asset)
312
+ lock.unlock()
313
+ } catch {
314
+ print("[ComnyxMediaPicker] Video processing error: \(error)")
315
+ }
316
+ }
317
+ continue
318
+ }
319
+ }
320
+
321
+ group.notify(queue: .main) { [weak self] in
322
+ print("[ComnyxMediaPicker] All assets processed. Count: \(allAssets.count)")
323
+ if allAssets.count == 0 {
324
+ self?.resolveBlock?(nil)
325
+ } else {
326
+ let result = allAssets.map { $0 }
327
+ print("[ComnyxMediaPicker] Resolving with \(result.count) assets")
328
+ self?.resolveBlock?(result)
329
+ }
330
+ self?.resolveBlock = nil
331
+ self?.rejectBlock = nil
332
+ }
333
+ }
334
+ }
335
+
336
+ // MARK: - UIImagePickerControllerDelegate (Fallback)
337
+
338
+ extension ComnyxMediaPicker: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
339
+ func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
340
+ picker.dismiss(animated: true)
341
+
342
+ if let imageURL = info[.imageURL] as? URL {
343
+ do {
344
+ let data = try Data(contentsOf: imageURL)
345
+ let (finalURL, finalData, finalMimeType) = compressImageIfNeeded(imageURL: imageURL, originalData: data)
346
+ resolveWithAsset(url: finalURL, type: "image", mimeType: finalMimeType, fileSize: finalData.count)
347
+ } catch {
348
+ rejectBlock?("FILE_ERROR", error.localizedDescription, error)
349
+ resolveBlock = nil
350
+ rejectBlock = nil
351
+ }
352
+ } else if let videoURL = info[.mediaURL] as? URL {
353
+ do {
354
+ let attrs = try FileManager.default.attributesOfItem(atPath: videoURL.path)
355
+ let fileSize = attrs[.size] as? Int ?? 0
356
+ let mime = mimeType(for: videoURL)
357
+ let thumbnailUri = generateVideoThumbnail(for: videoURL)
358
+ resolveWithAsset(url: videoURL, type: "video", mimeType: mime, fileSize: fileSize, thumbnailUri: thumbnailUri)
359
+ } catch {
360
+ rejectBlock?("FILE_ERROR", error.localizedDescription, error)
361
+ resolveBlock = nil
362
+ rejectBlock = nil
363
+ }
364
+ } else {
365
+ resolveBlock?(nil)
366
+ resolveBlock = nil
367
+ rejectBlock = nil
368
+ }
369
+ }
370
+
371
+ func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
372
+ picker.dismiss(animated: true)
373
+ resolveBlock?(nil)
374
+ resolveBlock = nil
375
+ rejectBlock = nil
376
+ }
377
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.cleanupTempFiles = cleanupTempFiles;
7
+ exports.deleteTempFile = deleteTempFile;
8
+ exports.generateThumbnail = generateThumbnail;
9
+ exports.openVideo = openVideo;
10
+ exports.pickMedia = pickMedia;
11
+ var _reactNative = require("react-native");
12
+ const {
13
+ ComnyxMediaPicker
14
+ } = _reactNative.NativeModules;
15
+ async function pickMedia() {
16
+ if (!ComnyxMediaPicker) {
17
+ return [];
18
+ }
19
+ const results = await ComnyxMediaPicker.pickMedia();
20
+ if (!results || !Array.isArray(results) || results.length === 0) return [];
21
+ return results.map(result => {
22
+ const isImage = result.type === 'image' || result.mimeType && result.mimeType.startsWith('image');
23
+ return {
24
+ uri: result.uri,
25
+ type: isImage ? 'image' : 'video',
26
+ fileName: result.fileName,
27
+ fileSize: result.fileSize,
28
+ mimeType: result.mimeType,
29
+ width: isImage ? result.width : undefined,
30
+ height: isImage ? result.height : undefined,
31
+ duration: isImage ? undefined : result.duration,
32
+ thumbnailUri: isImage ? undefined : result.thumbnailUri
33
+ };
34
+ });
35
+ }
36
+ async function openVideo(uri) {
37
+ if (!ComnyxMediaPicker) {
38
+ console.warn('[Comnyx] ComnyxMediaPicker native module is not available');
39
+ return;
40
+ }
41
+ await ComnyxMediaPicker.openVideo(uri);
42
+ }
43
+ async function generateThumbnail(videoUrl) {
44
+ if (!ComnyxMediaPicker) {
45
+ console.warn('[Comnyx] ComnyxMediaPicker native module is not available');
46
+ return null;
47
+ }
48
+ return ComnyxMediaPicker.generateThumbnail(videoUrl);
49
+ }
50
+ async function deleteTempFile(uri) {
51
+ if (!ComnyxMediaPicker) {
52
+ return;
53
+ }
54
+ return ComnyxMediaPicker.deleteTempFile(uri);
55
+ }
56
+ async function cleanupTempFiles() {
57
+ if (!ComnyxMediaPicker) {
58
+ return;
59
+ }
60
+ return ComnyxMediaPicker.cleanupTempFiles();
61
+ }
62
+ //# sourceMappingURL=NativeComnyxMediaPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","ComnyxMediaPicker","NativeModules","pickMedia","results","Array","isArray","length","map","result","isImage","type","mimeType","startsWith","uri","fileName","fileSize","width","undefined","height","duration","thumbnailUri","openVideo","console","warn","generateThumbnail","videoUrl","deleteTempFile","cleanupTempFiles"],"sourceRoot":"../../src","sources":["NativeComnyxMediaPicker.ts"],"mappings":";;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAGA,MAAM;EAAEC;AAAkB,CAAC,GAAGC,0BAAa;AAEpC,eAAeC,SAASA,CAAA,EAA0B;EACvD,IAAI,CAACF,iBAAiB,EAAE;IACtB,OAAO,EAAE;EACX;EACA,MAAMG,OAAO,GAAG,MAAMH,iBAAiB,CAACE,SAAS,CAAC,CAAC;EACnD,IAAI,CAACC,OAAO,IAAI,CAACC,KAAK,CAACC,OAAO,CAACF,OAAO,CAAC,IAAIA,OAAO,CAACG,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE;EAE1E,OAAOH,OAAO,CAACI,GAAG,CAAEC,MAAW,IAAK;IAClC,MAAMC,OAAO,GACXD,MAAM,CAACE,IAAI,KAAK,OAAO,IACtBF,MAAM,CAACG,QAAQ,IAAIH,MAAM,CAACG,QAAQ,CAACC,UAAU,CAAC,OAAO,CAAE;IAE1D,OAAO;MACLC,GAAG,EAAEL,MAAM,CAACK,GAAG;MACfH,IAAI,EAAED,OAAO,GAAI,OAAO,GAAc,OAAiB;MACvDK,QAAQ,EAAEN,MAAM,CAACM,QAAQ;MACzBC,QAAQ,EAAEP,MAAM,CAACO,QAAQ;MACzBJ,QAAQ,EAAEH,MAAM,CAACG,QAAQ;MACzBK,KAAK,EAAEP,OAAO,GAAGD,MAAM,CAACQ,KAAK,GAAGC,SAAS;MACzCC,MAAM,EAAET,OAAO,GAAGD,MAAM,CAACU,MAAM,GAAGD,SAAS;MAC3CE,QAAQ,EAAEV,OAAO,GAAGQ,SAAS,GAAGT,MAAM,CAACW,QAAQ;MAC/CC,YAAY,EAAEX,OAAO,GAAGQ,SAAS,GAAGT,MAAM,CAACY;IAC7C,CAAC;EACH,CAAC,CAAC;AACJ;AAEO,eAAeC,SAASA,CAACR,GAAW,EAAiB;EAC1D,IAAI,CAACb,iBAAiB,EAAE;IACtBsB,OAAO,CAACC,IAAI,CAAC,2DAA2D,CAAC;IACzE;EACF;EACA,MAAMvB,iBAAiB,CAACqB,SAAS,CAACR,GAAG,CAAC;AACxC;AAEO,eAAeW,iBAAiBA,CACrCC,QAAgB,EACQ;EACxB,IAAI,CAACzB,iBAAiB,EAAE;IACtBsB,OAAO,CAACC,IAAI,CAAC,2DAA2D,CAAC;IACzE,OAAO,IAAI;EACb;EACA,OAAOvB,iBAAiB,CAACwB,iBAAiB,CAACC,QAAQ,CAAC;AACtD;AAEO,eAAeC,cAAcA,CAACb,GAAW,EAAiB;EAC/D,IAAI,CAACb,iBAAiB,EAAE;IACtB;EACF;EACA,OAAOA,iBAAiB,CAAC0B,cAAc,CAACb,GAAG,CAAC;AAC9C;AAEO,eAAec,gBAAgBA,CAAA,EAAkB;EACtD,IAAI,CAAC3B,iBAAiB,EAAE;IACtB;EACF;EACA,OAAOA,iBAAiB,CAAC2B,gBAAgB,CAAC,CAAC;AAC7C","ignoreList":[]}
@@ -21,13 +21,32 @@ Object.defineProperty(exports, "getNewCustomerConversation", {
21
21
  return _conversations.getNewCustomerConversation;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "getUploadUrl", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _media.getUploadUrl;
28
+ }
29
+ });
24
30
  Object.defineProperty(exports, "sendCustomerMessage", {
25
31
  enumerable: true,
26
32
  get: function () {
27
33
  return _messages.sendCustomerMessage;
28
34
  }
29
35
  });
36
+ Object.defineProperty(exports, "sendMediaMessage", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _media.sendMediaMessage;
40
+ }
41
+ });
42
+ Object.defineProperty(exports, "uploadFileToS3", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _media.uploadFileToS3;
46
+ }
47
+ });
30
48
  var _customers = require("./customers.js");
31
49
  var _conversations = require("./conversations.js");
32
50
  var _messages = require("./messages.js");
51
+ var _media = require("./media.js");
33
52
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_customers","require","_conversations","_messages"],"sourceRoot":"../../../src","sources":["api/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;AAIA,IAAAE,SAAA,GAAAF,OAAA","ignoreList":[]}
1
+ {"version":3,"names":["_customers","require","_conversations","_messages","_media"],"sourceRoot":"../../../src","sources":["api/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,UAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;AAIA,IAAAE,SAAA,GAAAF,OAAA;AACA,IAAAG,MAAA,GAAAH,OAAA","ignoreList":[]}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getUploadUrl = getUploadUrl;
7
+ exports.sendMediaMessage = sendMediaMessage;
8
+ exports.uploadFileToS3 = uploadFileToS3;
9
+ var _reactNative = require("react-native");
10
+ var _api = require("./api.js");
11
+ var _media = require("../data/fake/media.js");
12
+ function getUploadUrl(fileName, contentType, options) {
13
+ if (options.fake) {
14
+ return (0, _media.getFakeUploadUrlResponse)(fileName);
15
+ }
16
+ return _api.axiosInstance.post('/api/media/presign', {
17
+ fileName,
18
+ fileType: contentType
19
+ }).then(res => res.data);
20
+ }
21
+ function uploadFileToS3(uploadUrl, fileUri, contentType, onProgress, options) {
22
+ if (options?.fake) {
23
+ return (0, _media.fakeUploadToS3)(onProgress);
24
+ }
25
+ return new Promise((resolve, reject) => {
26
+ const xhr = new XMLHttpRequest();
27
+ xhr.upload.addEventListener('progress', event => {
28
+ if (event.lengthComputable && onProgress) {
29
+ const percentage = Math.min(100, Math.round(event.loaded / event.total * 100));
30
+ onProgress(percentage);
31
+ }
32
+ });
33
+ xhr.addEventListener('load', () => {
34
+ if (xhr.status >= 200 && xhr.status < 300) {
35
+ resolve();
36
+ } else {
37
+ console.error('[Comnyx] Upload failed:', xhr.status, xhr.responseText);
38
+ reject(new Error(`Upload failed with status ${xhr.status}`));
39
+ }
40
+ });
41
+ xhr.addEventListener('error', () => {
42
+ console.error('[Comnyx] Upload XHR error, status:', xhr.status, 'response:', xhr.responseText);
43
+ reject(new Error('Upload failed'));
44
+ });
45
+ xhr.addEventListener('abort', () => {
46
+ reject(new Error('Upload aborted'));
47
+ });
48
+ xhr.open('PUT', uploadUrl);
49
+ xhr.setRequestHeader('Content-Type', contentType);
50
+
51
+ // React Native supports sending file URIs directly via XHR
52
+ const file = {
53
+ uri: _reactNative.Platform.OS === 'android' ? fileUri : fileUri.replace('file://', ''),
54
+ type: contentType,
55
+ name: fileUri.split('/').pop() || 'file'
56
+ };
57
+ xhr.send(file);
58
+ });
59
+ }
60
+ function sendMediaMessage(externalId, fileUrls, mediaType, content, options) {
61
+ if (options.fake) {
62
+ return (0, _media.getFakeMediaMessageResponse)(fileUrls, mediaType);
63
+ }
64
+ return _api.axiosInstance.post('/api/customers/message', {
65
+ externalId,
66
+ content,
67
+ files: fileUrls,
68
+ media_type: mediaType
69
+ }, {
70
+ headers: {
71
+ 'Accept': 'application/json',
72
+ 'Content-Type': 'application/json'
73
+ }
74
+ }).then(res => res.data);
75
+ }
76
+ //# sourceMappingURL=media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","_api","_media","getUploadUrl","fileName","contentType","options","fake","getFakeUploadUrlResponse","axiosInstance","post","fileType","then","res","data","uploadFileToS3","uploadUrl","fileUri","onProgress","fakeUploadToS3","Promise","resolve","reject","xhr","XMLHttpRequest","upload","addEventListener","event","lengthComputable","percentage","Math","min","round","loaded","total","status","console","error","responseText","Error","open","setRequestHeader","file","uri","Platform","OS","replace","type","name","split","pop","send","sendMediaMessage","externalId","fileUrls","mediaType","content","getFakeMediaMessageResponse","files","media_type","headers"],"sourceRoot":"../../../src","sources":["api/media.ts"],"mappings":"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","ignoreList":[]}
Binary file