@rentlydev/rently-tuya 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +281 -0
  3. package/android/build.gradle +182 -0
  4. package/android/gradle.properties +7 -0
  5. package/android/libs/airbrake-android-1.4.0.jar +0 -0
  6. package/android/src/main/AndroidManifest.xml +1 -0
  7. package/android/src/main/AndroidManifestNew.xml +25 -0
  8. package/android/src/main/java/com/rentlytuya/RentlyTuyaModule.kt +503 -0
  9. package/android/src/main/java/com/rentlytuya/RentlyTuyaPackage.kt +18 -0
  10. package/android/src/main/java/com/rentlytuya/TuyaActivity.kt +45 -0
  11. package/android/src/main/java/com/rentlytuya/alerts/FormatSDCardAlert.kt +83 -0
  12. package/android/src/main/java/com/rentlytuya/alerts/PermissionAlert.kt +91 -0
  13. package/android/src/main/java/com/rentlytuya/alerts/RemoveDoorbellAlert.kt +83 -0
  14. package/android/src/main/java/com/rentlytuya/alerts/RemoveDoorbellFailureAlert.kt +71 -0
  15. package/android/src/main/java/com/rentlytuya/alerts/ResetWifiAlert.kt +83 -0
  16. package/android/src/main/java/com/rentlytuya/alerts/SuccessAlert.kt +74 -0
  17. package/android/src/main/java/com/rentlytuya/components/Alert.kt +47 -0
  18. package/android/src/main/java/com/rentlytuya/components/AnchorButton.kt +125 -0
  19. package/android/src/main/java/com/rentlytuya/components/Button.kt +147 -0
  20. package/android/src/main/java/com/rentlytuya/components/CustomDatePicker.kt +275 -0
  21. package/android/src/main/java/com/rentlytuya/components/Divider.kt +53 -0
  22. package/android/src/main/java/com/rentlytuya/components/EncryptedImageView.kt +59 -0
  23. package/android/src/main/java/com/rentlytuya/components/Label.kt +155 -0
  24. package/android/src/main/java/com/rentlytuya/components/LoadingIndicator.kt +75 -0
  25. package/android/src/main/java/com/rentlytuya/components/Slider.kt +66 -0
  26. package/android/src/main/java/com/rentlytuya/components/Switch.kt +51 -0
  27. package/android/src/main/java/com/rentlytuya/components/VideoView.kt +45 -0
  28. package/android/src/main/java/com/rentlytuya/dashboard/AdvancedSettings.kt +259 -0
  29. package/android/src/main/java/com/rentlytuya/dashboard/Dashboard.kt +551 -0
  30. package/android/src/main/java/com/rentlytuya/dashboard/DoorbellSettings.kt +874 -0
  31. package/android/src/main/java/com/rentlytuya/dashboard/Messages.kt +463 -0
  32. package/android/src/main/java/com/rentlytuya/dashboard/NavController.kt +358 -0
  33. package/android/src/main/java/com/rentlytuya/dashboard/OfflineScreen.kt +160 -0
  34. package/android/src/main/java/com/rentlytuya/dashboard/Playback.kt +685 -0
  35. package/android/src/main/java/com/rentlytuya/tuya/Messages.kt +234 -0
  36. package/android/src/main/java/com/rentlytuya/tuya/Playback.kt +385 -0
  37. package/android/src/main/java/com/rentlytuya/tuya/ScreenCapture.kt +244 -0
  38. package/android/src/main/java/com/rentlytuya/tuya/Settings.kt +809 -0
  39. package/android/src/main/java/com/rentlytuya/tuya/Tuya.kt +497 -0
  40. package/android/src/main/java/com/rentlytuya/ui/theme/LocalColor.kt +80 -0
  41. package/android/src/main/java/com/rentlytuya/ui/theme/LocalFont.kt +35 -0
  42. package/android/src/main/java/com/rentlytuya/ui/theme/Theme.kt +34 -0
  43. package/android/src/main/java/com/rentlytuya/ui/theme/Type.kt +34 -0
  44. package/android/src/main/java/com/rentlytuya/util/Airbrake.kt +40 -0
  45. package/android/src/main/java/com/rentlytuya/util/DpConstants.kt +53 -0
  46. package/android/src/main/java/com/rentlytuya/util/FrescoManager.kt +86 -0
  47. package/android/src/main/java/com/rentlytuya/util/HeapAnalytics.kt +16 -0
  48. package/android/src/main/java/com/rentlytuya/util/KeylessModule.kt +81 -0
  49. package/android/src/main/java/com/rentlytuya/util/PermissionHandler.kt +75 -0
  50. package/android/src/main/java/com/rentlytuya/util/ReactParamsCheck.kt +152 -0
  51. package/android/src/main/java/com/rentlytuya/util/Util.kt +128 -0
  52. package/android/src/main/res/drawable/ic_close.xml +12 -0
  53. package/android/src/main/res/drawable/ic_launcher_background.xml +170 -0
  54. package/android/src/main/res/drawable/ic_launcher_foreground.xml +30 -0
  55. package/android/src/main/res/drawable/img_place_holder.png +0 -0
  56. package/android/src/main/res/drawable-hdpi/ic_action_arrow_backward.png +0 -0
  57. package/android/src/main/res/drawable-hdpi/ic_arrow_backward_white.png +0 -0
  58. package/android/src/main/res/drawable-hdpi/ic_arrow_blue_right.png +0 -0
  59. package/android/src/main/res/drawable-hdpi/ic_calendar.png +0 -0
  60. package/android/src/main/res/drawable-hdpi/ic_camera.png +0 -0
  61. package/android/src/main/res/drawable-hdpi/ic_message.png +0 -0
  62. package/android/src/main/res/drawable-hdpi/ic_pause.png +0 -0
  63. package/android/src/main/res/drawable-hdpi/ic_play.png +0 -0
  64. package/android/src/main/res/drawable-hdpi/ic_play_back.png +0 -0
  65. package/android/src/main/res/drawable-hdpi/ic_record.png +0 -0
  66. package/android/src/main/res/drawable-hdpi/ic_retry.png +0 -0
  67. package/android/src/main/res/drawable-hdpi/ic_setting.png +0 -0
  68. package/android/src/main/res/drawable-hdpi/ic_sound_off.png +0 -0
  69. package/android/src/main/res/drawable-hdpi/ic_sound_on.png +0 -0
  70. package/android/src/main/res/drawable-mdpi/ic_action_arrow_backward.png +0 -0
  71. package/android/src/main/res/drawable-mdpi/ic_arrow_backward_white.png +0 -0
  72. package/android/src/main/res/drawable-mdpi/ic_arrow_blue_right.png +0 -0
  73. package/android/src/main/res/drawable-mdpi/ic_battery_average.png +0 -0
  74. package/android/src/main/res/drawable-mdpi/ic_battery_charging.png +0 -0
  75. package/android/src/main/res/drawable-mdpi/ic_battery_full.png +0 -0
  76. package/android/src/main/res/drawable-mdpi/ic_battery_good.png +0 -0
  77. package/android/src/main/res/drawable-mdpi/ic_battery_low.png +0 -0
  78. package/android/src/main/res/drawable-mdpi/ic_calendar.png +0 -0
  79. package/android/src/main/res/drawable-mdpi/ic_camera.png +0 -0
  80. package/android/src/main/res/drawable-mdpi/ic_message.png +0 -0
  81. package/android/src/main/res/drawable-mdpi/ic_pause.png +0 -0
  82. package/android/src/main/res/drawable-mdpi/ic_play.png +0 -0
  83. package/android/src/main/res/drawable-mdpi/ic_play_back.png +0 -0
  84. package/android/src/main/res/drawable-mdpi/ic_record.png +0 -0
  85. package/android/src/main/res/drawable-mdpi/ic_retry.png +0 -0
  86. package/android/src/main/res/drawable-mdpi/ic_setting.png +0 -0
  87. package/android/src/main/res/drawable-mdpi/ic_sound_off.png +0 -0
  88. package/android/src/main/res/drawable-mdpi/ic_sound_on.png +0 -0
  89. package/android/src/main/res/drawable-xhdpi/ic_action_arrow_backward.png +0 -0
  90. package/android/src/main/res/drawable-xhdpi/ic_arrow_backward_white.png +0 -0
  91. package/android/src/main/res/drawable-xhdpi/ic_arrow_blue_right.png +0 -0
  92. package/android/src/main/res/drawable-xhdpi/ic_battery_average.png +0 -0
  93. package/android/src/main/res/drawable-xhdpi/ic_battery_charging.png +0 -0
  94. package/android/src/main/res/drawable-xhdpi/ic_battery_full.png +0 -0
  95. package/android/src/main/res/drawable-xhdpi/ic_battery_good.png +0 -0
  96. package/android/src/main/res/drawable-xhdpi/ic_battery_low.png +0 -0
  97. package/android/src/main/res/drawable-xhdpi/ic_calendar.png +0 -0
  98. package/android/src/main/res/drawable-xhdpi/ic_camera.png +0 -0
  99. package/android/src/main/res/drawable-xhdpi/ic_message.png +0 -0
  100. package/android/src/main/res/drawable-xhdpi/ic_pause.png +0 -0
  101. package/android/src/main/res/drawable-xhdpi/ic_play.png +0 -0
  102. package/android/src/main/res/drawable-xhdpi/ic_play_back.png +0 -0
  103. package/android/src/main/res/drawable-xhdpi/ic_record.png +0 -0
  104. package/android/src/main/res/drawable-xhdpi/ic_retry.png +0 -0
  105. package/android/src/main/res/drawable-xhdpi/ic_setting.png +0 -0
  106. package/android/src/main/res/drawable-xhdpi/ic_sound_off.png +0 -0
  107. package/android/src/main/res/drawable-xhdpi/ic_sound_on.png +0 -0
  108. package/android/src/main/res/drawable-xxhdpi/ic_action_arrow_backward.png +0 -0
  109. package/android/src/main/res/drawable-xxhdpi/ic_arrow_backward_white.png +0 -0
  110. package/android/src/main/res/drawable-xxhdpi/ic_arrow_blue_right.png +0 -0
  111. package/android/src/main/res/drawable-xxhdpi/ic_battery_average.png +0 -0
  112. package/android/src/main/res/drawable-xxhdpi/ic_battery_charging.png +0 -0
  113. package/android/src/main/res/drawable-xxhdpi/ic_battery_full.png +0 -0
  114. package/android/src/main/res/drawable-xxhdpi/ic_battery_good.png +0 -0
  115. package/android/src/main/res/drawable-xxhdpi/ic_battery_low.png +0 -0
  116. package/android/src/main/res/drawable-xxhdpi/ic_calendar.png +0 -0
  117. package/android/src/main/res/drawable-xxhdpi/ic_camera.png +0 -0
  118. package/android/src/main/res/drawable-xxhdpi/ic_message.png +0 -0
  119. package/android/src/main/res/drawable-xxhdpi/ic_pause.png +0 -0
  120. package/android/src/main/res/drawable-xxhdpi/ic_play.png +0 -0
  121. package/android/src/main/res/drawable-xxhdpi/ic_play_back.png +0 -0
  122. package/android/src/main/res/drawable-xxhdpi/ic_record.png +0 -0
  123. package/android/src/main/res/drawable-xxhdpi/ic_retry.png +0 -0
  124. package/android/src/main/res/drawable-xxhdpi/ic_setting.png +0 -0
  125. package/android/src/main/res/drawable-xxhdpi/ic_sound_off.png +0 -0
  126. package/android/src/main/res/drawable-xxhdpi/ic_sound_on.png +0 -0
  127. package/android/src/main/res/drawable-xxxhdpi/ic_action_arrow_backward.png +0 -0
  128. package/android/src/main/res/drawable-xxxhdpi/ic_arrow_backward_white.png +0 -0
  129. package/android/src/main/res/drawable-xxxhdpi/ic_arrow_blue_right.png +0 -0
  130. package/android/src/main/res/drawable-xxxhdpi/ic_calendar.png +0 -0
  131. package/android/src/main/res/drawable-xxxhdpi/ic_camera.png +0 -0
  132. package/android/src/main/res/drawable-xxxhdpi/ic_message.png +0 -0
  133. package/android/src/main/res/drawable-xxxhdpi/ic_pause.png +0 -0
  134. package/android/src/main/res/drawable-xxxhdpi/ic_play.png +0 -0
  135. package/android/src/main/res/drawable-xxxhdpi/ic_play_back.png +0 -0
  136. package/android/src/main/res/drawable-xxxhdpi/ic_record.png +0 -0
  137. package/android/src/main/res/drawable-xxxhdpi/ic_retry.png +0 -0
  138. package/android/src/main/res/drawable-xxxhdpi/ic_setting.png +0 -0
  139. package/android/src/main/res/drawable-xxxhdpi/ic_sound_off.png +0 -0
  140. package/android/src/main/res/drawable-xxxhdpi/ic_sound_on.png +0 -0
  141. package/android/src/main/res/font/quicksand_bold.ttf +0 -0
  142. package/android/src/main/res/font/quicksand_light.ttf +0 -0
  143. package/android/src/main/res/font/quicksand_medium.ttf +0 -0
  144. package/android/src/main/res/font/quicksand_regular.ttf +0 -0
  145. package/android/src/main/res/font/quicksand_semi_bold.ttf +0 -0
  146. package/android/src/main/res/values/strings.xml +121 -0
  147. package/android/thingMapping.gradle +200 -0
  148. package/android/thingMapping.json +2014 -0
  149. package/ios/components/Alert.swift +54 -0
  150. package/ios/components/AnchorButton.swift +100 -0
  151. package/ios/components/Button.swift +118 -0
  152. package/ios/components/CustomDatePicker.swift +275 -0
  153. package/ios/components/Divider.swift +28 -0
  154. package/ios/components/Image.swift +72 -0
  155. package/ios/components/Label.swift +105 -0
  156. package/ios/components/Switch.swift +61 -0
  157. package/ios/dashboard/AdvanceSettings.swift +127 -0
  158. package/ios/dashboard/Dashboard.swift +354 -0
  159. package/ios/dashboard/Messages.swift +361 -0
  160. package/ios/dashboard/NavigationController.swift +173 -0
  161. package/ios/dashboard/OfflineScreen.swift +109 -0
  162. package/ios/dashboard/Playback.swift +543 -0
  163. package/ios/dashboard/PreviewManager.swift +117 -0
  164. package/ios/dashboard/Settings.swift +672 -0
  165. package/ios/icons/Charging.png +0 -0
  166. package/ios/icons/Charging@2x.png +0 -0
  167. package/ios/icons/Charging@3x.png +0 -0
  168. package/ios/icons/back_arraow.png +0 -0
  169. package/ios/icons/back_arraow@2x.png +0 -0
  170. package/ios/icons/back_arraow@3x.png +0 -0
  171. package/ios/icons/battery_average.png +0 -0
  172. package/ios/icons/battery_average@2x.png +0 -0
  173. package/ios/icons/battery_average@3x.png +0 -0
  174. package/ios/icons/battery_full.png +0 -0
  175. package/ios/icons/battery_full@2x.png +0 -0
  176. package/ios/icons/battery_full@3x.png +0 -0
  177. package/ios/icons/battery_good.png +0 -0
  178. package/ios/icons/battery_good@2x.png +0 -0
  179. package/ios/icons/battery_good@3x.png +0 -0
  180. package/ios/icons/battery_low.png +0 -0
  181. package/ios/icons/battery_low@2x.png +0 -0
  182. package/ios/icons/battery_low@3x.png +0 -0
  183. package/ios/icons/calendar.png +0 -0
  184. package/ios/icons/calendar@2x.png +0 -0
  185. package/ios/icons/calendar@3x.png +0 -0
  186. package/ios/icons/cloud.png +0 -0
  187. package/ios/icons/cloud@2x.png +0 -0
  188. package/ios/icons/cloud@3x.png +0 -0
  189. package/ios/icons/down_update.png +0 -0
  190. package/ios/icons/down_update@2x.png +0 -0
  191. package/ios/icons/down_update@3x.png +0 -0
  192. package/ios/icons/exit_fullscreen.png +0 -0
  193. package/ios/icons/exit_fullscreen@2x.png +0 -0
  194. package/ios/icons/exit_fullscreen@3x.png +0 -0
  195. package/ios/icons/expand.png +0 -0
  196. package/ios/icons/fullscreen.png +0 -0
  197. package/ios/icons/fullscreen@2x.png +0 -0
  198. package/ios/icons/fullscreen@3x.png +0 -0
  199. package/ios/icons/image_placeholder.png +0 -0
  200. package/ios/icons/information.png +0 -0
  201. package/ios/icons/information@2x.png +0 -0
  202. package/ios/icons/information@3x.png +0 -0
  203. package/ios/icons/keyless_Setting.png +0 -0
  204. package/ios/icons/keyless_Setting@2x.png +0 -0
  205. package/ios/icons/keyless_Setting@3x.png +0 -0
  206. package/ios/icons/keyless_back@1x.png +0 -0
  207. package/ios/icons/keyless_back@2x.png +0 -0
  208. package/ios/icons/keyless_back@3x.png +0 -0
  209. package/ios/icons/keyless_filter.png +0 -0
  210. package/ios/icons/keyless_filter@2x.png +0 -0
  211. package/ios/icons/keyless_filter@3x.png +0 -0
  212. package/ios/icons/keyless_message.png +0 -0
  213. package/ios/icons/keyless_message@2x.png +0 -0
  214. package/ios/icons/keyless_message@3x.png +0 -0
  215. package/ios/icons/keyless_pause.png +0 -0
  216. package/ios/icons/keyless_pause@2x.png +0 -0
  217. package/ios/icons/keyless_pause@3x.png +0 -0
  218. package/ios/icons/keyless_photo.png +0 -0
  219. package/ios/icons/keyless_photo@2x.png +0 -0
  220. package/ios/icons/keyless_photo@3x.png +0 -0
  221. package/ios/icons/keyless_play.png +0 -0
  222. package/ios/icons/keyless_play@2x.png +0 -0
  223. package/ios/icons/keyless_play@3x.png +0 -0
  224. package/ios/icons/keyless_play_list.png +0 -0
  225. package/ios/icons/keyless_play_list@2x.png +0 -0
  226. package/ios/icons/keyless_play_list@3x.png +0 -0
  227. package/ios/icons/keyless_play_playback.png +0 -0
  228. package/ios/icons/keyless_play_playback@2x.png +0 -0
  229. package/ios/icons/keyless_play_playback@3x.png +0 -0
  230. package/ios/icons/keyless_record.png +0 -0
  231. package/ios/icons/keyless_record@2x.png +0 -0
  232. package/ios/icons/keyless_record@3x.png +0 -0
  233. package/ios/icons/keyless_record_on.png +0 -0
  234. package/ios/icons/keyless_record_on@2x.png +0 -0
  235. package/ios/icons/keyless_record_on@3x.png +0 -0
  236. package/ios/icons/keyless_sound_off.png +0 -0
  237. package/ios/icons/keyless_sound_off@2x.png +0 -0
  238. package/ios/icons/keyless_sound_off@3x.png +0 -0
  239. package/ios/icons/keyless_sound_on.png +0 -0
  240. package/ios/icons/keyless_sound_on@2x.png +0 -0
  241. package/ios/icons/keyless_sound_on@3x.png +0 -0
  242. package/ios/icons/keyless_speak_off.png +0 -0
  243. package/ios/icons/keyless_speak_off@2x.png +0 -0
  244. package/ios/icons/keyless_speak_on.png +0 -0
  245. package/ios/icons/keyless_speak_on@2x.png +0 -0
  246. package/ios/icons/keyless_speak_on@3x.png +0 -0
  247. package/ios/icons/refresh.png +0 -0
  248. package/ios/icons/refresh@2x.png +0 -0
  249. package/ios/icons/refresh@3x.png +0 -0
  250. package/ios/icons/ty_camera_control_hd_normal@2x.png +0 -0
  251. package/ios/icons/ty_camera_control_hd_normal@3x.png +0 -0
  252. package/ios/icons/ty_camera_control_sd_normal@2x.png +0 -0
  253. package/ios/icons/ty_camera_control_sd_normal@3x.png +0 -0
  254. package/ios/icons/upgrade_success.png +0 -0
  255. package/ios/icons/upgrade_success@2x.png +0 -0
  256. package/ios/icons/upgrade_success@3x.png +0 -0
  257. package/ios/listener/KeylessListener.m +8 -0
  258. package/ios/listener/KeylessListener.swift +56 -0
  259. package/ios/listener/RentlyTuya-Bridging-Header.h +5 -0
  260. package/ios/listener/RentlyTuya.mm +56 -0
  261. package/ios/listener/RentlyTuya.swift +544 -0
  262. package/ios/theme/LocalColor.swift +33 -0
  263. package/ios/theme/LocalFont.swift +32 -0
  264. package/ios/theme/Theme.swift +71 -0
  265. package/ios/tuya/CameraManager.swift +296 -0
  266. package/ios/tuya/SettingsManager.swift +475 -0
  267. package/ios/tuya/Tuya.swift +570 -0
  268. package/ios/tuya/TuyaMessages.swift +98 -0
  269. package/ios/utils/Airbrake.swift +31 -0
  270. package/ios/utils/AppUtility.swift +76 -0
  271. package/ios/utils/HeapAnalytics.swift +8 -0
  272. package/ios/utils/OrientationManager.swift +60 -0
  273. package/ios/utils/PromiseHandler.swift +36 -0
  274. package/lib/commonjs/index.js +90 -0
  275. package/lib/commonjs/index.js.map +1 -0
  276. package/lib/commonjs/package.json +1 -0
  277. package/lib/module/index.js +83 -0
  278. package/lib/module/index.js.map +1 -0
  279. package/lib/module/package.json +1 -0
  280. package/lib/typescript/commonjs/package.json +1 -0
  281. package/lib/typescript/commonjs/src/index.d.ts +75 -0
  282. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  283. package/lib/typescript/module/package.json +1 -0
  284. package/lib/typescript/module/src/index.d.ts +75 -0
  285. package/lib/typescript/module/src/index.d.ts.map +1 -0
  286. package/package.json +188 -0
  287. package/rently-tuya.podspec +52 -0
  288. package/src/index.tsx +158 -0
@@ -0,0 +1,543 @@
1
+ import SwiftUI
2
+ import ThingSmartCameraBase
3
+ import ThingSmartDeviceKit
4
+ import Combine
5
+
6
+ struct Playback: View {
7
+
8
+ @ObservedObject var tuya = Tuya.shared
9
+ @State var selectedDate: Date = Date()
10
+ @Binding var isDatePickerVisible: Bool
11
+
12
+ @State private var isSliderVisible: Bool = false
13
+ @State private var currentSliderValue: Double = 0
14
+ @State private var isEditingSlider: Bool = false
15
+ @State private var timer: Timer? = nil
16
+ @EnvironmentObject var orientationManager: OrientationManager
17
+
18
+ @State private var lastMoveOutDate: Date? = {
19
+ let dateFormatter = ISO8601DateFormatter()
20
+ return Tuya.shared.lastMoveoutAt.flatMap { dateFormatter.date(from: $0) }
21
+ }()
22
+
23
+ var body: some View {
24
+ VStack {
25
+ if !orientationManager.isLandscape {
26
+ VideoPlayerView(
27
+ isSliderVisible: $isSliderVisible,
28
+ currentSliderValue: $currentSliderValue,
29
+ isEditingSlider: $isEditingSlider,
30
+ showSlider: showSlider,
31
+ removeSlider: removeSlider
32
+ )
33
+
34
+ PlaybackList(
35
+ currentSliderValue: $currentSliderValue,
36
+ showSlider: showSlider
37
+ )
38
+
39
+ PlaybackBottomBar(
40
+ currentSliderValue: $currentSliderValue
41
+ )
42
+ .alert(
43
+ isPresented: $tuya.isScreenshotSuccess,
44
+ content: {
45
+ getSuccessAlert(
46
+ title: "A screenshot has been saved to your photo gallery."
47
+ )
48
+ })
49
+ } else {
50
+ VideoPlayerView(
51
+ isSliderVisible: $isSliderVisible,
52
+ currentSliderValue: $currentSliderValue,
53
+ isEditingSlider: $isEditingSlider,
54
+ showSlider: showSlider,
55
+ removeSlider: removeSlider
56
+ )
57
+ .edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
58
+ }
59
+ }
60
+ .onChange(of: orientationManager.isLandscape) { _ in
61
+ if orientationManager.isLandscape && isDatePickerVisible {
62
+ isDatePickerVisible = false
63
+ }
64
+ }
65
+ .onAppear {
66
+ print("Tuya: Harish \(String(describing: lastMoveOutDate))")
67
+ showSlider()
68
+ let calendar = Calendar.current
69
+ let year = calendar.component(.year, from: selectedDate)
70
+ let month = calendar.component(.month, from: selectedDate)
71
+ tuya.getAvailableDays(year: UInt(year), month: UInt(month))
72
+
73
+ tuya.enableMute(enable: true, type: .preview)
74
+ if tuya.isPreviewing {
75
+ tuya.stopPreview()
76
+ }
77
+ fetchVideos()
78
+ tuya.enableMute(enable: false, type: .playback)
79
+ setupTimer()
80
+ }
81
+ .onDisappear {
82
+ timer?.invalidate()
83
+ tuya.playbackVideoList = nil
84
+ isDatePickerVisible = false
85
+ }
86
+
87
+ if tuya.playbackLoader {
88
+ ProgressView()
89
+ .progressViewStyle(CircularProgressViewStyle(tint: LocalColor.Monochrome.black))
90
+ }
91
+
92
+ CustomDatePicker(
93
+ isVisible: $isDatePickerVisible,
94
+ selectedDate: $selectedDate,
95
+ availableDays: tuya.recordDays ?? [],
96
+ minDate: lastMoveOutDate
97
+ ) { date in
98
+ } onMonthChanged: { date in
99
+ tuya.getAvailableDays(
100
+ year: UInt(Calendar.current.component(.year, from: date)),
101
+ month: UInt(Calendar.current.component(.month, from: date))
102
+ )
103
+ } onCancel: {
104
+ isDatePickerVisible = false
105
+ } onOk: {
106
+ isDatePickerVisible = false
107
+ currentSliderValue = 0
108
+ fetchVideos()
109
+ } onAppear: {
110
+ tuya.getAvailableDays(
111
+ year: UInt(Calendar.current.component(.year, from: selectedDate)),
112
+ month: UInt(Calendar.current.component(.month, from: selectedDate))
113
+ )
114
+ }
115
+ }
116
+
117
+ private func setupTimer() {
118
+ timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
119
+ updateCurrentSliderValue()
120
+ }
121
+ }
122
+
123
+ private func updateCurrentSliderValue() {
124
+ guard let selectedItem = tuya.selectedItem,
125
+ let startTime = selectedItem["startTime"] as? Int,
126
+ let endTime = selectedItem["endTime"] as? Int else { return }
127
+
128
+ if tuya.isPlaybacking && !tuya.isPaused && !tuya.playbackLoader && !isEditingSlider {
129
+ if currentSliderValue < Double(endTime - startTime) {
130
+ currentSliderValue += 1
131
+ } else {
132
+ tuya.pausePlayback()
133
+ }
134
+ }
135
+ }
136
+
137
+ private func fetchVideos() {
138
+ guard Tuya.shared.isConnected else {
139
+ tuya.isPlaybacking = false
140
+ print("Tuya: Camera is not connected, hence unable to get the video list and start the playback")
141
+ return
142
+ }
143
+
144
+ let calendar = Calendar.current
145
+ let year = calendar.component(.year, from: selectedDate)
146
+ let month = calendar.component(.month, from: selectedDate)
147
+ let day = calendar.component(.day, from: selectedDate)
148
+
149
+ Tuya.shared.getVideoList(year: UInt(year), month: UInt(month), day: UInt(day))
150
+ }
151
+
152
+ private func showSlider() {
153
+ withAnimation(.easeInOut(duration: 0.3)) {
154
+ isSliderVisible = true
155
+ }
156
+
157
+ removeSlider()
158
+ }
159
+
160
+ private func removeSlider() {
161
+ DispatchQueue.main.asyncAfter(deadline: .now() + 8) {
162
+ if !isEditingSlider {
163
+ withAnimation(.easeInOut(duration: 0.3)) {
164
+ isSliderVisible = false
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ struct VideoPlayerView: View {
172
+
173
+ @ObservedObject var tuya = Tuya.shared
174
+ @Binding var isSliderVisible: Bool
175
+ @Binding var currentSliderValue: Double
176
+ @Binding var isEditingSlider: Bool
177
+ var showSlider: () -> Void
178
+ var removeSlider: () -> Void
179
+ @EnvironmentObject var orientationManager: OrientationManager
180
+
181
+ var body: some View {
182
+ ZStack {
183
+ if tuya.isPlaybacking {
184
+ if tuya.selectedItem == nil {
185
+ Rectangle()
186
+ .fill(LocalColor.Monochrome.black)
187
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
188
+ } else {
189
+ PreviewView()
190
+
191
+ if isSliderVisible {
192
+ SliderView(
193
+ currentSliderValue: $currentSliderValue,
194
+ isEditingSlider: $isEditingSlider,
195
+ removeSlider: removeSlider
196
+ )
197
+ }
198
+ }
199
+ } else {
200
+ Rectangle()
201
+ .fill(LocalColor.Monochrome.black)
202
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
203
+ }
204
+ }
205
+ .onTapGesture {
206
+ showSlider()
207
+ }
208
+ .frame(
209
+ maxWidth: .infinity,
210
+ maxHeight: orientationManager.isLandscape ? .infinity : 250
211
+ )
212
+ .edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
213
+ .alert(
214
+ isPresented: $tuya.isRecordingFailure,
215
+ content: {
216
+ getSuccessAlert(
217
+ title: "Failed to save the recording to your photo gallery."
218
+ )
219
+ })
220
+ }
221
+ }
222
+
223
+ struct SliderView: View {
224
+
225
+ @ObservedObject var tuya = Tuya.shared
226
+ @Binding var currentSliderValue: Double
227
+ @Binding var isEditingSlider: Bool
228
+ var removeSlider: () -> Void
229
+
230
+ var body: some View {
231
+ VStack {
232
+ Spacer()
233
+
234
+ if let selectedItem = tuya.selectedItem,
235
+ let startTime = selectedItem["startTime"] as? Int,
236
+ let endTime = selectedItem["endTime"] as? Int {
237
+
238
+ let duration = calculateVideoDuration(from: startTime, to: endTime)
239
+
240
+ HStack {
241
+ Label(
242
+ title: "\(formatTime(from: currentSliderValue)) / \(duration)",
243
+ m: true,
244
+ white: true,
245
+ left: true
246
+ )
247
+
248
+ Spacer()
249
+ }
250
+ .padding(.leading, 4)
251
+
252
+ Slider(
253
+ value: $currentSliderValue,
254
+ in: 0...Double(endTime - startTime),
255
+ onEditingChanged: { editing in
256
+ isEditingSlider = editing
257
+ if !editing {
258
+ let playTime = startTime + Int(currentSliderValue)
259
+ startPlayback(playTime: playTime, startTime: startTime, endTime: endTime)
260
+
261
+ removeSlider()
262
+ }
263
+ }
264
+ )
265
+ .accentColor(LocalColor.Primary.medium)
266
+ }
267
+ }
268
+ .padding()
269
+ .background(Color.black.opacity(0.4))
270
+ .frame(maxWidth: .infinity)
271
+ }
272
+
273
+ private func formatTime(from seconds: Double) -> String {
274
+ let hours = Int(seconds) / 3600
275
+ let minutes = (Int(seconds) % 3600) / 60
276
+ let secs = Int(seconds) % 60
277
+ return String(format: "%02d:%02d:%02d", hours, minutes, secs)
278
+ }
279
+ }
280
+
281
+ struct PlaybackList: View {
282
+
283
+ @ObservedObject var tuya = Tuya.shared
284
+ @Binding var currentSliderValue: Double
285
+ var showSlider: () -> Void
286
+
287
+ var body: some View {
288
+ VStack {
289
+ if tuya.playbackVideoList == nil || tuya.playbackVideoList?.isEmpty == true {
290
+ VStack {
291
+ Spacer()
292
+ Label(
293
+ id: "noPlaybackVideos",
294
+ title: "No Playback videos available at the moment.",
295
+ xl18: true,
296
+ black: true,
297
+ semiBold: true,
298
+ center: true
299
+ )
300
+ .padding(.horizontal, 30)
301
+ Spacer()
302
+ }
303
+ } else {
304
+ ScrollView {
305
+ if let playbackList = tuya.playbackVideoList, !playbackList.isEmpty {
306
+ ForEach(0..<playbackList.count, id: \.self) { index in
307
+ VideoItem(
308
+ item: playbackList[index],
309
+ currentSliderValue: $currentSliderValue,
310
+ showSlider: showSlider
311
+ )
312
+ }
313
+ }
314
+ }
315
+ .background(LocalColor.Main.light)
316
+ }
317
+ }
318
+ .frame(maxWidth: .infinity)
319
+ .background(LocalColor.Main.light)
320
+ .alert(
321
+ isPresented: $tuya.isRecordingSuccess,
322
+ content: {
323
+ getSuccessAlert(
324
+ title: "A recording has been saved to your photo gallery."
325
+ )
326
+ })
327
+ }
328
+ }
329
+
330
+ struct VideoItem: View {
331
+
332
+ @ObservedObject var tuya = Tuya.shared
333
+ var item: [AnyHashable: Any]
334
+ @Binding var currentSliderValue: Double
335
+ var showSlider: () -> Void
336
+
337
+ var body: some View {
338
+ let isSelected = (tuya.selectedItem?["startTime"] as? Int) == (item["startTime"] as? Int)
339
+
340
+ HStack {
341
+ VStack(alignment: .leading, spacing: 5) {
342
+ Label(
343
+ title: "\(convertTimestampToTime(item["startTime"] as? Int))",
344
+ l: true,
345
+ primary: true,
346
+ bold: true,
347
+ left: true
348
+ )
349
+
350
+ Label(
351
+ title: ("Duration: \(calculateVideoDuration(from: item["startTime"] as? Int, to: item["endTime"] as? Int))"),
352
+ m: true,
353
+ secondary: true,
354
+ left: true
355
+ )
356
+ .padding(.top, 5)
357
+ }
358
+ .padding(15)
359
+
360
+ Spacer()
361
+ }
362
+ .frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/)
363
+ .background(isSelected ? LocalColor.Secondary.light : LocalColor.Monochrome.white)
364
+ .cornerRadius(5)
365
+ .padding(.top, 8)
366
+ .padding(.horizontal, 10)
367
+ .onTapGesture {
368
+ tuya.selectedItem = item
369
+ currentSliderValue = 0
370
+ startPlayback(
371
+ playTime: item["startTime"] as? Int,
372
+ startTime: item["startTime"] as? Int,
373
+ endTime: item["endTime"] as? Int
374
+ )
375
+ showSlider()
376
+ }
377
+ }
378
+
379
+ private func convertTimestampToTime(_ timestamp: Int?) -> String {
380
+ guard let timestamp = timestamp else { return "Invalid Time" }
381
+ let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
382
+ let formatter = DateFormatter()
383
+ formatter.dateFormat = "hh:mm a"
384
+ return formatter.string(from: date)
385
+ }
386
+ }
387
+
388
+ private func startPlayback(playTime: Int?, startTime: Int?, endTime: Int?) {
389
+
390
+ guard let startTime = startTime, let endTime = endTime, let playTime = playTime else {
391
+ print("Tuya: Invalid start/end/play time")
392
+ return
393
+ }
394
+
395
+ if Tuya.shared.isConnected {
396
+ Tuya.shared.startPlayback(
397
+ playTime: playTime,
398
+ startTime: startTime,
399
+ stopTime: endTime
400
+ )
401
+ } else {
402
+ Tuya.shared.isPlaybacking = false
403
+ print("Tuya: Camera is disconnected, hence unable to start the video")
404
+ }
405
+ }
406
+
407
+ func calculateVideoDuration(from startTime: Int?, to endTime: Int?) -> String {
408
+ guard let startTime = startTime, let endTime = endTime else { return "Invalid Duration" }
409
+ let durationInSeconds = TimeInterval(endTime - startTime)
410
+ let hours = Int(durationInSeconds) / 3600
411
+ let minutes = (Int(durationInSeconds) % 3600) / 60
412
+ let seconds = Int(durationInSeconds) % 60
413
+
414
+ return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
415
+ }
416
+
417
+ struct PlaybackBottomBar: View {
418
+
419
+ @ObservedObject var tuya = Tuya.shared
420
+ @Binding var currentSliderValue: Double
421
+
422
+ var body: some View {
423
+ HStack {
424
+ Spacer()
425
+
426
+ IconButton(
427
+ imageName: tuya.isPlaybackMuted ? "keyless_sound_off" : "keyless_sound_on",
428
+ event: tuya.isPlaybackMuted ? "Playback Speaker On" : "Playback Speaker Off",
429
+ size: tuya.isPlaybackMuted ? 28 : 40,
430
+ action: {
431
+ guard tuya.isConnected else {
432
+ print("Tuya: Camera not connected. Speaker action disabled.")
433
+ return
434
+ }
435
+
436
+ guard tuya.isPlaybacking else {
437
+ print("Tuya: Playback not started. Speaker action disabled.")
438
+ return
439
+ }
440
+
441
+ if (tuya.isPlaybackMuted) {
442
+ tuya.enableMute(enable: false, type: .playback)
443
+ } else {
444
+ tuya.enableMute(enable: true, type: .playback)
445
+ }
446
+ }
447
+ )
448
+
449
+ Spacer()
450
+
451
+ IconButton(
452
+ imageName: tuya.isRecording ? "keyless_record_on" : "keyless_record",
453
+ event: tuya.isRecording ? "Playback Recording Off" : "Playback Recording On",
454
+ size: tuya.isRecording ? 40 : 28,
455
+ action: {
456
+ guard tuya.isConnected else {
457
+ print("Tuya: Camera not connected. Start/Stop recording action disabled.")
458
+ return
459
+ }
460
+
461
+ guard tuya.isPlaybacking else {
462
+ print("Tuya: Playback not started. Start/Stop recording action disabled.")
463
+ return
464
+ }
465
+
466
+ tuya.isRecording ? tuya.stopRecording() : tuya.startRecording()
467
+ }
468
+ )
469
+
470
+ Spacer()
471
+
472
+ IconButton(
473
+ imageName: tuya.isPaused ? "keyless_play_playback" : "keyless_pause",
474
+ event: tuya.isPaused ? "Resume Playback" : "Pause Playback",
475
+ size: 45,
476
+ action: {
477
+ guard tuya.isConnected else {
478
+ print("Tuya: Camera not connected. Resume/Pause action disabled.")
479
+ return
480
+ }
481
+
482
+ guard tuya.isPlaybacking else {
483
+ print("Tuya: Playback is not started. Resume/Pause action disabled.")
484
+ return
485
+ }
486
+
487
+ if tuya.isPaused {
488
+ guard let selectedItem = tuya.selectedItem,
489
+ let startTime = selectedItem["startTime"] as? Int,
490
+ let endTime = selectedItem["endTime"] as? Int else { return }
491
+
492
+ let videoDuration = Double(endTime - startTime)
493
+
494
+ if currentSliderValue >= videoDuration {
495
+ // If playback is completed then Restart from the beginning
496
+ currentSliderValue = 0
497
+ tuya.startPlayback(
498
+ playTime: startTime,
499
+ startTime: startTime,
500
+ stopTime: endTime
501
+ )
502
+ } else {
503
+ tuya.resumePlayback()
504
+ }
505
+
506
+ } else {
507
+ tuya.pausePlayback()
508
+ }
509
+ }
510
+ )
511
+
512
+ Spacer()
513
+
514
+ IconButton(
515
+ imageName: "keyless_photo",
516
+ event: "Playback Screenshot",
517
+ size: 28,
518
+ action: {
519
+ guard tuya.isConnected else {
520
+ print("Tuya: Camera not connected. Screenshot action disabled.")
521
+ return
522
+ }
523
+
524
+ guard tuya.isPlaybacking else {
525
+ print("Tuya: Playback is not started. Screenshot action disabled.")
526
+ return
527
+ }
528
+
529
+ tuya.screenshot()
530
+ }
531
+ )
532
+
533
+ Spacer()
534
+ }
535
+ .padding(.top)
536
+ .padding(.horizontal)
537
+ .frame(height: 70)
538
+ .padding(.bottom, 15)
539
+ .background(LocalColor.Monochrome.grey)
540
+ .edgesIgnoringSafeArea(.horizontal)
541
+ }
542
+ }
543
+
@@ -0,0 +1,117 @@
1
+ import SwiftUI
2
+ import ThingSmartCameraBase
3
+
4
+ struct PreviewView: UIViewRepresentable {
5
+
6
+ class Coordinator: NSObject, UIScrollViewDelegate {
7
+ var scrollView: UIScrollView?
8
+
9
+ override init() {
10
+ super.init()
11
+
12
+ UIDevice.current.beginGeneratingDeviceOrientationNotifications()
13
+ NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil)
14
+ NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: OrientationManager.orientationDidChangeNotification, object: nil)
15
+ }
16
+
17
+ deinit {
18
+ UIDevice.current.endGeneratingDeviceOrientationNotifications()
19
+ NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
20
+ NotificationCenter.default.removeObserver(self, name: OrientationManager.orientationDidChangeNotification, object: nil)
21
+ }
22
+
23
+ func viewForZooming(in scrollView: UIScrollView) -> UIView? {
24
+ return scrollView.subviews.first
25
+ }
26
+
27
+ func scrollViewDidZoom(_ scrollView: UIScrollView) {
28
+ guard let zoomView = scrollView.subviews.first else { return }
29
+
30
+ let offsetX = max((scrollView.bounds.size.width - zoomView.frame.size.width) * 0.5, 0)
31
+ let offsetY = max((scrollView.bounds.size.height - zoomView.frame.size.height) * 0.5, 0)
32
+
33
+ zoomView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX,
34
+ y: scrollView.contentSize.height * 0.5 + offsetY)
35
+ }
36
+
37
+ func resetZoom() {
38
+ guard let scrollView = scrollView else { return }
39
+ scrollView.setZoomScale(1.0, animated: true)
40
+ scrollView.setContentOffset(CGPoint.zero, animated: true)
41
+ }
42
+
43
+ @objc func orientationChanged() {
44
+ resetZoom()
45
+ }
46
+ }
47
+
48
+ func makeUIView(context: Context) -> UIView {
49
+ let containerView = UIView()
50
+ let scrollView = UIScrollView()
51
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
52
+ scrollView.minimumZoomScale = 1.0
53
+ scrollView.maximumZoomScale = 4.0
54
+ scrollView.delegate = context.coordinator
55
+ scrollView.bounces = false
56
+ scrollView.showsVerticalScrollIndicator = false
57
+ scrollView.showsHorizontalScrollIndicator = false
58
+ scrollView.clipsToBounds = true
59
+
60
+ containerView.addSubview(scrollView)
61
+
62
+
63
+ NSLayoutConstraint.activate([
64
+ scrollView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
65
+ scrollView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
66
+ scrollView.topAnchor.constraint(equalTo: containerView.topAnchor),
67
+ scrollView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
68
+ ])
69
+
70
+ if let videoView = Tuya.shared.cameraType?.videoView() {
71
+ videoView.contentMode = .scaleAspectFit
72
+ scrollView.addSubview(videoView)
73
+ videoView.translatesAutoresizingMaskIntoConstraints = false
74
+
75
+ NSLayoutConstraint.activate([
76
+ videoView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
77
+ videoView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor),
78
+ videoView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0),
79
+ videoView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0)
80
+ ])
81
+ } else {
82
+ print("Tuya: Video view is nil")
83
+ }
84
+
85
+ context.coordinator.scrollView = scrollView
86
+ context.coordinator.resetZoom()
87
+ return containerView
88
+ }
89
+
90
+ func updateUIView(_ uiView: UIView, context: Context) {
91
+ guard let scrollView = context.coordinator.scrollView else { return }
92
+ if let videoView = Tuya.shared.cameraType?.videoView() {
93
+ videoView.setNeedsDisplay()
94
+ context.coordinator.resetZoom()
95
+ } else {
96
+ print("Tuya: Video view is nil in updateUIView")
97
+ }
98
+ }
99
+
100
+ func makeCoordinator() -> Coordinator {
101
+ Coordinator()
102
+ }
103
+ }
104
+
105
+ func openLivePreview(appFlavor: String) {
106
+
107
+ DispatchQueue.main.async {
108
+ Themes.configureTheme(for: appFlavor)
109
+ let orientationManager = OrientationManager()
110
+ let dashboardView = NavigationController().environmentObject(orientationManager)
111
+ let hostingController = UIHostingController(rootView: dashboardView)
112
+ hostingController.modalPresentationStyle = .fullScreen
113
+ if let rootVC = UIApplication.shared.windows.first?.rootViewController {
114
+ rootVC.present(hostingController, animated: true, completion: nil)
115
+ }
116
+ }
117
+ }