import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:just_audio/just_audio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:record_mp3_plus/record_mp3_plus.dart'; import 'package:tangheem/classes/colors.dart'; import 'package:tangheem/classes/consts.dart'; import 'package:tangheem/classes/utils.dart'; import 'package:tangheem/extensions/string_extensions.dart'; import 'package:tangheem/extensions/widget_extensions.dart'; import 'package:shared_storage/shared_storage.dart' as saf; class AyaRecordWidget extends StatefulWidget { AyaRecordWidget({Key key}) : super(key: key); @override _AyaRecordWidgetState createState() => _AyaRecordWidgetState(); } class _AyaRecordWidgetState extends State { bool _isRecording = false; AudioPlayer _audioPlayer; bool _isAudioHaveError = false; @override void initState() { super.initState(); _audioPlayer = AudioPlayer(); } @override void dispose() { _audioPlayer.dispose(); if (recordFilePath != null && File(recordFilePath).existsSync()) { File(recordFilePath).deleteSync(); } super.dispose(); } Future checkPermission() async { if (!await Permission.microphone.isGranted) { PermissionStatus status = await Permission.microphone.request(); if (status != PermissionStatus.granted) { return false; } } return true; } void startRecord() async { bool hasPermission = await checkPermission(); if (hasPermission) { recordFilePath = await getFilePath(); _isRecording = true; RecordMp3.instance.start(recordFilePath, (type) { _isRecording = false; setState(() {}); }); } else { Utils.showToast("يجب أن تمنح الإذن للتسجيل"); } setState(() {}); } void stopRecord() async { bool _isStop = RecordMp3.instance.stop(); if (_isStop) { _isRecording = false; setState(() {}); } } String recordFilePath; void play() async { if (recordFilePath != null && File(recordFilePath).existsSync()) { try { await _audioPlayer.setFilePath(recordFilePath); _audioPlayer.play(); } catch (ex) { _isAudioHaveError = true; } } else { Utils.showToast("يجب عليك التسجيل أولا"); } } Future getFilePath() async { Directory storageDirectory = await getApplicationDocumentsDirectory(); if (Platform.isIOS) { storageDirectory = await getTemporaryDirectory(); } String sdPath = storageDirectory.path + "/record"; var d = Directory(sdPath); if (!d.existsSync()) { d.createSync(recursive: true); } return sdPath + "/temp${DateTime.now().millisecondsSinceEpoch}.mp3"; } @override Widget build(BuildContext context) { bool isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; return Column( children: [ Container( height: 38, width: double.infinity, alignment: Alignment.center, decoration: BoxDecoration( color: ColorConsts.brownB1CColor, borderRadius: BorderRadius.only( topRight: Radius.circular(20.0), topLeft: Radius.circular(20.0), ), ), child: "سجل الآية بصوتك".toText(16)), Stack( alignment: Alignment.bottomCenter, children: [ Container( margin: EdgeInsets.only(bottom: 35), padding: EdgeInsets.only(bottom: 14, top: 14, left: 16, right: 16), width: double.infinity, decoration: BoxDecoration( color: Colors.transparent, border: Border.all(width: 1, color: ColorConsts.greyE0Color), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ // if (!(recordFilePath != null && File(recordFilePath).existsSync())) SizedBox(height: 16), if (recordFilePath != null && File(recordFilePath).existsSync()) Container( height: 50, margin: EdgeInsets.only(bottom: 14), padding: EdgeInsets.only(left: 12, right: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(30), ), child: Row( children: [ Expanded( child: StreamBuilder( stream: _audioPlayer.positionStream, builder: (context, snapshot) { final state = snapshot.data; return SliderTheme( data: SliderTheme.of(context).copyWith( inactiveTrackColor: ColorConsts.sliderBackground, activeTrackColor: ColorConsts.secondaryOrange, trackHeight: 3.0, thumbColor: ColorConsts.primaryBlack, thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6.0), overlayColor: ColorConsts.primaryBlack.withAlpha(32), overlayShape: RoundSliderOverlayShape(overlayRadius: 6.0), ), child: Directionality( textDirection: TextDirection.ltr, child: Slider( value: (state?.inSeconds ?? 0) + 0.0, min: 0, max: (_audioPlayer?.duration?.inSeconds ?? 0) + 0.0, onChanged: (value) {}, ), ), ); }, ), ), SizedBox(width: 8), StreamBuilder( stream: _audioPlayer.positionStream, builder: (context, snapshot) { final state = snapshot.data; return Text( _durationTime(state) ?? "", style: TextStyle(color: ColorConsts.textGrey1, height: 1.1, fontFamily: "Roboto", fontSize: 16), ); }, ), SizedBox(width: 8), StreamBuilder( stream: _audioPlayer.playerStateStream, builder: (context, snapshot) { final state = snapshot.data?.playing ?? false; if (state) { if (_audioPlayer?.duration?.inSeconds == _audioPlayer?.position?.inSeconds) { _audioPlayer.pause(); _audioPlayer.seek(Duration.zero); } } return IconButton( highlightColor: Colors.transparent, splashColor: Colors.transparent, constraints: BoxConstraints(), padding: EdgeInsets.only(right: 0), icon: SvgPicture.asset(state ? "assets/icons/pause.svg" : "assets/icons/play_aya.svg", height: 16, width: 16), onPressed: () { state ? _audioPlayer.pause() : _isAudioHaveError ? Utils.showToast("خطأ في تحميل ملف الصوت") : play(); }); }, ), ], ), ), InkWell( borderRadius: BorderRadius.circular(20), onTap: () async { if (Platform.isAndroid && (await DeviceInfoPlugin().androidInfo).version.sdkInt > 29) { if (recordFilePath != null && File(recordFilePath).existsSync()) { saveAudioToAboveAndroid32(recordFilePath, "Tangheem${DateTime.now().millisecondsSinceEpoch}.mp3", "audio/mpeg"); } else { Utils.showToast("يجب عليك تسجيل صوتك أولا"); } } else if (await _requestStoragePermission()) { if (recordFilePath != null && File(recordFilePath).existsSync()) { saveToPhoneStorage(recordFilePath); } else { Utils.showToast("يجب عليك تسجيل صوتك أولا"); } } else { Utils.showToast("يجب أن تمنح الإذن لتنزيل التسجيل"); } }, child: Container( height: 31, width: 135, padding: EdgeInsets.only(left: 12, right: 12), alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: ColorConsts.greenLightColor, ), child: "تحميل التسجيل".toText(14), ), ), ], ), ), Positioned( right: 24, child: Container( height: 80, width: 80, padding: EdgeInsets.all(16), decoration: BoxDecoration( shape: BoxShape.circle, color: _isRecording ? Color(0xffe5ced3) : Color(0xffD3E5CE), border: Border.all( width: 1, color: _isRecording ? Color(0xffc6b0ae) : Color(0xffAEC6B5), ), ), child: SvgPicture.asset("assets/icons/new/mic.svg", color: Colors.white), ).onPress( () { if (_isRecording) { stopRecord(); } else { startRecord(); } }, ), ), ], ), ], ); } Future _requestStoragePermission() async { Map statuses = await [Permission.storage].request(); return statuses[Permission.storage].isGranted; } String _durationTime(Duration duration) { if (duration == null) { return "00:00"; } String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "$twoDigitMinutes:$twoDigitSeconds"; } void saveToPhoneStorage(String filePath) async { File file = File(filePath); Directory storageDirectory; if (Platform.isAndroid) { storageDirectory = await getExternalStorageDirectory(); } else if (Platform.isIOS) { storageDirectory = await getApplicationDocumentsDirectory(); } else { return; } String storagePath = storageDirectory.path; if (storagePath.contains("/Android/data")) { storagePath = storagePath.substring(0, storagePath.indexOf("/Android/data")); } if (Platform.isAndroid) { storagePath += "/Download/Tangheem/"; } else { storagePath += "/Recording/"; } var d = Directory(storagePath); if (!d.existsSync()) { d.createSync(recursive: true); } storagePath += "Tangheem${DateTime.now().millisecondsSinceEpoch}.mp3"; print("storagePath:$storagePath"); await file.copy(storagePath); Utils.showToast("تم التنزيل"); } void saveAudioToAboveAndroid32(String pathUrl, String fileName, String fileType) async { List _persistedPermissionUris = await saf.persistedUriPermissions(); if (_persistedPermissionUris.isNotEmpty) { saf.UriPermission permissionUri = _persistedPermissionUris.first; startFileAboveAndroid32(pathUrl, fileName, permissionUri.uri, fileType); } else { Uri selectedDocumentUris = await saf.openDocumentTree(); if (selectedDocumentUris == null) return; saveAudioToAboveAndroid32(pathUrl, fileName, fileType); } } void startFileAboveAndroid32(String filePath, String fileName, Uri persistentUri, String fileType) async { try { File file = File(filePath); final documentFile = await persistentUri.toDocumentFile(); final child = await documentFile?.child(fileName); if (child == null) { documentFile?.createFileAsBytes( mimeType: fileType, bytes: file.readAsBytesSync(), displayName: fileName, ); } else { documentFile?.writeToFileAsBytes( bytes: file.readAsBytesSync(), mode: FileMode.write, ); } Utils.showToast("تم حفظ الملف بنجاح"); } catch (ex) { print(ex); Utils.hideLoading(context); } } }