mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-04 04:04:18 +00:00 
			
		
		
		
	Start to record MP3 files
This commit is contained in:
		
							
								
								
									
										30
									
								
								lib/ui/dialogs/alert_dialog.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/ui/dialogs/alert_dialog.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Simple alert dialog
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// @author Pierre Hubert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Future<void> alert(BuildContext context, String msg) async {
 | 
				
			||||||
 | 
					  await showDialog(context: context, builder: (c) => _AlertDialog(msg: msg));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _AlertDialog extends StatelessWidget {
 | 
				
			||||||
 | 
					  final String msg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const _AlertDialog({Key key, @required this.msg})
 | 
				
			||||||
 | 
					      : assert(msg != null),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return AlertDialog(
 | 
				
			||||||
 | 
					      content: Text(msg),
 | 
				
			||||||
 | 
					      actions: <Widget>[
 | 
				
			||||||
 | 
					        MaterialButton(
 | 
				
			||||||
 | 
					          child: Text("OK"),
 | 
				
			||||||
 | 
					          onPressed: () => Navigator.of(context).pop(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:comunic/models/api_request.dart';
 | 
					import 'package:comunic/models/api_request.dart';
 | 
				
			||||||
 | 
					import 'package:comunic/ui/dialogs/record_audio_dialog.dart';
 | 
				
			||||||
import 'package:comunic/utils/files_utils.dart';
 | 
					import 'package:comunic/utils/files_utils.dart';
 | 
				
			||||||
import 'package:comunic/utils/intl_utils.dart';
 | 
					import 'package:comunic/utils/intl_utils.dart';
 | 
				
			||||||
import 'package:comunic/utils/ui_utils.dart';
 | 
					import 'package:comunic/utils/ui_utils.dart';
 | 
				
			||||||
@@ -17,6 +18,7 @@ enum _FileChoices {
 | 
				
			|||||||
  TAKE_PICTURE,
 | 
					  TAKE_PICTURE,
 | 
				
			||||||
  PICK_VIDEO,
 | 
					  PICK_VIDEO,
 | 
				
			||||||
  TAKE_VIDEO,
 | 
					  TAKE_VIDEO,
 | 
				
			||||||
 | 
					  RECORD_AUDIO,
 | 
				
			||||||
  PICK_OTHER_FILE,
 | 
					  PICK_OTHER_FILE,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,12 +67,20 @@ List<_PickFileOption> get _optionsList => [
 | 
				
			|||||||
          icon: Icons.videocam,
 | 
					          icon: Icons.videocam,
 | 
				
			||||||
          canEnable: (l) => l.any(isVideo)),
 | 
					          canEnable: (l) => l.any(isVideo)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Audio
 | 
				
			||||||
 | 
					      _PickFileOption(
 | 
				
			||||||
 | 
					          value: _FileChoices.RECORD_AUDIO,
 | 
				
			||||||
 | 
					          label: tr("Record audio"),
 | 
				
			||||||
 | 
					          icon: Icons.mic,
 | 
				
			||||||
 | 
					          canEnable: (l) => l.any(isAudio)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Other
 | 
					      // Other
 | 
				
			||||||
      _PickFileOption(
 | 
					      _PickFileOption(
 | 
				
			||||||
          value: _FileChoices.PICK_OTHER_FILE,
 | 
					          value: _FileChoices.PICK_OTHER_FILE,
 | 
				
			||||||
          label: tr("Browse files"),
 | 
					          label: tr("Browse files"),
 | 
				
			||||||
          icon: Icons.folder_open,
 | 
					          icon: Icons.folder_open,
 | 
				
			||||||
          canEnable: (l) => l.any((el) => !isImage(el) && !isVideo(el))),
 | 
					          canEnable: (l) =>
 | 
				
			||||||
 | 
					              l.any((el) => !isImage(el) && !isVideo(el) && !isAudio(el))),
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<BytesFile> showPickFileDialog({
 | 
					Future<BytesFile> showPickFileDialog({
 | 
				
			||||||
@@ -138,6 +148,13 @@ Future<BytesFile> showPickFileDialog({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Record audio file
 | 
				
			||||||
 | 
					    case _FileChoices.RECORD_AUDIO:
 | 
				
			||||||
 | 
					      final bytes = await showRecordAudioDialog(context);
 | 
				
			||||||
 | 
					      if (bytes == null) return null;
 | 
				
			||||||
 | 
					      file = BytesFile("record.mp3", bytes);
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Pick other files
 | 
					    // Pick other files
 | 
				
			||||||
    case _FileChoices.PICK_OTHER_FILE:
 | 
					    case _FileChoices.PICK_OTHER_FILE:
 | 
				
			||||||
      final pickedFile = await FilePicker.platform.pickFiles(
 | 
					      final pickedFile = await FilePicker.platform.pickFiles(
 | 
				
			||||||
@@ -179,7 +196,7 @@ class _BottomSheetPickOption extends StatelessWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) => Container(
 | 
					  Widget build(BuildContext context) => Container(
 | 
				
			||||||
        height: 300,
 | 
					        height: 255,
 | 
				
			||||||
        child: ListView.builder(
 | 
					        child: ListView.builder(
 | 
				
			||||||
          itemCount: options.length,
 | 
					          itemCount: options.length,
 | 
				
			||||||
          itemBuilder: (c, i) => ListTile(
 | 
					          itemBuilder: (c, i) => ListTile(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										298
									
								
								lib/ui/dialogs/record_audio_dialog.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								lib/ui/dialogs/record_audio_dialog.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,298 @@
 | 
				
			|||||||
 | 
					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';
 | 
				
			||||||
 | 
					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';
 | 
				
			||||||
 | 
					import 'package:record/record.dart';
 | 
				
			||||||
 | 
					import 'package:video_player/video_player.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Record audio dialog
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// @author Pierre Hubert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Record audio
 | 
				
			||||||
 | 
					Future<Uint8List> showRecordAudioDialog(BuildContext context) async {
 | 
				
			||||||
 | 
					  if (!await Record.hasPermission()) {
 | 
				
			||||||
 | 
					    await alert(
 | 
				
			||||||
 | 
					        context, "Permission d'accéder au périphérique audio refusée !");
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final res = await showDialog(
 | 
				
			||||||
 | 
					    context: context,
 | 
				
			||||||
 | 
					    builder: (c) => Scaffold(
 | 
				
			||||||
 | 
					      body: _RecordAudioDialog(),
 | 
				
			||||||
 | 
					      backgroundColor: Colors.transparent,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    barrierDismissible: false,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (await Record.isRecording()) await Record.stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return res;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _RecordAudioDialog extends StatefulWidget {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  __RecordAudioDialogState createState() => __RecordAudioDialogState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class __RecordAudioDialogState extends State<_RecordAudioDialog> {
 | 
				
			||||||
 | 
					  String _recordPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  File get _recordFile => _recordPath == null ? null : File(_recordPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool _recording = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get _hasRecord => !_recording && _recordPath != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  VideoPlayerController _videoPlayerController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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();
 | 
				
			||||||
 | 
					    return bytes;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    _disposePlayer();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _disposePlayer() {
 | 
				
			||||||
 | 
					    if (_videoPlayerController != null) {
 | 
				
			||||||
 | 
					      _videoPlayerController.dispose();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _videoPlayerController = null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return AlertDialog(
 | 
				
			||||||
 | 
					      title: Text(tr("Audio record")),
 | 
				
			||||||
 | 
					      content: _buildContent(),
 | 
				
			||||||
 | 
					      actions: <Widget>[
 | 
				
			||||||
 | 
					        _ActionButton(
 | 
				
			||||||
 | 
					          visible: !_recording,
 | 
				
			||||||
 | 
					          text: tr("Cancel"),
 | 
				
			||||||
 | 
					          onPressed: () => Navigator.of(context).pop(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        _ActionButton(
 | 
				
			||||||
 | 
					          visible: _hasRecord,
 | 
				
			||||||
 | 
					          text: tr("Send"),
 | 
				
			||||||
 | 
					          onPressed: () => Navigator.of(context).pop(_bytes),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String get _status {
 | 
				
			||||||
 | 
					    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),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final dir = await getTemporaryDirectory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      _recordPath = path.join(dir.absolute.path, "tmp-audio-record.m4a");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await Record.start(path: _recordPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setState(() => _recording = true);
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      logError(e, s);
 | 
				
			||||||
 | 
					      snack(context, tr("Error while recording!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _stopRecording() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await Record.stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setState(() => _recording = false);
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      logError(e, s);
 | 
				
			||||||
 | 
					      snack(context, tr("Error while recording!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _playRecord() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      _disposePlayer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      _videoPlayerController = VideoPlayerController.file(File(_recordPath));
 | 
				
			||||||
 | 
					      await _videoPlayerController.initialize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      _videoPlayerController.addListener(() async {
 | 
				
			||||||
 | 
					        if (_videoPlayerController == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (_videoPlayerController.value.position ==
 | 
				
			||||||
 | 
					            _videoPlayerController.value.duration) _stopPlayback();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await _videoPlayerController.play();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setState(() {
 | 
				
			||||||
 | 
					        _playing = true;
 | 
				
			||||||
 | 
					        _paused = false;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      logError(e, s);
 | 
				
			||||||
 | 
					      snack(context, tr("Error while playing record!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _pausePlayback() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await _videoPlayerController.pause();
 | 
				
			||||||
 | 
					      setState(() => _paused = true);
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      logError(e, s);
 | 
				
			||||||
 | 
					      snack(context, tr("Error while pausing playback!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _resumePlayback() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await _videoPlayerController.play();
 | 
				
			||||||
 | 
					      setState(() => _paused = false);
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      logError(e, s);
 | 
				
			||||||
 | 
					      snack(context, tr("Error while resuming playback!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _stopPlayback() async {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      _disposePlayer();
 | 
				
			||||||
 | 
					      setState(() {
 | 
				
			||||||
 | 
					        _paused = false;
 | 
				
			||||||
 | 
					        _playing = false;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (e, s) {
 | 
				
			||||||
 | 
					      logError(e, s);
 | 
				
			||||||
 | 
					      snack(context, tr("Error while stopping playback!"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _RecordAction extends StatelessWidget {
 | 
				
			||||||
 | 
					  final bool visible;
 | 
				
			||||||
 | 
					  final IconData icon;
 | 
				
			||||||
 | 
					  final void Function() onTap;
 | 
				
			||||||
 | 
					  final Color color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const _RecordAction({
 | 
				
			||||||
 | 
					    Key key,
 | 
				
			||||||
 | 
					    @required this.visible,
 | 
				
			||||||
 | 
					    @required this.icon,
 | 
				
			||||||
 | 
					    @required this.onTap,
 | 
				
			||||||
 | 
					    this.color,
 | 
				
			||||||
 | 
					  })  : assert(visible != null),
 | 
				
			||||||
 | 
					        assert(icon != null),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const _ActionButton({
 | 
				
			||||||
 | 
					    Key key,
 | 
				
			||||||
 | 
					    this.visible,
 | 
				
			||||||
 | 
					    this.text,
 | 
				
			||||||
 | 
					    this.onPressed,
 | 
				
			||||||
 | 
					  })  : assert(visible != null),
 | 
				
			||||||
 | 
					        assert(text != null),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    if (!visible) return Container();
 | 
				
			||||||
 | 
					    return MaterialButton(
 | 
				
			||||||
 | 
					      onPressed: onPressed,
 | 
				
			||||||
 | 
					      child: Text(text.toUpperCase()),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -49,5 +49,8 @@ Future<PickedFile> pickImage(BuildContext context) async {
 | 
				
			|||||||
/// Check if a mime type maps to an image or not
 | 
					/// Check if a mime type maps to an image or not
 | 
				
			||||||
bool isImage(String mimeType) => mimeType.startsWith("image/");
 | 
					bool isImage(String mimeType) => mimeType.startsWith("image/");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Check if a mime type maps to an image or not
 | 
					/// Check if a mime type maps to a video or not
 | 
				
			||||||
bool isVideo(String mimeType) => mimeType.startsWith("video/mp4");
 | 
					bool isVideo(String mimeType) => mimeType.startsWith("video/mp4");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Check if a mime type maps to an audio file or not
 | 
				
			||||||
 | 
					bool isAudio(String mimeType) => mimeType.startsWith("audio/mpeg");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,10 @@ void showSimpleSnack(BuildContext context, String message) {
 | 
				
			|||||||
  Scaffold.of(context).showSnackBar(SnackBar(content: Text(message)));
 | 
					  Scaffold.of(context).showSnackBar(SnackBar(content: Text(message)));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void snack(BuildContext context, String message) {
 | 
				
			||||||
 | 
					  Scaffold.of(context).showSnackBar(SnackBar(content: Text(message)));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Show an alert dialog to ask the user to enter a string
 | 
					/// Show an alert dialog to ask the user to enter a string
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// Returns entered string if the dialog is confirmed, null else
 | 
					/// Returns entered string if the dialog is confirmed, null else
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -464,6 +464,13 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.1.0"
 | 
					    version: "2.1.0"
 | 
				
			||||||
 | 
					  record:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: record
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "1.0.2"
 | 
				
			||||||
  rxdart:
 | 
					  rxdart:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -112,6 +112,9 @@ dependencies:
 | 
				
			|||||||
  # Create video thumbnails
 | 
					  # Create video thumbnails
 | 
				
			||||||
  video_thumbnail: ^0.2.5+1
 | 
					  video_thumbnail: ^0.2.5+1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Record audio file
 | 
				
			||||||
 | 
					  record: ^1.0.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
    sdk: flutter
 | 
					    sdk: flutter
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user