Skip to content

Commit

Permalink
feat: New audio message design with displayed body
Browse files Browse the repository at this point in the history
  • Loading branch information
krille-chan committed Sep 11, 2024
1 parent 9b6d45c commit a732ea6
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 86 deletions.
235 changes: 149 additions & 86 deletions lib/pages/chat/events/audio_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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),
],
),
);
Expand Down
1 change: 1 addition & 0 deletions lib/pages/chat/events/message_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class MessageContent extends StatelessWidget {
return AudioPlayerWidget(
event,
color: textColor,
fontSize: fontSize,
);
}
return MessageDownloadContent(event, textColor);
Expand Down

0 comments on commit a732ea6

Please sign in to comment.