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(ui): voice recording attachment builder #1907

Merged
merged 4 commits into from May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/stream_chat/CHANGELOG.md
@@ -1,3 +1,8 @@
## Unreleased

✅ Added
- Added `voiceRecording` attachment type

## 7.2.0-hotfix.1

- Version to keep in sync with the rest of the packages
Expand Down
1 change: 1 addition & 0 deletions packages/stream_chat/lib/src/core/models/attachment.dart
Expand Up @@ -17,6 +17,7 @@ mixin AttachmentType {
static const giphy = 'giphy';
static const video = 'video';
static const audio = 'audio';
static const voiceRecording = 'voiceRecording';

/// Application custom types.
static const urlPreview = 'url_preview';
Expand Down
5 changes: 5 additions & 0 deletions packages/stream_chat_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
## Unreleased

✅ Added
- Added `VoiceRecordingAttachmentBuilder`, for displaying voice recording attachments in the chat.

## 7.2.0-hotfix.1

🔄 Changed
Expand Down
Expand Up @@ -155,7 +155,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down Expand Up @@ -204,6 +204,7 @@
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
Expand Down
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
@@ -1,26 +1,17 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/attachment/attachment.dart';
import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.dart';
import 'package:stream_chat_flutter/src/stream_chat.dart';
import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/utils/utils.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

part 'fallback_attachment_builder.dart';

part 'file_attachment_builder.dart';

part 'gallery_attachment_builder.dart';

part 'giphy_attachment_builder.dart';

part 'image_attachment_builder.dart';

part 'mixed_attachment_builder.dart';

part 'url_attachment_builder.dart';

part 'video_attachment_builder.dart';
part 'voice_recording_attachment_builder/voice_recording_attachment_builder.dart';

/// {@template streamAttachmentWidgetTapCallback}
/// Signature for a function that's called when the user taps on an attachment.
Expand Down Expand Up @@ -120,6 +111,9 @@
padding: padding,
onAttachmentTap: onAttachmentTap,
),

VoiceRecordingAttachmentBuilder(),

Check warning on line 115 in packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart

View check run for this annotation

Codecov / codecov/patch

packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart#L115

Added line #L115 was not covered by tests

// We don't handle URL attachments if the message is a reply.
if (message.quotedMessage == null)
UrlAttachmentBuilder(
Expand Down
@@ -0,0 +1,134 @@
import 'dart:async';

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

/// {@template StreamVoiceRecordingListPlayer}
/// Display many audios and displays a list of AudioPlayerMessage.
/// {@endtemplate}
class StreamVoiceRecordingListPlayer extends StatefulWidget {
/// {@macro StreamVoiceRecordingListPlayer}
const StreamVoiceRecordingListPlayer({
super.key,
required this.playList,
this.attachmentBorderRadiusGeometry,
this.constraints,
});

/// List of audio attachments.
final List<PlayListItem> playList;

/// The border radius of each audio.
final BorderRadiusGeometry? attachmentBorderRadiusGeometry;

/// Constraints of audio attachments
final BoxConstraints? constraints;

@override
State<StreamVoiceRecordingListPlayer> createState() =>
_StreamVoiceRecordingListPlayerState();
}

class _StreamVoiceRecordingListPlayerState
extends State<StreamVoiceRecordingListPlayer> {
final _player = AudioPlayer();
late StreamSubscription<PlayerState> _playerStateChangedSubscription;

Widget _createAudioPlayer(int index, PlayListItem item) {
final url = item.assetUrl;
Widget child;

if (url == null) {
child = const StreamVoiceRecordingLoading();
} else {
child = StreamVoiceRecordingPlayer(
player: _player,
duration: item.duration,
waveBars: item.waveForm,
index: index,
);
}

final theme =
StreamChatTheme.of(context).voiceRecordingTheme.listPlayerTheme;

return Container(
margin: theme.margin,
constraints: widget.constraints,
decoration: BoxDecoration(
color: theme.backgroundColor,
border: Border.all(
color: theme.borderColor!,
),
borderRadius:
widget.attachmentBorderRadiusGeometry ?? theme.borderRadius,
),
child: child,
);
}

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

Check warning on line 75 in packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_builder/stream_voice_recording_list_player.dart

View check run for this annotation

Codecov / codecov/patch

packages/stream_chat_flutter/lib/src/attachment/builder/voice_recording_attachment_builder/stream_voice_recording_list_player.dart#L74-L75

Added lines #L74 - L75 were not covered by tests
}
}

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

_playerStateChangedSubscription =
_player.playerStateStream.listen(_playerStateListener);
}

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

_playerStateChangedSubscription.cancel();
_player.dispose();
}

@override
Widget build(BuildContext context) {
final playList = widget.playList
.where((attachment) => attachment.assetUrl != null)
.map((attachment) => AudioSource.uri(Uri.parse(attachment.assetUrl!)))
.toList();

final audioSource = ConcatenatingAudioSource(children: playList);

_player
..setShuffleModeEnabled(false)
..setLoopMode(LoopMode.off)
..setAudioSource(audioSource, preload: false);

return Column(
children: widget.playList.mapIndexed(_createAudioPlayer).toList(),
);
}
}

/// {@template PlayListItem}
/// Represents an audio attachment meta data.
/// {@endtemplate}
class PlayListItem {
/// {@macro PlayListItem}
const PlayListItem({
this.assetUrl,
required this.duration,
required this.waveForm,
});

/// The url of the audio.
final String? assetUrl;

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

/// The wave form of the audio.
final List<double> waveForm;
}
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';

/// {@template StreamVoiceRecordingLoading}
/// 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 StreamVoiceRecordingLoading extends StatelessWidget {
/// {@macro StreamVoiceRecordingLoading}
const StreamVoiceRecordingLoading({super.key});

@override
Widget build(BuildContext context) {
final theme = StreamChatTheme.of(context).voiceRecordingTheme.loadingTheme;

return Padding(
padding: theme.padding!,
child: SizedBox(
height: theme.size!.height,
width: theme.size!.width,
child: CircularProgressIndicator(
strokeWidth: theme.strokeWidth!,
color: theme.color,
),
),
);
}
}