video_operation_view.dart 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import 'dart:async';
  2. import 'package:eitc_erm_dental_flutter/funcs.dart';
  3. import 'package:eitc_erm_dental_flutter/sp_util.dart';
  4. import 'package:eitc_erm_dental_flutter/widget/count_down_text.dart';
  5. import 'package:eitc_erm_dental_flutter/widget/operation_button.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter_screenutil/flutter_screenutil.dart';
  8. ///视频操作视图
  9. class VideoOperationView extends StatefulWidget {
  10. final VideoOperationViewController controller;
  11. final void Function() onTakePhoto;
  12. final void Function() onStartRecord;
  13. final void Function() onStopRecord;
  14. final void Function() onStopVideo;
  15. final String area;
  16. const VideoOperationView(
  17. {super.key,
  18. required this.controller,
  19. required this.onTakePhoto,
  20. required this.onStartRecord,
  21. required this.onStopRecord,
  22. required this.onStopVideo,
  23. required this.area});
  24. @override
  25. State<VideoOperationView> createState() => _VideoOperationViewState();
  26. }
  27. class _VideoOperationViewState extends State<VideoOperationView> {
  28. late final CountDownTextController _countController =
  29. CountDownTextController(_onCount);
  30. ///是否正在倒计时
  31. bool _isCountingDown = false;
  32. ///录像时间
  33. int _recordingTime = 0;
  34. Timer? _recordingTimer;
  35. @override
  36. void initState() {
  37. super.initState();
  38. widget.controller._setCountController(_countController);
  39. widget.controller._recordingState.addListener(_onUpdateRecordingState);
  40. widget.controller._isCountingDown.addListener(_onUpdateIsCountingDown);
  41. }
  42. ///更新录像状态
  43. void _onUpdateRecordingState() {
  44. if (!mounted) {
  45. return;
  46. }
  47. setState(() {
  48. if (widget.controller.isRecording) {
  49. _recordingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
  50. setState(() {
  51. _recordingTime = timer.tick;
  52. });
  53. });
  54. } else {
  55. _recordingTimer?.cancel();
  56. _recordingTime = 0;
  57. }
  58. });
  59. }
  60. ///更新倒计时状态
  61. void _onUpdateIsCountingDown() {
  62. setState(() {
  63. _isCountingDown = widget.controller._isCountingDown.value;
  64. });
  65. }
  66. @override
  67. void dispose() {
  68. super.dispose();
  69. _recordingTimer?.cancel();
  70. widget.controller._recordingState.removeListener(_onUpdateRecordingState);
  71. widget.controller._isCountingDown.removeListener(_onUpdateIsCountingDown);
  72. }
  73. @override
  74. Widget build(BuildContext context) {
  75. return Stack(
  76. children: [
  77. _getButtons(),
  78. _getCountHold(),
  79. ],
  80. );
  81. }
  82. ///获取倒计时,保持等
  83. Widget _getCountHold() {
  84. TextStyle? style =
  85. Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white);
  86. return Align(
  87. alignment: Alignment.topCenter,
  88. child: Padding(
  89. padding: EdgeInsets.only(top: 10.h),
  90. child: Column(
  91. children: [
  92. SizedBox(
  93. height: 30.h,
  94. ),
  95. //静止不动提示
  96. Visibility(
  97. visible: _isCountingDown,
  98. child: Column(
  99. children: [
  100. Text(getS().pleaseHold, style: style),
  101. SizedBox(
  102. height: 10.h,
  103. ),
  104. //倒计时
  105. CountDownText(
  106. controller: _countController,
  107. textStyle: style,
  108. ),
  109. SizedBox(
  110. height: 10.h,
  111. ),
  112. ],
  113. )),
  114. //录像计时
  115. Visibility(
  116. visible: widget.controller.isRecording,
  117. child: Text(
  118. _getRecordingTimeStr(),
  119. style: style,
  120. )),
  121. Visibility(
  122. visible: widget.area.isNotEmpty && !widget.controller.isRecording,
  123. child: Text(
  124. textAlign: TextAlign.center,
  125. getS().takePhotoAreaHint(toothAreaTranslate(widget.area)),
  126. style: style),
  127. )
  128. ],
  129. ),
  130. ),
  131. );
  132. }
  133. ///获取录像时间文本
  134. String _getRecordingTimeStr() {
  135. Duration duration = Duration(seconds: _recordingTime);
  136. return "${"${duration.inMinutes}".padLeft(2, "0")}:${"${duration.inSeconds % 60}".padLeft(2, "0")}";
  137. }
  138. ///获取按钮
  139. Widget _getButtons() {
  140. return OrientationBuilder(builder: (ctx, orientation) {
  141. Alignment alignment = orientation == Orientation.landscape
  142. ? Alignment.centerRight
  143. : Alignment.bottomCenter;
  144. //拍照按钮
  145. Widget buttonCamera = OperationButton(
  146. text: getS().takePhoto,
  147. onTap: _takePhoto,
  148. child: const Icon(
  149. Icons.camera_alt_outlined,
  150. color: Colors.white,
  151. ));
  152. //录像按钮
  153. Widget buttonRecord = OperationButton(
  154. text: getS().record,
  155. onTap: () {
  156. if (widget.controller.isRecording) {
  157. _stopRecord();
  158. } else {
  159. _startRecord();
  160. }
  161. },
  162. child: Container(
  163. width: 17.5.r,
  164. height: 17.5.r,
  165. decoration: BoxDecoration(
  166. color: widget.controller._recordingState.value
  167. ? Colors.red
  168. : Colors.white,
  169. shape: BoxShape.circle),
  170. ));
  171. //根据横竖屏显示在不同位置
  172. return Align(
  173. alignment: alignment,
  174. child: Container(
  175. padding: orientation == Orientation.landscape
  176. ? EdgeInsets.only(right: 20.w)
  177. : EdgeInsets.only(bottom: 20.h),
  178. child: orientation == Orientation.landscape
  179. ? Column(
  180. mainAxisSize: MainAxisSize.min,
  181. children: [
  182. buttonCamera,
  183. SizedBox(
  184. height: 40.h,
  185. ),
  186. buttonRecord,
  187. ],
  188. )
  189. : Row(
  190. mainAxisSize: MainAxisSize.min,
  191. children: [
  192. buttonRecord,
  193. SizedBox(
  194. width: 40.w,
  195. ),
  196. buttonCamera,
  197. ],
  198. ),
  199. ),
  200. );
  201. });
  202. }
  203. ///拍照
  204. void _takePhoto() async {
  205. if (!await requestStoreagePermission()) {
  206. showToast(text: getS().storagePermissionRejectHint);
  207. return;
  208. }
  209. bool isDelay = await SpUtil.getEnableDelayShot();
  210. int delay = await SpUtil.getDelayShotTime();
  211. _countController.startCount(isDelay ? delay : 0);
  212. if (isDelay) {
  213. widget.controller._isCountingDown.value = true;
  214. }
  215. }
  216. ///开始录像
  217. void _startRecord() async {
  218. if (!await requestStoreagePermission()) {
  219. showToast(text: getS().storagePermissionRejectHint);
  220. return;
  221. }
  222. widget.onStartRecord();
  223. }
  224. ///停止录像
  225. void _stopRecord() {
  226. widget.onStopRecord();
  227. }
  228. ///关闭视频
  229. void _stopVideo() {
  230. widget.onStopVideo();
  231. widget.controller._isCountingDown.value = false;
  232. }
  233. ///拍照倒计时
  234. void _onCount(int tick, bool complete) {
  235. if (!complete) {
  236. return;
  237. }
  238. widget.onTakePhoto();
  239. widget.controller._isCountingDown.value = false;
  240. }
  241. }
  242. class VideoOperationViewController {
  243. CountDownTextController? _countController;
  244. final ValueNotifier<bool> _recordingState = ValueNotifier(false);
  245. final ValueNotifier<bool> _isCountingDown = ValueNotifier(false);
  246. void _setCountController(CountDownTextController controller) {
  247. _countController = controller;
  248. }
  249. void dispose() {
  250. _countController?.stopCount();
  251. _recordingState.dispose();
  252. _isCountingDown.dispose();
  253. }
  254. void updateRecordingState(bool isRecording) {
  255. if (_recordingState.value == isRecording) {
  256. return;
  257. }
  258. _recordingState.value = isRecording;
  259. }
  260. bool get isRecording => _recordingState.value;
  261. }