Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(llc, ui): Async audio [DoNotMerge] #1486

Draft
wants to merge 153 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 138 commits
Commits
Show all changes
153 commits
Select commit Hold shift + click to select a range
89360ed
Adding record button to actions
leandroBorgesFerreira Feb 27, 2023
ebba58c
Diving onPress and onHold button
leandroBorgesFerreira Feb 27, 2023
fe5477c
Sending audio record
leandroBorgesFerreira Feb 27, 2023
0759e00
Adding audio player attachment
leandroBorgesFerreira Feb 27, 2023
f6bf3c3
Making audio play with message
leandroBorgesFerreira Feb 27, 2023
966d43e
Making button onHold instead of two taps
leandroBorgesFerreira Feb 28, 2023
7d4f953
Cancelling audio on drag
leandroBorgesFerreira Feb 28, 2023
531c0da
Debugging button as a slider
leandroBorgesFerreira Feb 28, 2023
33d2549
Icon sliding behind input field
leandroBorgesFerreira Feb 28, 2023
ff901a6
Feedback when recording
leandroBorgesFerreira Feb 28, 2023
5c4537a
Better slide
leandroBorgesFerreira Feb 28, 2023
e79b711
Adding RecordTimer
leandroBorgesFerreira Feb 28, 2023
de73c0d
Speed reproduction button
leandroBorgesFerreira Mar 1, 2023
4d3c9c5
Adding audio attachments to message compose
leandroBorgesFerreira Mar 1, 2023
c949929
Presenting the lsit of audios
leandroBorgesFerreira Mar 1, 2023
f5494a4
Expanding audio message
leandroBorgesFerreira Mar 1, 2023
e2ab04e
Adding file name
leandroBorgesFerreira Mar 1, 2023
fcc3dfd
Changing time when user seeks the audio
leandroBorgesFerreira Mar 2, 2023
c058557
Sharing the same AudioPlayer for a group of audio notes - part 1
leandroBorgesFerreira Mar 2, 2023
8f02139
Playing audios in sequence for same attachment group
leandroBorgesFerreira Mar 2, 2023
edd15a3
Fixing timer for seeking state and fixing speed button
leandroBorgesFerreira Mar 2, 2023
acf4bc2
Fixing initial state restart after reaching audio group end
leandroBorgesFerreira Mar 2, 2023
4427026
Using duration stream instead of future correctly display each audio
leandroBorgesFerreira Mar 2, 2023
d12cc00
Place holder for voiceAttachments inside message input
leandroBorgesFerreira Mar 2, 2023
0b764fc
Showing player inside message composer
leandroBorgesFerreira Mar 2, 2023
9734909
Adding remove button
leandroBorgesFerreira Mar 2, 2023
dcb2418
Removing name from audio, fixing height in message list and change sp…
leandroBorgesFerreira Mar 3, 2023
621f567
Adding action button
leandroBorgesFerreira Mar 3, 2023
3cbfc1a
Fixing size of file
leandroBorgesFerreira Mar 3, 2023
6cd1c54
Fixing _twoDigits method and displaying time correctly
leandroBorgesFerreira Mar 3, 2023
3b6f061
Sending messages directly
leandroBorgesFerreira Mar 3, 2023
65626c1
Adding AudioPlayerWrapper
leandroBorgesFerreira Mar 3, 2023
ede6dec
Fixing state for AudioListPlayer
leandroBorgesFerreira Mar 3, 2023
bfa3ab1
Creating recording button without onHolding to activate
leandroBorgesFerreira Mar 3, 2023
b3cdfb8
Moving record logic to message input widget
leandroBorgesFerreira Mar 3, 2023
869f0a4
Fixing logic for onhold button
leandroBorgesFerreira Mar 4, 2023
1bfe51d
Audio AudioWaveSlider with basic functionality
leandroBorgesFerreira Mar 6, 2023
4c6bb89
Making AudioWaveSlider move with progress
leandroBorgesFerreira Mar 6, 2023
44522a9
Adding padding
leandroBorgesFerreira Mar 6, 2023
5f68e48
Improving position and expansion of AudioWaveSlider
leandroBorgesFerreira Mar 6, 2023
dd92f58
Creating seek functionality
leandroBorgesFerreira Mar 6, 2023
df2fe1d
Code identation
leandroBorgesFerreira Mar 7, 2023
d68149a
Disposing _audioRecorder and small suggar syntax for adding audio att…
leandroBorgesFerreira Mar 7, 2023
e547ef3
Adding pause button for audio recording
leandroBorgesFerreira Mar 8, 2023
f7270c5
Making recorder stop when recording is paused
leandroBorgesFerreira Mar 9, 2023
630be58
Calculating duration for audio attachment
leandroBorgesFerreira Mar 9, 2023
4982c99
Using duration build only if duration was not provided
leandroBorgesFerreira Mar 9, 2023
086e970
Adding a trash bin and refactoring UI for recording in input Widget
leandroBorgesFerreira Mar 9, 2023
188a207
Adding real wave bars to audio messages in message compose
leandroBorgesFerreira Mar 9, 2023
3bd0271
Creating WaveBarsParser
leandroBorgesFerreira Mar 9, 2023
d4d2d65
Fixing random wave bars while I don't implement the parser for waveba…
leandroBorgesFerreira Mar 9, 2023
a6a8b35
Changing the slider button
leandroBorgesFerreira Mar 10, 2023
9771cb4
Polishing AudioWaveSlider
leandroBorgesFerreira Mar 10, 2023
4ea5073
Making slider button customizable
leandroBorgesFerreira Mar 10, 2023
ed0018b
Small change in bars
leandroBorgesFerreira Mar 10, 2023
1044b55
Fixing position of overlay of record tap and styling it better
leandroBorgesFerreira Mar 10, 2023
fb8c7f8
Adding file icon to avoid shrinking the AudioWaveSlider
leandroBorgesFerreira Mar 10, 2023
4e8bb1d
Keeping action button
leandroBorgesFerreira Mar 10, 2023
82fd8c8
Fixing record duration
leandroBorgesFerreira Mar 11, 2023
80c2cba
Printing amplitude bars when recording
leandroBorgesFerreira Mar 13, 2023
a6df20f
Base for AudioWaveBars
leandroBorgesFerreira Mar 13, 2023
d372e8a
Adding AudioWaveBars with reversed logic
leandroBorgesFerreira Mar 13, 2023
418c75b
Fixing logic for AudioWaveBars
leandroBorgesFerreira Mar 13, 2023
66e5ab3
Small fix for maxHeight in _AudioWaveBarsState
leandroBorgesFerreira Mar 13, 2023
eb9d959
Adding normalization for wave bars
leandroBorgesFerreira Mar 14, 2023
57d2bdc
Fixing audio_normalization
leandroBorgesFerreira Mar 14, 2023
3735377
Fixing normalization
leandroBorgesFerreira Mar 14, 2023
2f907aa
Fixing audio when received
leandroBorgesFerreira Mar 14, 2023
d8c1602
Improving calculus of normalized bars
leandroBorgesFerreira Mar 14, 2023
962c3f4
Removing asBroadcastStream which causes speed up of bars after pause/…
leandroBorgesFerreira Mar 14, 2023
0fb617c
Removing nested builders from AudioPlayerMessage
leandroBorgesFerreira Mar 14, 2023
ba2d287
Removing last nested builders
leandroBorgesFerreira Mar 14, 2023
4634466
Better position of components in AudioPlayerMessage
leandroBorgesFerreira Mar 14, 2023
f03e495
Resizing speed button
leandroBorgesFerreira Mar 14, 2023
5c61966
Small fix for speed and action button
leandroBorgesFerreira Mar 14, 2023
6c54017
Not preloading audio messages
leandroBorgesFerreira Mar 14, 2023
2d657a7
Simple design for audio reply
leandroBorgesFerreira Mar 15, 2023
0b01e5d
Completing reply for voicenote
leandroBorgesFerreira Mar 15, 2023
d50d6c5
Fixing edit message for audio messages
leandroBorgesFerreira Mar 15, 2023
45df9d7
Update CHANGELOG.md
leandroBorgesFerreira Mar 15, 2023
73d000c
Update CHANGELOG.md
leandroBorgesFerreira Mar 15, 2023
452c6c4
Static analyses fix
leandroBorgesFerreira Mar 15, 2023
228c689
Fixing _amplitudeController close
leandroBorgesFerreira Mar 15, 2023
2e775ca
Verifying unit tests
leandroBorgesFerreira Mar 15, 2023
de443da
Fixing colors
leandroBorgesFerreira Mar 15, 2023
360728d
Making audio wave customizable
leandroBorgesFerreira Mar 15, 2023
2e56389
Format
leandroBorgesFerreira Mar 15, 2023
a599ee1
Fixing colors
leandroBorgesFerreira Mar 16, 2023
ab57ab0
Fixing barHeightRatio in AudioWaveSlider
leandroBorgesFerreira Mar 16, 2023
7492873
Fixing loading of AudioPlayerMessage
leandroBorgesFerreira Mar 16, 2023
40abe5e
Adding null check for Overlay
leandroBorgesFerreira Mar 16, 2023
ba463b5
Fixing static analyse
leandroBorgesFerreira Mar 16, 2023
7ef1e55
Documentation
leandroBorgesFerreira Mar 16, 2023
e96d27e
Fixing action button and speed button for message input
leandroBorgesFerreira Mar 16, 2023
56e167a
Docs for classes and removing unused methods
leandroBorgesFerreira Mar 16, 2023
f2aea59
File rename
leandroBorgesFerreira Mar 16, 2023
25b7baa
Updating logic for sending voice message directly
leandroBorgesFerreira Mar 16, 2023
12ea437
Removing unnecessary space
leandroBorgesFerreira Mar 16, 2023
eae133c
Removing debug print(
leandroBorgesFerreira Mar 16, 2023
095f2d5
Format script
leandroBorgesFerreira Mar 16, 2023
d3b1080
Adding translation
leandroBorgesFerreira Mar 16, 2023
3e48e53
Changing translation
leandroBorgesFerreira Mar 16, 2023
9479986
Place holder for translations
leandroBorgesFerreira Mar 16, 2023
f8f23b8
Reseting min value for WaveBarsNormalizer
leandroBorgesFerreira Mar 16, 2023
41e3a78
Adding translations
leandroBorgesFerreira Mar 17, 2023
4190370
Adding expand in the list normalization
leandroBorgesFerreira Mar 17, 2023
ea461fd
Fixing unit audio_normalization_test
leandroBorgesFerreira Mar 17, 2023
9a3cac0
Update stream_chat_localizations_ko.dart
leandroBorgesFerreira Mar 17, 2023
ff9155c
Converting RecordButton to StatelessWidget
leandroBorgesFerreira Mar 17, 2023
2ee617c
Adding start/resume/pause Button Builder
leandroBorgesFerreira Mar 17, 2023
06596ee
adding CancelRecordButtonBuilder
leandroBorgesFerreira Mar 17, 2023
3f796d7
Implementing message channel preview for audio record
leandroBorgesFerreira Mar 17, 2023
9514bba
CHanging voicenote to audiorecording and adding builders
leandroBorgesFerreira Mar 17, 2023
61fc72d
Adding copyWith for AudioWaveBars
leandroBorgesFerreira Mar 17, 2023
e1e0a71
Adding holdToStartRecording to add_new_lang.dart
leandroBorgesFerreira Mar 17, 2023
cd2fc87
Update stream_channel_list_controller.dart
leandroBorgesFerreira Mar 17, 2023
b557734
Reverting change in stream_channel_list_controller.dart
leandroBorgesFerreira Mar 17, 2023
f02091b
Fixing golden test for EditMessageSheet
leandroBorgesFerreira Mar 17, 2023
94f9cdd
Adding holdToStartRecording to unit tests
leandroBorgesFerreira Mar 17, 2023
9e4c57e
omg I have codecov
leandroBorgesFerreira Mar 17, 2023
63457cc
Format script
leandroBorgesFerreira Mar 17, 2023
4233b99
Using round instead of floor
leandroBorgesFerreira Mar 18, 2023
bd04c6a
Polishing list_normalization logic
leandroBorgesFerreira Mar 18, 2023
b266141
Adding HapticFeedback
leandroBorgesFerreira Mar 19, 2023
664e1c4
Adding a simple animation
leandroBorgesFerreira Mar 19, 2023
acdd1ea
small animation polish
leandroBorgesFerreira Mar 19, 2023
b0064f2
Changing sampling to 100ms
leandroBorgesFerreira Mar 20, 2023
f69c0b6
Fixing signal of minValue for Height normalisation
leandroBorgesFerreira Mar 20, 2023
efb09e8
Documentation update
leandroBorgesFerreira Mar 20, 2023
a3d5ecf
Format
leandroBorgesFerreira Mar 21, 2023
ad07d42
Update packages/stream_chat_localizations/lib/src/stream_chat_localiz…
leandroBorgesFerreira Mar 21, 2023
4c295b2
shouldRepaint optimization
leandroBorgesFerreira Mar 21, 2023
12604e2
Extracting _playerStateListener
leandroBorgesFerreira Mar 21, 2023
5aaffc7
Adding confirmRecordButtonBuilder
leandroBorgesFerreira Mar 21, 2023
a49c67b
Removing empty space
leandroBorgesFerreira Mar 21, 2023
d7477e8
Reverting back shouldRepaint logic for audio waves
leandroBorgesFerreira Mar 22, 2023
b059eb1
Update packages/stream_chat_flutter/lib/src/attachment/audio/audio_lo…
leandroBorgesFerreira Mar 22, 2023
d5f8d93
Apply suggestions from code review
xsahil03x Mar 22, 2023
00c27f3
Removing code from StreamMessageInput
leandroBorgesFerreira Mar 22, 2023
b86705e
Removing all factory constructors
leandroBorgesFerreira Mar 22, 2023
0f32c81
Separating code from StreamMessageInput
leandroBorgesFerreira Mar 22, 2023
d2103d7
Const Contructor
leandroBorgesFerreira Mar 22, 2023
bfdb1fa
Changing name and small logic change for AudioPlayerComposeMessage
leandroBorgesFerreira Mar 22, 2023
1d5a039
Adding unit tests for record controller
leandroBorgesFerreira Mar 23, 2023
b725b15
Using builder instead of setState in AudioPlayerAttachment
leandroBorgesFerreira Mar 23, 2023
00c1a67
Changing StreamRecordController and adding possibility to controll re…
leandroBorgesFerreira Mar 23, 2023
b57db33
Adding more tests to message_input_test
leandroBorgesFerreira Mar 23, 2023
fa1519a
Simple test for StreamMessageInput
leandroBorgesFerreira Mar 23, 2023
f7bb902
Adding mocked test
leandroBorgesFerreira Mar 23, 2023
f3cf776
Logic fix and docs
leandroBorgesFerreira Mar 23, 2023
34b4ccd
Fixing unit tests and including audio in unit tests
leandroBorgesFerreira Mar 23, 2023
7861da6
Adding more unit tests
leandroBorgesFerreira Mar 23, 2023
e22c371
Merge branch 'develop' into AsyncAudio
xsahil03x Apr 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/stream_chat/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

✅ Added

- Support for async audio messages.
- Added `presence` property to `Channel::watch` method.

## 5.3.0
Expand Down
1 change: 1 addition & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

✅ Added

- Support for async audio messages.
- Now it is possible to customize the max lines of the title of a url attachment. Before it was always 1 line.
- Added `attachmentActionsModalBuilder` parameter to `StreamMessageWidget` that allows to customize `AttachmentActionsModal`.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';

/// {@template AudioLoadingMessage}
/// Loading widget for audio message. Use this when the url from the audio
/// message is still not available. One use situation in when the audio is
/// still being uploaded.
/// {@endtemplate}
class AudioLoadingMessage extends StatelessWidget {
/// {@macro AudioLoadingMessage}
const AudioLoadingMessage({super.key});

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
),
),
Padding(
padding: EdgeInsets.only(left: 16),
child: Icon(Icons.mic),
),
],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';
import 'package:stream_chat_flutter/src/attachment/audio/audio_loading_attachment.dart';
import 'package:stream_chat_flutter/src/attachment/audio/audio_wave_slider.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

/// {@template AudioPlayerMessage}
/// Embedded player for audio messages. It displays the data for the audio
/// message and allow the user to interact with the player providing buttons
/// to play/pause, seek the audio and change the speed of reproduction.
///
/// When waveBars are not provided they are shown as 0 bars.
/// {@endtemplate}
class AudioPlayerMessage extends StatefulWidget {
/// {@macro AudioPlayerMessage}
const AudioPlayerMessage({
super.key,
required this.player,
required this.duration,
this.waveBars,
this.index = 0,
this.fileSize,
this.actionButton,
this.singleAudio = false,
});

/// The player of the audio.
final AudioPlayer player;

/// The wave bars of the recorded audio from 0 to 1. When not provided
/// this Widget shows then as small dots.
final List<double>? waveBars;

/// The duration of the audio.
final Duration duration;

/// The index of the audio inside the play list. If not provided, this is
/// assumed to be zero.
final int index;

/// The file size in bits.
final int? fileSize;

/// An action button to be used.
final Widget? actionButton;

/// Marks if this is a single audio message or this audio is inside.
/// If this is true, the audio will reset itself when it ends, otherwise
/// it gets managed by the player.
final bool singleAudio;

@override
_AudioPlayerMessageState createState() => _AudioPlayerMessageState();
}

class _AudioPlayerMessageState extends State<AudioPlayerMessage> {
var _seeking = false;
late StreamSubscription<PlayerState> _playStateSubscription;
var _waitingForLoad = false;

@override
void initState() {
super.initState();

_playStateSubscription =
widget.player.playerStateStream.listen(_playerStateListener);
}

@override
void dispose() {
super.dispose();

widget.player.dispose();
_playStateSubscription.cancel();
}

@override
Widget build(BuildContext context) {
if (widget.duration != Duration.zero) {
return _content(widget.duration);
} else {
return StreamBuilder<Duration?>(
stream: widget.player.durationStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return _content(snapshot.data!);
} else if (snapshot.hasError) {
return const Center(child: Text('Error!!'));
} else {
return const AudioLoadingMessage();
}
},
);
}
}

Widget _content(Duration totalDuration) {
final streamChatThemeData = StreamChatTheme.of(context).primaryIconTheme;

return Container(
padding: const EdgeInsets.all(8),
height: 60,
child: Row(
children: <Widget>[
SizedBox(
width: 36,
height: 36,
child: _controlButton(streamChatThemeData),
),
Padding(
padding: const EdgeInsets.only(left: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_timer(totalDuration),
_fileSizeWidget(widget.fileSize),
],
),
),
_audioWaveSlider(totalDuration),
_speedAndActionButton(),
],
),
);
}

void _playerStateListener(PlayerState state) async {
if (state.processingState == ProcessingState.completed &&
widget.singleAudio) {
await widget.player.stop();
await widget.player.seek(Duration.zero, index: 0);
}

final currentAudio = widget.player.currentIndex == widget.index;

if (currentAudio && state.processingState == ProcessingState.ready) {
setState(() {
_waitingForLoad = false;
});
} else if (currentAudio &&
state.processingState == ProcessingState.loading) {
setState(() {
_waitingForLoad = true;
});
}
}

Widget _controlButton(IconThemeData iconTheme) {
return StreamBuilder<bool>(
initialData: false,
stream: _playingThisStream(),
builder: (context, snapshot) {
final playingThis = snapshot.data == true;

final icon = playingThis ? Icons.pause : Icons.play_arrow;

if (!_waitingForLoad) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 6),
backgroundColor: Colors.white,
shape: const CircleBorder(),
),
child: Icon(icon, color: Colors.black),
onPressed: () {
if (playingThis) {
_pause();
} else {
_play();
}
},
);
} else {
return const CircularProgressIndicator(strokeWidth: 3);
}
},
);
}

Widget _speedAndActionButton() {
final showSpeed = _playingThisStream().flatMap((showSpeed) =>
widget.player.speedStream.map((speed) => showSpeed ? speed : -1.0));

return StreamBuilder<double>(
initialData: -1,
stream: showSpeed,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data! > 0) {
final speed = snapshot.data!;
return SizedBox(
width: 44,
height: 36,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 2,
backgroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
),
child: Text(
'${speed}x',
style: const TextStyle(
color: Colors.black,
fontSize: 12,
),
),
onPressed: () {
setState(() {
if (speed == 2) {
widget.player.setSpeed(1);
} else {
widget.player.setSpeed(speed + 0.5);
}
});
},
),
);
} else {
if (widget.actionButton != null) {
return widget.actionButton!;
} else {
return SizedBox(
width: 44,
height: 36,
child: StreamSvgIcon.filetypeAac(),
);
}
}
},
);
}

Widget _fileSizeWidget(int? fileSize) {
if (fileSize != null) {
return Text(
fileSize.toHumanReadableSize(),
style: const TextStyle(fontSize: 10),
);
} else {
return const SizedBox.shrink();
}
}

Widget _timer(Duration totalDuration) {
return StreamBuilder<Duration>(
stream: widget.player.positionStream,
builder: (context, snapshot) {
if (snapshot.hasData &&
(widget.player.currentIndex == widget.index &&
(widget.player.playing ||
snapshot.data!.inMilliseconds > 0 ||
_seeking))) {
return Text(snapshot.data!.toMinutesAndSeconds());
} else {
return Text(totalDuration.toMinutesAndSeconds());
}
},
);
}

Widget _audioWaveSlider(Duration totalDuration) {
final positionStream = widget.player.currentIndexStream.flatMap(
(index) => widget.player.positionStream.map((duration) => _sliderValue(
duration,
totalDuration,
index,
)),
);

return Expanded(
child: AudioWaveSlider(
bars: widget.waveBars ?? List<double>.filled(50, 0),
progressStream: positionStream,
onChangeStart: (val) {
setState(() {
_seeking = true;
});
},
onChanged: (val) {
widget.player.pause();
widget.player.seek(
totalDuration * val,
index: widget.index,
);
},
onChangeEnd: () {
setState(() {
_seeking = false;
});
},
),
);
}

double _sliderValue(
Duration duration,
Duration totalDuration,
int? currentIndex,
) {
if (widget.index != currentIndex) {
return 0;
} else {
return min(duration.inMicroseconds / totalDuration.inMicroseconds, 1);
}
}

Stream<bool> _playingThisStream() {
return widget.player.playingStream.flatMap((playing) {
return widget.player.currentIndexStream.map(
(index) => playing && index == widget.index,
);
});
}

Future<void> _play() async {
if (widget.index != widget.player.currentIndex) {
widget.player.seek(Duration.zero, index: widget.index);
}

widget.player.play();
}

Future<void> _pause() {
return widget.player.pause();
}
}
Loading