feat: New audio message design with displayed body

pull/1336/head
krille-chan 1 year ago
parent 9b6d45c382
commit a732ea62e2
No known key found for this signature in database

@ -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<AudioPlayerWidget> {
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<AudioPlayerWidget> {
super.dispose();
}
void _startAction() {
if (status == AudioPlayerStatus.downloaded) {
_playAction();
} else {
_downloadAction();
}
}
Future<void> _downloadAction() async {
if (status != AudioPlayerStatus.notDownloaded) return;
setState(() => status = AudioPlayerStatus.downloading);
@ -125,9 +142,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
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<AudioPlayerWidget> {
final statusText = this.statusText ??= _durationString ?? '00:00';
final audioPlayer = this.audioPlayer;
final body = widget.event.content.tryGet<String>('body') ??
widget.event.content.tryGet<String>('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: <Widget>[
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: <Widget>[
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),
],
),
);

@ -151,6 +151,7 @@ class MessageContent extends StatelessWidget {
return AudioPlayerWidget(
event,
color: textColor,
fontSize: fontSize,
);
}
return MessageDownloadContent(event, textColor);

Loading…
Cancel
Save