import 'dart:async'; import 'dart:io'; import 'package:chat/data/constants.dart'; import 'package:chat/generated/i18n.dart'; import 'package:chat/photo/ui/dialog/not_permission_dialog.dart'; import 'package:chat/utils/screen.dart'; import 'package:chat/utils/sound_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter_audio_recorder/flutter_audio_recorder.dart'; import 'package:oktoast/oktoast.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import 'package:permission_handler/permission_handler.dart'; const int soundSizeLimit = 61; const double RecorderRadius = 30; class RecordView extends StatefulWidget { final double keyboardHeight; final Function sendMsg; RecordView({this.keyboardHeight, this.sendMsg}); @override _RecordViewState createState() => _RecordViewState(); } class _RecordViewState extends State { Timer counter; int totalCount = 0; bool _isRecording = false; Timer recordTimer; FlutterAudioRecorder recorder; bool isCancelState = false; bool _isCounting = false; bool hasPermission = false; //录音权限 bool isTaped = false; @override void initState() { super.initState(); } void hide() { counter.cancel(); counter = null; } @override void deactivate() { super.deactivate(); cancelRecorder(); } @override void dispose() { print('record view dispose'); anchorKey = null; counter?.cancel(); super.dispose(); } _timeCounter() { if (!_isCounting) { return Container( height: 40, ); } int h = totalCount ~/ 3600; int m = (totalCount % 3600) ~/ 60; int s = totalCount % 60; var hStr = h >= 10 ? h.toString() : '0' + h.toString(); var mStr = m >= 10 ? h.toString() : '0' + m.toString(); var sStr = s >= 10 ? s.toString() : '0' + s.toString(); return Container( height: 40, alignment: Alignment.center, child: Text('$hStr:$mStr:$sStr', textScaleFactor: 1.0, style: TextStyle(color: Colors.black, fontSize: 30))); } _startTimer() { totalCount = 0; _isCounting = true; setState(() {}); counter?.cancel(); counter = Timer.periodic(Duration(seconds: 1), (Timer timer) { totalCount += 1; setState(() {}); }); } _stopTimer() { _isCounting = false; if (mounted) { setState(() {}); } counter?.cancel(); } askRecordPermission() async { final PermissionStatus statusFirst = await PermissionHandler() .checkPermissionStatus(PermissionGroup.microphone); print('录音权限 $statusFirst'); final PermissionStatus storageStatus = await PermissionHandler() .checkPermissionStatus(PermissionGroup.storage); print('storage权限 $storageStatus'); if (statusFirst == PermissionStatus.granted && storageStatus == PermissionStatus.granted) { hasPermission = true; } else { cancelRecorder(); if(statusFirst == PermissionStatus.unknown || (statusFirst == PermissionStatus.denied && Platform.isAndroid)){ Map permissionRequestResult = await PermissionHandler().requestPermissions( [PermissionGroup.microphone, PermissionGroup.storage]); var status = permissionRequestResult[PermissionGroup.microphone]; var storageStatus = permissionRequestResult[PermissionGroup.storage]; if (status == PermissionStatus.granted && storageStatus == PermissionStatus.granted) { hasPermission = true; } }else{ var result = await showDialog( context: context, builder: (ctx) => NotPermissionDialog(I18n.of(context).video_permission), ); if (result == true) { PermissionHandler().openAppSettings(); } } } } double tempX; double tempY; GlobalKey anchorKey = GlobalKey(); @override Widget build(BuildContext context) { var tipStr; if (_isRecording) { if (isCancelState) { tipStr = I18n.of(context).cancel; } else { tipStr = I18n.of(context).voice_tips2; } } else { tipStr = I18n.of(context).voice_tips; } return InkWell(onTap: (){},child: Container( width: double.infinity, height: double.infinity, padding: EdgeInsets.only(top: 10, bottom: 20), color: Colors.white, alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ _timeCounter(), Expanded( child: GestureDetector( child: Container( key: anchorKey, width: 100, height: 100, child: Icon( IconData(0xe64f, fontFamily: Constants.IconFontFamily), color: isTaped ? Colors.white : Color(0xFF018DFE), size: 64), decoration: BoxDecoration( color: isTaped ? Color(0xFF018DFE) : Colors.white, shape: BoxShape.circle, border: Border.all(color: Color(0xFF60919F)), boxShadow: [ BoxShadow( color: Color(0xFFD0D0D0), //阴影颜色 blurRadius: 5.0, //阴影大小 ), ]), ), onTap: () { //只是短暂按压,取消发送 print('onTap'); cancelRecorder(); }, onTapUp: (TapUpDetails details) { print('onTapUp'); isTaped = false; setState(() {}); }, onTapDown: (TapDownDetails details) { print('onTapDown'); isTaped = true; setState(() {}); if (!hasPermission) { askRecordPermission(); } RenderBox renderBox = anchorKey.currentContext.findRenderObject(); var offset = renderBox.localToGlobal(Offset( renderBox.size.width / 2, renderBox.size.height / 2)); tempX = offset.dx; tempY = offset.dy; }, onLongPressStart: (LongPressStartDetails details) { print('onLongPressStart'); if (hasPermission) { if (!_isRecording) { startRecorder(context); } }else { cancelRecorder(); } }, onLongPressEnd: (LongPressEndDetails details) { print('onLongPressEnd'); isTaped = false; setState(() {}); if (checkValideArea(details.globalPosition)) { print('在范围内'); stopRecorder(); } else { print('不在范围内'); cancelRecorder(); } }, onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) { if (!checkValideArea(details.globalPosition)) { if (!isCancelState) { setState(() { isCancelState = true; }); } } else { if (isCancelState) { setState(() { isCancelState = false; }); } } }, )), SizedBox(height: 10), Container( height: 40, alignment: Alignment.center, child: Text(tipStr, textScaleFactor: 1.0,), ) ], )),); } checkValideArea(Offset position) { double offset = RecorderRadius * Screen.scale; return (position.dx - tempX).abs() <= offset && (position.dy - tempY).abs() <= offset; } void startRecorder(BuildContext context) async { if (!hasPermission) { return; } SoundUtils().stop(); try { var directory = await getTemporaryDirectory(); var curTime = DateTime.now().millisecondsSinceEpoch; var path = p.join(directory.path, 'record$curTime.wav'); recorder = FlutterAudioRecorder(path, audioFormat: AudioFormat.WAV, sampleRate: 16000); // or AudioFormat.WAV await recorder.initialized; await recorder.start(); print('######开始录音'); _startTimer(); //60分钟后如果还在录,自动停止 recordTimer = Timer(Duration(seconds: soundSizeLimit), () { stopRecorder(); }); this.setState(() { this._isRecording = true; }); } catch (err) { print('startRecorder error: $err'); } } void cancelRecorder() async { recordTimer?.cancel(); isTaped = false; await recorder?.stop(); _isRecording = false; _stopTimer(); } void stopRecorder() async { if (!hasPermission || !_isRecording) { return; } try { var recording = await recorder.stop(); this._isRecording = false; var filePath = recording.path; var time = recording.duration.inMilliseconds; print("Stop recording: $filePath time : $time"); recordTimer?.cancel(); _stopTimer(); _sendSound(filePath, time); this.setState(() {}); } catch (err) { print('stopRecorder error: $err'); } } _sendSound(soundPath, int duration) async { var fileData = File(soundPath).readAsBytesSync(); print('fileData size ${fileData.length}'); if (duration < 1000) { //小于1秒 showToast(I18n.of(context).time_little); return; } else { print('发送音频文件${DateTime.now()}'); widget.sendMsg(soundPath,duration); } } }