Hibok
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

357 lines
10 KiB

  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:chat/data/constants.dart';
  4. import 'package:chat/generated/i18n.dart';
  5. import 'package:chat/photo/ui/dialog/not_permission_dialog.dart';
  6. import 'package:chat/utils/screen.dart';
  7. import 'package:chat/utils/sound_util.dart';
  8. import 'package:flutter/material.dart';
  9. import 'package:flutter_audio_recorder/flutter_audio_recorder.dart';
  10. import 'package:oktoast/oktoast.dart';
  11. import 'package:path_provider/path_provider.dart';
  12. import 'package:path/path.dart' as p;
  13. import 'package:permission_handler/permission_handler.dart';
  14. const int soundSizeLimit = 61;
  15. const double RecorderRadius = 30;
  16. class RecordView extends StatefulWidget {
  17. final double keyboardHeight;
  18. final Function sendMsg;
  19. RecordView({this.keyboardHeight, this.sendMsg});
  20. @override
  21. _RecordViewState createState() => _RecordViewState();
  22. }
  23. class _RecordViewState extends State<RecordView> {
  24. Timer counter;
  25. int totalCount = 0;
  26. bool _isRecording = false;
  27. Timer recordTimer;
  28. FlutterAudioRecorder recorder;
  29. bool isCancelState = false;
  30. bool _isCounting = false;
  31. bool hasPermission = false; //录音权限
  32. bool isTaped = false;
  33. @override
  34. void initState() {
  35. super.initState();
  36. }
  37. void hide() {
  38. counter.cancel();
  39. counter = null;
  40. }
  41. @override
  42. void deactivate() {
  43. super.deactivate();
  44. cancelRecorder();
  45. }
  46. @override
  47. void dispose() {
  48. print('record view dispose');
  49. anchorKey = null;
  50. counter?.cancel();
  51. super.dispose();
  52. }
  53. _timeCounter() {
  54. if (!_isCounting) {
  55. return Container(
  56. height: 40,
  57. );
  58. }
  59. int h = totalCount ~/ 3600;
  60. int m = (totalCount % 3600) ~/ 60;
  61. int s = totalCount % 60;
  62. var hStr = h >= 10 ? h.toString() : '0' + h.toString();
  63. var mStr = m >= 10 ? h.toString() : '0' + m.toString();
  64. var sStr = s >= 10 ? s.toString() : '0' + s.toString();
  65. return Container(
  66. height: 40,
  67. alignment: Alignment.center,
  68. child: Text('$hStr:$mStr:$sStr',
  69. textScaleFactor: 1.0,
  70. style: TextStyle(color: Colors.black, fontSize: 30)));
  71. }
  72. _startTimer() {
  73. totalCount = 0;
  74. _isCounting = true;
  75. setState(() {});
  76. counter?.cancel();
  77. counter = Timer.periodic(Duration(seconds: 1), (Timer timer) {
  78. totalCount += 1;
  79. setState(() {});
  80. });
  81. }
  82. _stopTimer() {
  83. _isCounting = false;
  84. if (mounted) {
  85. setState(() {});
  86. }
  87. counter?.cancel();
  88. }
  89. askRecordPermission() async {
  90. final PermissionStatus statusFirst = await PermissionHandler()
  91. .checkPermissionStatus(PermissionGroup.microphone);
  92. print('录音权限 $statusFirst');
  93. final PermissionStatus storageStatus = await PermissionHandler()
  94. .checkPermissionStatus(PermissionGroup.storage);
  95. print('storage权限 $storageStatus');
  96. if (statusFirst == PermissionStatus.granted &&
  97. storageStatus == PermissionStatus.granted) {
  98. hasPermission = true;
  99. } else {
  100. cancelRecorder();
  101. if(statusFirst == PermissionStatus.unknown || (statusFirst == PermissionStatus.denied && Platform.isAndroid)){
  102. Map<PermissionGroup, PermissionStatus> permissionRequestResult =
  103. await PermissionHandler().requestPermissions(
  104. [PermissionGroup.microphone, PermissionGroup.storage]);
  105. var status = permissionRequestResult[PermissionGroup.microphone];
  106. var storageStatus = permissionRequestResult[PermissionGroup.storage];
  107. if (status == PermissionStatus.granted &&
  108. storageStatus == PermissionStatus.granted) {
  109. hasPermission = true;
  110. }
  111. }else{
  112. var result = await showDialog(
  113. context: context,
  114. builder: (ctx) => NotPermissionDialog(I18n.of(context).video_permission),
  115. );
  116. if (result == true) {
  117. PermissionHandler().openAppSettings();
  118. }
  119. }
  120. }
  121. }
  122. double tempX;
  123. double tempY;
  124. GlobalKey anchorKey = GlobalKey();
  125. @override
  126. Widget build(BuildContext context) {
  127. var tipStr;
  128. if (_isRecording) {
  129. if (isCancelState) {
  130. tipStr = I18n.of(context).cancel;
  131. } else {
  132. tipStr = I18n.of(context).voice_tips2;
  133. }
  134. } else {
  135. tipStr = I18n.of(context).voice_tips;
  136. }
  137. return InkWell(onTap: (){},child: Container(
  138. width: double.infinity,
  139. height: double.infinity,
  140. padding: EdgeInsets.only(top: 10, bottom: 20),
  141. color: Colors.white,
  142. alignment: Alignment.center,
  143. child: Column(
  144. mainAxisAlignment: MainAxisAlignment.center,
  145. children: <Widget>[
  146. _timeCounter(),
  147. Expanded(
  148. child: GestureDetector(
  149. child: Container(
  150. key: anchorKey,
  151. width: 100,
  152. height: 100,
  153. child: Icon(
  154. IconData(0xe64f, fontFamily: Constants.IconFontFamily),
  155. color: isTaped ? Colors.white : Color(0xFF018DFE),
  156. size: 64),
  157. decoration: BoxDecoration(
  158. color: isTaped ? Color(0xFF018DFE) : Colors.white,
  159. shape: BoxShape.circle,
  160. border: Border.all(color: Color(0xFF60919F)),
  161. boxShadow: [
  162. BoxShadow(
  163. color: Color(0xFFD0D0D0), //阴影颜色
  164. blurRadius: 5.0, //阴影大小
  165. ),
  166. ]),
  167. ),
  168. onTap: () {
  169. //只是短暂按压,取消发送
  170. print('onTap');
  171. cancelRecorder();
  172. },
  173. onTapUp: (TapUpDetails details) {
  174. print('onTapUp');
  175. isTaped = false;
  176. setState(() {});
  177. },
  178. onTapDown: (TapDownDetails details) {
  179. print('onTapDown');
  180. isTaped = true;
  181. setState(() {});
  182. if (!hasPermission) {
  183. askRecordPermission();
  184. }
  185. RenderBox renderBox =
  186. anchorKey.currentContext.findRenderObject();
  187. var offset = renderBox.localToGlobal(Offset(
  188. renderBox.size.width / 2, renderBox.size.height / 2));
  189. tempX = offset.dx;
  190. tempY = offset.dy;
  191. },
  192. onLongPressStart: (LongPressStartDetails details) {
  193. print('onLongPressStart');
  194. if (hasPermission) {
  195. if (!_isRecording) {
  196. startRecorder(context);
  197. }
  198. }else
  199. {
  200. cancelRecorder();
  201. }
  202. },
  203. onLongPressEnd: (LongPressEndDetails details) {
  204. print('onLongPressEnd');
  205. isTaped = false;
  206. setState(() {});
  207. if (checkValideArea(details.globalPosition)) {
  208. print('在范围内');
  209. stopRecorder();
  210. } else {
  211. print('不在范围内');
  212. cancelRecorder();
  213. }
  214. },
  215. onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
  216. if (!checkValideArea(details.globalPosition)) {
  217. if (!isCancelState) {
  218. setState(() {
  219. isCancelState = true;
  220. });
  221. }
  222. } else {
  223. if (isCancelState) {
  224. setState(() {
  225. isCancelState = false;
  226. });
  227. }
  228. }
  229. },
  230. )),
  231. SizedBox(height: 10),
  232. Container(
  233. height: 40,
  234. alignment: Alignment.center,
  235. child: Text(tipStr, textScaleFactor: 1.0,),
  236. )
  237. ],
  238. )),);
  239. }
  240. checkValideArea(Offset position) {
  241. double offset = RecorderRadius * Screen.scale;
  242. return (position.dx - tempX).abs() <= offset &&
  243. (position.dy - tempY).abs() <= offset;
  244. }
  245. void startRecorder(BuildContext context) async {
  246. if (!hasPermission) {
  247. return;
  248. }
  249. SoundUtils().stop();
  250. try {
  251. var directory = await getTemporaryDirectory();
  252. var curTime = DateTime.now().millisecondsSinceEpoch;
  253. var path = p.join(directory.path, 'record$curTime.wav');
  254. recorder = FlutterAudioRecorder(path,
  255. audioFormat: AudioFormat.WAV,
  256. sampleRate: 16000); // or AudioFormat.WAV
  257. await recorder.initialized;
  258. await recorder.start();
  259. print('######开始录音');
  260. _startTimer();
  261. //60分钟后如果还在录,自动停止
  262. recordTimer = Timer(Duration(seconds: soundSizeLimit), () {
  263. stopRecorder();
  264. });
  265. this.setState(() {
  266. this._isRecording = true;
  267. });
  268. } catch (err) {
  269. print('startRecorder error: $err');
  270. }
  271. }
  272. void cancelRecorder() async {
  273. recordTimer?.cancel();
  274. isTaped = false;
  275. await recorder?.stop();
  276. _isRecording = false;
  277. _stopTimer();
  278. }
  279. void stopRecorder() async {
  280. if (!hasPermission || !_isRecording) {
  281. return;
  282. }
  283. try {
  284. var recording = await recorder.stop();
  285. this._isRecording = false;
  286. var filePath = recording.path;
  287. var time = recording.duration.inMilliseconds;
  288. print("Stop recording: $filePath time : $time");
  289. recordTimer?.cancel();
  290. _stopTimer();
  291. _sendSound(filePath, time);
  292. this.setState(() {});
  293. } catch (err) {
  294. print('stopRecorder error: $err');
  295. }
  296. }
  297. _sendSound(soundPath, int duration) async {
  298. var fileData = File(soundPath).readAsBytesSync();
  299. print('fileData size ${fileData.length}');
  300. if (duration < 1000) {
  301. //小于1秒
  302. showToast(I18n.of(context).time_little);
  303. return;
  304. } else {
  305. print('发送音频文件${DateTime.now()}');
  306. widget.sendMsg(soundPath,duration);
  307. }
  308. }
  309. }