voice recording added and improvements
parent
72a18c8774
commit
3935d8ffbc
@ -0,0 +1,10 @@
|
||||
<svg viewBox="0 0 25.3 39.53" xmlns="http://www.w3.org/2000/svg">
|
||||
<g data-name="Layer 2" id="Layer_2">
|
||||
<g data-name="Layer 13" id="Layer_13">
|
||||
<g>
|
||||
<path style="fill: #ff8c67" d="M25.3,20.42a1.4,1.4,0,1,0-2.79,0,9.86,9.86,0,0,1-19.72,0A1.37,1.37,0,0,0,1.4,19,1.37,1.37,0,0,0,0,20.42,12.65,12.65,0,0,0,11.26,33v3.72H6.19a1.4,1.4,0,1,0,0,2.79H19.11a1.4,1.4,0,1,0,0-2.79H14.05V33A12.65,12.65,0,0,0,25.3,20.42Z"/>
|
||||
<path style="fill: #ff8c67" d="M12.65,0A7.78,7.78,0,0,0,4.88,7.77v12.6a7.77,7.77,0,1,0,15.54.05V7.77A7.78,7.78,0,0,0,12.65,0Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
@ -0,0 +1,17 @@
|
||||
<svg viewBox="0 0 97.34 97.34" xmlns="http://www.w3.org/2000/svg">
|
||||
<g data-name="Layer 2" id="Layer_2">
|
||||
<g data-name="Layer 13" id="Layer_13">
|
||||
<g data-name="Group 20-2" id="Group_20-2">
|
||||
<circle style="fill: #ff8764;opacity: 0.25" cx="48.67" cy="48.67" r="48.67"/>
|
||||
<circle style="fill: #ff8764;opacity: 0.5" cx="48.67" cy="48.67" r="44.31"/>
|
||||
<circle style="fill: #ff8764" cx="48.67" cy="48.67" r="36.32"/>
|
||||
<g data-name="Group 12-2" id="Group_12-2">
|
||||
<g data-name="Group 11-2" id="Group_11-2">
|
||||
<path style="fill: #fff" d="M63.27,48.66a1.46,1.46,0,0,0-2.92,0h0A11.68,11.68,0,1,1,37,49v-.33a1.46,1.46,0,0,0-2.92,0A14.59,14.59,0,0,0,47.22,63.2v5.91a1.46,1.46,0,1,0,2.92,0V63.19A14.59,14.59,0,0,0,63.27,48.66Z" data-name="Path 32-2" id="Path_32-2"/>
|
||||
<path style="fill: #fff" d="M48.67,56a8.79,8.79,0,0,0,8.77-8.76V35.53a8.77,8.77,0,0,0-17.53,0V47.2A8.78,8.78,0,0,0,48.67,56ZM42.83,35.53a5.84,5.84,0,1,1,11.68,0V47.2a5.84,5.84,0,1,1-11.68,0Z" data-name="Path 33-2" id="Path_33-2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,30 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 108.18 97.62" xmlns="http://www.w3.org/2000/svg">
|
||||
<g data-name="Layer 2" id="Layer_2">
|
||||
<g data-name="Layer 13" id="Layer_13">
|
||||
<g>
|
||||
<g data-name="Group 20-2" id="Group_20-2">
|
||||
<circle style="fill: #ff8764;opacity: 0.25" cx="48.67" cy="48.95" r="48.67"/>
|
||||
<circle style="fill: #ff8764;opacity: 0.5" cx="48.67" cy="48.95" r="44.31"/>
|
||||
<circle style="fill: #ff8764" cx="48.67" cy="48.95" r="36.32"/>
|
||||
<g data-name="Group 12-2" id="Group_12-2">
|
||||
<g data-name="Group 11-2" id="Group_11-2">
|
||||
<path style="fill: #fff" d="M63.27,48.94a1.46,1.46,0,0,0-2.92,0h0A11.68,11.68,0,0,1,37,49.27v-.33a1.46,1.46,0,0,0-2.92,0A14.59,14.59,0,0,0,47.22,63.48v5.91a1.46,1.46,0,1,0,2.92,0V63.47A14.59,14.59,0,0,0,63.27,48.94Z" data-name="Path 32-2" id="Path_32-2"/>
|
||||
<path style="fill: #fff" d="M48.67,56.24a8.79,8.79,0,0,0,8.77-8.77V35.81a8.77,8.77,0,1,0-17.53,0V47.48A8.78,8.78,0,0,0,48.67,56.24ZM42.83,35.81a5.84,5.84,0,1,1,11.68,0V47.48a5.84,5.84,0,1,1-11.68,0Z" data-name="Path 33-2" id="Path_33-2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g data-name="Layer 2" id="Layer_2-2">
|
||||
<g data-name="Layer 1-2" id="Layer_1-2">
|
||||
<g>
|
||||
<image style="opacity: 0.15000000596046448;isolation: isolate" height="60" transform="translate(48.18)" width="60" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAAAsSAAALEgHS3X78AAAFh0lEQVRoQ+2a23LbOBBEm5K9WTvZ///R9Uq2JWEfyDYbrcGFF1cqTqZqChAJUDjoATCiPaSU8DvZodXgq9kf4K9uD60GW2wYhqHVxi198qayO3AAuQjau+89AcNezzPQwUq/7qaDCOt7gW8GFlAH9uvRhERwyepZu63gq4EDRQerq3sbNQeNPmfgW6BXAQeqOuAhqGs7NQdLAG6F6wq/CnzxpmWwESh9sDo/s2+k5E08BXW1NAzDsBR6EXABVqGO8lnrDu7AhLpOdZZaZ9ssxJdCdwMHsKriUcojxuce7bpDc5Cq6lX8MpUD7idBbRF0F3ABVpVUUHe9zz40DVtC0o9SJ/h16ncHjU5rAnfAEurR/EFKuqoM3Cv7HvhxKtmHpmGOXpWbwJNFm5Mq+gjgLynVdQKosgJrCL9hhHub/HUq+b1uN7neFdpV4OCsdWUfkMN9K7gqHQFfMIO+Tq4RocuAlqz0emg9CkfK0qnoNwB/mz/hHpr9uGklzOtUYc+IlwCkH/2AeZdvhnYReFI3ckJr+BLwCcCz1J+me6oygYFxoKrueXJOTguWriG/PqTFXF2u20fM6j4B+D75sziBuZ4VmOuX6rKdhz6Q7+jqB8zQTZVD4EKe7OvXw/kZI+wP5OBUWYE5SCpM4BPySVHVorOa11Rptg+tprCHMd13ZlWY0P8gh9aw1jWswB7KtOiMVmiuYY51U0j7+vUEg8C6fglKaKrcAvZQvsn90vl8xJyUUN0BlSPqDtgSDZY9IU1ghf6BfmDCAvFxpecyJ/wd87iocrae3UoKl2AjaB47ehzppvUdcUgDM1QJlqBn5IkMd3vdyVXlorVCmqbg0U4dncV6LFFhDtQ3LVVdYXkm6y6vz1BYWnUdt4B9bbjCuoEpeOTRsXTDPSxDWPtopnYUd+hNCkdh7Sor+CNy+JI7sMKW+jisQ3bBAm2FaQ6vk+Bru8f1eamjvX+PKgspm9YC9rVR8yi8PDJcjdYzo2e7dcMC88641KJNgdlO6Xo1Ieiwrf0BtBXWL0kV19z2ap/dVeFaO/ckpU7goslsAQMxHMur+aXiuhsrcK2Pp5EKvypyasA1dSNITfvexDVdTLjfpTXBUPdUchf4lsKRugoaATJZ0NyYz7qiDMzfwq/iCl9SvBsWaAMDOSzd81xCnpBnRJ4u6q8hPlOBT+Y6Cap6tE90gd8Bp5SS/BzuUVdfyzgsw5h9osTDgV8A/IccWhXX9e2wTWspnKRUReiqrue57KfpYg2YLwBexAnuKmtoR+s4LXrjYVYKZ/2ZRhBN+xSWr29KwGxzwgj5AuDfqXSlCe1hDXSo3LNLe0gfkK9fT//YR0P/FeUfD/qsM0ZgQtNPKCvctXZpIfC0jjmoaB1fMOfOmjKyvSunL+ccWEOeKlNpDWkqrDt2tGFVwVshrSrfMA5UVX5Dnsv6pLi6JWA9iwnnO3bPGgaA6t+Ni8AFlQl9Qa7qRzfkqhGW6nL35rHE0HeVuRGepU51FXiRukBbYWB+CDeGwUptxxDTnTd6U6ET6UedJjCafLi6umF1qQs0gEVlD21g/NKPpnLPB/8gznD29R5Bv1ud92rHUdOaCgehTWB+VlgFVlVdXX9eBH2x+lVKVzcBbXWBDmAxfZhC8x6/nAO7IH5r4cCqMpeDwrGubVbBAp3AQWgDcUgR+IgZuPRaBtZPlfZSQXVyF8ECncBAERq4B9ejy0FLO7tC3AruoIthASz/P63KXybopXdYLWD3EuRqWGAFMIDor4ssa65t3ZKUNf9ouwYWWAlM6wD36zVz6Kg+VjYMehMwrQLu11qmg9kVlLYLMM3AgX5Qt2xQe4DSdgVWC+AX2Z6Qap8GXDNOxmdB1eynAP9MW/unll/W/gB/dfsfuIIUwHATj10AAAAASUVORK5CYII="/>
|
||||
<g>
|
||||
<circle style="fill: #e4697b" cx="77.91" cy="20.73" r="11.5"/>
|
||||
<circle style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 2.090909004211426px" cx="77.91" cy="20.73" r="11.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@ -0,0 +1,311 @@
|
||||
import 'dart:io';
|
||||
|
||||
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/record_mp3.dart';
|
||||
import 'package:tangheem/classes/colors.dart';
|
||||
import 'package:tangheem/classes/utils.dart';
|
||||
|
||||
class AyaRecordWidget extends StatefulWidget {
|
||||
AyaRecordWidget({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AyaRecordWidgetState createState() => _AyaRecordWidgetState();
|
||||
}
|
||||
|
||||
class _AyaRecordWidgetState extends State<AyaRecordWidget> {
|
||||
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<bool> 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<String> getFilePath() async {
|
||||
Directory storageDirectory = await getApplicationDocumentsDirectory();
|
||||
String sdPath = storageDirectory.path + "/record";
|
||||
var d = Directory(sdPath);
|
||||
if (!d.existsSync()) {
|
||||
d.createSync(recursive: true);
|
||||
}
|
||||
return sdPath + "/test.mp3";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 4, bottom: 20),
|
||||
padding: EdgeInsets.only(bottom: 20),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 60,
|
||||
width: double.infinity,
|
||||
margin: EdgeInsets.only(bottom: 8),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConsts.primaryBlue,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
topRight: Radius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
"assets/icons/mic_icon.svg",
|
||||
width: 25,
|
||||
height: 25,
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
"سجل الآية بصوتك",
|
||||
style: TextStyle(fontSize: 18, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
if (_isRecording) {
|
||||
stopRecord();
|
||||
} else {
|
||||
startRecord();
|
||||
}
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
_isRecording ? "assets/icons/recording_on.svg" : "assets/icons/recording_off.svg",
|
||||
width: 60,
|
||||
height: 60,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 50,
|
||||
margin: EdgeInsets.all(16),
|
||||
padding: EdgeInsets.only(left: 12, right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorConsts.secondaryWhite,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: StreamBuilder<Duration>(
|
||||
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<Duration>(
|
||||
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<PlayerState>(
|
||||
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();
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
onTap: () async {
|
||||
if (await _requestStoragePermission()) {
|
||||
if (recordFilePath != null && File(recordFilePath).existsSync()) {
|
||||
saveToPhoneStorage(recordFilePath);
|
||||
} else {
|
||||
Utils.showToast("يجب عليك تسجيل صوتك أولا");
|
||||
}
|
||||
} else {
|
||||
Utils.showToast("يجب أن تمنح الإذن لتنزيل التسجيل");
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
height: 40,
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
alignment: Alignment.centerRight,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: ColorConsts.gradientPink,
|
||||
gradient: LinearGradient(
|
||||
stops: [0.0, 0.5],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [ColorConsts.gradientPink, ColorConsts.gradientOrange],
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"تنزيل التسجيل",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white, height: 1.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _requestStoragePermission() async {
|
||||
Map<Permission, PermissionStatus> 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 = await getExternalStorageDirectory();
|
||||
|
||||
String storagePath = storageDirectory.path;
|
||||
if (storagePath.contains("/Android/data")) {
|
||||
storagePath = storagePath.substring(0, storagePath.indexOf("/Android/data"));
|
||||
}
|
||||
storagePath += "/tangheem/record/";
|
||||
var d = Directory(storagePath);
|
||||
if (!d.existsSync()) {
|
||||
d.createSync(recursive: true);
|
||||
}
|
||||
storagePath += "Tangheem${DateTime.now().millisecondsSinceEpoch}.mp3";
|
||||
await file.copy(storagePath);
|
||||
Utils.showToast("تم التنزيل");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue