Implemented StreamAttachmentPackage and fixed operations in widgets linked to full screen implementation, also fixed tests
This commit is contained in:
@@ -91,7 +91,6 @@ linter:
|
||||
- prefer_constructors_over_static_methods
|
||||
- prefer_contains
|
||||
- prefer_equal_for_default_values
|
||||
- prefer_expression_function_bodies
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
|
||||
@@ -250,10 +250,9 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
|
||||
return StreamChannel(
|
||||
channel: channel,
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: message.attachments,
|
||||
mediaAttachmentPackages: message.getAttachmentPackageList(),
|
||||
startIndex: message.attachments.indexOf(attachment),
|
||||
userName: message.user?.name,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -144,11 +144,11 @@ class StreamImageAttachment extends StreamAttachmentWidget {
|
||||
return StreamChannel(
|
||||
channel: channel,
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: message.attachments,
|
||||
mediaAttachmentPackages:
|
||||
message.getAttachmentPackageList(),
|
||||
startIndex:
|
||||
message.attachments.indexOf(attachment),
|
||||
userName: message.user?.name,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -91,11 +91,11 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
builder: (_) => StreamChannel(
|
||||
channel: channel,
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: message.attachments,
|
||||
mediaAttachmentPackages:
|
||||
message.getAttachmentPackageList(),
|
||||
startIndex:
|
||||
message.attachments.indexOf(attachment),
|
||||
userName: message.user?.name,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,14 +9,18 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
typedef AttachmentDownloader = Future<String> Function(
|
||||
Attachment attachment, {
|
||||
ProgressCallback? progressCallback,
|
||||
DownloadedPathCallback? downloadedPathCallback,
|
||||
});
|
||||
|
||||
/// Callback to receive the path once the attachment asset is downloaded
|
||||
typedef DownloadedPathCallback = void Function(String? path);
|
||||
|
||||
/// Widget that shows the options in the gallery view
|
||||
class AttachmentActionsModal extends StatelessWidget {
|
||||
/// Returns a new [AttachmentActionsModal]
|
||||
const AttachmentActionsModal({
|
||||
Key? key,
|
||||
required this.currentIndex,
|
||||
required this.attachment,
|
||||
required this.message,
|
||||
this.onShowMessage,
|
||||
this.imageDownloader,
|
||||
@@ -28,12 +32,12 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
this.customActions = const [],
|
||||
}) : super(key: key);
|
||||
|
||||
/// The attachment object for which the actions are to be performed
|
||||
final Attachment attachment;
|
||||
|
||||
/// The message containing the attachments
|
||||
final Message message;
|
||||
|
||||
/// Current page index
|
||||
final int currentIndex;
|
||||
|
||||
/// Callback to show the message
|
||||
final VoidCallback? onShowMessage;
|
||||
|
||||
@@ -62,7 +66,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
/// specified attributes overridden.
|
||||
AttachmentActionsModal copyWith({
|
||||
Key? key,
|
||||
int? currentIndex,
|
||||
Attachment? attachment,
|
||||
Message? message,
|
||||
VoidCallback? onShowMessage,
|
||||
AttachmentDownloader? imageDownloader,
|
||||
@@ -75,7 +79,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
}) =>
|
||||
AttachmentActionsModal(
|
||||
key: key ?? this.key,
|
||||
currentIndex: currentIndex ?? this.currentIndex,
|
||||
attachment: attachment ?? this.attachment,
|
||||
message: message ?? this.message,
|
||||
onShowMessage: onShowMessage ?? this.onShowMessage,
|
||||
imageDownloader: imageDownloader ?? this.imageDownloader,
|
||||
@@ -138,7 +142,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
if (showSave)
|
||||
_buildButton(
|
||||
context,
|
||||
message.attachments[currentIndex].type == 'video'
|
||||
attachment.type == 'video'
|
||||
? context.translations.saveVideoLabel
|
||||
: context.translations.saveImageLabel,
|
||||
StreamSvgIcon.iconSave(
|
||||
@@ -146,15 +150,16 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
color: theme.colorTheme.textLowEmphasis,
|
||||
),
|
||||
() {
|
||||
final attachment = message.attachments[currentIndex];
|
||||
final isImage = attachment.type == 'image';
|
||||
final Future<String?> Function(
|
||||
Attachment, {
|
||||
void Function(int, int) progressCallback,
|
||||
DownloadedPathCallback downloadedPathCallback,
|
||||
}) saveFile = fileDownloader ?? _downloadAttachment;
|
||||
final Future<String?> Function(
|
||||
Attachment, {
|
||||
void Function(int, int) progressCallback,
|
||||
DownloadedPathCallback downloadedPathCallback,
|
||||
}) saveImage = imageDownloader ?? _downloadAttachment;
|
||||
final downloader = isImage ? saveImage : saveFile;
|
||||
|
||||
@@ -162,15 +167,25 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
ValueNotifier<_DownloadProgress?>(
|
||||
_DownloadProgress.initial(),
|
||||
);
|
||||
final downloadedPathNotifier = ValueNotifier<String?>(
|
||||
null,
|
||||
);
|
||||
|
||||
downloader(
|
||||
attachment,
|
||||
progressCallback: (received, total) {
|
||||
print('>>>>>>>>>>>>>>>');
|
||||
print('>>r : $received');
|
||||
print('>>t : $total');
|
||||
print('>>>>>>>>>>>>>>>');
|
||||
progressNotifier.value = _DownloadProgress(
|
||||
total,
|
||||
received,
|
||||
);
|
||||
},
|
||||
downloadedPathCallback: (String? path) {
|
||||
downloadedPathNotifier.value = path;
|
||||
},
|
||||
).catchError((e, stk) {
|
||||
progressNotifier.value = null;
|
||||
});
|
||||
@@ -186,6 +201,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
builder: (context) => _buildDownloadProgressDialog(
|
||||
context,
|
||||
progressNotifier,
|
||||
downloadedPathNotifier,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -204,8 +220,12 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
if (message.attachments.length > 1 ||
|
||||
message.text?.isNotEmpty == true) {
|
||||
final currentAttachmentIndex =
|
||||
message.attachments.indexWhere(
|
||||
(element) => element.id == attachment.id,
|
||||
);
|
||||
final remainingAttachments = [...message.attachments]
|
||||
..removeAt(currentIndex);
|
||||
..removeAt(currentAttachmentIndex);
|
||||
channel.updateMessage(message.copyWith(
|
||||
attachments: remainingAttachments,
|
||||
));
|
||||
@@ -285,73 +305,86 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
Widget _buildDownloadProgressDialog(
|
||||
BuildContext context,
|
||||
ValueNotifier<_DownloadProgress?> progressNotifier,
|
||||
ValueNotifier<String?> downloadedFilePathNotifier,
|
||||
) {
|
||||
final theme = StreamChatTheme.of(context);
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: progressNotifier,
|
||||
builder: (_, _DownloadProgress? progress, __) {
|
||||
// Pop the dialog in case the progress is null or it's completed.
|
||||
if (progress == null || progress.toProgressIndicatorValue == 1.0) {
|
||||
valueListenable: downloadedFilePathNotifier,
|
||||
builder: (_, String? path, __) {
|
||||
final _downloadComplete = path != null && path.isNotEmpty;
|
||||
// Pop the dialog in case the download has completed
|
||||
if (_downloadComplete) {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => Navigator.of(context).maybePop(),
|
||||
);
|
||||
}
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 182,
|
||||
width: 182,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: theme.colorTheme.barsBg,
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: progressNotifier,
|
||||
builder: (_, _DownloadProgress? progress, __) {
|
||||
// Pop the dialog in case the progress is null.
|
||||
if (progress == null) {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() => Navigator.of(context).maybePop(),
|
||||
);
|
||||
}
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Center(
|
||||
child: progress == null
|
||||
? SizedBox(
|
||||
height: 100,
|
||||
width: 100,
|
||||
child: StreamSvgIcon.error(
|
||||
color: theme.colorTheme.disabled,
|
||||
),
|
||||
)
|
||||
: progress.toProgressIndicatorValue == 1.0
|
||||
child: Container(
|
||||
height: 182,
|
||||
width: 182,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: theme.colorTheme.barsBg,
|
||||
),
|
||||
child: Center(
|
||||
child: progress == null
|
||||
? SizedBox(
|
||||
key: const Key('completedIcon'),
|
||||
height: 160,
|
||||
width: 160,
|
||||
child: StreamSvgIcon.check(
|
||||
height: 100,
|
||||
width: 100,
|
||||
child: StreamSvgIcon.error(
|
||||
color: theme.colorTheme.disabled,
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 100,
|
||||
width: 100,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: progress.toProgressIndicatorValue,
|
||||
strokeWidth: 8,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
theme.colorTheme.accentPrimary,
|
||||
),
|
||||
: _downloadComplete
|
||||
? SizedBox(
|
||||
key: const Key('completedIcon'),
|
||||
height: 160,
|
||||
width: 160,
|
||||
child: StreamSvgIcon.check(
|
||||
color: theme.colorTheme.disabled,
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
'${progress.toPercentage}%',
|
||||
style: theme.textTheme.headline.copyWith(
|
||||
color: theme.colorTheme.textLowEmphasis,
|
||||
)
|
||||
: SizedBox(
|
||||
height: 100,
|
||||
width: 100,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
strokeWidth: 8,
|
||||
color: theme.colorTheme.accentPrimary,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
'${progress.receivedValueInMB} MB',
|
||||
style:
|
||||
theme.textTheme.headline.copyWith(
|
||||
color:
|
||||
theme.colorTheme.textLowEmphasis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -360,6 +393,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
Future<String?> _downloadAttachment(
|
||||
Attachment attachment, {
|
||||
ProgressCallback? progressCallback,
|
||||
DownloadedPathCallback? downloadedPathCallback,
|
||||
}) async {
|
||||
String? filePath;
|
||||
final appDocDir = await getTemporaryDirectory();
|
||||
@@ -375,6 +409,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
onReceiveProgress: progressCallback,
|
||||
);
|
||||
final result = await ImageGallerySaver.saveFile(filePath!);
|
||||
downloadedPathCallback?.call((result as Map)['filePath']);
|
||||
return (result as Map)['filePath'];
|
||||
}
|
||||
}
|
||||
@@ -388,6 +423,8 @@ class _DownloadProgress {
|
||||
final int total;
|
||||
final int received;
|
||||
|
||||
String get receivedValueInMB => ((received / 1024) / 1024).toStringAsFixed(2);
|
||||
|
||||
double get toProgressIndicatorValue => received / total;
|
||||
|
||||
int get toPercentage => (received * 100) ~/ total;
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_attachment_package.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
@@ -32,8 +33,7 @@ class StreamFullScreenMedia extends StatefulWidget {
|
||||
/// Instantiate a new FullScreenImage
|
||||
const StreamFullScreenMedia({
|
||||
Key? key,
|
||||
required this.mediaAttachments,
|
||||
required this.message,
|
||||
required this.mediaAttachmentPackages,
|
||||
this.startIndex = 0,
|
||||
String? userName,
|
||||
this.onShowMessage,
|
||||
@@ -43,10 +43,7 @@ class StreamFullScreenMedia extends StatefulWidget {
|
||||
super(key: key);
|
||||
|
||||
/// The url of the image
|
||||
final List<Attachment> mediaAttachments;
|
||||
|
||||
/// Message where attachments are attached
|
||||
final Message message;
|
||||
final List<StreamAttachmentPackage> mediaAttachmentPackages;
|
||||
|
||||
/// First index of media shown
|
||||
final int startIndex;
|
||||
@@ -100,8 +97,8 @@ class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
duration: const Duration(milliseconds: 300),
|
||||
);
|
||||
_pageController = PageController(initialPage: widget.startIndex);
|
||||
for (var i = 0; i < widget.mediaAttachments.length; i++) {
|
||||
final attachment = widget.mediaAttachments[i];
|
||||
for (var i = 0; i < widget.mediaAttachmentPackages.length; i++) {
|
||||
final attachment = widget.mediaAttachmentPackages[i].attachment;
|
||||
if (attachment.type != 'video') continue;
|
||||
final package = VideoPackage(attachment, showControls: true);
|
||||
videoPackages[attachment.id] = package;
|
||||
@@ -114,7 +111,8 @@ class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
return;
|
||||
}
|
||||
|
||||
final currentAttachment = widget.mediaAttachments[widget.startIndex];
|
||||
final currentAttachment =
|
||||
widget.mediaAttachmentPackages[widget.startIndex].attachment;
|
||||
|
||||
await Future.wait(videoPackages.values.map(
|
||||
(it) => it.initialize(),
|
||||
@@ -142,7 +140,8 @@ class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
return;
|
||||
}
|
||||
|
||||
final currentAttachment = widget.mediaAttachments[val];
|
||||
final currentAttachment =
|
||||
widget.mediaAttachmentPackages[val].attachment;
|
||||
|
||||
for (final e in videoPackages.values) {
|
||||
if (e._attachment != currentAttachment) {
|
||||
@@ -157,7 +156,9 @@ class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
}
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
final attachment = widget.mediaAttachments[index];
|
||||
final currentAttachmentPackage =
|
||||
widget.mediaAttachmentPackages[index];
|
||||
final attachment = currentAttachmentPackage.attachment;
|
||||
if (attachment.type == 'image' || attachment.type == 'giphy') {
|
||||
final imageUrl = attachment.imageUrl ??
|
||||
attachment.assetUrl ??
|
||||
@@ -174,7 +175,7 @@ class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
maxScale: PhotoViewComputedScale.covered,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: widget.mediaAttachments,
|
||||
tag: widget.mediaAttachmentPackages,
|
||||
),
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: ColorTween(
|
||||
@@ -218,53 +219,66 @@ class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
itemCount: widget.mediaAttachments.length,
|
||||
itemCount: widget.mediaAttachmentPackages.length,
|
||||
),
|
||||
FadeTransition(
|
||||
opacity: _opacityAnimation,
|
||||
child: ValueListenableBuilder<int>(
|
||||
valueListenable: _currentPage,
|
||||
builder: (context, value, child) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StreamGalleryHeader(
|
||||
userName: widget.userName,
|
||||
sentAt: context.translations.sentAtText(
|
||||
date: widget.message.createdAt,
|
||||
time: widget.message.createdAt,
|
||||
),
|
||||
onBackPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
message: widget.message,
|
||||
currentIndex: value,
|
||||
onShowMessage: () {
|
||||
widget.onShowMessage?.call(
|
||||
widget.message,
|
||||
StreamChannel.of(context).channel,
|
||||
);
|
||||
},
|
||||
attachmentActionsModalBuilder:
|
||||
widget.attachmentActionsModalBuilder,
|
||||
),
|
||||
if (!widget.message.isEphemeral)
|
||||
StreamGalleryFooter(
|
||||
currentPage: value,
|
||||
totalPages: widget.mediaAttachments.length,
|
||||
mediaAttachments: widget.mediaAttachments,
|
||||
message: widget.message,
|
||||
mediaSelectedCallBack: (val) {
|
||||
_currentPage.value = val;
|
||||
_pageController.animateToPage(
|
||||
val,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
builder: (context, value, child) {
|
||||
final _currentAttachmentPackage =
|
||||
widget.mediaAttachmentPackages[value];
|
||||
final _currentMessage = _currentAttachmentPackage.message;
|
||||
final _currentAttachment =
|
||||
_currentAttachmentPackage.attachment;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
StreamGalleryHeader(
|
||||
userName: widget.userName,
|
||||
sentAt: context.translations.sentAtText(
|
||||
date: widget
|
||||
.mediaAttachmentPackages[_currentPage.value]
|
||||
.message
|
||||
.createdAt,
|
||||
time: widget
|
||||
.mediaAttachmentPackages[_currentPage.value]
|
||||
.message
|
||||
.createdAt,
|
||||
),
|
||||
onBackPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
message: _currentMessage,
|
||||
attachment: _currentAttachment,
|
||||
onShowMessage: () {
|
||||
widget.onShowMessage?.call(
|
||||
_currentMessage,
|
||||
StreamChannel.of(context).channel,
|
||||
);
|
||||
},
|
||||
attachmentActionsModalBuilder:
|
||||
widget.attachmentActionsModalBuilder,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!_currentMessage.isEphemeral)
|
||||
StreamGalleryFooter(
|
||||
currentPage: value,
|
||||
totalPages: widget.mediaAttachmentPackages.length,
|
||||
mediaAttachmentPackages:
|
||||
widget.mediaAttachmentPackages,
|
||||
mediaSelectedCallBack: (val) {
|
||||
_currentPage.value = val;
|
||||
_pageController.animateToPage(
|
||||
val,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -18,16 +18,15 @@ typedef GalleryFooter = StreamGalleryFooter;
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryFooter extends StatefulWidget
|
||||
implements PreferredSizeWidget {
|
||||
/// Creates a channel header
|
||||
/// Creates a StreamGalleryFooter
|
||||
const StreamGalleryFooter({
|
||||
Key? key,
|
||||
required this.message,
|
||||
this.onBackPressed,
|
||||
this.onTitleTap,
|
||||
this.onImageTap,
|
||||
this.currentPage = 0,
|
||||
this.totalPages = 0,
|
||||
this.mediaAttachments = const [],
|
||||
required this.mediaAttachmentPackages,
|
||||
this.mediaSelectedCallBack,
|
||||
this.backgroundColor,
|
||||
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
|
||||
@@ -50,10 +49,7 @@ class StreamGalleryFooter extends StatefulWidget
|
||||
final int totalPages;
|
||||
|
||||
/// All attachments to show
|
||||
final List<Attachment> mediaAttachments;
|
||||
|
||||
/// Message which attachments are attached to
|
||||
final Message message;
|
||||
final List<StreamAttachmentPackage> mediaAttachmentPackages;
|
||||
|
||||
/// Callback when media is selected
|
||||
final ValueChanged<int>? mediaSelectedCallBack;
|
||||
@@ -99,8 +95,8 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
color: galleryFooterThemeData.shareIconColor,
|
||||
),
|
||||
onPressed: () async {
|
||||
final attachment =
|
||||
widget.mediaAttachments[widget.currentPage];
|
||||
final attachment = widget
|
||||
.mediaAttachmentPackages[widget.currentPage].attachment;
|
||||
final url = attachment.imageUrl ??
|
||||
attachment.assetUrl ??
|
||||
attachment.thumbUrl!;
|
||||
@@ -171,7 +167,7 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
builder: (context) {
|
||||
const crossAxisCount = 3;
|
||||
final noOfRowToShowInitially =
|
||||
widget.mediaAttachments.length > crossAxisCount ? 2 : 1;
|
||||
widget.mediaAttachmentPackages.length > crossAxisCount ? 2 : 1;
|
||||
final size = MediaQuery.of(context).size;
|
||||
final initialChildSize =
|
||||
48 + (size.width * noOfRowToShowInitially) / crossAxisCount;
|
||||
@@ -212,7 +208,7 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
child: GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.mediaAttachments.length,
|
||||
itemCount: widget.mediaAttachmentPackages.length,
|
||||
padding: const EdgeInsets.all(1),
|
||||
// ignore: lines_longer_than_80_chars
|
||||
gridDelegate:
|
||||
@@ -223,7 +219,10 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
Widget media;
|
||||
final attachment = widget.mediaAttachments[index];
|
||||
final attachmentPackage =
|
||||
widget.mediaAttachmentPackages[index];
|
||||
final attachment = attachmentPackage.attachment;
|
||||
final message = attachmentPackage.message;
|
||||
if (attachment.type == 'video') {
|
||||
media = InkWell(
|
||||
onTap: () => widget.mediaSelectedCallBack!(index),
|
||||
@@ -253,7 +252,7 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
return Stack(
|
||||
children: [
|
||||
media,
|
||||
if (widget.message.user != null)
|
||||
if (message.user != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Container(
|
||||
@@ -272,7 +271,7 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
],
|
||||
),
|
||||
child: StreamUserAvatar(
|
||||
user: widget.message.user!,
|
||||
user: message.user!,
|
||||
constraints:
|
||||
BoxConstraints.tight(const Size(24, 24)),
|
||||
showOnlineStatus: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ class StreamGalleryHeader extends StatelessWidget
|
||||
const StreamGalleryHeader({
|
||||
Key? key,
|
||||
required this.message,
|
||||
this.currentIndex = 0,
|
||||
required this.attachment,
|
||||
this.showBackButton = true,
|
||||
this.onBackPressed,
|
||||
this.onShowMessage,
|
||||
@@ -60,15 +60,15 @@ class StreamGalleryHeader extends StatelessWidget
|
||||
/// Message which attachments are attached to
|
||||
final Message message;
|
||||
|
||||
/// The attachment that's currently in focus
|
||||
final Attachment attachment;
|
||||
|
||||
/// Username of sender
|
||||
final String userName;
|
||||
|
||||
/// Text which connotes the time the message was sent
|
||||
final String sentAt;
|
||||
|
||||
/// Stores the current index of media shown
|
||||
final int currentIndex;
|
||||
|
||||
/// The background color of this [StreamGalleryHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@@ -146,14 +146,14 @@ class StreamGalleryHeader extends StatelessWidget
|
||||
StreamChatTheme.of(context).galleryHeaderTheme;
|
||||
|
||||
final defaultModal = AttachmentActionsModal(
|
||||
attachment: attachment,
|
||||
message: message,
|
||||
currentIndex: currentIndex,
|
||||
onShowMessage: onShowMessage,
|
||||
);
|
||||
|
||||
final effectiveModal = attachmentActionsModalBuilder?.call(
|
||||
context,
|
||||
message.attachments[currentIndex],
|
||||
attachment,
|
||||
defaultModal,
|
||||
) ??
|
||||
defaultModal;
|
||||
|
||||
@@ -136,10 +136,9 @@ class StreamImageGroup extends StatelessWidget {
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: images,
|
||||
mediaAttachmentPackages: message.getAttachmentPackageList(),
|
||||
startIndex: index,
|
||||
userName: message.user?.name,
|
||||
message: message,
|
||||
onShowMessage: onShowMessage,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// The [StreamAttachmentPackage] class is basically meant to wrap
|
||||
/// individual attachments with their corresponding message
|
||||
class StreamAttachmentPackage {
|
||||
/// Default constructor to prepare an [StreamAttachmentPackage] object
|
||||
StreamAttachmentPackage({
|
||||
required this.attachment,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
/// This is the individual attachment
|
||||
final Attachment attachment;
|
||||
|
||||
/// This is the message that the attachment belongs to
|
||||
/// The message object may have attachemnt(s) other than the one packaged
|
||||
final Message message;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_attachment_package.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -429,3 +430,19 @@ int levenshtein(String s, String t, {bool caseSensitive = true}) {
|
||||
|
||||
return v1[t.length];
|
||||
}
|
||||
|
||||
/// An easy way to handle attachment related operations on a message
|
||||
extension AttachmentPackagesX on Message {
|
||||
/// This extension will return a List of type [StreamAttachmentPackage] from the
|
||||
/// existing attachments of the message
|
||||
List<StreamAttachmentPackage> getAttachmentPackageList() {
|
||||
final _attachmentPackages = List<StreamAttachmentPackage>.generate(
|
||||
attachments.length,
|
||||
(index) => StreamAttachmentPackage(
|
||||
attachment: attachments[index],
|
||||
message: this,
|
||||
),
|
||||
);
|
||||
return _attachmentPackages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ export 'src/message_search_item.dart';
|
||||
export 'src/message_search_list_view.dart';
|
||||
export 'src/message_text.dart';
|
||||
export 'src/message_widget.dart';
|
||||
export 'src/multi_overlay.dart';
|
||||
export 'src/option_list_tile.dart';
|
||||
export 'src/reaction_icon.dart';
|
||||
export 'src/reaction_picker.dart';
|
||||
export 'src/sending_indicator.dart';
|
||||
export 'src/stream_attachment_package.dart';
|
||||
export 'src/stream_chat.dart';
|
||||
export 'src/stream_chat_theme.dart';
|
||||
export 'src/stream_neumorphic_button.dart';
|
||||
|
||||
@@ -15,7 +15,7 @@ dependencies:
|
||||
chewie: ^1.3.0
|
||||
collection: ^1.15.0
|
||||
diacritic: ^0.1.3
|
||||
dio: ^4.0.0
|
||||
dio: ^4.0.6
|
||||
ezanimation: ^0.6.0
|
||||
file_picker: ^4.1.3
|
||||
flutter:
|
||||
|
||||
@@ -9,13 +9,16 @@ import 'mocks.dart';
|
||||
|
||||
class MockAttachmentDownloader extends Mock {
|
||||
ProgressCallback? progressCallback;
|
||||
DownloadedPathCallback? downloadedPathCallback;
|
||||
Completer<String> completer = Completer();
|
||||
|
||||
Future<String> call(
|
||||
Attachment attachment, {
|
||||
ProgressCallback? progressCallback,
|
||||
DownloadedPathCallback? downloadedPathCallback,
|
||||
}) {
|
||||
this.progressCallback = progressCallback;
|
||||
this.downloadedPathCallback = downloadedPathCallback;
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +41,19 @@ void main() {
|
||||
|
||||
final themeData = ThemeData();
|
||||
final streamTheme = StreamChatThemeData.fromTheme(themeData);
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'text.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: themeData,
|
||||
@@ -45,19 +61,8 @@ void main() {
|
||||
streamChatThemeData: streamTheme,
|
||||
client: client,
|
||||
child: AttachmentActionsModal(
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'text.jpg',
|
||||
),
|
||||
],
|
||||
),
|
||||
currentIndex: 0,
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -80,6 +85,19 @@ void main() {
|
||||
|
||||
final themeData = ThemeData();
|
||||
final streamTheme = StreamChatThemeData.fromTheme(themeData);
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'text.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: themeData,
|
||||
@@ -87,19 +105,8 @@ void main() {
|
||||
streamChatThemeData: streamTheme,
|
||||
client: client,
|
||||
child: AttachmentActionsModal(
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'text.jpg',
|
||||
),
|
||||
],
|
||||
),
|
||||
currentIndex: 0,
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -122,6 +129,19 @@ void main() {
|
||||
|
||||
final themeData = ThemeData();
|
||||
final streamTheme = StreamChatThemeData.fromTheme(themeData);
|
||||
final attachment = Attachment(
|
||||
type: 'video',
|
||||
title: 'video.mp4',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: themeData,
|
||||
@@ -130,19 +150,8 @@ void main() {
|
||||
client: client,
|
||||
child: SizedBox(
|
||||
child: AttachmentActionsModal(
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'video',
|
||||
title: 'video.mp4',
|
||||
),
|
||||
],
|
||||
),
|
||||
currentIndex: 0,
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -166,16 +175,17 @@ void main() {
|
||||
|
||||
final mockObserver = MockNavigatorObserver();
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
@@ -188,7 +198,7 @@ void main() {
|
||||
child: SizedBox(
|
||||
child: AttachmentActionsModal(
|
||||
message: message,
|
||||
currentIndex: 0,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -212,6 +222,20 @@ void main() {
|
||||
final streamTheme = StreamChatThemeData.fromTheme(themeData);
|
||||
final onShowMessage = MockVoidCallback();
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: themeData,
|
||||
@@ -221,18 +245,8 @@ void main() {
|
||||
child: SizedBox(
|
||||
child: AttachmentActionsModal(
|
||||
onShowMessage: onShowMessage,
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
]),
|
||||
currentIndex: 0,
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -255,20 +269,22 @@ void main() {
|
||||
when(() => client.state).thenReturn(clientState);
|
||||
when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
|
||||
|
||||
final targetAttachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final remainingAttachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
targetAttachment,
|
||||
remainingAttachment,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -283,7 +299,7 @@ void main() {
|
||||
channel: mockChannel,
|
||||
child: AttachmentActionsModal(
|
||||
message: message,
|
||||
currentIndex: 0,
|
||||
attachment: targetAttachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -309,16 +325,17 @@ void main() {
|
||||
when(() => client.state).thenReturn(clientState);
|
||||
when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -333,7 +350,7 @@ void main() {
|
||||
channel: mockChannel,
|
||||
child: AttachmentActionsModal(
|
||||
message: message,
|
||||
currentIndex: 0,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -358,15 +375,16 @@ void main() {
|
||||
when(() => client.state).thenReturn(clientState);
|
||||
when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id'));
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -381,7 +399,7 @@ void main() {
|
||||
channel: mockChannel,
|
||||
child: AttachmentActionsModal(
|
||||
message: message,
|
||||
currentIndex: 0,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -402,6 +420,20 @@ void main() {
|
||||
|
||||
final imageDownloader = MockAttachmentDownloader();
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
builder: (context, child) => StreamChat(
|
||||
@@ -411,18 +443,8 @@ void main() {
|
||||
home: SizedBox(
|
||||
child: AttachmentActionsModal(
|
||||
imageDownloader: imageDownloader,
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'image.jpg',
|
||||
),
|
||||
]),
|
||||
currentIndex: 0,
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -457,6 +479,19 @@ void main() {
|
||||
|
||||
final fileDownloader = MockAttachmentDownloader();
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'video',
|
||||
title: 'video.mp4',
|
||||
);
|
||||
final message = Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
attachment,
|
||||
]);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
builder: (context, child) => StreamChat(
|
||||
@@ -466,18 +501,8 @@ void main() {
|
||||
home: SizedBox(
|
||||
child: AttachmentActionsModal(
|
||||
fileDownloader: fileDownloader,
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
),
|
||||
attachments: [
|
||||
Attachment(
|
||||
type: 'video',
|
||||
title: 'video.mp4',
|
||||
),
|
||||
]),
|
||||
currentIndex: 0,
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -63,22 +63,29 @@ void main() {
|
||||
Event(type: EventType.typingStart),
|
||||
}));
|
||||
|
||||
final attachment = Attachment(
|
||||
type: 'image',
|
||||
title: 'demo image',
|
||||
imageUrl: '',
|
||||
);
|
||||
final message = Message(
|
||||
createdAt: DateTime.now(),
|
||||
attachments: [
|
||||
attachment,
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: StreamChat(
|
||||
client: client,
|
||||
child: StreamChannel(
|
||||
channel: channel,
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: [
|
||||
Attachment(
|
||||
type: 'image',
|
||||
title: 'demo image',
|
||||
imageUrl: '',
|
||||
mediaAttachmentPackages: [
|
||||
StreamAttachmentPackage(
|
||||
attachment: attachment,
|
||||
message: message,
|
||||
),
|
||||
],
|
||||
message: Message(
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -38,7 +38,7 @@ void main() {
|
||||
onWillPop: () async => false,
|
||||
child: Scaffold(
|
||||
body: StreamGalleryFooter(
|
||||
message: Message(),
|
||||
mediaAttachmentPackages: Message().getAttachmentPackageList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -75,7 +75,7 @@ void main() {
|
||||
_context = context;
|
||||
return Scaffold(
|
||||
appBar: StreamGalleryFooter(
|
||||
message: Message(),
|
||||
mediaAttachmentPackages: Message().getAttachmentPackageList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -118,7 +118,7 @@ void main() {
|
||||
_context = context;
|
||||
return Scaffold(
|
||||
appBar: StreamGalleryFooter(
|
||||
message: Message(),
|
||||
mediaAttachmentPackages: Message().getAttachmentPackageList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -66,9 +66,11 @@ void main() {
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
_context = context;
|
||||
final _message = Message();
|
||||
return Scaffold(
|
||||
appBar: StreamGalleryHeader(
|
||||
message: Message(),
|
||||
message: _message,
|
||||
attachment: _message.attachments[0],
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -105,9 +107,11 @@ void main() {
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
_context = context;
|
||||
final _message = Message();
|
||||
return Scaffold(
|
||||
appBar: StreamGalleryHeader(
|
||||
message: Message(),
|
||||
message: _message,
|
||||
attachment: _message.attachments[0],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user