diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart index 4f31aa8b0..37e20c364 100644 --- a/lib/pages/chat.dart +++ b/lib/pages/chat.dart @@ -356,22 +356,26 @@ class ChatController extends State { void voiceMessageAction() async { if (await Record().hasPermission() == false) return; - final result = await showDialog( + final result = await showDialog( context: context, useRootNavigator: false, builder: (c) => const RecordingDialog(), ); if (result == null) return; - final audioFile = File(result); - // as we already explicitly say send in the recording dialog, - // we do not need the send file dialog anymore. We can just send this straight away. + final audioFile = File(result['path']); + final file = MatrixAudioFile( + bytes: audioFile.readAsBytesSync(), + name: audioFile.path, + ); await showFutureLoadingDialog( context: context, - future: () => room.sendFileEvent( - MatrixAudioFile( - bytes: audioFile.readAsBytesSync(), name: audioFile.path), - inReplyTo: replyEvent, - ), + future: () => + room.sendFileEvent(file, inReplyTo: replyEvent, extraContent: { + 'info': { + ...file.info, + 'duration': result['duration'], + } + }), ); setState(() { replyEvent = null; diff --git a/lib/pages/recording_dialog.dart b/lib/pages/recording_dialog.dart index 06c54553d..aac1ac0ae 100644 --- a/lib/pages/recording_dialog.dart +++ b/lib/pages/recording_dialog.dart @@ -133,8 +133,10 @@ class _RecordingDialogState extends State { onPressed: () async { _recorderSubscription?.cancel(); await _audioRecorder.stop(); - Navigator.of(context, rootNavigator: false) - .pop(_recordedPath); + Navigator.of(context, rootNavigator: false).pop({ + 'path': _recordedPath, + 'duration': _duration.inMilliseconds, + }); }, child: Text(L10n.of(context).send.toUpperCase()), ), diff --git a/lib/widgets/event_content/audio_player.dart b/lib/widgets/event_content/audio_player.dart index e40725f6b..109f3b36b 100644 --- a/lib/widgets/event_content/audio_player.dart +++ b/lib/widgets/event_content/audio_player.dart @@ -36,7 +36,7 @@ class _AudioPlayerState extends State { StreamSubscription onPlayerStateChanged; StreamSubscription onPlayerError; - String statusText = '00:00'; + String statusText; double currentPosition = 0; double maxPosition = 0; @@ -128,60 +128,71 @@ class _AudioPlayerState extends State { } } + static const double buttonSize = 36; + + String get _durationString { + final durationInt = widget.event.content + .tryGetMap('info') + ?.tryGet('duration'); + if (durationInt == null) return null; + final duration = Duration(milliseconds: durationInt); + return '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}'; + } + @override Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 30, - child: status == AudioPlayerStatus.downloading - ? const CircularProgressIndicator.adaptive(strokeWidth: 2) - : IconButton( - icon: Icon( - audioPlayer.state == PlayerState.PLAYING - ? Icons.pause_outlined - : Icons.play_arrow_outlined, - color: widget.color, + statusText ??= _durationString ?? '00:00'; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: buttonSize, + height: buttonSize, + child: status == AudioPlayerStatus.downloading + ? CircularProgressIndicator(strokeWidth: 2, color: widget.color) + : InkWell( + borderRadius: BorderRadius.circular(64), + child: Material( + color: widget.color.withAlpha(64), + borderRadius: BorderRadius.circular(64), + child: Icon( + audioPlayer.state == PlayerState.PLAYING + ? Icons.pause_outlined + : Icons.play_arrow_outlined, + color: widget.color, + ), + ), + onLongPress: () => widget.event.saveFile(context), + onTap: () { + if (status == AudioPlayerStatus.downloaded) { + _playAction(); + } else { + _downloadAction(); + } + }, ), - tooltip: audioPlayer.state == PlayerState.PLAYING - ? L10n.of(context).audioPlayerPause - : L10n.of(context).audioPlayerPlay, - onPressed: () { - if (status == AudioPlayerStatus.downloaded) { - _playAction(); - } else { - _downloadAction(); - } - }, - ), - ), - Expanded( - child: Slider( - activeColor: Theme.of(context).colorScheme.secondaryVariant, - inactiveColor: widget.color.withAlpha(64), - value: currentPosition, - onChanged: (double position) => - audioPlayer.seek(Duration(milliseconds: position.toInt())), - max: status == AudioPlayerStatus.downloaded ? maxPosition : 0, - min: 0, ), - ), - Text( - statusText, - style: TextStyle( - color: widget.color, + Expanded( + child: Slider( + activeColor: Theme.of(context).colorScheme.secondaryVariant, + inactiveColor: widget.color.withAlpha(64), + value: currentPosition, + onChanged: (double position) => + audioPlayer.seek(Duration(milliseconds: position.toInt())), + max: status == AudioPlayerStatus.downloaded ? maxPosition : 0, + min: 0, + ), ), - ), - const SizedBox(width: 8), - IconButton( - icon: Icon( - Icons.download_outlined, - color: widget.color, + Text( + statusText, + style: TextStyle( + color: widget.color, + ), ), - onPressed: () => widget.event.saveFile(context), - ), - ], + ], + ), ); } }