shakebug-react-native 1.0.14

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 (296) hide show
  1. package/DeviceInfo/index.js.flow +177 -0
  2. package/DeviceInfo/index.ts +1034 -0
  3. package/DeviceInfo/internal/asyncHookWrappers.ts +59 -0
  4. package/DeviceInfo/internal/devicesWithDynamicIsland.ts +30 -0
  5. package/DeviceInfo/internal/devicesWithNotch.ts +374 -0
  6. package/DeviceInfo/internal/nativeInterface.ts +28 -0
  7. package/DeviceInfo/internal/privateTypes.ts +231 -0
  8. package/DeviceInfo/internal/supported-platform-info.ts +97 -0
  9. package/DeviceInfo/internal/types.ts +19 -0
  10. package/DeviceInfo/web/index.js +223 -0
  11. package/GetLocals/src/NativeRNLocalize.ts +18 -0
  12. package/GetLocals/src/constants.ts +269 -0
  13. package/GetLocals/src/index.ts +56 -0
  14. package/GetLocals/src/module.ts +51 -0
  15. package/GetLocals/src/module.web.ts +148 -0
  16. package/GetLocals/src/types.ts +31 -0
  17. package/GetLocation/LocationError.ts +20 -0
  18. package/GetLocation/index.ts +46 -0
  19. package/GetLocation/utils.ts +89 -0
  20. package/ImageOrVedioPicker/src/index.ts +28 -0
  21. package/ImageOrVedioPicker/src/platforms/NativeImagePicker.ts +10 -0
  22. package/ImageOrVedioPicker/src/platforms/native.ts +61 -0
  23. package/ImageOrVedioPicker/src/platforms/web.ts +159 -0
  24. package/ImageOrVedioPicker/src/types.ts +74 -0
  25. package/NetInfo/src/__tests__/eventListenerCallbacks.spec.ts +332 -0
  26. package/NetInfo/src/__tests__/fetch.spec.ts +479 -0
  27. package/NetInfo/src/index.ts +186 -0
  28. package/NetInfo/src/internal/__mocks__/nativeModule.ts +13 -0
  29. package/NetInfo/src/internal/defaultConfiguration.ts +17 -0
  30. package/NetInfo/src/internal/defaultConfiguration.web.ts +17 -0
  31. package/NetInfo/src/internal/internetReachability.ts +180 -0
  32. package/NetInfo/src/internal/nativeInterface.ts +43 -0
  33. package/NetInfo/src/internal/nativeInterface.web.ts +27 -0
  34. package/NetInfo/src/internal/nativeModule.ts +15 -0
  35. package/NetInfo/src/internal/nativeModule.web.ts +306 -0
  36. package/NetInfo/src/internal/privateTypes.ts +41 -0
  37. package/NetInfo/src/internal/state.ts +137 -0
  38. package/NetInfo/src/internal/types.ts +128 -0
  39. package/README.md +246 -0
  40. package/RecordScreen/src/index.tsx +75 -0
  41. package/ScreenShot/src/RNViewShot.js +3 -0
  42. package/ScreenShot/src/RNViewShot.web.js +42 -0
  43. package/ScreenShot/src/index.js +308 -0
  44. package/Sensor/src/index.js +14 -0
  45. package/Sensor/src/rnsensors.js +57 -0
  46. package/Sensor/src/sensors.js +97 -0
  47. package/VideoPlayer/PlayVideo.tsx +51 -0
  48. package/VideoPlayer/index.js +69 -0
  49. package/android/README.md +14 -0
  50. package/android/build/generated/source/buildConfig/debug/com/shakebug/BuildConfig.java +10 -0
  51. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +42 -0
  52. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output-metadata.json +18 -0
  53. package/android/build/intermediates/aar_metadata/debug/aar-metadata.properties +6 -0
  54. package/android/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +1 -0
  55. package/android/build/intermediates/compile_library_classes_jar/debug/classes.jar +0 -0
  56. package/android/build/intermediates/compile_r_class_jar/debug/R.jar +0 -0
  57. package/android/build/intermediates/compile_symbol_list/debug/R.txt +47 -0
  58. package/android/build/intermediates/compiled_local_resources/debug/out/xml_imagepicker_provider_paths.xml.flat +0 -0
  59. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +12 -0
  60. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  61. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  62. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  63. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +2 -0
  64. package/android/build/intermediates/javac/debug/classes/com/shakebug/BuildConfig.class +0 -0
  65. package/android/build/intermediates/javac/debug/classes/com/shakebug/CustomMediaMetadataRetriever.class +0 -0
  66. package/android/build/intermediates/javac/debug/classes/com/shakebug/ImageMetadata.class +0 -0
  67. package/android/build/intermediates/javac/debug/classes/com/shakebug/ImagePickerProvider.class +0 -0
  68. package/android/build/intermediates/javac/debug/classes/com/shakebug/Metadata.class +0 -0
  69. package/android/build/intermediates/javac/debug/classes/com/shakebug/Options.class +0 -0
  70. package/android/build/intermediates/javac/debug/classes/com/shakebug/RecordScreenModule$1.class +0 -0
  71. package/android/build/intermediates/javac/debug/classes/com/shakebug/RecordScreenModule.class +0 -0
  72. package/android/build/intermediates/javac/debug/classes/com/shakebug/Shakebug.class +0 -0
  73. package/android/build/intermediates/javac/debug/classes/com/shakebug/ShakebugPackage.class +0 -0
  74. package/android/build/intermediates/javac/debug/classes/com/shakebug/Utility.class +0 -0
  75. package/android/build/intermediates/javac/debug/classes/com/shakebug/Utils.class +0 -0
  76. package/android/build/intermediates/javac/debug/classes/com/shakebug/VideoMetadata.class +0 -0
  77. package/android/build/intermediates/javac/debug/classes/com/shakebug/ViewShot$1.class +0 -0
  78. package/android/build/intermediates/javac/debug/classes/com/shakebug/ViewShot$2.class +0 -0
  79. package/android/build/intermediates/javac/debug/classes/com/shakebug/ViewShot$Formats.class +0 -0
  80. package/android/build/intermediates/javac/debug/classes/com/shakebug/ViewShot$Results.class +0 -0
  81. package/android/build/intermediates/javac/debug/classes/com/shakebug/ViewShot$ReusableByteArrayOutputStream.class +0 -0
  82. package/android/build/intermediates/javac/debug/classes/com/shakebug/ViewShot.class +0 -0
  83. package/android/build/intermediates/local_only_symbol_list/debug/R-def.txt +49 -0
  84. package/android/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +82 -0
  85. package/android/build/intermediates/merged_manifest/debug/AndroidManifest.xml +42 -0
  86. package/android/build/intermediates/navigation_json/debug/navigation.json +1 -0
  87. package/android/build/intermediates/packaged_res/debug/values/values.xml +25 -0
  88. package/android/build/intermediates/packaged_res/debug/xml/imagepicker_provider_paths.xml +4 -0
  89. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/BuildConfig.class +0 -0
  90. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/CustomMediaMetadataRetriever.class +0 -0
  91. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ImageMetadata.class +0 -0
  92. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ImagePickerProvider.class +0 -0
  93. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/Metadata.class +0 -0
  94. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/Options.class +0 -0
  95. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/RecordScreenModule$1.class +0 -0
  96. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/RecordScreenModule.class +0 -0
  97. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/Shakebug.class +0 -0
  98. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ShakebugPackage.class +0 -0
  99. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/Utility.class +0 -0
  100. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/Utils.class +0 -0
  101. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/VideoMetadata.class +0 -0
  102. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ViewShot$1.class +0 -0
  103. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ViewShot$2.class +0 -0
  104. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ViewShot$Formats.class +0 -0
  105. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ViewShot$Results.class +0 -0
  106. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ViewShot$ReusableByteArrayOutputStream.class +0 -0
  107. package/android/build/intermediates/runtime_library_classes_dir/debug/com/shakebug/ViewShot.class +0 -0
  108. package/android/build/intermediates/symbol_list_with_package_name/debug/package-aware-r.txt +48 -0
  109. package/android/build/outputs/logs/manifest-merger-debug-report.txt +97 -0
  110. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  111. package/android/build.gradle +235 -0
  112. package/android/src/main/AndroidManifest.xml +40 -0
  113. package/android/src/main/java/com/shakebug/AmazonFireDeviceConnectivityPoller.java +170 -0
  114. package/android/src/main/java/com/shakebug/BaseUtils.java +33 -0
  115. package/android/src/main/java/com/shakebug/BroadcastReceiverConnectivityReceiver.java +131 -0
  116. package/android/src/main/java/com/shakebug/ConnectivityReceiver.java +318 -0
  117. package/android/src/main/java/com/shakebug/CrashLoggerModule.java +48 -0
  118. package/android/src/main/java/com/shakebug/DateUtil.java +64 -0
  119. package/android/src/main/java/com/shakebug/DeviceType.java +18 -0
  120. package/android/src/main/java/com/shakebug/DeviceUtil.java +46 -0
  121. package/android/src/main/java/com/shakebug/DrawingView.java +90 -0
  122. package/android/src/main/java/com/shakebug/DrawingViewManager.java +38 -0
  123. package/android/src/main/java/com/shakebug/GetLocation.java +147 -0
  124. package/android/src/main/java/com/shakebug/ImageMetadata.java +42 -0
  125. package/android/src/main/java/com/shakebug/ImagePickerProvider.java +8 -0
  126. package/android/src/main/java/com/shakebug/Metadata.java +46 -0
  127. package/android/src/main/java/com/shakebug/NetInfoModule.java +79 -0
  128. package/android/src/main/java/com/shakebug/NetInfoUtils.java +50 -0
  129. package/android/src/main/java/com/shakebug/NetworkCallbackConnectivityReceiver.java +209 -0
  130. package/android/src/main/java/com/shakebug/Options.java +42 -0
  131. package/android/src/main/java/com/shakebug/RNDeviceModule.java +1036 -0
  132. package/android/src/main/java/com/shakebug/RNInstallReferrerClient.java +124 -0
  133. package/android/src/main/java/com/shakebug/RNLocalizeModule.java +79 -0
  134. package/android/src/main/java/com/shakebug/RNLocalizeModuleImpl.java +258 -0
  135. package/android/src/main/java/com/shakebug/ReactNativeGetLocationModule.java +95 -0
  136. package/android/src/main/java/com/shakebug/RecordScreenModule.java +202 -0
  137. package/android/src/main/java/com/shakebug/ScreenshotDetectorModule.java +63 -0
  138. package/android/src/main/java/com/shakebug/SettingsUtil.java +44 -0
  139. package/android/src/main/java/com/shakebug/Shakebug.java +181 -0
  140. package/android/src/main/java/com/shakebug/ShakebugPackage.java +75 -0
  141. package/android/src/main/java/com/shakebug/ShakebugSDKImagePickerModule.java +56 -0
  142. package/android/src/main/java/com/shakebug/ShakebugSDKImagePickerModuleImpl.java +230 -0
  143. package/android/src/main/java/com/shakebug/ShakebugSDKScreenShotModule.java +196 -0
  144. package/android/src/main/java/com/shakebug/SharedPreferencesModule.java +39 -0
  145. package/android/src/main/java/com/shakebug/UnitConverter.java +48 -0
  146. package/android/src/main/java/com/shakebug/Utility.java +29 -0
  147. package/android/src/main/java/com/shakebug/Utils.java +593 -0
  148. package/android/src/main/java/com/shakebug/VideoMetadata.java +91 -0
  149. package/android/src/main/java/com/shakebug/VideoPlayerModule.java +199 -0
  150. package/android/src/main/java/com/shakebug/VideoThumbnailModule.java +215 -0
  151. package/android/src/main/java/com/shakebug/VideoTrimModule.java +372 -0
  152. package/android/src/main/java/com/shakebug/ViewShot.java +627 -0
  153. package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +54 -0
  154. package/android/src/main/java/com/shakebug/callback/SingleCallback.java +11 -0
  155. package/android/src/main/java/com/shakebug/interfaces/IVideoTrimmerView.java +5 -0
  156. package/android/src/main/java/com/shakebug/interfaces/VideoPlayerListner.java +5 -0
  157. package/android/src/main/java/com/shakebug/interfaces/VideoTrimListener.java +10 -0
  158. package/android/src/main/java/com/shakebug/resolver/DeviceIdResolver.java +87 -0
  159. package/android/src/main/java/com/shakebug/resolver/DeviceTypeResolver.java +94 -0
  160. package/android/src/main/java/com/shakebug/thread/BackgroundExecutor.java +246 -0
  161. package/android/src/main/java/com/shakebug/thread/UiThreadExecutor.java +120 -0
  162. package/android/src/main/java/com/shakebug/types/CellularGeneration.java +60 -0
  163. package/android/src/main/java/com/shakebug/types/ConnectionType.java +26 -0
  164. package/android/src/main/java/com/shakebug/utils/StorageUtil.java +139 -0
  165. package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +89 -0
  166. package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +534 -0
  167. package/android/src/main/java/com/shakebug/widgets/SpacesItemDecoration2.java +32 -0
  168. package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +429 -0
  169. package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +477 -0
  170. package/android/src/main/java/com/shakebug/widgets/ZVideoView.java +48 -0
  171. package/android/src/main/res/drawable/custom_progress.xml +16 -0
  172. package/android/src/main/res/drawable/custom_thumb.xml +4 -0
  173. package/android/src/main/res/drawable/ic_close_button.xml +10 -0
  174. package/android/src/main/res/drawable/ic_video_pause_black.png +0 -0
  175. package/android/src/main/res/drawable/ic_video_play_black.png +0 -0
  176. package/android/src/main/res/drawable/ic_video_thumb_handle.png +0 -0
  177. package/android/src/main/res/drawable/icon_seek_bar.png +0 -0
  178. package/android/src/main/res/layout/video_player_view.xml +121 -0
  179. package/android/src/main/res/layout/video_thumb_item_layout.xml +16 -0
  180. package/android/src/main/res/layout/video_trimmer_view.xml +148 -0
  181. package/android/src/main/res/values/colors.xml +17 -0
  182. package/android/src/main/res/values/strings.xml +14 -0
  183. package/android/src/main/res/xml/imagepicker_provider_paths.xml +4 -0
  184. package/ios/CammeraPermission/RNCameraPermission.h +16 -0
  185. package/ios/CammeraPermission/RNCameraPermission.m +46 -0
  186. package/ios/DeviceInfo/DeviceUID.h +14 -0
  187. package/ios/DeviceInfo/DeviceUID.m +202 -0
  188. package/ios/DeviceInfo/EnvironmentUtil.h +41 -0
  189. package/ios/DeviceInfo/EnvironmentUtil.m +56 -0
  190. package/ios/DeviceInfo/RNDeviceInfo.h +22 -0
  191. package/ios/DeviceInfo/RNDeviceInfo.m +918 -0
  192. package/ios/GetLocals/ShakebugSDKRNLocalize.h +21 -0
  193. package/ios/GetLocals/ShakebugSDKRNLocalize.m +296 -0
  194. package/ios/GetLocation/SettingsUtil.h +16 -0
  195. package/ios/GetLocation/SettingsUtil.m +21 -0
  196. package/ios/GetLocation/ShakebugSDKLocationModule.h +17 -0
  197. package/ios/GetLocation/ShakebugSDKLocationModule.m +218 -0
  198. package/ios/ImageOrVideoPicker/ShakebugSDKImagePickerManager.h +40 -0
  199. package/ios/ImageOrVideoPicker/ShakebugSDKImagePickerManager.m +588 -0
  200. package/ios/ImageOrVideoPicker/ShakebugSDKImagePickerUtils.h +26 -0
  201. package/ios/ImageOrVideoPicker/ShakebugSDKImagePickerUtils.m +194 -0
  202. package/ios/MicrophonePermissionManager.h +14 -0
  203. package/ios/MicrophonePermissionManager.m +35 -0
  204. package/ios/NetInfo/RNCConnectionState.h +42 -0
  205. package/ios/NetInfo/RNCConnectionState.m +119 -0
  206. package/ios/NetInfo/RNCConnectionStateWatcher.h +31 -0
  207. package/ios/NetInfo/RNCConnectionStateWatcher.m +136 -0
  208. package/ios/NetInfo/RNCNetInfo.h +13 -0
  209. package/ios/NetInfo/RNCNetInfo.m +269 -0
  210. package/ios/RecordScreen/ShakebugSDKRecordScreen.h +31 -0
  211. package/ios/RecordScreen/ShakebugSDKRecordScreen.m +309 -0
  212. package/ios/ScreenShot/ShakebugSDKRNViewShot.h +6 -0
  213. package/ios/ScreenShot/ShakebugSDKRNViewShot.m +186 -0
  214. package/ios/ScreenshotDetector/ScreenShotDetector.h +12 -0
  215. package/ios/ScreenshotDetector/ScreenShotDetector.m +37 -0
  216. package/ios/Senors/RNSensorsAccelerometer.h +22 -0
  217. package/ios/Senors/RNSensorsAccelerometer.m +162 -0
  218. package/ios/Senors/RNSensorsBarometer.h +22 -0
  219. package/ios/Senors/RNSensorsBarometer.m +141 -0
  220. package/ios/Senors/RNSensorsGravity.h +22 -0
  221. package/ios/Senors/RNSensorsGravity.m +165 -0
  222. package/ios/Senors/RNSensorsGyroscope.h +22 -0
  223. package/ios/Senors/RNSensorsGyroscope.m +160 -0
  224. package/ios/Senors/RNSensorsMagnetometer.h +22 -0
  225. package/ios/Senors/RNSensorsMagnetometer.m +163 -0
  226. package/ios/Senors/RNSensorsOrientation.h +22 -0
  227. package/ios/Senors/RNSensorsOrientation.m +189 -0
  228. package/ios/Senors/RNSensorsUtils.h +7 -0
  229. package/ios/Senors/RNSensorsUtils.m +9 -0
  230. package/ios/Shakebug.h +7 -0
  231. package/ios/Shakebug.m +16 -0
  232. package/ios/Shakebug.xcodeproj/project.pbxproj +872 -0
  233. package/ios/Shakebug.xcodeproj/xcshareddata/xcschemes/Shakebug.xcscheme +66 -0
  234. package/ios/Shakebug.xcworkspace/contents.xcworkspacedata +10 -0
  235. package/ios/Shakebug.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  236. package/ios/SketchCanvas/DrawingView.h +18 -0
  237. package/ios/SketchCanvas/DrawingView.m +96 -0
  238. package/ios/SketchCanvas/DrawingViewManager.h +12 -0
  239. package/ios/SketchCanvas/DrawingViewManager.m +35 -0
  240. package/ios/Storage/SharedPreferencesModule.h +13 -0
  241. package/ios/Storage/SharedPreferencesModule.m +35 -0
  242. package/ios/Thumbnaill/VideoThumbnailManager.h +12 -0
  243. package/ios/Thumbnaill/VideoThumbnailManager.m +73 -0
  244. package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +372 -0
  245. package/ios/TrimVideo/VideoTrim-Bridging-Header.h +10 -0
  246. package/ios/TrimVideo/VideoTrim.m +23 -0
  247. package/ios/TrimVideo/VideoTrimmer.swift +810 -0
  248. package/ios/TrimVideo/VideoTrimmerThumb.swift +119 -0
  249. package/ios/TrimVideo/VideoTrimmerViewController.swift +301 -0
  250. package/ios/VideoPlayer/VideoPlayerModule.h +14 -0
  251. package/ios/VideoPlayer/VideoPlayerModule.m +29 -0
  252. package/ios/VideoPlayer/VideoPlayerViewController.h +18 -0
  253. package/ios/VideoPlayer/VideoPlayerViewController.m +29 -0
  254. package/package.json +45 -0
  255. package/shakebug-react-native.podspec +50 -0
  256. package/src/APIs/AddIssue.js +70 -0
  257. package/src/APIs/EndSession.js +32 -0
  258. package/src/APIs/Event.js +39 -0
  259. package/src/APIs/MatchKey.js +40 -0
  260. package/src/APIs/StartSession.js +84 -0
  261. package/src/Assets/images/OpenSketch.png +0 -0
  262. package/src/Assets/images/Push.png +0 -0
  263. package/src/Assets/images/RemoveTextView.png +0 -0
  264. package/src/Assets/images/black.png +0 -0
  265. package/src/Assets/images/blue.png +0 -0
  266. package/src/Assets/images/borderdBlack.png +0 -0
  267. package/src/Assets/images/borderdBlue.png +0 -0
  268. package/src/Assets/images/borderdGreen.png +0 -0
  269. package/src/Assets/images/borderdRed.png +0 -0
  270. package/src/Assets/images/borderdYellow.png +0 -0
  271. package/src/Assets/images/clear.png +0 -0
  272. package/src/Assets/images/close.png +0 -0
  273. package/src/Assets/images/green.png +0 -0
  274. package/src/Assets/images/next.png +0 -0
  275. package/src/Assets/images/play.png +0 -0
  276. package/src/Assets/images/red.png +0 -0
  277. package/src/Assets/images/shake.png +0 -0
  278. package/src/Assets/images/shakebugLogo.png +0 -0
  279. package/src/Assets/images/stopButton.png +0 -0
  280. package/src/Assets/images/text_icon.png +0 -0
  281. package/src/Assets/images/yellow.png +0 -0
  282. package/src/Componanats/CustomAlert.js +80 -0
  283. package/src/Componanats/ErrorHandler.js +80 -0
  284. package/src/Componanats/GlobalStateContext.js +22 -0
  285. package/src/Componanats/KeyboardAvoidingWrapper.js +43 -0
  286. package/src/Componanats/NavigationContext.js +42 -0
  287. package/src/Componanats/ShakeFunction.js +562 -0
  288. package/src/Componanats/localization.js +482 -0
  289. package/src/Screens/AddDetailsScreen.js +462 -0
  290. package/src/Screens/FirstScreen.js +629 -0
  291. package/src/Screens/Shakebug.js +487 -0
  292. package/src/Screens/StrockView.js +20 -0
  293. package/src/Screens/TrimVideoScreen.js +81 -0
  294. package/src/Screens/TutorialScreen.js +36 -0
  295. package/src/index.tsx +117 -0
  296. package/trimVideo/src/index.tsx +141 -0
@@ -0,0 +1,810 @@
1
+ //
2
+ // VideoTrimmer.swift
3
+ // Shakebug
4
+ //
5
+ // Created by ideveloper2 on 26/04/24.
6
+ // Copyright © 2024 Facebook. All rights reserved.
7
+ //
8
+
9
+ import UIKit
10
+ import AVFoundation
11
+
12
+ // Controls that allows trimming a range and scrubbing a progress indicator
13
+ @available(iOS 13.0, *)
14
+ @IBDesignable class VideoTrimmer: UIControl {
15
+
16
+ // events for changing selectedRange ("trimming")
17
+ static let didBeginTrimming = UIControl.Event(rawValue: 0b00000001 << 24)
18
+ static let selectedRangeChanged = UIControl.Event(rawValue: 0b00000010 << 24)
19
+ static let didEndTrimming = UIControl.Event(rawValue: 0b00000100 << 24)
20
+
21
+ // events for scrubbing the progress indicator ("scrubbing")
22
+ static let didBeginScrubbing = UIControl.Event(rawValue: 0b00001000 << 24)
23
+ static let progressChanged = UIControl.Event(rawValue: 0b00010000 << 24)
24
+ static let didEndScrubbing = UIControl.Event(rawValue: 0b00100000 << 24)
25
+
26
+ private struct Thumbnail {
27
+ let uuid = UUID()
28
+ let imageView: UIImageView
29
+ let time: CMTime
30
+ }
31
+
32
+ let thumbView = VideoTrimmerThumb()
33
+ private let wrapperView = UIView()
34
+ private let shadowView = UIView()
35
+ private let thumbnailClipView = UIView()
36
+ private let thumbnailWrapperView = UIView()
37
+ private let thumbnailTrackView = UIView()
38
+ private let thumbnailLeadingCoverView = UIView()
39
+ private let thumbnailTrailingCoverView = UIView()
40
+ private let leadingThumbRest = UIView()
41
+ private let trailingThumbRest = UIView()
42
+ private let progressIndicator = UIView()
43
+ private let progressIndicatorControl = UIControl()
44
+
45
+ // defines how much the control is insetted from its sides:
46
+ // this is set to 16, so that you can have the control fullscreen (and have it
47
+ // edge-to-edge when zooming in)
48
+ @IBInspectable var horizontalInset: CGFloat = 16 {
49
+ didSet {
50
+ guard horizontalInset != oldValue else {return}
51
+ setNeedsLayout()
52
+ }
53
+ }
54
+
55
+ // the asset to use
56
+ var asset: AVAsset? {
57
+ didSet {
58
+ if let asset = asset {
59
+ let duration = asset.duration
60
+ range = CMTimeRange(start: .zero, duration: duration)
61
+ selectedRange = range
62
+ lastKnownViewSizeForThumbnailGeneration = .zero
63
+ setNeedsLayout()
64
+ }
65
+ }
66
+ }
67
+
68
+ // the video composition to use
69
+ var videoComposition: AVVideoComposition? {
70
+ didSet {
71
+ lastKnownViewSizeForThumbnailGeneration = .zero
72
+ setNeedsLayout()
73
+ }
74
+ }
75
+
76
+ // a clip cannot be trimmed shorter than this duration
77
+ var minimumDuration: CMTime = CMTime(seconds: 1, preferredTimescale: 600)
78
+ var maximumDuration: CMTime = .positiveInfinity
79
+
80
+ // the available range of the asset.
81
+ // Will be set to the full duration of the asset when assigning a new asset
82
+ var range: CMTimeRange = .invalid {
83
+ didSet {
84
+ setNeedsLayout()
85
+ }
86
+ }
87
+
88
+ // the range that is selected, will be set to the full duration
89
+ // when changing asset.
90
+ var selectedRange: CMTimeRange = .invalid {
91
+ didSet {
92
+ setNeedsLayout()
93
+ }
94
+ }
95
+
96
+ // defines what to do with the progress indicator
97
+ enum ProgressIndicatorMode {
98
+ case hiddenOnlyWhenTrimming // the progress indicator gets hidden when the user starts trimming
99
+ case alwaysShown // the progress indicator is always shown, even when the user is trimming
100
+ case alwaysHidden // the progress indicator is never shown
101
+ }
102
+ var progressIndicatorMode = ProgressIndicatorMode.hiddenOnlyWhenTrimming {
103
+ didSet {
104
+ updateProgressIndicator()
105
+ }
106
+ }
107
+
108
+ func setProgressIndicatorMode(_ mode: ProgressIndicatorMode, animated: Bool) {
109
+ guard progressIndicatorMode != mode else {return}
110
+
111
+ if animated == true {
112
+ UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
113
+ self.progressIndicatorMode = mode
114
+ self.layoutIfNeeded()
115
+ })
116
+ } else {
117
+ progressIndicatorMode = mode
118
+ }
119
+ }
120
+
121
+ // defines where the progress indicator is shown.
122
+ var progress: CMTime = .zero {
123
+ didSet {
124
+ setNeedsLayout()
125
+ }
126
+ }
127
+
128
+ func setProgress(_ progress: CMTime, animated: Bool) {
129
+ guard CMTimeCompare(self.progress, progress) != 0 else {return}
130
+
131
+ self.progress = progress
132
+ if animated == true {
133
+ UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
134
+ self.layoutIfNeeded()
135
+ })
136
+ }
137
+ }
138
+
139
+
140
+ // defines if the user is trimming or not, and if so, which edge
141
+ enum TrimmingState {
142
+ case none // user isn't trimming
143
+ case leading // user is trimming the leading part of the asset
144
+ case trailing // user is trimming the trailing part of the asset
145
+ }
146
+
147
+ private(set) var trimmingState = TrimmingState.none {
148
+ didSet {
149
+ UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
150
+ self.shadowView.layer.shadowOpacity = (self.trimmingState != .none ? 0.5 : 0.25)
151
+ self.shadowView.layer.shadowRadius = (self.trimmingState != .none ? 4 : 2)
152
+ })
153
+ }
154
+ }
155
+
156
+ // yes if the user is zoomed in
157
+ private(set) var isZoomedIn = false
158
+ private(set) var zoomedInRange: CMTimeRange = .zero
159
+
160
+
161
+ // yes if the user is scrubbing the progress indicator
162
+ private(set) var isScrubbing = false
163
+
164
+ // background color for the track
165
+ var trackBackgroundColor = UIColor.black {
166
+ didSet {
167
+ thumbnailWrapperView.backgroundColor = trackBackgroundColor
168
+ }
169
+ }
170
+
171
+ // background color for the place where the thumbs rest on when the selectedRange == range
172
+ var thumbRestColor = UIColor.black {
173
+ didSet {
174
+ leadingThumbRest.backgroundColor = thumbRestColor
175
+ trailingThumbRest.backgroundColor = thumbRestColor
176
+ }
177
+ }
178
+
179
+ // the range that's currently visible: could be less than "range" when zoomed in
180
+ var visibleRange: CMTimeRange {
181
+ return isZoomedIn == true ? zoomedInRange : range
182
+ }
183
+
184
+ // the time that's currently selected by the user when trimming
185
+ var selectedTime: CMTime {
186
+ switch trimmingState {
187
+ case .none: return .zero
188
+ case .leading: return selectedRange.start
189
+ case .trailing: return selectedRange.end
190
+ }
191
+ }
192
+
193
+ // gesture recognizers used. Can be used, for instance, to
194
+ // require a tableview panGestureRecognizer to fail
195
+ private (set) var leadingGestureRecognizer: UILongPressGestureRecognizer!
196
+ private (set) var trailingGestureRecognizer: UILongPressGestureRecognizer!
197
+ private (set) var progressGestureRecognizer: UILongPressGestureRecognizer!
198
+ private (set) var thumbnailInteractionGestureRecognizer: UILongPressGestureRecognizer!
199
+
200
+ // private stuff
201
+ private var grabberOffset = CGFloat(0)
202
+ private var zoomWaitTimer: Timer?
203
+
204
+ private var lastKnownViewSizeForThumbnailGeneration: CGSize = .zero
205
+ private var thumbnailSize: CGSize = .zero
206
+ private var lastKnownThumbnailRange: CMTimeRange = .zero
207
+ private var thumbnails = Array<Thumbnail>()
208
+ private var generator: AVAssetImageGenerator?
209
+
210
+ private var impactFeedbackGenerator: UIImpactFeedbackGenerator?
211
+ private var didClampWhilePanning = false
212
+
213
+
214
+ // MARK: - Private
215
+ private func setup() {
216
+ addSubview(thumbnailClipView)
217
+ thumbnailClipView.addSubview(thumbnailWrapperView)
218
+ thumbnailWrapperView.addSubview(leadingThumbRest)
219
+ thumbnailWrapperView.addSubview(trailingThumbRest)
220
+ thumbnailWrapperView.addSubview(thumbnailTrackView)
221
+ thumbnailWrapperView.addSubview(thumbnailLeadingCoverView)
222
+ thumbnailWrapperView.addSubview(thumbnailTrailingCoverView)
223
+
224
+ progressIndicator.backgroundColor = .white
225
+ progressIndicator.layer.shadowColor = UIColor.black.cgColor
226
+ progressIndicator.layer.shadowOffset = .zero
227
+ progressIndicator.layer.shadowRadius = 2
228
+ progressIndicator.layer.shadowOpacity = 0.25
229
+ progressIndicator.layer.cornerRadius = 2
230
+ progressIndicator.layer.cornerCurve = .continuous
231
+
232
+ addSubview(shadowView)
233
+ wrapperView.clipsToBounds = true
234
+ shadowView.addSubview(wrapperView)
235
+ wrapperView.addSubview(thumbView)
236
+
237
+ wrapperView.addSubview(progressIndicator)
238
+ wrapperView.addSubview(progressIndicatorControl)
239
+
240
+ thumbnailClipView.clipsToBounds = true
241
+ thumbnailTrackView.clipsToBounds = true
242
+ thumbnailLeadingCoverView.backgroundColor = UIColor(white: 0, alpha: 0.75)
243
+ thumbnailTrailingCoverView.backgroundColor = UIColor(white: 0, alpha: 0.75)
244
+
245
+ leadingThumbRest.backgroundColor = thumbRestColor
246
+ trailingThumbRest.backgroundColor = thumbRestColor
247
+
248
+ thumbnailWrapperView.backgroundColor = trackBackgroundColor
249
+ thumbnailWrapperView.layer.cornerRadius = 6
250
+ thumbnailWrapperView.layer.cornerCurve = .continuous
251
+
252
+ leadingThumbRest.layer.cornerRadius = 6
253
+ leadingThumbRest.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
254
+ leadingThumbRest.layer.cornerCurve = .continuous
255
+
256
+ trailingThumbRest.layer.cornerRadius = 6
257
+ trailingThumbRest.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
258
+ trailingThumbRest.layer.cornerCurve = .continuous
259
+
260
+ shadowView.layer.shadowColor = UIColor.black.cgColor
261
+ shadowView.layer.shadowOffset = .zero
262
+ shadowView.layer.shadowRadius = 2
263
+ shadowView.layer.shadowOpacity = 0.25
264
+
265
+ leadingGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(leadingGrabberPanned(_:)))
266
+ leadingGestureRecognizer.allowableMovement = CGFloat.greatestFiniteMagnitude
267
+ leadingGestureRecognizer.minimumPressDuration = 0
268
+ thumbView.leadingGrabber.addGestureRecognizer(leadingGestureRecognizer)
269
+
270
+ trailingGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(trailingGrabberPanned(_:)))
271
+ trailingGestureRecognizer.allowableMovement = CGFloat.greatestFiniteMagnitude
272
+ trailingGestureRecognizer.minimumPressDuration = 0
273
+ thumbView.trailingGrabber.addGestureRecognizer(trailingGestureRecognizer)
274
+
275
+ progressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(progressGrabberPanned(_:)))
276
+ progressGestureRecognizer.allowableMovement = CGFloat.greatestFiniteMagnitude
277
+ progressGestureRecognizer.minimumPressDuration = 0
278
+ progressGestureRecognizer.require(toFail: leadingGestureRecognizer)
279
+ progressGestureRecognizer.require(toFail: trailingGestureRecognizer)
280
+ progressIndicatorControl.addGestureRecognizer(progressGestureRecognizer)
281
+
282
+ thumbnailInteractionGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(thumbnailPanned(_:)))
283
+ thumbnailInteractionGestureRecognizer.allowableMovement = CGFloat.greatestFiniteMagnitude
284
+ thumbnailInteractionGestureRecognizer.minimumPressDuration = 0
285
+ thumbnailInteractionGestureRecognizer.require(toFail: leadingGestureRecognizer)
286
+ thumbnailInteractionGestureRecognizer.require(toFail: trailingGestureRecognizer)
287
+ thumbView.addGestureRecognizer(thumbnailInteractionGestureRecognizer)
288
+ }
289
+
290
+ private func regenerateThumbnailsIfNeeded() {
291
+ let size = bounds.size
292
+ guard size.width > 0 && size.height > 0 else {return}
293
+ guard lastKnownViewSizeForThumbnailGeneration != size || CMTimeRangeEqual(lastKnownThumbnailRange, visibleRange) == false else {return}
294
+ guard let asset = asset else {return}
295
+ guard let track = asset.tracks(withMediaType: .video).first else {return}
296
+
297
+ lastKnownViewSizeForThumbnailGeneration = size
298
+ lastKnownThumbnailRange = visibleRange
299
+
300
+ let naturalSize = track.naturalSize
301
+ let transform = track.preferredTransform
302
+ let fixedSize = naturalSize.applyingVideoTransform(transform)
303
+
304
+ let generator = AVAssetImageGenerator(asset: asset)
305
+ generator.apertureMode = .cleanAperture
306
+ generator.videoComposition = videoComposition
307
+ self.generator = generator
308
+
309
+ let height = size.height - thumbView.edgeHeight * 2
310
+ thumbnailSize = CGSize(width: height / fixedSize.height * fixedSize.width, height: height)
311
+ let numberOfThumbnails = Int(ceil(size.width / thumbnailSize.width))
312
+
313
+ var newThumbnails = Array<Thumbnail>()
314
+ let thumbnailDuration = visibleRange.duration.seconds / Double(numberOfThumbnails)
315
+ var times = Array<NSValue>()
316
+ // we add some extra thumbnails as padding
317
+ for index in -3..<numberOfThumbnails + 6 {
318
+ let time = CMTimeAdd(visibleRange.start, CMTime(seconds: thumbnailDuration * Double(index), preferredTimescale: asset.duration.timescale * 2))
319
+ guard CMTimeCompare(time, .zero) != -1 else {continue}
320
+ times.append(NSValue(time: time))
321
+
322
+ let newThumbnail = Thumbnail(imageView: UIImageView(), time: time)
323
+ self.thumbnailTrackView.addSubview(newThumbnail.imageView)
324
+ newThumbnails.append(newThumbnail)
325
+ }
326
+
327
+ generator.appliesPreferredTrackTransform = true
328
+ generator.maximumSize = CGSize(width: thumbnailSize.width * UIScreen.main.scale, height: thumbnailSize.height * UIScreen.main.scale)
329
+
330
+ let oldThumbnails = thumbnails
331
+ thumbnails.append(contentsOf: newThumbnails)
332
+
333
+ UIView.animate(withDuration: 0.25, delay: 0.25, options: [.beginFromCurrentState], animations: {
334
+ oldThumbnails.forEach {$0.imageView.alpha = 0}
335
+ }, completion: { _ in
336
+ oldThumbnails.forEach {$0.imageView.removeFromSuperview()}
337
+ let uuidsToRemove = Set(oldThumbnails.map({$0.uuid}))
338
+ self.thumbnails.removeAll(where: {uuidsToRemove.contains($0.uuid)})
339
+ })
340
+
341
+ var seenIndex = 0
342
+ generator.requestedTimeToleranceBefore = .zero
343
+ generator.requestedTimeToleranceAfter = .zero
344
+ generator.generateCGImagesAsynchronously(forTimes: times) { requestedTime, cgImage, actualTime, result, error in
345
+ DispatchQueue.main.async {
346
+ seenIndex += 1
347
+
348
+ guard let cgImage = cgImage else {return}
349
+ let image = UIImage(cgImage: cgImage)
350
+
351
+ let imageView = newThumbnails[seenIndex - 1].imageView
352
+ UIView.transition(with: imageView, duration: 0.25, options: [.transitionCrossDissolve], animations: {
353
+ imageView.image = image
354
+ })
355
+ }
356
+ }
357
+ }
358
+
359
+ private func timeForLocation(_ x: CGFloat) -> CMTime {
360
+ let size = bounds.size
361
+ let inset = thumbView.chevronWidth + horizontalInset
362
+ let offset = x - inset
363
+
364
+ let availableWidth = size.width - inset * 2
365
+ let visibleDurationInSeconds = CGFloat(visibleRange.duration.seconds)
366
+ let ratio = visibleDurationInSeconds != 0 ? availableWidth / visibleDurationInSeconds : 0
367
+
368
+ let timeDifference = CMTime(seconds: Double(offset / ratio), preferredTimescale: 600)
369
+ return CMTimeAdd(visibleRange.start, timeDifference)
370
+ }
371
+
372
+ private func locationForTime(_ time: CMTime) -> CGFloat {
373
+ let size = bounds.size
374
+ let inset = thumbView.chevronWidth + horizontalInset
375
+ let availableWidth = size.width - inset * 2
376
+
377
+ let offset = CMTimeSubtract(time, visibleRange.start)
378
+
379
+ let visibleDurationInSeconds = CGFloat(visibleRange.duration.seconds)
380
+ let ratio = visibleDurationInSeconds != 0 ? availableWidth / visibleDurationInSeconds : 0
381
+
382
+ let location = CGFloat(offset.seconds) * ratio
383
+ return SnapToDevicePixels(location) + inset
384
+ }
385
+
386
+ private func startZoomWaitTimer() {
387
+ stopZoomWaitTimer()
388
+ guard isZoomedIn == false else {return}
389
+ zoomWaitTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { [weak self] _ in
390
+ guard let self = self else {return}
391
+ self.stopZoomWaitTimer()
392
+ self.zoomIfNeeded()
393
+ })
394
+ }
395
+
396
+ private func stopZoomWaitTimer() {
397
+ zoomWaitTimer?.invalidate()
398
+ zoomWaitTimer = nil
399
+ }
400
+
401
+ private func stopZoomIfNeeded() {
402
+ stopZoomWaitTimer()
403
+ isZoomedIn = false
404
+ animateChanges()
405
+ }
406
+
407
+ private func zoomIfNeeded() {
408
+ guard isZoomedIn == false else {return}
409
+
410
+ let size = bounds.size
411
+ let inset = thumbView.chevronWidth + horizontalInset
412
+ let availableWidth = size.width - inset * 2
413
+ let newDuration = CGFloat(range.duration.seconds > 4 ? 2.0 : range.duration.seconds * 0.5)
414
+
415
+ let durationTime = CMTime(seconds: Double(newDuration), preferredTimescale: 600)
416
+
417
+ if trimmingState == .leading {
418
+ let position = locationForTime(selectedRange.start) - inset
419
+ let start = position / availableWidth * newDuration
420
+ zoomedInRange = CMTimeRange(start: CMTimeSubtract(selectedRange.start, CMTime(seconds: Double(start), preferredTimescale: 600)), duration: durationTime)
421
+ } else {
422
+ let position = locationForTime(selectedRange.end) - inset
423
+
424
+ let durationToStart = position / availableWidth * newDuration
425
+ let newStart = CMTimeSubtract(selectedRange.end, CMTime(seconds: Double(durationToStart), preferredTimescale: 600))
426
+ zoomedInRange = CMTimeRange(start: newStart, duration: durationTime)
427
+ }
428
+
429
+ isZoomedIn = true
430
+ animateChanges()
431
+
432
+ UISelectionFeedbackGenerator().selectionChanged()
433
+ }
434
+
435
+ private func animateChanges() {
436
+ setNeedsLayout()
437
+ thumbView.setNeedsLayout()
438
+ UIView.animate(withDuration: 0.5, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
439
+ self.layoutIfNeeded()
440
+ self.thumbView.layoutIfNeeded()
441
+ })
442
+ }
443
+
444
+ private func startPanning() {
445
+ sendActions(for: Self.didBeginTrimming)
446
+ UISelectionFeedbackGenerator().selectionChanged()
447
+
448
+ didClampWhilePanning = false
449
+
450
+ impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
451
+ impactFeedbackGenerator?.prepare()
452
+
453
+ UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
454
+ self.updateProgressIndicator()
455
+ })
456
+ }
457
+
458
+ private func stopPanning() {
459
+ trimmingState = .none
460
+ stopZoomIfNeeded()
461
+ impactFeedbackGenerator = nil
462
+ sendActions(for: Self.didEndTrimming)
463
+
464
+ UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
465
+ self.updateProgressIndicator()
466
+ })
467
+ }
468
+
469
+ private func updateProgressIndicator() {
470
+ switch progressIndicatorMode {
471
+ case .alwaysHidden:
472
+ progressIndicator.alpha = 0
473
+ progressIndicatorControl.isUserInteractionEnabled = false
474
+
475
+ case .alwaysShown:
476
+ progressIndicator.alpha = 1
477
+ progressIndicatorControl.isUserInteractionEnabled = true
478
+ setNeedsLayout()
479
+
480
+ case .hiddenOnlyWhenTrimming:
481
+ progressIndicator.alpha = (trimmingState == .none ? 1 : 0)
482
+ progressIndicatorControl.isUserInteractionEnabled = (trimmingState == .none)
483
+ if trimmingState == .none {
484
+ setNeedsLayout()
485
+ if UIView.inheritedAnimationDuration > 0 {
486
+ UIView.performWithoutAnimation {
487
+ layoutIfNeeded()
488
+ }
489
+ }
490
+ }
491
+ }
492
+ progressIndicatorControl.alpha = progressIndicator.alpha
493
+ }
494
+
495
+
496
+ // MARK: - Input
497
+ @objc private func thumbnailPanned(_ sender: UILongPressGestureRecognizer) {
498
+ progressGrabberPanned(sender)
499
+ }
500
+
501
+
502
+ @objc private func progressGrabberPanned(_ sender: UILongPressGestureRecognizer) {
503
+
504
+ func handleChanged() {
505
+ let location = sender.location(in: self)
506
+ var time = timeForLocation(location.x + grabberOffset)
507
+
508
+ var didClamp = false
509
+ if CMTimeCompare(time, selectedRange.start) == -1 {
510
+ time = selectedRange.start
511
+ didClamp = true
512
+ }
513
+ if CMTimeCompare(time, selectedRange.end) == 1 {
514
+ time = selectedRange.end
515
+ didClamp = true
516
+ }
517
+
518
+ if didClamp == true && didClamp != didClampWhilePanning {
519
+ impactFeedbackGenerator?.impactOccurred()
520
+ }
521
+ didClampWhilePanning = didClamp
522
+
523
+ progress = time
524
+ setNeedsLayout()
525
+ sendActions(for: Self.progressChanged)
526
+ }
527
+ switch sender.state {
528
+ case .began:
529
+
530
+ UISelectionFeedbackGenerator().selectionChanged()
531
+ impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
532
+ impactFeedbackGenerator?.prepare()
533
+ didClampWhilePanning = false
534
+
535
+ isScrubbing = true
536
+ sendActions(for: Self.didBeginScrubbing)
537
+ handleChanged()
538
+
539
+ case .changed:
540
+ handleChanged()
541
+
542
+ case .ended, .cancelled:
543
+ impactFeedbackGenerator = nil
544
+
545
+ isScrubbing = false
546
+ sendActions(for: Self.didEndScrubbing)
547
+
548
+ case .possible, .failed:
549
+ break
550
+
551
+ @unknown default:
552
+ break
553
+ }
554
+ }
555
+
556
+
557
+ @objc private func leadingGrabberPanned(_ sender: UILongPressGestureRecognizer) {
558
+ switch sender.state {
559
+ case .began:
560
+ trimmingState = .leading
561
+ grabberOffset = thumbView.chevronWidth - sender.location(in: thumbView.leadingGrabber).x
562
+
563
+ startPanning()
564
+
565
+ case .changed:
566
+ let location = sender.location(in: self)
567
+ var time = timeForLocation(location.x + grabberOffset)
568
+ let newDuration = CMTimeSubtract(selectedRange.end, time)
569
+ let newRange = CMTimeRange(start: time, end: selectedRange.end)
570
+
571
+ if CMTimeCompare(newRange.duration, maximumDuration) == 1 {
572
+ return
573
+ }
574
+
575
+ if CMTimeCompare(newRange.start, .zero) == -1 { // prevent start time to be less than 0
576
+ return
577
+ }
578
+
579
+ var didClamp = false
580
+ if CMTimeCompare(newDuration, minimumDuration) == -1 {
581
+ // time = CMTimeSubtract(selectedRange.end, minimumDuration)
582
+ // didClamp = true
583
+ return
584
+ }
585
+ if CMTimeCompare(time, range.start) == -1 {
586
+ time = range.start
587
+ didClamp = true
588
+ }
589
+ if CMTimeCompare(time, range.end) == 1 {
590
+ time = range.end
591
+ didClamp = true
592
+ }
593
+
594
+
595
+ if didClamp == true && didClamp != didClampWhilePanning {
596
+ impactFeedbackGenerator?.impactOccurred()
597
+ } else {
598
+ // if didClamp == false && CMTimeCompare(progress, time) == 0 && CMTimeCompare(progress, range.start) != 0 && CMTimeCompare(progress, range.end) != 0 {
599
+ // impactFeedbackGenerator?.impactOccurred(intensity: 0.5)
600
+ // didClamp = true
601
+ // }
602
+ }
603
+ didClampWhilePanning = didClamp
604
+
605
+ selectedRange = newRange
606
+ sendActions(for: Self.selectedRangeChanged)
607
+ setNeedsLayout()
608
+
609
+ startZoomWaitTimer()
610
+
611
+ case .ended:
612
+ stopPanning()
613
+
614
+ case .cancelled:
615
+ stopPanning()
616
+
617
+ case .possible, .failed:
618
+ break
619
+
620
+ @unknown default:
621
+ break
622
+ }
623
+ }
624
+
625
+ @objc private func trailingGrabberPanned(_ sender: UILongPressGestureRecognizer) {
626
+ switch sender.state {
627
+ case .began:
628
+ trimmingState = .trailing
629
+ grabberOffset = sender.location(in: thumbView.trailingGrabber).x
630
+
631
+ startPanning()
632
+
633
+ case .changed:
634
+ let location = sender.location(in: self)
635
+ var time = timeForLocation(location.x - grabberOffset)
636
+ let newDuration = CMTimeSubtract(time, selectedRange.start)
637
+ let newRange = CMTimeRange(start: selectedRange.start, end: time)
638
+
639
+ if CMTimeCompare(newRange.duration, maximumDuration) == 1 {
640
+ return
641
+ }
642
+
643
+ if CMTimeCompare(newRange.end, range.duration) == 1 { // prevent endTime to be greater than video endTime
644
+ return
645
+ }
646
+
647
+ var didClamp = false
648
+ if CMTimeCompare(newDuration, minimumDuration) == -1 {
649
+ // time = CMTimeAdd(selectedRange.start, minimumDuration)
650
+ // didClamp = true
651
+ return
652
+ }
653
+ if CMTimeCompare(time, range.start) == -1 {
654
+ time = range.start
655
+ didClamp = true
656
+ }
657
+ if CMTimeCompare(time, range.end) == 1 {
658
+ time = range.end
659
+ didClamp = true
660
+ }
661
+
662
+ if didClamp == true && didClamp != didClampWhilePanning {
663
+ impactFeedbackGenerator?.impactOccurred()
664
+ } else {
665
+ // if didClamp == false && CMTimeCompare(progress, time) == 0 && CMTimeCompare(progress, range.start) != 0 && CMTimeCompare(progress, range.end) != 0 {
666
+ // impactFeedbackGenerator?.impactOccurred(intensity: 0.5)
667
+ // didClamp = true
668
+ // }
669
+ }
670
+ didClampWhilePanning = didClamp
671
+
672
+ selectedRange = newRange
673
+ sendActions(for: Self.selectedRangeChanged)
674
+ setNeedsLayout()
675
+
676
+ startZoomWaitTimer()
677
+
678
+ case .ended:
679
+ stopPanning()
680
+
681
+ case .cancelled:
682
+ stopPanning()
683
+
684
+ case .possible, .failed:
685
+ break
686
+
687
+ @unknown default:
688
+ break
689
+ }
690
+ }
691
+
692
+ // MARK: - UIView
693
+
694
+ override var intrinsicContentSize: CGSize {
695
+ return CGSize(width: UIView.noIntrinsicMetric, height: 50)
696
+ }
697
+
698
+ override func layoutSubviews() {
699
+ super.layoutSubviews()
700
+
701
+ let size = bounds.size
702
+ let inset = thumbView.chevronWidth
703
+ var left = locationForTime(selectedRange.start) - inset
704
+ var right = locationForTime(selectedRange.end) + inset
705
+
706
+ if right > bounds.width {
707
+ right = bounds.width + inset * 2
708
+ }
709
+
710
+ if left < 0 {
711
+ left = -inset
712
+ }
713
+
714
+ let rect = CGRect(origin: .zero, size: size)
715
+ shadowView.frame = rect
716
+ wrapperView.frame = rect
717
+ thumbView.frame = CGRect(x: left, y: 0, width: max(right - left, inset * 2), height: size.height)
718
+
719
+ let isZoomedToEnd = (trimmingState == .leading && isZoomedIn == true)
720
+
721
+ let thumbnailOffset = (isZoomedIn == true ? horizontalInset + inset + 6 : 0)
722
+ let coverOffset = thumbnailOffset - horizontalInset
723
+ let coverStartOffset = (isZoomedIn == false ? inset : 0)
724
+
725
+ let thumbnailRect = rect.insetBy(dx: horizontalInset - thumbnailOffset, dy: thumbView.edgeHeight)
726
+ thumbnailClipView.frame = rect
727
+ thumbnailWrapperView.frame = thumbnailRect
728
+ thumbnailTrackView.frame = CGRect(origin: .zero, size: CGSize(width: thumbnailRect.width - (isZoomedToEnd == false ? inset : 0), height: thumbnailRect.height))
729
+ thumbnailLeadingCoverView.frame = CGRect(x: coverStartOffset, y: 0, width: left + inset * 0.5 + coverOffset - coverStartOffset, height: thumbnailRect.height)
730
+ thumbnailTrailingCoverView.frame = CGRect(x: right - inset * 0.5 + coverOffset, y: 0, width: thumbnailRect.width - coverStartOffset - (right - inset * 0.5 + coverOffset), height: thumbnailRect.height)
731
+
732
+ leadingThumbRest.frame = CGRect(x: 0, y: 0, width: inset, height: thumbnailRect.height)
733
+ trailingThumbRest.frame = CGRect(x: thumbnailRect.width - inset, y: 0, width: inset, height: thumbnailRect.height)
734
+
735
+ if progressIndicator.alpha > 0 {
736
+ let progressWidth = CGFloat(4)
737
+ let progressIndicatorOffset = locationForTime(progress)
738
+ let progressLeft = min(max(thumbView.frame.minX + inset, progressIndicatorOffset - progressWidth * 0.5), thumbView.frame.maxX - inset - progressWidth)
739
+ progressIndicator.frame = CGRect(x: progressLeft, y: thumbnailRect.minY, width: progressWidth, height: thumbnailRect.height)
740
+
741
+ let progressControlWidth = CGFloat(24)
742
+
743
+ var progressControlLeft = max(thumbView.frame.minX + inset, progressLeft)
744
+ var progressControlRight = progressLeft + progressControlWidth
745
+ if progressControlRight > thumbView.frame.maxX - inset {
746
+ progressControlRight = thumbView.frame.maxX - inset
747
+ progressControlLeft = max(thumbView.frame.minX + inset, progressControlRight - progressControlWidth)
748
+ }
749
+ progressIndicatorControl.frame = CGRect(x: progressControlLeft, y: thumbnailRect.minY, width: progressControlRight - progressControlLeft, height: thumbnailRect.height)
750
+ }
751
+
752
+ regenerateThumbnailsIfNeeded()
753
+
754
+ for thumbnail in thumbnails {
755
+ let position = locationForTime(thumbnail.time) - horizontalInset + thumbnailOffset
756
+ let frame = CGRect(x: position, y: 0, width: thumbnailSize.width, height: thumbnailSize.height)
757
+ if thumbnail.imageView.bounds.width == 0 {
758
+ UIView.performWithoutAnimation {
759
+ thumbnail.imageView.frame = frame
760
+ }
761
+ } else {
762
+ thumbnail.imageView.frame = frame
763
+ }
764
+ }
765
+ }
766
+
767
+ override init(frame: CGRect) {
768
+ super.init(frame: frame)
769
+ setup()
770
+ }
771
+
772
+ required init?(coder: NSCoder) {
773
+ super.init(coder: coder)
774
+ setup()
775
+ }
776
+ }
777
+
778
+ // MARK: -
779
+
780
+ fileprivate func SnapToDevicePixels(_ value: CGFloat, scale: CGFloat? = nil) -> CGFloat {
781
+ let actualScale = scale ?? UIScreen.main.scale
782
+ return round(value * actualScale) / actualScale
783
+ }
784
+
785
+ fileprivate func SnapToDevicePixels(_ rect: CGRect, scale: CGFloat? = nil) -> CGRect {
786
+ return CGRect(x: SnapToDevicePixels(rect.origin.x, scale: scale),
787
+ y: SnapToDevicePixels(rect.origin.y, scale: scale),
788
+ width: SnapToDevicePixels(rect.maxX - rect.minX, scale: scale),
789
+ height: SnapToDevicePixels(rect.maxY - rect.minY, scale: scale))
790
+ }
791
+
792
+ fileprivate extension CGRect {
793
+ func snappedToDevicePixels(scale: CGFloat? = nil) -> CGRect {
794
+ return SnapToDevicePixels(self, scale: scale)
795
+ }
796
+ }
797
+
798
+ fileprivate extension CGSize {
799
+ func ceiled() -> CGSize {
800
+ return CGSize(width: ceil(width), height: ceil((height)))
801
+ }
802
+
803
+ func snappedToDevicePixels(scale: CGFloat? = nil) -> CGSize {
804
+ return CGSize(width: SnapToDevicePixels(width, scale: scale), height: SnapToDevicePixels(height, scale: scale))
805
+ }
806
+
807
+ func applyingVideoTransform(_ transform: CGAffineTransform) -> CGSize {
808
+ return CGRect(origin: .zero, size: self).applying(transform).size
809
+ }
810
+ }