From a732ea62e29c76af7fa170769c3a8764559d16d4 Mon Sep 17 00:00:00 2001 From: krille-chan Date: Wed, 11 Sep 2024 11:45:46 +0200 Subject: [PATCH] feat: New audio message design with displayed body --- lib/pages/chat/events/audio_player.dart | 235 +++++++++++++-------- lib/pages/chat/events/message_content.dart | 1 + 2 files changed, 150 insertions(+), 86 deletions(-) diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index d4667bb9d..80492f284 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -4,24 +4,33 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:just_audio/just_audio.dart'; import 'package:matrix/matrix.dart'; import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; +import 'package:fluffychat/utils/url_launcher.dart'; import '../../../utils/matrix_sdk_extensions/event_extension.dart'; class AudioPlayerWidget extends StatefulWidget { final Color color; + final double fontSize; final Event event; static String? currentId; static const int wavesCount = 40; - const AudioPlayerWidget(this.event, {this.color = Colors.black, super.key}); + const AudioPlayerWidget( + this.event, { + this.color = Colors.black, + required this.fontSize, + super.key, + }); @override AudioPlayerState createState() => AudioPlayerState(); @@ -39,8 +48,8 @@ class AudioPlayerState extends State { StreamSubscription? onPlayerError; String? statusText; - int currentPosition = 0; - double maxPosition = 0; + double currentPosition = 0; + double maxPosition = 1; MatrixFile? matrixFile; File? audioFile; @@ -58,6 +67,14 @@ class AudioPlayerState extends State { super.dispose(); } + void _startAction() { + if (status == AudioPlayerStatus.downloaded) { + _playAction(); + } else { + _downloadAction(); + } + } + Future _downloadAction() async { if (status != AudioPlayerStatus.notDownloaded) return; setState(() => status = AudioPlayerStatus.downloading); @@ -125,9 +142,7 @@ class AudioPlayerState extends State { setState(() { statusText = '${state.inMinutes.toString().padLeft(2, '0')}:${(state.inSeconds % 60).toString().padLeft(2, '0')}'; - currentPosition = ((state.inMilliseconds.toDouble() / maxPosition) * - AudioPlayerWidget.wavesCount) - .round(); + currentPosition = state.inMilliseconds.toDouble(); }); if (state.inMilliseconds.toDouble() == maxPosition) { audioPlayer.stop(); @@ -222,103 +237,151 @@ class AudioPlayerState extends State { final statusText = this.statusText ??= _durationString ?? '00:00'; final audioPlayer = this.audioPlayer; + + final body = widget.event.content.tryGet('body') ?? + widget.event.content.tryGet('filename'); + final displayBody = body != null && + body.isNotEmpty && + widget.event.content['org.matrix.msc1767.audio'] == null; + return Padding( padding: const EdgeInsets.all(12.0), - child: Row( + child: Column( 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?.playerState.playing == true - ? Icons.pause_outlined - : Icons.play_arrow_outlined, - color: widget.color, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: FluffyThemes.columnWidth), + 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), + onLongPress: () => widget.event.saveFile(context), + onTap: _startAction, + child: Material( + color: widget.color.withAlpha(64), + borderRadius: BorderRadius.circular(64), + child: Icon( + audioPlayer?.playerState.playing == true + ? Icons.pause_outlined + : Icons.play_arrow_outlined, + color: widget.color, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + children: [ + for (var i = 0; + i < AudioPlayerWidget.wavesCount; + i++) + Expanded( + child: Container( + height: 32, + alignment: Alignment.center, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 1, + ), + decoration: BoxDecoration( + color: widget.color.withAlpha(96), + borderRadius: BorderRadius.circular(32), + ), + height: 32 * (waveform[i] / 1024), + ), + ), + ), + ], + ), ), - ), - onLongPress: () => widget.event.saveFile(context), - onTap: () { - if (status == AudioPlayerStatus.downloaded) { - _playAction(); - } else { - _downloadAction(); - } - }, + SizedBox( + height: 32, + child: Slider( + thumbColor: widget.event.senderId == + widget.event.room.client.userID + ? theme.colorScheme.onPrimary + : theme.colorScheme.primary, + activeColor: Colors.transparent, + inactiveColor: Colors.transparent, + max: maxPosition, + value: currentPosition, + onChanged: (position) => audioPlayer == null + ? _startAction() + : audioPlayer.seek( + Duration(milliseconds: position.round()), + ), + ), + ), + ], ), - ), - const SizedBox(width: 8), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - for (var i = 0; i < AudioPlayerWidget.wavesCount; i++) - GestureDetector( - onTapDown: (_) => audioPlayer?.seek( - Duration( - milliseconds: - (maxPosition / AudioPlayerWidget.wavesCount).round() * - i, + ), + const SizedBox(width: 8), + SizedBox( + width: 36, + child: Text( + statusText, + style: TextStyle( + color: widget.color, + fontSize: 12, ), ), - child: Container( - height: 32, - color: widget.color.withAlpha(0), - alignment: Alignment.center, - child: Opacity( - opacity: currentPosition > i ? 1 : 0.5, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 1), - decoration: BoxDecoration( - color: widget.color, - borderRadius: BorderRadius.circular(2), + ), + const SizedBox(width: 8), + Badge( + isLabelVisible: audioPlayer != null, + label: audioPlayer == null + ? null + : Text( + '${audioPlayer.speed.toString()}x', ), - width: 2, - height: 32 * (waveform[i] / 1024), - ), + backgroundColor: theme.colorScheme.secondary, + textColor: theme.colorScheme.onSecondary, + child: InkWell( + splashColor: widget.color.withAlpha(128), + borderRadius: BorderRadius.circular(64), + onTap: audioPlayer == null ? null : _toggleSpeed, + child: Icon( + Icons.mic_none_outlined, + color: widget.color, ), ), ), - ], + const SizedBox(width: 8), + ], + ), ), - const SizedBox(width: 8), - SizedBox( - width: 36, - child: Text( - statusText, + if (displayBody) + Linkify( + text: body, style: TextStyle( color: widget.color, - fontSize: 12, + fontSize: widget.fontSize, ), - ), - ), - const SizedBox(width: 8), - Badge( - isLabelVisible: audioPlayer != null, - label: audioPlayer == null - ? null - : Text( - '${audioPlayer.speed.toString()}x', - ), - backgroundColor: theme.colorScheme.secondary, - textColor: theme.colorScheme.onSecondary, - child: InkWell( - splashColor: widget.color.withAlpha(128), - borderRadius: BorderRadius.circular(64), - onTap: audioPlayer == null ? null : _toggleSpeed, - child: Icon( - Icons.mic_none_outlined, - color: widget.color, + options: const LinkifyOptions(humanize: false), + linkStyle: TextStyle( + color: widget.color.withAlpha(150), + fontSize: widget.fontSize, + decoration: TextDecoration.underline, + decorationColor: widget.color.withAlpha(150), ), + onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), - ), - const SizedBox(width: 8), ], ), ); diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 5aeb650a4..415e37428 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -151,6 +151,7 @@ class MessageContent extends StatelessWidget { return AudioPlayerWidget( event, color: textColor, + fontSize: fontSize, ); } return MessageDownloadContent(event, textColor);