video_operation_view.dart 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. import 'dart:async';
  2. import 'package:eitc_erm_dental_flutter/exts.dart';
  3. import 'package:eitc_erm_dental_flutter/funcs.dart';
  4. import 'package:eitc_erm_dental_flutter/sp_util.dart';
  5. import 'package:eitc_erm_dental_flutter/widget/count_down_text.dart';
  6. import 'package:eitc_erm_dental_flutter/widget/operation_button.dart';
  7. import 'package:flutter/material.dart';
  8. import 'package:flutter_screenutil/flutter_screenutil.dart';
  9. ///视频操作视图
  10. class VideoOperationView extends StatefulWidget {
  11. final VideoOperationViewController controller;
  12. final void Function() onTakePhoto;
  13. final void Function() onStartRecord;
  14. final void Function() onStopRecord;
  15. final void Function() onStopVideo;
  16. final String area;
  17. const VideoOperationView(
  18. {super.key,
  19. required this.controller,
  20. required this.onTakePhoto,
  21. required this.onStartRecord,
  22. required this.onStopRecord,
  23. required this.onStopVideo,
  24. required this.area});
  25. @override
  26. State<VideoOperationView> createState() => _VideoOperationViewState();
  27. }
  28. class _VideoOperationViewState extends State<VideoOperationView> {
  29. late final CountDownTextController _countController =
  30. CountDownTextController(_onCount);
  31. ///是否正在倒计时
  32. bool _isCountingDown = false;
  33. ///录像时间
  34. int _recordingTime = 0;
  35. Timer? _recordingTimer;
  36. @override
  37. void initState() {
  38. super.initState();
  39. widget.controller._setCountController(_countController);
  40. widget.controller.recordingState.addListener(_onUpdateRecordingState);
  41. widget.controller.isCountingDown.addListener(_onUpdateIsCountingDown);
  42. }
  43. ///更新录像状态
  44. void _onUpdateRecordingState() {
  45. if (!mounted) {
  46. return;
  47. }
  48. setState(() {
  49. if (widget.controller.isRecording) {
  50. _recordingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
  51. setState(() {
  52. _recordingTime = timer.tick;
  53. });
  54. });
  55. } else {
  56. _recordingTimer?.cancel();
  57. _recordingTime = 0;
  58. }
  59. });
  60. }
  61. ///更新倒计时状态
  62. void _onUpdateIsCountingDown() {
  63. setState(() {
  64. _isCountingDown = widget.controller.isCounting;
  65. });
  66. }
  67. @override
  68. void dispose() {
  69. super.dispose();
  70. _recordingTimer?.cancel();
  71. _countController.dispose();
  72. widget.controller.recordingState.removeListener(_onUpdateRecordingState);
  73. widget.controller.isCountingDown.removeListener(_onUpdateIsCountingDown);
  74. }
  75. @override
  76. Widget build(BuildContext context) {
  77. return Stack(
  78. children: [
  79. _getButtons(),
  80. _getCountHold(),
  81. ],
  82. );
  83. }
  84. ///获取倒计时,保持等
  85. Widget _getCountHold() {
  86. TextStyle? style = context.titleMedium?.copyWith(color: Colors.white);
  87. return Align(
  88. alignment: Alignment.topCenter,
  89. child: Padding(
  90. padding: EdgeInsets.only(top: 10.h),
  91. child: Column(
  92. children: [
  93. SizedBox(
  94. height: 30.h,
  95. ),
  96. //静止不动提示
  97. Visibility(
  98. visible: _isCountingDown,
  99. child: Column(
  100. children: [
  101. Text(getS().pleaseHold, style: style),
  102. SizedBox(
  103. height: 10.h,
  104. ),
  105. //倒计时
  106. CountDownText(
  107. controller: _countController,
  108. textStyle: style,
  109. ),
  110. SizedBox(
  111. height: 10.h,
  112. ),
  113. ],
  114. )),
  115. //录像计时
  116. Visibility(
  117. visible: widget.controller.isRecording,
  118. child: Text(
  119. _getRecordingTimeStr(),
  120. style: style,
  121. )),
  122. Visibility(
  123. visible: widget.area.isNotEmpty && !widget.controller.isRecording,
  124. child: Text(
  125. textAlign: TextAlign.center,
  126. getS().takePhotoAreaHint(toothAreaTranslate(widget.area)),
  127. style: style),
  128. )
  129. ],
  130. ),
  131. ),
  132. );
  133. }
  134. ///获取录像时间文本
  135. String _getRecordingTimeStr() {
  136. Duration duration = Duration(seconds: _recordingTime);
  137. return "${"${duration.inMinutes}".padLeft(2, "0")}:${"${duration.inSeconds % 60}".padLeft(2, "0")}";
  138. }
  139. ///获取按钮
  140. Widget _getButtons() {
  141. return OrientationBuilder(builder: (ctx, orientation) {
  142. Alignment alignment = orientation == Orientation.landscape
  143. ? Alignment.centerRight
  144. : Alignment.bottomCenter;
  145. //拍照按钮
  146. Widget buttonCamera = OperationButton(
  147. text: getS().takePhoto,
  148. onTap: _takePhoto,
  149. child: const Icon(
  150. Icons.camera_alt_outlined,
  151. color: Colors.white,
  152. ));
  153. //录像按钮
  154. Widget buttonRecord = OperationButton(
  155. text: getS().record,
  156. onTap: () {
  157. if (widget.controller.isRecording) {
  158. _stopRecord();
  159. } else {
  160. _startRecord();
  161. }
  162. },
  163. child: Container(
  164. width: 17.5.r,
  165. height: 17.5.r,
  166. decoration: BoxDecoration(
  167. color:
  168. widget.controller.isRecording ? Colors.red : 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.isCounting = 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.isCounting = false;
  232. }
  233. ///拍照倒计时
  234. void _onCount(int tick, bool complete) {
  235. if (!complete) {
  236. return;
  237. }
  238. widget.onTakePhoto();
  239. widget.controller.isCounting = false;
  240. }
  241. }
  242. class VideoOperationViewController {
  243. CountDownTextController? _countController;
  244. ValueNotifier<bool>? _recordingState;
  245. ValueNotifier<bool> get recordingState =>
  246. _recordingState ??= ValueNotifier(false);
  247. ValueNotifier<bool>? _isCountingDown;
  248. ValueNotifier<bool> get isCountingDown =>
  249. _isCountingDown ??= ValueNotifier(false);
  250. void _setCountController(CountDownTextController controller) {
  251. _countController = controller;
  252. }
  253. void dispose() {
  254. _countController?.stopCount();
  255. _recordingState?.dispose();
  256. _recordingState = null;
  257. _isCountingDown?.dispose();
  258. _isCountingDown = null;
  259. }
  260. void updateRecordingState(bool isRecording) {
  261. if (recordingState.value == isRecording) {
  262. return;
  263. }
  264. recordingState.value = isRecording;
  265. }
  266. bool get isRecording => recordingState.value;
  267. bool get isCounting => isCountingDown.value;
  268. set isCounting(bool bo) => isCountingDown.value = bo;
  269. }