Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions packages/stream_chat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Unreleased

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

## 7.2.0-hotfix.1

🔄 Changed
Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -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 @@ abstract class StreamAttachmentWidgetBuilder {
padding: padding,
onAttachmentTap: onAttachmentTap,
),

VoiceRecordingAttachmentBuilder(),

// We don't handle URL attachments if the message is a reply.
if (message.quotedMessage == null)
UrlAttachmentBuilder(
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

@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;
}
Original file line number Diff line number Diff line change
@@ -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,
),
),
);
}
}
Loading