1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-25 14:29:22 +00:00
comunicmobile/lib/ui/dialogs/record_audio_dialog.dart

299 lines
7.1 KiB
Dart
Raw Normal View History

2021-03-12 19:52:26 +00:00
import 'dart:io';
import 'dart:typed_data';
import 'package:comunic/ui/dialogs/alert_dialog.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/log_utils.dart';
2021-03-12 20:19:40 +00:00
import 'package:comunic/utils/permission_utils.dart';
2021-03-12 19:52:26 +00:00
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
2021-03-12 20:19:40 +00:00
import 'package:permission_handler/permission_handler.dart';
import 'package:record_mp3/record_mp3.dart';
2021-03-12 19:52:26 +00:00
import 'package:video_player/video_player.dart';
/// Record audio dialog
///
/// @author Pierre Hubert
/// Record audio
Future<Uint8List?> showRecordAudioDialog(BuildContext context) async {
2021-03-12 20:19:40 +00:00
// Request record permission
if (!await requestPermission(context, Permission.microphone)) {
alert(context, tr("Did not get permission to access microphone!"));
2021-03-12 19:52:26 +00:00
return null;
}
final res = await showDialog(
context: context,
builder: (c) => Scaffold(
body: _RecordAudioDialog(),
backgroundColor: Colors.transparent,
),
barrierDismissible: false,
);
return res;
}
class _RecordAudioDialog extends StatefulWidget {
@override
__RecordAudioDialogState createState() => __RecordAudioDialogState();
}
class __RecordAudioDialogState extends State<_RecordAudioDialog> {
String? _recordPath;
2021-03-12 19:52:26 +00:00
File? get _recordFile => _recordPath == null ? null : File(_recordPath!);
2021-03-12 19:52:26 +00:00
bool _recording = false;
bool get _hasRecord => !_recording && _recordPath != null;
VideoPlayerController? _videoPlayerController;
2021-03-12 19:52:26 +00:00
bool _playing = false;
bool _paused = false;
/// Get record data. This getter can be accessed only once
Uint8List get _bytes {
final bytes = _recordFile!.readAsBytesSync();
File(_recordPath!).deleteSync();
2021-03-12 19:52:26 +00:00
return bytes;
}
@override
void dispose() {
_disposePlayer();
2021-03-12 20:19:40 +00:00
RecordMp3.instance.stop();
2021-03-12 19:52:26 +00:00
super.dispose();
}
void _disposePlayer() {
if (_videoPlayerController != null) {
_videoPlayerController!.dispose();
2021-03-12 19:52:26 +00:00
}
_videoPlayerController = null;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(tr("Audio record")!),
2021-03-12 19:52:26 +00:00
content: _buildContent(),
actions: <Widget>[
_ActionButton(
visible: !_recording,
text: tr("Cancel")!,
2021-03-12 19:52:26 +00:00
onPressed: () => Navigator.of(context).pop(),
),
_ActionButton(
visible: _hasRecord,
text: tr("Send")!,
2021-03-12 19:52:26 +00:00
onPressed: () => Navigator.of(context).pop(_bytes),
),
],
);
}
String? get _status {
2021-03-12 19:52:26 +00:00
if (_recording)
return tr("Recording...");
else if (_paused)
return tr("Playback paused...");
else if (_playing)
return tr("Playing...");
else if (!_hasRecord)
return tr("Ready");
else
return tr("Done");
}
Widget _buildContent() => Row(
children: <Widget>[
Text(_status!),
2021-03-12 19:52:26 +00:00
Spacer(),
// Start recording
_RecordAction(
visible: !_recording && !_playing,
icon: Icons.fiber_manual_record,
onTap: _startRecording,
color: Colors.red,
),
// Stop recording
_RecordAction(
visible: _recording,
icon: Icons.stop,
onTap: _stopRecording,
color: Colors.red,
),
// Play recording
_RecordAction(
visible: !_recording && _hasRecord && !_playing,
icon: Icons.play_arrow,
onTap: _playRecord,
),
// Pause playback
_RecordAction(
visible: _playing && !_paused,
icon: Icons.pause,
onTap: _pausePlayback,
),
// Resume recording
_RecordAction(
visible: _paused,
icon: Icons.play_arrow,
onTap: _resumePlayback,
),
// Stop recording
_RecordAction(
visible: _playing,
icon: Icons.stop,
onTap: _stopPlayback,
),
],
);
void _startRecording() async {
try {
if (_recordFile != null) _recordFile!.deleteSync();
2021-03-12 19:52:26 +00:00
final dir = await getTemporaryDirectory();
2021-03-12 20:19:40 +00:00
_recordPath = path.join(dir.absolute.path, "tmp-audio-record.mp3");
2021-03-12 19:52:26 +00:00
RecordMp3.instance.start(_recordPath!, (fail) {
2021-03-12 20:19:40 +00:00
print(fail);
snack(context, tr("Failed to start recording!")!);
2021-03-12 20:19:40 +00:00
});
2021-03-12 19:52:26 +00:00
setState(() => _recording = true);
} catch (e, s) {
logError(e, s);
snack(context, tr("Error while recording!")!);
2021-03-12 19:52:26 +00:00
}
}
void _stopRecording() async {
try {
2021-03-12 20:19:40 +00:00
RecordMp3.instance.stop();
2021-03-12 19:52:26 +00:00
setState(() => _recording = false);
} catch (e, s) {
logError(e, s);
snack(context, tr("Error while recording!")!);
2021-03-12 19:52:26 +00:00
}
}
void _playRecord() async {
try {
_disposePlayer();
_videoPlayerController = VideoPlayerController.file(File(_recordPath!));
await _videoPlayerController!.initialize();
2021-03-12 19:52:26 +00:00
_videoPlayerController!.addListener(() async {
2021-03-12 19:52:26 +00:00
if (_videoPlayerController == null) return;
if (_videoPlayerController!.value.position ==
_videoPlayerController!.value.duration) _stopPlayback();
2021-03-12 19:52:26 +00:00
});
await _videoPlayerController!.play();
2021-03-12 19:52:26 +00:00
setState(() {
_playing = true;
_paused = false;
});
} catch (e, s) {
logError(e, s);
snack(context, tr("Error while playing record!")!);
2021-03-12 19:52:26 +00:00
}
}
void _pausePlayback() async {
try {
await _videoPlayerController!.pause();
2021-03-12 19:52:26 +00:00
setState(() => _paused = true);
} catch (e, s) {
logError(e, s);
snack(context, tr("Error while pausing playback!")!);
2021-03-12 19:52:26 +00:00
}
}
void _resumePlayback() async {
try {
await _videoPlayerController!.play();
2021-03-12 19:52:26 +00:00
setState(() => _paused = false);
} catch (e, s) {
logError(e, s);
snack(context, tr("Error while resuming playback!")!);
2021-03-12 19:52:26 +00:00
}
}
void _stopPlayback() async {
try {
_disposePlayer();
setState(() {
_paused = false;
_playing = false;
});
} catch (e, s) {
logError(e, s);
snack(context, tr("Error while stopping playback!")!);
2021-03-12 19:52:26 +00:00
}
}
}
class _RecordAction extends StatelessWidget {
final bool visible;
final IconData icon;
final void Function() onTap;
final Color? color;
2021-03-12 19:52:26 +00:00
const _RecordAction({
Key? key,
required this.visible,
required this.icon,
required this.onTap,
2021-03-12 19:52:26 +00:00
this.color,
2022-03-11 16:09:37 +00:00
}) : super(key: key);
2021-03-12 19:52:26 +00:00
@override
Widget build(BuildContext context) {
if (!visible) return Container(width: 0, height: 0);
return IconButton(icon: Icon(icon, color: color), onPressed: onTap);
}
}
class _ActionButton extends StatelessWidget {
final bool visible;
final String text;
final void Function()? onPressed;
2021-03-12 19:52:26 +00:00
const _ActionButton({
Key? key,
required this.visible,
required this.text,
2021-03-12 19:52:26 +00:00
this.onPressed,
2022-03-11 16:09:37 +00:00
}) : super(key: key);
2021-03-12 19:52:26 +00:00
@override
Widget build(BuildContext context) {
if (!visible) return Container();
return MaterialButton(
onPressed: onPressed,
child: Text(text.toUpperCase()),
);
}
}