shakebug-react-native 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +17 -1
  2. package/VideoPlayer/index.js +37 -23
  3. package/android/build.gradle +0 -1
  4. package/android/src/main/java/com/shakebug/CreateThumbnailModule.java +217 -0
  5. package/android/src/main/java/com/shakebug/NetInfoModule.java +1 -2
  6. package/android/src/main/java/com/shakebug/RNDeviceModule.java +1 -3
  7. package/android/src/main/java/com/shakebug/ShakebugPackage.java +10 -10
  8. package/android/src/main/java/com/shakebug/{Shakebug.java → ShakebugSensors.java} +16 -22
  9. package/ios/GetLocals/ShakebugSDKRNLocalize.h +1 -0
  10. package/ios/Thumbnaill/{VideoThumbnailManager.h → CreateThumbnail.h} +2 -1
  11. package/ios/Thumbnaill/CreateThumbnail.m +82 -0
  12. package/package.json +2 -5
  13. package/shakebug-react-native.podspec +0 -1
  14. package/src/APIs/AddIssue.js +13 -4
  15. package/src/APIs/AddReview.js +46 -0
  16. package/src/APIs/CustomUserId.js +47 -0
  17. package/src/APIs/Event.js +8 -3
  18. package/src/APIs/StartSession.js +6 -2
  19. package/src/Assets/images/solar_star-bold.png +0 -0
  20. package/src/Assets/images/solar_star-line-duotone.png +0 -0
  21. package/src/Componanats/ReviewModal.js +171 -0
  22. package/src/Componanats/ShakeFunction.js +6 -3
  23. package/src/Screens/AddDetailsScreen.js +14 -10
  24. package/src/Screens/FirstScreen.js +2 -2
  25. package/src/Screens/Shakebug.js +63 -6
  26. package/src/Screens/TrimVideoScreen.js +13 -7
  27. package/src/index.tsx +6 -2
  28. package/android/src/main/java/com/shakebug/ShakebugSDKScreenShotModule.java +0 -196
  29. package/android/src/main/java/com/shakebug/VideoPlayerModule.java +0 -199
  30. package/android/src/main/java/com/shakebug/VideoThumbnailModule.java +0 -215
  31. package/android/src/main/java/com/shakebug/VideoTrimModule.java +0 -372
  32. package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +0 -54
  33. package/android/src/main/java/com/shakebug/utils/StorageUtil.java +0 -139
  34. package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +0 -89
  35. package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +0 -534
  36. package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +0 -429
  37. package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +0 -477
  38. package/ios/Thumbnaill/VideoThumbnailManager.m +0 -73
  39. package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +0 -372
  40. package/ios/TrimVideo/VideoTrim-Bridging-Header.h +0 -10
  41. package/ios/TrimVideo/VideoTrim.m +0 -23
  42. package/ios/TrimVideo/VideoTrimmer.swift +0 -810
  43. package/ios/TrimVideo/VideoTrimmerThumb.swift +0 -119
  44. package/ios/TrimVideo/VideoTrimmerViewController.swift +0 -301
@@ -1,372 +0,0 @@
1
- //
2
- // VideoTrim.swift
3
- // Shakebug
4
- //
5
- // Created by ideveloper2 on 26/04/24.
6
- // Copyright © 2024 Facebook. All rights reserved.
7
- //
8
-
9
- import React
10
- import Photos
11
- import ffmpegkit
12
-
13
- @objc(ShakebugSDKVideoTrim)
14
- class ShakebugSDKVideoTrim: RCTEventEmitter {
15
- private let FILE_PREFIX = "trimmedVideo"
16
- private var hasListeners = false
17
- private var isShowing = false
18
-
19
- private var saveToPhoto = true
20
- private var removeAfterSavedToPhoto = false
21
- private var trimmingText = "Trimming video..."
22
-
23
- @objc
24
- static override func requiresMainQueueSetup() -> Bool {
25
- return true
26
- }
27
-
28
- override func supportedEvents() -> [String]! {
29
- return ["ShakebugSDKVideoTrim"]
30
- }
31
-
32
- override func startObserving() {
33
- hasListeners = true
34
- }
35
-
36
- override func stopObserving() {
37
- hasListeners = false
38
- }
39
-
40
- @objc(isValidVideo:withResolver:withRejecter:)
41
- func isValidVideo(uri: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
42
- if let destPath = copyFileToDocumentDir(uri: uri) {
43
- resolve(UIVideoEditorController.canEditVideo(atPath: destPath.path))
44
- let _ = deleteFile(url: destPath) // remove the file we just copied to document directory
45
- } else {
46
- resolve(false)
47
- }
48
- }
49
-
50
- @objc(showEditor:withConfig:)
51
- func showEditor(uri: String, config: NSDictionary){
52
- if isShowing {
53
- return
54
- }
55
-
56
- saveToPhoto = config["saveToPhoto"] as? Bool ?? true
57
- removeAfterSavedToPhoto = config["removeAfterSavedToPhoto"] as? Bool ?? false
58
-
59
- let enableCancelDialog = config["enableCancelDialog"] as? Bool ?? true
60
- let cancelDialogTitle = config["cancelDialogTitle"] as? String ?? "Warning!"
61
- let cancelDialogMessage = config["cancelDialogMessage"] as? String ?? "Are you sure want to cancel?"
62
- let cancelDialogCancelText = config["cancelDialogCancelText"] as? String ?? "Close"
63
- let cancelDialogConfirmText = config["cancelDialogConfirmText"] as? String ?? "Proceed"
64
-
65
- let enableSaveDialog = config["enableSaveDialog"] as? Bool ?? true
66
- let saveDialogTitle = config["saveDialogTitle"] as? String ?? "Confirmation!"
67
- let saveDialogMessage = config["saveDialogMessage"] as? String ?? "Are you sure want to save?"
68
- let saveDialogCancelText = config["saveDialogCancelText"] as? String ?? "Close"
69
- let saveDialogConfirmText = config["saveDialogConfirmText"] as? String ?? "Proceed"
70
- trimmingText = config["trimmingText"] as? String ?? "Trimming video..."
71
- let fullScreenModalIOS = config["fullScreenModalIOS"] as? Bool ?? false
72
-
73
- if let destPath = copyFileToDocumentDir(uri: uri) {
74
- if UIVideoEditorController.canEditVideo(atPath: destPath.path) {
75
- DispatchQueue.main.async {
76
- if #available(iOS 13.0, *) {
77
- let vc = VideoTrimmerViewController()
78
- vc.asset = AVURLAsset(url: destPath, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
79
-
80
- if let maxDuration = config["maxDuration"] as? Int {
81
- vc.maximumDuration = maxDuration
82
- }
83
-
84
- if let minDuration = config["minDuration"] as? Int {
85
- vc.minimumDuration = minDuration
86
- }
87
-
88
- if let cancelBtnText = config["cancelButtonText"] as? String, !cancelBtnText.isEmpty {
89
- vc.cancelBtnText = cancelBtnText
90
- }
91
-
92
- if let saveButtonText = config["saveButtonText"] as? String, !saveButtonText.isEmpty {
93
- vc.saveButtonText = saveButtonText
94
- }
95
-
96
- vc.cancelBtnClicked = {
97
- if !enableCancelDialog {
98
- let _ = self.deleteFile(url: destPath) // remove the file we just copied to document directory
99
- self.emitEventToJS("onCancelTrimming", eventData: nil)
100
-
101
- vc.dismiss(animated: true, completion: {
102
- self.emitEventToJS("onHide", eventData: nil)
103
- self.isShowing = false
104
- })
105
- return
106
- }
107
-
108
- // Create Alert
109
- let dialogMessage = UIAlertController(title: cancelDialogTitle, message: cancelDialogMessage, preferredStyle: .alert)
110
-
111
- // Create OK button with action handler
112
- let ok = UIAlertAction(title: cancelDialogConfirmText, style: .destructive, handler: { (action) -> Void in
113
- let _ = self.deleteFile(url: destPath) // remove the file we just copied to document directory
114
- self.emitEventToJS("onCancelTrimming", eventData: nil)
115
-
116
- vc.dismiss(animated: true, completion: {
117
- self.emitEventToJS("onHide", eventData: nil)
118
- self.isShowing = false
119
- })
120
- })
121
-
122
- // Create Cancel button with action handlder
123
- let cancel = UIAlertAction(title: cancelDialogCancelText, style: .cancel)
124
-
125
- //Add OK and Cancel button to an Alert object
126
- dialogMessage.addAction(ok)
127
- dialogMessage.addAction(cancel)
128
-
129
- // Present alert message to user
130
- if let root = RCTPresentedViewController() {
131
- root.present(dialogMessage, animated: true, completion: nil)
132
- }
133
- }
134
-
135
- vc.saveBtnClicked = {(selectedRange: CMTimeRange) in
136
- if !enableSaveDialog {
137
- self.trim(viewController: vc,inputFile: destPath, videoDuration: vc.asset.duration.seconds, startTime: selectedRange.start.seconds, endTime: selectedRange.end.seconds)
138
- return
139
- }
140
-
141
- // Create Alert
142
- let dialogMessage = UIAlertController(title: saveDialogTitle, message: saveDialogMessage, preferredStyle: .alert)
143
-
144
- // Create OK button with action handler
145
- let ok = UIAlertAction(title: saveDialogConfirmText, style: .default, handler: { (action) -> Void in
146
- self.trim(viewController: vc,inputFile: destPath, videoDuration: vc.asset.duration.seconds, startTime: selectedRange.start.seconds, endTime: selectedRange.end.seconds)
147
- })
148
-
149
- // Create Cancel button with action handlder
150
- let cancel = UIAlertAction(title: saveDialogCancelText, style: .cancel)
151
-
152
- //Add OK and Cancel button to an Alert object
153
- dialogMessage.addAction(ok)
154
- dialogMessage.addAction(cancel)
155
-
156
- // Present alert message to user
157
- if let root = RCTPresentedViewController() {
158
- root.present(dialogMessage, animated: true, completion: nil)
159
- }
160
- }
161
-
162
- vc.isModalInPresentation = true // prevent modal closed by swipe down
163
-
164
- if fullScreenModalIOS {
165
- vc.modalPresentationStyle = .fullScreen
166
- }
167
-
168
- if let root = RCTPresentedViewController() {
169
- root.present(vc, animated: true, completion: {
170
- self.emitEventToJS("onShow", eventData: nil)
171
- self.isShowing = true
172
- })
173
- }
174
- }
175
- }
176
- } else {
177
- let eventPayload: [String: Any] = ["message": "File is not a valid video"]
178
- self.emitEventToJS("onError", eventData: eventPayload)
179
- }
180
- } else {
181
- let eventPayload: [String: Any] = ["message": "File is invalid"]
182
- self.emitEventToJS("onError", eventData: eventPayload)
183
- }
184
- }
185
-
186
- private func copyFileToDocumentDir(uri: String) -> URL? {
187
- if let videoURL = URL(string: uri) {
188
- // Save the video to the document directory
189
- let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
190
- // Extract the file extension from the videoURL
191
- let fileExtension = videoURL.pathExtension
192
-
193
- // Define the filename with the correct file extension
194
- let timestamp = Int(Date().timeIntervalSince1970)
195
- let destinationURL = documentsDirectory.appendingPathComponent("\(FILE_PREFIX)_original_\(timestamp).\(fileExtension)")
196
-
197
- do {
198
- try FileManager.default.copyItem(at: videoURL, to: destinationURL)
199
- } catch {
200
- print("Error while copying file to document directory \(error)")
201
- return nil
202
- }
203
-
204
- return destinationURL
205
- } else {
206
- return nil
207
- }
208
- }
209
-
210
- private func emitEventToJS(_ eventName: String, eventData: [String: Any]?) {
211
- if hasListeners {
212
- var modifiedEventData = eventData ?? [:] // If eventData is nil, create an empty dictionary
213
- modifiedEventData["name"] = eventName
214
- sendEvent(withName: "ShakebugSDKVideoTrim", body: modifiedEventData)
215
- }
216
- }
217
-
218
- @objc(listFiles:withRejecter:)
219
- func listFiles(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
220
- let files = listFiles()
221
- resolve(files.map{ $0.absoluteString })
222
- }
223
-
224
- @objc(cleanFiles:withRejecter:)
225
- func cleanFiles(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
226
- let files = listFiles()
227
- var successCount = 0
228
- for file in files {
229
- let state = deleteFile(url: file)
230
-
231
- if state == 0 {
232
- successCount += 1
233
- }
234
- }
235
-
236
- resolve(successCount)
237
- }
238
-
239
- @objc(deleteFile:withResolver:withRejecter:)
240
- func deleteFile(uri: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
241
- let state = deleteFile(url: URL(string: uri)!)
242
- resolve(state == 0)
243
- }
244
-
245
- private func listFiles() -> [URL] {
246
- var files: [URL] = []
247
-
248
- let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
249
-
250
- do {
251
- let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil)
252
-
253
- for fileURL in directoryContents {
254
- if fileURL.lastPathComponent.starts(with: FILE_PREFIX) {
255
- files.append(fileURL)
256
- }
257
- }
258
- } catch {
259
- print("[listFiles] Error when retrieving files: \(error)")
260
- }
261
-
262
- return files
263
- }
264
-
265
- private func deleteFile(url: URL) -> Int {
266
- do {
267
- if FileManager.default.fileExists(atPath: url.path) {
268
- try FileManager.default.removeItem(at: url)
269
-
270
- return 0
271
- }
272
-
273
- return 1
274
- } catch {
275
- print("[deleteFile] Error deleting files: \(error)")
276
-
277
- return 2
278
- }
279
- }
280
-
281
- @available(iOS 13.0, *)
282
- private func trim(viewController: VideoTrimmerViewController, inputFile: URL, videoDuration: Double, startTime: Double, endTime: Double) {
283
- let timestamp = Int(Date().timeIntervalSince1970)
284
- let outputName = "\(FILE_PREFIX)_\(timestamp).mp4" // use mp4 to prevent any issue with ffmpeg about file extension
285
- let outputFile = "\(inputFile.deletingLastPathComponent().absoluteURL)\(outputName)"
286
- let cmd = "-ss \(startTime * 1000)ms -to \(endTime * 1000)ms -i \(inputFile) -c copy \(outputFile)";
287
-
288
- self.emitEventToJS("onStartTrimming", eventData: nil)
289
-
290
- // Create Alert
291
- let dialogMessage = UIAlertController(title: trimmingText, message: nil, preferredStyle: .alert)
292
-
293
- // Present alert message to user
294
- let progressView = UIProgressView(frame: .zero)
295
- progressView.tintColor = .systemBlue
296
- if let root = RCTPresentedViewController() {
297
- root.present(dialogMessage, animated: true, completion: {
298
- dialogMessage.view.addSubview(progressView)
299
-
300
- progressView.translatesAutoresizingMaskIntoConstraints = false
301
- NSLayoutConstraint.activate([
302
- progressView.leadingAnchor.constraint(equalTo: dialogMessage.view.leadingAnchor, constant: 8),
303
- progressView.trailingAnchor.constraint(equalTo: dialogMessage.view.trailingAnchor, constant: -8),
304
- progressView.bottomAnchor.constraint(equalTo: dialogMessage.view.bottomAnchor, constant: -8)
305
- ])
306
- })
307
- }
308
-
309
- FFmpegKit.executeAsync(cmd, withCompleteCallback: { session in
310
- let _ = self.deleteFile(url: inputFile) // remove the file we just copied to document directory
311
-
312
- let state = session?.getState()
313
- let returnCode = session?.getReturnCode()
314
-
315
- if ReturnCode.isSuccess(returnCode) {
316
- let eventPayload: [String: Any] = ["outputPath": outputFile]
317
- self.emitEventToJS("onFinishTrimming", eventData: eventPayload)
318
-
319
- if (self.saveToPhoto) {
320
- PHPhotoLibrary.requestAuthorization { status in
321
- guard status == .authorized else {
322
- let eventPayload: [String: Any] = ["message": "Permission to access Photo Library is not granted"]
323
- self.emitEventToJS("onError", eventData: eventPayload)
324
- return
325
- }
326
-
327
- PHPhotoLibrary.shared().performChanges({
328
- let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(string: outputFile)!)
329
- request?.creationDate = Date()
330
- }) { success, error in
331
- if success {
332
- print("Edited video saved to Photo Library successfully.")
333
-
334
- if self.removeAfterSavedToPhoto {
335
- let _ = self.deleteFile(url: URL(string: outputFile)!)
336
- }
337
- } else {
338
- let eventPayload: [String: Any] = ["message": "Failed to save edited video to Photo Library: \(error?.localizedDescription ?? "Unknown error")"]
339
- self.emitEventToJS("onError", eventData: eventPayload)
340
- }
341
- }
342
- }
343
- }
344
- } else {
345
- // CANCEL + FAILURE
346
- let eventPayload: [String: Any] = ["message": "Command failed with state \(String(describing: FFmpegKitConfig.sessionState(toString: state ?? .failed))) and rc \(String(describing: returnCode)).\(String(describing: session?.getFailStackTrace()))"]
347
- self.emitEventToJS("onError", eventData: eventPayload)
348
- }
349
-
350
- // some how in case we trim a very short video the view controller is still visible after first .dismiss call
351
- // even the file is successfully saved
352
- // that's why we need a small delay here to ensure vc will be dismissed
353
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
354
- dialogMessage.dismiss(animated: false)
355
- viewController.dismiss(animated: true, completion: {
356
- self.emitEventToJS("onHide", eventData: nil)
357
- self.isShowing = false
358
- })
359
- }
360
- }, withLogCallback: { log in
361
-
362
- }, withStatisticsCallback: { statistics in
363
- let timeInMilliseconds = statistics?.getTime() ?? 0;
364
- if timeInMilliseconds > 0 {
365
- let completePercentage = timeInMilliseconds / (videoDuration * 1000); // from 0 -> 1
366
- DispatchQueue.main.async {
367
- progressView.setProgress(Float(completePercentage), animated: true)
368
- }
369
- }
370
- })
371
- }
372
- }
@@ -1,10 +0,0 @@
1
- //
2
- // VideoTrim-Bridging-Header.h
3
- // Shakebug
4
- //
5
- // Created by ideveloper2 on 26/04/24.
6
- // Copyright © 2024 Facebook. All rights reserved.
7
- //
8
-
9
- #import <React/RCTBridgeModule.h>
10
- #import <React/RCTViewManager.h>
@@ -1,23 +0,0 @@
1
- //
2
- // VideoTrim.m
3
- // Shakebug
4
- //
5
- // Created by ideveloper2 on 26/04/24.
6
- // Copyright © 2024 Facebook. All rights reserved.
7
- //
8
-
9
- #import <React/RCTBridgeModule.h>
10
- #import <React/RCTEventEmitter.h>
11
-
12
- @interface RCT_EXTERN_MODULE(ShakebugSDKVideoTrim, RCTEventEmitter)
13
-
14
- RCT_EXTERN_METHOD(showEditor:(NSString*)uri withConfig:(NSDictionary *)config)
15
- RCT_EXTERN_METHOD(isValidVideo:(NSString*)uri withResolver:(RCTPromiseResolveBlock)resolve
16
- withRejecter:(RCTPromiseRejectBlock)reject)
17
- RCT_EXTERN_METHOD(listFiles:(RCTPromiseResolveBlock)resolve
18
- withRejecter:(RCTPromiseRejectBlock)reject)
19
- RCT_EXTERN_METHOD(cleanFiles:(RCTPromiseResolveBlock)resolve
20
- withRejecter:(RCTPromiseRejectBlock)reject)
21
- RCT_EXTERN_METHOD(deleteFile:(NSString*)uri withResolver:(RCTPromiseResolveBlock)resolve
22
- withRejecter:(RCTPromiseRejectBlock)reject)
23
- @end