* fix: fixed long system messages overflowing * fix: fixed long system messages overflowing * [UI-Kit] Refactor, add support for downloading attachments. Signed-off-by: Sahil Kumar <[email protected]> * fix: header casing * [UI-Kit] Disable attachment download dialog pop using back button Signed-off-by: Sahil Kumar <[email protected]> * stream_chat_flutter fix svg on web * [UI-Kit] Pop attachment download dialog in case of any error. Signed-off-by: Sahil Kumar <[email protected]> * restore svg * feat: add pr title linter * fix test * fix client headers * update system message design * update gh token env var * add scopes * expose systemMessageBuilder in messagelistview * fix(ui): fix button size * add dispatch workflow * add dispatch workflow * update dispatch acton * add runson * fix ref * fix ref * update secret * specify repo * fix: list scroll keyboard behaviour (#304) * fix: Keyboard now closes by clicking on space or scrolling. * fix: Exposed system and normal message tap builders * fmt: dartfmt * feat: Add support for messages filter in `MessageListView` and `MessageListCore` (#303) * [MessageListView, MessageListCore] Add support for message filter Signed-off-by: Sahil Kumar <[email protected]> * [UI-Kit -> Pubspec] Remove `stream_chat_flutter_core` relative import Signed-off-by: Sahil Kumar <[email protected]> * add relative import * add relative import * remove svg for web * remove badge logic on web * style: Update for team lint (#297) * Update for team lint * fix tests * remove pedantic and sort deps * remove jiffy from system message Co-authored-by: Salvatore Giordano <[email protected]> * add section highlighting sample app repo (#307) Co-authored-by: Salvatore Giordano <[email protected]> * add null check to debounce * fix messagelistview loading errors * remove core relative import * fix: fixes message newline issue (#308) * fix: minor * fix: generate image thumbnails and use transparent image as placeholder for cached network images (#310) * update cdn handling * fix image attachment with new cdn * fix(ui): Fix message input permission request crash (#311) Signed-off-by: Sahil Kumar <[email protected]> * fix(llc): update member presence and add skip_push to message (#314) * update member presence * add skip_push to message * fix tests * fix: Fixed shimmer overflow (#316) * fix dialogs * fix(llc): Fix attachment upload state uneven progress. (#315) * feat(llc): Add rate limiter functions. fix(llc): Fix attachment upload state uneven progress. Signed-off-by: Sahil Kumar <[email protected]> * feat(llc): Export `async.dart` and `rate_limit.dart` Signed-off-by: Sahil Kumar <[email protected]> * refactor(llc): Replace our `rate_limit` implementation with the dart port of js `lodash` to better handlw `cancel` and `flush` functions. Signed-off-by: Sahil Kumar <[email protected]> * fix(UI-Kit): Make android example run on real devices Signed-off-by: Sahil Kumar <[email protected]> * fix offline behaviour Co-authored-by: Salvatore Giordano <[email protected]> * feat: Introduce onAttachmentTap on messages attachment (#309) * introdce onAttachmentTap on message * correct indent * sdfsd Co-authored-by: Salvatore Giordano <[email protected]> * feat: added customization options in main widgets (#312) * polish channelpreview customization options * polish channelheader customization options * polish messageinput customization options * polish threadheader customization options * polish channellistheader customization options * show the parent message if no messages in thread * fix edit message * fix channelinfo textstyle * fix review * fix useravatar * add ontitle tap to thread header * add custom message actions * add borderradius and button builder * add ontap to messageinput custom send button * fix bottom sheet * add doc * use placeholder image * add doc * fix listtile density * remove subtitle from ChannelListHeaderTheme.copyWith * add InputDecoration.merge extension * fix: use shimmer in images * fix: remove notification badge logic from the sdk and move it to sample app * fix import * fix: Method being called during build (#317) * method call bug fix * dartfmt * fix: show error messages as system and keep them in the message input (#319) * fix: pre release (#324) * use share_plus plugin * bump dependency versions * fix ui for web * unfocus messageinput only on commands * fix default error for messagesearchlistview * fix thumbnail animation * fix lint * ignore example in linter * update changelogs * fix lint * fix lint * update stream_chat pana min * add doc Co-authored-by: Sahil Kumar <[email protected]> Co-authored-by: Deven Joshi <[email protected]> Co-authored-by: Neevash Ramdial (Nash) <[email protected]> Co-authored-by: Nelson Nunes <[email protected]>
325 lines
11 KiB
Dart
325 lines
11 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
import '../stream_chat_flutter.dart';
|
|
import 'extension.dart';
|
|
|
|
/// Callback to download an attachment asset
|
|
typedef AttachmentDownloader = Future<String> Function(
|
|
Attachment attachment, {
|
|
ProgressCallback progressCallback,
|
|
});
|
|
|
|
/// Widget that shows the options in the gallery view
|
|
class AttachmentActionsModal extends StatelessWidget {
|
|
/// The message containing the attachments
|
|
final Message message;
|
|
|
|
/// Current page index
|
|
final currentIndex;
|
|
|
|
/// Callback to show the message
|
|
final VoidCallback onShowMessage;
|
|
|
|
/// Callback to download images
|
|
final AttachmentDownloader imageDownloader;
|
|
|
|
/// Callback to provide download files
|
|
final AttachmentDownloader fileDownloader;
|
|
|
|
/// Returns a new [AttachmentActionsModal]
|
|
const AttachmentActionsModal({
|
|
this.message,
|
|
this.currentIndex,
|
|
this.onShowMessage,
|
|
this.imageDownloader,
|
|
this.fileDownloader,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.translucent,
|
|
onTap: () => Navigator.maybePop(context),
|
|
child: _buildPage(context),
|
|
);
|
|
}
|
|
|
|
Widget _buildPage(context) {
|
|
final theme = StreamChatTheme.of(context);
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
SizedBox(height: kToolbarHeight),
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 8.0),
|
|
child: Container(
|
|
width: MediaQuery.of(context).size.width * 0.5,
|
|
clipBehavior: Clip.hardEdge,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16.0),
|
|
),
|
|
child: Container(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_buildButton(
|
|
context,
|
|
'Reply',
|
|
StreamSvgIcon.iconCurveLineLeftUp(
|
|
size: 24.0,
|
|
color: theme.colorTheme.grey,
|
|
),
|
|
() {
|
|
Navigator.pop(context, ReturnActionType.reply);
|
|
},
|
|
),
|
|
_buildButton(
|
|
context,
|
|
'Show in Chat',
|
|
StreamSvgIcon.eye(
|
|
size: 24.0,
|
|
color: theme.colorTheme.black,
|
|
),
|
|
onShowMessage,
|
|
),
|
|
_buildButton(
|
|
context,
|
|
'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}',
|
|
StreamSvgIcon.iconSave(
|
|
size: 24.0,
|
|
color: theme.colorTheme.grey,
|
|
),
|
|
() {
|
|
final attachment = message.attachments[currentIndex];
|
|
final isImage = attachment.type == 'image';
|
|
final saveFile = fileDownloader ?? _downloadAttachment;
|
|
final saveImage = imageDownloader ?? _downloadAttachment;
|
|
final downloader = isImage ? saveImage : saveFile;
|
|
|
|
final progressNotifier = ValueNotifier<_DownloadProgress>(
|
|
_DownloadProgress.initial(),
|
|
);
|
|
|
|
downloader(
|
|
attachment,
|
|
progressCallback: (received, total) {
|
|
progressNotifier.value = _DownloadProgress(
|
|
total,
|
|
received,
|
|
);
|
|
},
|
|
).catchError((_) {
|
|
progressNotifier.value = null;
|
|
});
|
|
|
|
// Closing attachment actions modal before opening
|
|
// attachment download dialog
|
|
Navigator.pop(context);
|
|
|
|
showDialog(
|
|
barrierDismissible: false,
|
|
context: context,
|
|
barrierColor: theme.colorTheme.overlay,
|
|
builder: (context) => _buildDownloadProgressDialog(
|
|
context,
|
|
progressNotifier,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
if (StreamChat.of(context).user.id == message.user.id)
|
|
_buildButton(
|
|
context,
|
|
'Delete',
|
|
StreamSvgIcon.delete(
|
|
size: 24.0,
|
|
color: theme.colorTheme.accentRed,
|
|
),
|
|
() {
|
|
final channel = StreamChannel.of(context).channel;
|
|
if (message.attachments.length > 1 ||
|
|
message.text.isNotEmpty) {
|
|
final remainingAttachments = [...message.attachments]
|
|
..removeAt(currentIndex);
|
|
channel.updateMessage(message.copyWith(
|
|
attachments: remainingAttachments,
|
|
));
|
|
Navigator.pop(context);
|
|
Navigator.pop(context);
|
|
} else {
|
|
channel.deleteMessage(message).then((value) {
|
|
Navigator.pop(context);
|
|
Navigator.pop(context);
|
|
});
|
|
}
|
|
},
|
|
color: theme.colorTheme.accentRed,
|
|
),
|
|
]
|
|
.map<Widget>((e) =>
|
|
Align(alignment: Alignment.centerRight, child: e))
|
|
.insertBetween(
|
|
Container(
|
|
height: 1,
|
|
color: theme.colorTheme.greyWhisper,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildButton(
|
|
context,
|
|
String title,
|
|
StreamSvgIcon icon,
|
|
VoidCallback onTap, {
|
|
Color color,
|
|
}) {
|
|
return Material(
|
|
color: StreamChatTheme.of(context).colorTheme.white,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
|
child: Row(
|
|
children: [
|
|
icon,
|
|
SizedBox(width: 16),
|
|
Text(
|
|
title,
|
|
style: StreamChatTheme.of(context)
|
|
.textTheme
|
|
.body
|
|
.copyWith(color: color),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDownloadProgressDialog(
|
|
BuildContext context,
|
|
ValueNotifier<_DownloadProgress> progressNotifier,
|
|
) {
|
|
final theme = StreamChatTheme.of(context);
|
|
return WillPopScope(
|
|
onWillPop: () => Future.value(false),
|
|
child: 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) {
|
|
Future.delayed(
|
|
const Duration(milliseconds: 500),
|
|
Navigator.of(context).pop,
|
|
);
|
|
}
|
|
return Material(
|
|
type: MaterialType.transparency,
|
|
child: Center(
|
|
child: Container(
|
|
height: 182,
|
|
width: 182,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16),
|
|
color: theme.colorTheme.white,
|
|
),
|
|
child: Center(
|
|
child: progress == null
|
|
? Container(
|
|
height: 100,
|
|
width: 100,
|
|
child: StreamSvgIcon.error(
|
|
color: theme.colorTheme.greyGainsboro,
|
|
),
|
|
)
|
|
: progress.toProgressIndicatorValue == 1.0
|
|
? Container(
|
|
height: 160,
|
|
width: 160,
|
|
child: StreamSvgIcon.check(
|
|
color: theme.colorTheme.greyGainsboro,
|
|
),
|
|
)
|
|
: Container(
|
|
height: 100,
|
|
width: 100,
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
CircularProgressIndicator(
|
|
value: progress.toProgressIndicatorValue,
|
|
strokeWidth: 8.0,
|
|
valueColor: AlwaysStoppedAnimation(
|
|
theme.colorTheme.accentBlue,
|
|
),
|
|
),
|
|
Center(
|
|
child: Text(
|
|
'${progress.toPercentage}%',
|
|
style: theme.textTheme.headline.copyWith(
|
|
color: theme.colorTheme.grey,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<String> _downloadAttachment(
|
|
Attachment attachment, {
|
|
ProgressCallback progressCallback,
|
|
}) async {
|
|
String filePath;
|
|
final appDocDir = await getTemporaryDirectory();
|
|
await Dio().download(
|
|
attachment.assetUrl ?? attachment.imageUrl ?? attachment.thumbUrl,
|
|
(Headers responseHeaders) {
|
|
final contentType = responseHeaders[Headers.contentTypeHeader];
|
|
final mimeType = contentType.first?.split('/')?.last;
|
|
filePath ??= '${appDocDir.path}/${attachment.id}.$mimeType';
|
|
return filePath;
|
|
},
|
|
onReceiveProgress: progressCallback,
|
|
);
|
|
final result = await ImageGallerySaver.saveFile(filePath);
|
|
return (result as Map)['filePath'];
|
|
}
|
|
}
|
|
|
|
class _DownloadProgress {
|
|
final int total;
|
|
final int received;
|
|
|
|
const _DownloadProgress(this.total, this.received);
|
|
|
|
factory _DownloadProgress.initial() {
|
|
return _DownloadProgress(double.maxFinite.toInt(), 0);
|
|
}
|
|
|
|
double get toProgressIndicatorValue => received / total;
|
|
|
|
int get toPercentage => (received * 100) ~/ total;
|
|
}
|