Merge branch 'develop' into feat/messageInput
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
export 'attachment_error.dart';
|
||||
export 'attachment_error.dart';
|
||||
export 'attachment_upload_state_builder.dart';
|
||||
export 'attachment_widget.dart' show AttachmentSource;
|
||||
export 'file_attachment.dart';
|
||||
|
||||
@@ -195,7 +195,7 @@ class _FileTypeImage extends StatelessWidget {
|
||||
shape: _getDefaultShape(context),
|
||||
child: source.when(
|
||||
local: () => StreamVideoThumbnailImage(
|
||||
video: attachment.file!.path!,
|
||||
video: attachment.file!.path,
|
||||
placeholderBuilder: (_) => const Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
@@ -205,7 +205,7 @@ class _FileTypeImage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
network: () => StreamVideoThumbnailImage(
|
||||
video: attachment.assetUrl!,
|
||||
video: attachment.assetUrl,
|
||||
placeholderBuilder: (_) => const Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
|
||||
@@ -17,6 +17,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
|
||||
this.onShowMessage,
|
||||
this.onReplyMessage,
|
||||
this.onAttachmentTap,
|
||||
this.attachmentActionsModalBuilder,
|
||||
});
|
||||
|
||||
/// {@macro showMessageCallback}
|
||||
@@ -28,6 +29,9 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
|
||||
/// {@macro onAttachmentTap}
|
||||
final OnAttachmentTap? onAttachmentTap;
|
||||
|
||||
/// {@macro attachmentActionsBuilder}
|
||||
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageUrl =
|
||||
@@ -260,6 +264,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
|
||||
userName: message.user!.name,
|
||||
onShowMessage: onShowMessage,
|
||||
onReplyMessage: onReplyMessage,
|
||||
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+3
-1
@@ -123,7 +123,9 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase {
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempPath = Uri.file(tempDir.path, windows: CurrentPlatform.isWindows);
|
||||
final tempFilePath = tempPath.resolve(fileName!).path;
|
||||
final tempFilePath = tempPath
|
||||
.resolve(fileName!)
|
||||
.toFilePath(windows: CurrentPlatform.isWindows);
|
||||
print(tempFilePath);
|
||||
|
||||
final attachmentFileBytes = attachmentFile.bytes;
|
||||
|
||||
@@ -22,6 +22,7 @@ class StreamImageAttachment extends StreamAttachmentWidget {
|
||||
this.imageThumbnailSize = const Size(400, 400),
|
||||
this.imageThumbnailResizeType = 'clip',
|
||||
this.imageThumbnailCropType = 'center',
|
||||
this.attachmentActionsModalBuilder,
|
||||
});
|
||||
|
||||
/// The [StreamMessageThemeData] to use for the image title
|
||||
@@ -52,26 +53,40 @@ class StreamImageAttachment extends StreamAttachmentWidget {
|
||||
/// Defaults to [center]
|
||||
final String /*center|top|bottom|left|right*/ imageThumbnailCropType;
|
||||
|
||||
/// {@macro attachmentActionsBuilder}
|
||||
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return source.when(
|
||||
local: () {
|
||||
if (attachment.localUri == null || attachment.file?.bytes == null) {
|
||||
return AttachmentError(constraints: constraints);
|
||||
}
|
||||
return _buildImageAttachment(
|
||||
context,
|
||||
Image.memory(
|
||||
attachment.file!.bytes!,
|
||||
height: constraints?.maxHeight,
|
||||
width: constraints?.maxWidth,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, _, __) => Image.asset(
|
||||
'images/placeholder.png',
|
||||
package: 'stream_chat_flutter',
|
||||
if (attachment.file?.bytes != null) {
|
||||
return _buildImageAttachment(
|
||||
context,
|
||||
Image.memory(
|
||||
attachment.file!.bytes!,
|
||||
height: constraints?.maxHeight,
|
||||
width: constraints?.maxWidth,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: _imageErrorBuilder,
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else if (attachment.localUri != null) {
|
||||
return _buildImageAttachment(
|
||||
context,
|
||||
Image.asset(
|
||||
attachment.localUri!.path,
|
||||
height: constraints?.maxHeight,
|
||||
width: constraints?.maxWidth,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: _imageErrorBuilder,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return AttachmentError(
|
||||
constraints: constraints,
|
||||
);
|
||||
}
|
||||
},
|
||||
network: () {
|
||||
var imageUrl =
|
||||
@@ -116,6 +131,12 @@ class StreamImageAttachment extends StreamAttachmentWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _imageErrorBuilder(BuildContext _, Object __, StackTrace? ___) =>
|
||||
Image.asset(
|
||||
'images/placeholder.png',
|
||||
package: 'stream_chat_flutter',
|
||||
);
|
||||
|
||||
Widget _buildImageAttachment(BuildContext context, Widget imageWidget) {
|
||||
return Container(
|
||||
constraints: constraints,
|
||||
@@ -144,6 +165,8 @@ class StreamImageAttachment extends StreamAttachmentWidget {
|
||||
userName: message.user!.name,
|
||||
onShowMessage: onShowMessage,
|
||||
onReplyMessage: onReplyMessage,
|
||||
attachmentActionsModalBuilder:
|
||||
attachmentActionsModalBuilder,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@ class StreamImageGroup extends StatelessWidget {
|
||||
this.imageThumbnailSize = const Size(400, 400),
|
||||
this.imageThumbnailResizeType = 'clip',
|
||||
this.imageThumbnailCropType = 'center',
|
||||
this.attachmentActionsModalBuilder,
|
||||
});
|
||||
|
||||
/// List of attachments to show
|
||||
@@ -54,6 +55,9 @@ class StreamImageGroup extends StatelessWidget {
|
||||
/// Defaults to [center]
|
||||
final String /*center|top|bottom|left|right*/ imageThumbnailCropType;
|
||||
|
||||
/// {@macro attachmentActionsBuilder}
|
||||
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
@@ -154,6 +158,7 @@ class StreamImageGroup extends StatelessWidget {
|
||||
userName: message.user!.name,
|
||||
onShowMessage: onShowMessage,
|
||||
onReplyMessage: onReplyMessage,
|
||||
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -170,6 +175,7 @@ class StreamImageGroup extends StatelessWidget {
|
||||
imageThumbnailSize: imageThumbnailSize,
|
||||
imageThumbnailResizeType: imageThumbnailResizeType,
|
||||
imageThumbnailCropType: imageThumbnailCropType,
|
||||
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class StreamUrlAttachment extends StatelessWidget {
|
||||
if (urlAttachment.title != null)
|
||||
Text(
|
||||
urlAttachment.title!.trim(),
|
||||
maxLines: 1,
|
||||
maxLines: messageTheme.urlAttachmentTitleMaxLine ?? 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: chatThemeData.textTheme.body
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
|
||||
@@ -17,6 +17,7 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
this.onShowMessage,
|
||||
this.onReplyMessage,
|
||||
this.onAttachmentTap,
|
||||
this.attachmentActionsModalBuilder,
|
||||
});
|
||||
|
||||
/// The [StreamMessageThemeData] to use for the title
|
||||
@@ -31,6 +32,9 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
/// {@macro onAttachmentTap}
|
||||
final OnAttachmentTap? onAttachmentTap;
|
||||
|
||||
/// {@macro attachmentActionsBuilder}
|
||||
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return source.when(
|
||||
@@ -41,7 +45,7 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
return _buildVideoAttachment(
|
||||
context,
|
||||
StreamVideoThumbnailImage(
|
||||
video: attachment.file!.path!,
|
||||
video: attachment.file!.path,
|
||||
thumbUrl: attachment.thumbUrl,
|
||||
constraints: constraints,
|
||||
),
|
||||
@@ -54,7 +58,7 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
return _buildVideoAttachment(
|
||||
context,
|
||||
StreamVideoThumbnailImage(
|
||||
video: attachment.assetUrl!,
|
||||
video: attachment.assetUrl,
|
||||
thumbUrl: attachment.thumbUrl,
|
||||
constraints: constraints,
|
||||
),
|
||||
@@ -86,6 +90,8 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
userName: message.user!.name,
|
||||
onShowMessage: onShowMessage,
|
||||
onReplyMessage: onReplyMessage,
|
||||
attachmentActionsModalBuilder:
|
||||
attachmentActionsModalBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+5
-3
@@ -33,10 +33,10 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
/// Callback to download [attachment].
|
||||
final AttachmentDownloader? attachmentDownloader;
|
||||
|
||||
/// Show reply option
|
||||
/// Show "reply" option
|
||||
final bool showReply;
|
||||
|
||||
/// Show show in chat option
|
||||
/// Show "show in chat" option
|
||||
final bool showShowInChat;
|
||||
|
||||
/// Show save option
|
||||
@@ -55,6 +55,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
Attachment? attachment,
|
||||
Message? message,
|
||||
VoidCallback? onShowMessage,
|
||||
VoidCallback? onReply,
|
||||
AttachmentDownloader? attachmentDownloader,
|
||||
bool? showReply,
|
||||
bool? showShowInChat,
|
||||
@@ -67,6 +68,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
attachment: attachment ?? this.attachment,
|
||||
message: message ?? this.message,
|
||||
onShowMessage: onShowMessage ?? this.onShowMessage,
|
||||
onReply: onReply ?? this.onReply,
|
||||
attachmentDownloader: attachmentDownloader ?? this.attachmentDownloader,
|
||||
showReply: showReply ?? this.showReply,
|
||||
showShowInChat: showShowInChat ?? this.showShowInChat,
|
||||
@@ -112,7 +114,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
size: 24,
|
||||
color: theme.colorTheme.textLowEmphasis,
|
||||
),
|
||||
() => Navigator.of(context).pop(ReturnActionType.reply),
|
||||
onReply,
|
||||
),
|
||||
if (showShowInChat)
|
||||
_buildButton(
|
||||
|
||||
@@ -162,8 +162,8 @@ class StreamChannelHeader extends StatelessWidget
|
||||
showMessage: showConnectionStateTile && showStatus,
|
||||
message: statusString,
|
||||
child: AppBar(
|
||||
toolbarTextStyle: theme.textTheme.bodyText2,
|
||||
titleTextStyle: theme.textTheme.headline6,
|
||||
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||
titleTextStyle: theme.textTheme.titleLarge,
|
||||
systemOverlayStyle: theme.brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark,
|
||||
|
||||
@@ -131,8 +131,8 @@ class StreamChannelListHeader extends StatelessWidget
|
||||
showMessage: showConnectionStateTile && showStatus,
|
||||
message: statusString,
|
||||
child: AppBar(
|
||||
toolbarTextStyle: theme.textTheme.bodyText2,
|
||||
titleTextStyle: theme.textTheme.headline6,
|
||||
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||
titleTextStyle: theme.textTheme.titleLarge,
|
||||
systemOverlayStyle: theme.brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark,
|
||||
@@ -181,7 +181,10 @@ class StreamChannelListHeader extends StatelessWidget
|
||||
package: 'stream_chat_flutter',
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: color,
|
||||
colorFilter: ColorFilter.mode(
|
||||
color,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// TODO: remove in v6 as this is no longer used. Currently exported.
|
||||
|
||||
/// Return action for coming back from pages
|
||||
@Deprecated('''
|
||||
ReturnActionType has been deprecated and is no longer used.''')
|
||||
enum ReturnActionType {
|
||||
/// No return action
|
||||
none,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -61,6 +60,7 @@ class StreamGalleryFooter extends StatefulWidget
|
||||
|
||||
class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
final shareButtonKey = GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const showShareButton = !kIsWeb;
|
||||
@@ -108,12 +108,19 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
await file.writeAsBytes(bytes);
|
||||
final box =
|
||||
shareButtonKey.currentContext?.findRenderObject();
|
||||
final size = shareButtonKey.currentContext?.size;
|
||||
|
||||
final position =
|
||||
(box! as RenderBox).localToGlobal(Offset.zero);
|
||||
|
||||
await Share.shareXFiles(
|
||||
[XFile(filePath)],
|
||||
sharePositionOrigin:
|
||||
Rect.fromLTWH(position.dx, position.dy, 0, 0),
|
||||
sharePositionOrigin: Rect.fromLTWH(
|
||||
position.dx,
|
||||
position.dy,
|
||||
size?.width ?? 50,
|
||||
(size?.height ?? 2) / 2,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -219,8 +226,8 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: StreamVideoThumbnailImage(
|
||||
video: (attachment.file?.path ??
|
||||
attachment.assetUrl)!,
|
||||
video:
|
||||
attachment.file?.path ?? attachment.assetUrl,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -80,8 +80,8 @@ class StreamGalleryHeader extends StatelessWidget
|
||||
final galleryHeaderThemeData = StreamGalleryHeaderTheme.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return AppBar(
|
||||
toolbarTextStyle: theme.textTheme.bodyText2,
|
||||
titleTextStyle: theme.textTheme.headline6,
|
||||
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||
titleTextStyle: theme.textTheme.titleLarge,
|
||||
systemOverlayStyle: theme.brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark,
|
||||
|
||||
@@ -18,6 +18,9 @@ abstract class Translations {
|
||||
/// The label for showing no users
|
||||
String get noUsersLabel;
|
||||
|
||||
/// The label for showing no photo or video
|
||||
String get noPhotoOrVideoLabel;
|
||||
|
||||
/// The text for showing user is online
|
||||
String get userOnlineText;
|
||||
|
||||
@@ -78,7 +81,10 @@ abstract class Translations {
|
||||
|
||||
/// The text for showing the unread messages count
|
||||
/// in the [StreamMessageListView]
|
||||
String unreadMessagesSeparatorText(int unreadCount);
|
||||
String unreadMessagesSeparatorText(
|
||||
@Deprecated('unreadCount is not used anymore and will be removed ')
|
||||
int unreadCount,
|
||||
);
|
||||
|
||||
/// The label for "connected" in [StreamConnectionStatusBuilder]
|
||||
String get connectedLabel;
|
||||
@@ -373,6 +379,9 @@ class DefaultTranslations implements Translations {
|
||||
@override
|
||||
String get noUsersLabel => 'There are no users currently';
|
||||
|
||||
@override
|
||||
String get noPhotoOrVideoLabel => 'There is no photo or video';
|
||||
|
||||
@override
|
||||
String get retryLabel => 'Retry';
|
||||
|
||||
@@ -791,12 +800,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments
|
||||
String get linkDisabledError => 'Links are disabled';
|
||||
|
||||
@override
|
||||
String unreadMessagesSeparatorText(int unreadCount) {
|
||||
if (unreadCount == 1) {
|
||||
return '1 unread message';
|
||||
}
|
||||
return '$unreadCount unread messages';
|
||||
}
|
||||
String unreadMessagesSeparatorText(int unreadCount) => 'New messages';
|
||||
|
||||
@override
|
||||
String get enableFileAccessMessage => 'Please enable access to files'
|
||||
|
||||
+8
-115
@@ -2,6 +2,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart' hide ButtonStyle;
|
||||
import 'package:stream_chat_flutter/src/message_actions_modal/mam_widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@template messageActionsModal}
|
||||
@@ -97,34 +98,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
|
||||
Widget _showMessageOptionsModal() {
|
||||
final mediaQueryData = MediaQuery.of(context);
|
||||
final size = mediaQueryData.size;
|
||||
final user = StreamChat.of(context).currentUser;
|
||||
final orientation = mediaQueryData.orientation;
|
||||
|
||||
final roughMaxSize = size.width * 2 / 3;
|
||||
var messageTextLength = widget.message.text!.length;
|
||||
if (widget.message.quotedMessage != null) {
|
||||
var quotedMessageLength =
|
||||
(widget.message.quotedMessage!.text?.length ?? 0) + 40;
|
||||
if (widget.message.quotedMessage!.attachments.isNotEmpty) {
|
||||
quotedMessageLength += 40;
|
||||
}
|
||||
if (quotedMessageLength > messageTextLength) {
|
||||
messageTextLength = quotedMessageLength;
|
||||
}
|
||||
}
|
||||
final roughSentenceSize = messageTextLength *
|
||||
(widget.messageTheme.messageTextStyle?.fontSize ?? 1) *
|
||||
1.2;
|
||||
final divFactor = widget.message.attachments.isNotEmpty
|
||||
? 1
|
||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||
|
||||
final fontSize = widget.messageTheme.messageTextStyle?.fontSize;
|
||||
final streamChatThemeData = StreamChatTheme.of(context);
|
||||
|
||||
final numberOfReactions =
|
||||
StreamChatConfiguration.of(context).reactionIcons.length;
|
||||
final shiftFactor =
|
||||
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
|
||||
final child = Center(
|
||||
@@ -142,11 +121,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
builder: (context, constraints) {
|
||||
return Align(
|
||||
alignment: Alignment(
|
||||
_calculateReactionsHorizontalAlignmentValue(
|
||||
calculateReactionsHorizontalAlignment(
|
||||
user,
|
||||
divFactor,
|
||||
shiftFactor,
|
||||
widget.message,
|
||||
constraints,
|
||||
fontSize,
|
||||
orientation,
|
||||
),
|
||||
0,
|
||||
),
|
||||
@@ -156,7 +136,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 10),
|
||||
IgnorePointer(
|
||||
child: widget.messageWidget,
|
||||
),
|
||||
@@ -281,93 +261,6 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
);
|
||||
}
|
||||
|
||||
double _calculateReactionsHorizontalAlignmentValue(
|
||||
User? user,
|
||||
num divFactor,
|
||||
double shiftFactor,
|
||||
BoxConstraints constraints,
|
||||
) {
|
||||
var result = 0.0;
|
||||
var cont = true;
|
||||
if (user?.id == widget.message.user?.id) {
|
||||
if (divFactor >= 1.0) {
|
||||
// This calculation is hacky and does not cover all bases!!!
|
||||
// A better option is needed!
|
||||
|
||||
// Landscape calculations
|
||||
if (constraints.maxWidth == 1350) {
|
||||
// 12.7 iPad Pro
|
||||
result = shiftFactor + 0.5;
|
||||
cont = false;
|
||||
} else if (constraints.maxWidth == 1178) {
|
||||
// 11 inch iPad Pro
|
||||
result = shiftFactor + 0.42;
|
||||
cont = false;
|
||||
} else if (constraints.maxWidth == 1164) {
|
||||
// iPad Air 4
|
||||
result = shiftFactor + 0.4;
|
||||
cont = false;
|
||||
} else if (constraints.maxWidth == 1117) {
|
||||
// iPad Mini 6
|
||||
result = shiftFactor + 0.37;
|
||||
cont = false;
|
||||
} else if (constraints.maxWidth == 1064) {
|
||||
// iPad 9th gen
|
||||
result = shiftFactor + 0.33;
|
||||
cont = false;
|
||||
} else if (constraints.maxWidth == 1008) {
|
||||
// 9.7 inch iPad Pro
|
||||
result = shiftFactor + 0.3;
|
||||
cont = false;
|
||||
} else if (constraints.maxWidth >= 200 && constraints.maxWidth <= 400) {
|
||||
// Phone (?)
|
||||
result = shiftFactor - 0.2;
|
||||
cont = false;
|
||||
}
|
||||
|
||||
if (cont) {
|
||||
// Portrait calculations
|
||||
if (constraints.maxWidth == 1008) {
|
||||
// 12.7 iPad Pro
|
||||
result = shiftFactor + 0.3;
|
||||
} else if (constraints.maxWidth == 818) {
|
||||
// 11 inch iPad Pro
|
||||
result = shiftFactor + 0.07;
|
||||
} else if (constraints.maxWidth == 804) {
|
||||
// iPad Air 4
|
||||
result = shiftFactor + 0.04;
|
||||
} else if (constraints.maxWidth == 794) {
|
||||
// iPad 9th gen
|
||||
result = shiftFactor + 0.02;
|
||||
} else if (constraints.maxWidth >= 752) {
|
||||
// 9.7 inch iPad Pro
|
||||
result = shiftFactor - 0.05;
|
||||
} else if (constraints.maxWidth == 728) {
|
||||
// iPad Mini 6
|
||||
result = shiftFactor - 0.1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = 1.2 - divFactor;
|
||||
}
|
||||
} else {
|
||||
if (divFactor >= 1.0) {
|
||||
result = shiftFactor + 0.2;
|
||||
} else {
|
||||
result = -(1.2 - divFactor);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure reactions don't get pushed past the edge of the screen.
|
||||
//
|
||||
// Hacky!!! Needs improvement!!!
|
||||
if (result > 1) {
|
||||
return 1;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
InkWell _buildCustomAction(
|
||||
BuildContext context,
|
||||
StreamMessageAction messageAction,
|
||||
|
||||
+29
-39
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// Shows a modal material design bottom sheet.
|
||||
@@ -107,44 +107,34 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
|
||||
controller: controller,
|
||||
initialAttachments: initialAttachments,
|
||||
builder: (context, controller, child) {
|
||||
return PlatformWidget(
|
||||
web: (context) {
|
||||
return webOrDesktopAttachmentPickerBuilder.call(
|
||||
context: context,
|
||||
controller: controller,
|
||||
customOptions: customOptions?.map(
|
||||
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
|
||||
),
|
||||
attachmentThumbnailSize: attachmentThumbnailSize,
|
||||
attachmentThumbnailFormat: attachmentThumbnailFormat,
|
||||
attachmentThumbnailQuality: attachmentThumbnailQuality,
|
||||
attachmentThumbnailScale: attachmentThumbnailScale,
|
||||
);
|
||||
},
|
||||
mobile: (context) {
|
||||
return mobileAttachmentPickerBuilder.call(
|
||||
context: context,
|
||||
controller: controller,
|
||||
customOptions: customOptions,
|
||||
attachmentThumbnailSize: attachmentThumbnailSize,
|
||||
attachmentThumbnailFormat: attachmentThumbnailFormat,
|
||||
attachmentThumbnailQuality: attachmentThumbnailQuality,
|
||||
attachmentThumbnailScale: attachmentThumbnailScale,
|
||||
);
|
||||
},
|
||||
desktop: (context) {
|
||||
return webOrDesktopAttachmentPickerBuilder.call(
|
||||
context: context,
|
||||
controller: controller,
|
||||
customOptions: customOptions?.map(
|
||||
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
|
||||
),
|
||||
attachmentThumbnailSize: attachmentThumbnailSize,
|
||||
attachmentThumbnailFormat: attachmentThumbnailFormat,
|
||||
attachmentThumbnailQuality: attachmentThumbnailQuality,
|
||||
attachmentThumbnailScale: attachmentThumbnailScale,
|
||||
);
|
||||
},
|
||||
final currentPlatform = defaultTargetPlatform;
|
||||
final isWebOrDesktop = kIsWeb ||
|
||||
currentPlatform == TargetPlatform.macOS ||
|
||||
currentPlatform == TargetPlatform.linux ||
|
||||
currentPlatform == TargetPlatform.windows;
|
||||
|
||||
if (isWebOrDesktop) {
|
||||
return webOrDesktopAttachmentPickerBuilder.call(
|
||||
context: context,
|
||||
controller: controller,
|
||||
customOptions: customOptions?.map(
|
||||
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
|
||||
),
|
||||
attachmentThumbnailSize: attachmentThumbnailSize,
|
||||
attachmentThumbnailFormat: attachmentThumbnailFormat,
|
||||
attachmentThumbnailQuality: attachmentThumbnailQuality,
|
||||
attachmentThumbnailScale: attachmentThumbnailScale,
|
||||
);
|
||||
}
|
||||
|
||||
return mobileAttachmentPickerBuilder.call(
|
||||
context: context,
|
||||
controller: controller,
|
||||
customOptions: customOptions,
|
||||
attachmentThumbnailSize: attachmentThumbnailSize,
|
||||
attachmentThumbnailFormat: attachmentThumbnailFormat,
|
||||
attachmentThumbnailQuality: attachmentThumbnailQuality,
|
||||
attachmentThumbnailScale: attachmentThumbnailScale,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -276,7 +276,7 @@ class _ParseAttachments extends StatelessWidget {
|
||||
'video': (_, attachment) {
|
||||
return StreamVideoThumbnailImage(
|
||||
key: ValueKey(attachment.assetUrl),
|
||||
video: attachment.file?.path ?? attachment.assetUrl!,
|
||||
video: attachment.file?.path ?? attachment.assetUrl,
|
||||
constraints: BoxConstraints.loose(const Size(32, 32)),
|
||||
errorBuilder: (_, __) => AttachmentError(
|
||||
constraints: BoxConstraints.loose(const Size(32, 32)),
|
||||
|
||||
@@ -1220,7 +1220,7 @@ class StreamMessageInputState extends State<StreamMessageInput>
|
||||
104,
|
||||
),
|
||||
),
|
||||
video: (attachment.file?.path ?? attachment.assetUrl)!,
|
||||
video: attachment.file?.path ?? attachment.assetUrl,
|
||||
),
|
||||
Positioned(
|
||||
left: 8,
|
||||
|
||||
@@ -80,7 +80,6 @@ class StreamMessageTextField extends StatefulWidget {
|
||||
this.textAlignVertical,
|
||||
this.textDirection,
|
||||
this.readOnly = false,
|
||||
ToolbarOptions? toolbarOptions,
|
||||
this.showCursor,
|
||||
this.autofocus = false,
|
||||
this.obscuringCharacter = '•',
|
||||
@@ -155,31 +154,7 @@ class StreamMessageTextField extends StatefulWidget {
|
||||
keyboardType = keyboardType ??
|
||||
(maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
||||
enableInteractiveSelection =
|
||||
enableInteractiveSelection ?? (!readOnly || !obscureText),
|
||||
toolbarOptions = toolbarOptions ??
|
||||
(obscureText
|
||||
? (readOnly
|
||||
// No point in even offering "Select All" in a read-only obscured
|
||||
// field.
|
||||
? const ToolbarOptions()
|
||||
// Writable, but obscured.
|
||||
: const ToolbarOptions(
|
||||
selectAll: true,
|
||||
paste: true,
|
||||
))
|
||||
: (readOnly
|
||||
// Read-only, not obscured.
|
||||
? const ToolbarOptions(
|
||||
selectAll: true,
|
||||
copy: true,
|
||||
)
|
||||
// Writable, not obscured.
|
||||
: const ToolbarOptions(
|
||||
copy: true,
|
||||
cut: true,
|
||||
selectAll: true,
|
||||
paste: true,
|
||||
)));
|
||||
enableInteractiveSelection ?? (!readOnly || !obscureText);
|
||||
|
||||
/// Controls the message being edited.
|
||||
///
|
||||
@@ -304,13 +279,6 @@ class StreamMessageTextField extends StatefulWidget {
|
||||
/// {@macro flutter.widgets.editableText.readOnly}
|
||||
final bool readOnly;
|
||||
|
||||
/// Configuration of toolbar options.
|
||||
///
|
||||
/// If not set, select all and paste will default to be enabled. Copy and cut
|
||||
/// will be disabled if [obscureText] is true. If [readOnly] is true,
|
||||
/// paste and cut will be disabled regardless.
|
||||
final ToolbarOptions toolbarOptions;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.showCursor}
|
||||
final bool? showCursor;
|
||||
|
||||
@@ -720,7 +688,6 @@ class _StreamMessageTextFieldState extends State<StreamMessageTextField>
|
||||
textAlignVertical: widget.textAlignVertical,
|
||||
textDirection: widget.textDirection,
|
||||
readOnly: widget.readOnly,
|
||||
toolbarOptions: widget.toolbarOptions,
|
||||
showCursor: widget.showCursor,
|
||||
autofocus: widget.autofocus,
|
||||
obscuringCharacter: widget.obscuringCharacter,
|
||||
|
||||
+1
-3
@@ -24,9 +24,7 @@ class UnreadMessagesSeparator extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
context.translations.unreadMessagesSeparatorText(
|
||||
unreadCount,
|
||||
),
|
||||
context.translations.unreadMessagesSeparatorText(unreadCount),
|
||||
textAlign: TextAlign.center,
|
||||
style: StreamChannelHeaderTheme.of(context).subtitleStyle,
|
||||
),
|
||||
|
||||
@@ -32,6 +32,7 @@ class BottomRow extends StatelessWidget {
|
||||
this.deletedBottomRowBuilder,
|
||||
this.onThreadTap,
|
||||
this.usernameBuilder,
|
||||
this.sendingIndicatorBuilder,
|
||||
});
|
||||
|
||||
/// {@macro messageIsDeleted}
|
||||
@@ -88,6 +89,61 @@ class BottomRow extends StatelessWidget {
|
||||
/// {@macro usernameBuilder}
|
||||
final Widget Function(BuildContext, Message)? usernameBuilder;
|
||||
|
||||
/// {@macro sendingIndicatorBuilder}
|
||||
final Widget Function(BuildContext, Message)? sendingIndicatorBuilder;
|
||||
|
||||
/// {@template copyWith}
|
||||
/// Creates a copy of [BottomRow] with specified attributes
|
||||
/// overridden.
|
||||
/// {@endtemplate}
|
||||
BottomRow copyWith({
|
||||
Key? key,
|
||||
bool? isDeleted,
|
||||
Message? message,
|
||||
bool? showThreadReplyIndicator,
|
||||
bool? showInChannel,
|
||||
bool? showTimeStamp,
|
||||
bool? showUsername,
|
||||
bool? reverse,
|
||||
bool? showSendingIndicator,
|
||||
bool? hasUrlAttachments,
|
||||
bool? isGiphy,
|
||||
bool? isOnlyEmoji,
|
||||
StreamMessageThemeData? messageTheme,
|
||||
StreamChatThemeData? streamChatTheme,
|
||||
bool? hasNonUrlAttachments,
|
||||
StreamChatState? streamChat,
|
||||
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
|
||||
void Function(Message)? onThreadTap,
|
||||
Widget Function(BuildContext, Message)? usernameBuilder,
|
||||
Widget Function(BuildContext, Message)? sendingIndicatorBuilder,
|
||||
}) =>
|
||||
BottomRow(
|
||||
key: key ?? this.key,
|
||||
isDeleted: isDeleted ?? this.isDeleted,
|
||||
message: message ?? this.message,
|
||||
showThreadReplyIndicator:
|
||||
showThreadReplyIndicator ?? this.showThreadReplyIndicator,
|
||||
showInChannel: showInChannel ?? this.showInChannel,
|
||||
showTimeStamp: showTimeStamp ?? this.showTimeStamp,
|
||||
showUsername: showUsername ?? this.showUsername,
|
||||
reverse: reverse ?? this.reverse,
|
||||
showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator,
|
||||
hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments,
|
||||
isGiphy: isGiphy ?? this.isGiphy,
|
||||
isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji,
|
||||
messageTheme: messageTheme ?? this.messageTheme,
|
||||
streamChatTheme: streamChatTheme ?? this.streamChatTheme,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments,
|
||||
streamChat: streamChat ?? this.streamChat,
|
||||
deletedBottomRowBuilder:
|
||||
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
|
||||
onThreadTap: onThreadTap ?? this.onThreadTap,
|
||||
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
|
||||
sendingIndicatorBuilder:
|
||||
sendingIndicatorBuilder ?? this.sendingIndicatorBuilder,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isDeleted) {
|
||||
@@ -147,13 +203,14 @@ class BottomRow extends StatelessWidget {
|
||||
),
|
||||
if (showSendingIndicator)
|
||||
WidgetSpan(
|
||||
child: SendingIndicatorWrapper(
|
||||
messageTheme: messageTheme,
|
||||
message: message,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
streamChat: streamChat,
|
||||
streamChatTheme: streamChatTheme,
|
||||
),
|
||||
child: sendingIndicatorBuilder?.call(context, message) ??
|
||||
SendingIndicatorWrapper(
|
||||
messageTheme: messageTheme,
|
||||
message: message,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
streamChat: streamChat,
|
||||
streamChatTheme: streamChatTheme,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ class StreamMessageText extends StatelessWidget {
|
||||
.translate(language)
|
||||
.replaceMentions()
|
||||
.text
|
||||
?.replaceAll('\n', '\n\n');
|
||||
?.replaceAll('\n', '\n\n')
|
||||
.trim();
|
||||
final themeData = Theme.of(context);
|
||||
return MarkdownBody(
|
||||
data: messageText ?? '',
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction
|
||||
import 'package:stream_chat_flutter/src/context_menu_items/stream_chat_context_menu_item.dart';
|
||||
import 'package:stream_chat_flutter/src/dialogs/dialogs.dart';
|
||||
import 'package:stream_chat_flutter/src/message_actions_modal/message_actions_modal.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/bottom_row.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/reactions/message_reactions_modal.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
@@ -79,8 +80,15 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
this.userAvatarBuilder,
|
||||
this.editMessageInputBuilder,
|
||||
this.textBuilder,
|
||||
this.bottomRowBuilder,
|
||||
this.deletedBottomRowBuilder,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.bottomRowBuilder,
|
||||
this.bottomRowBuilderWithDefaultWidget,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.deletedBottomRowBuilder,
|
||||
this.customAttachmentBuilders,
|
||||
this.padding,
|
||||
this.textPadding = const EdgeInsets.symmetric(
|
||||
@@ -92,11 +100,19 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
this.onQuotedMessageTap,
|
||||
this.customActions = const [],
|
||||
this.onAttachmentTap,
|
||||
this.usernameBuilder,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.usernameBuilder,
|
||||
this.imageAttachmentThumbnailSize = const Size(400, 400),
|
||||
this.imageAttachmentThumbnailResizeType = 'clip',
|
||||
this.imageAttachmentThumbnailCropType = 'center',
|
||||
}) : attachmentBuilders = {
|
||||
this.attachmentActionsModalBuilder,
|
||||
}) : assert(
|
||||
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
|
||||
'You can only use one of the two bottom row builders',
|
||||
),
|
||||
attachmentBuilders = {
|
||||
'image': (context, message, attachments) {
|
||||
final border = RoundedRectangleBorder(
|
||||
side: attachmentBorderSide ??
|
||||
@@ -129,6 +145,8 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
imageThumbnailResizeType:
|
||||
imageAttachmentThumbnailResizeType,
|
||||
imageThumbnailCropType: imageAttachmentThumbnailCropType,
|
||||
attachmentActionsModalBuilder:
|
||||
attachmentActionsModalBuilder,
|
||||
),
|
||||
),
|
||||
attachmentShape: border,
|
||||
@@ -156,6 +174,7 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
imageThumbnailSize: imageAttachmentThumbnailSize,
|
||||
imageThumbnailResizeType: imageAttachmentThumbnailResizeType,
|
||||
imageThumbnailCropType: imageAttachmentThumbnailCropType,
|
||||
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
|
||||
),
|
||||
attachmentShape: border,
|
||||
);
|
||||
@@ -189,6 +208,8 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
onAttachmentTap(message, attachment);
|
||||
}
|
||||
: null,
|
||||
attachmentActionsModalBuilder:
|
||||
attachmentActionsModalBuilder,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
@@ -306,7 +327,13 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
/// {@template bottomRowBuilder}
|
||||
/// Widget builder for building a bottom row below the message
|
||||
/// {@endtemplate}
|
||||
final Widget Function(BuildContext, Message)? bottomRowBuilder;
|
||||
final BottomRowBuilder? bottomRowBuilder;
|
||||
|
||||
/// {@template bottomRowBuilderWithDefaultWidget}
|
||||
/// Widget builder for building a bottom row below the message.
|
||||
/// Also contains the default bottom row widget.
|
||||
/// {@endtemplate}
|
||||
final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget;
|
||||
|
||||
/// {@template deletedBottomRowBuilder}
|
||||
/// Widget builder for building a bottom row below a deleted message
|
||||
@@ -512,6 +539,9 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
/// {@macro onMessageWidgetAttachmentTap}
|
||||
final OnMessageWidgetAttachmentTap? onAttachmentTap;
|
||||
|
||||
/// {@macro attachmentActionsBuilder}
|
||||
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
|
||||
|
||||
/// Size of the image attachment thumbnail.
|
||||
final Size imageAttachmentThumbnailSize;
|
||||
|
||||
@@ -537,9 +567,19 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
void Function(Message)? onReplyTap,
|
||||
Widget Function(BuildContext, Message)? editMessageInputBuilder,
|
||||
Widget Function(BuildContext, Message)? textBuilder,
|
||||
Widget Function(BuildContext, Message)? usernameBuilder,
|
||||
Widget Function(BuildContext, Message)? bottomRowBuilder,
|
||||
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') Widget Function(BuildContext, Message)? usernameBuilder,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') BottomRowBuilder? bottomRowBuilder,
|
||||
BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
|
||||
void Function(BuildContext, Message)? onMessageActions,
|
||||
Message? message,
|
||||
StreamMessageThemeData? messageTheme,
|
||||
@@ -586,7 +626,31 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
Size? imageAttachmentThumbnailSize,
|
||||
String? imageAttachmentThumbnailResizeType,
|
||||
String? imageAttachmentThumbnailCropType,
|
||||
AttachmentActionsBuilder? attachmentActionsModalBuilder,
|
||||
}) {
|
||||
assert(
|
||||
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
|
||||
'You can only use one of the two bottom row builders',
|
||||
);
|
||||
|
||||
var _bottomRowBuilderWithDefaultWidget =
|
||||
bottomRowBuilderWithDefaultWidget ??
|
||||
this.bottomRowBuilderWithDefaultWidget;
|
||||
|
||||
_bottomRowBuilderWithDefaultWidget ??= (context, message, defaultWidget) {
|
||||
final _bottomRowBuilder = bottomRowBuilder ?? this.bottomRowBuilder;
|
||||
if (_bottomRowBuilder != null) {
|
||||
return _bottomRowBuilder(context, message);
|
||||
}
|
||||
|
||||
return defaultWidget.copyWith(
|
||||
onThreadTap: onThreadTap ?? this.onThreadTap,
|
||||
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
|
||||
deletedBottomRowBuilder:
|
||||
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
|
||||
);
|
||||
};
|
||||
|
||||
return StreamMessageWidget(
|
||||
key: key ?? this.key,
|
||||
onMentionTap: onMentionTap ?? this.onMentionTap,
|
||||
@@ -595,10 +659,7 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
editMessageInputBuilder:
|
||||
editMessageInputBuilder ?? this.editMessageInputBuilder,
|
||||
textBuilder: textBuilder ?? this.textBuilder,
|
||||
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
|
||||
bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder,
|
||||
deletedBottomRowBuilder:
|
||||
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
|
||||
bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget,
|
||||
onMessageActions: onMessageActions ?? this.onMessageActions,
|
||||
message: message ?? this.message,
|
||||
messageTheme: messageTheme ?? this.messageTheme,
|
||||
@@ -652,6 +713,8 @@ class StreamMessageWidget extends StatefulWidget {
|
||||
this.imageAttachmentThumbnailResizeType,
|
||||
imageAttachmentThumbnailCropType: imageAttachmentThumbnailCropType ??
|
||||
this.imageAttachmentThumbnailCropType,
|
||||
attachmentActionsModalBuilder:
|
||||
attachmentActionsModalBuilder ?? this.attachmentActionsModalBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -838,52 +901,69 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
|
||||
? Alignment.centerRight
|
||||
: Alignment.centerLeft,
|
||||
widthFactor: widget.widthFactor,
|
||||
child: MessageWidgetContent(
|
||||
streamChatTheme: _streamChatTheme,
|
||||
showUsername: showUsername,
|
||||
showTimeStamp: showTimeStamp,
|
||||
showThreadReplyIndicator: showThreadReplyIndicator,
|
||||
showSendingIndicator: showSendingIndicator,
|
||||
showInChannel: showInChannel,
|
||||
isGiphy: isGiphy,
|
||||
isOnlyEmoji: isOnlyEmoji,
|
||||
hasUrlAttachments: hasUrlAttachments,
|
||||
messageTheme: widget.messageTheme,
|
||||
reverse: widget.reverse,
|
||||
message: widget.message,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
shouldShowReactions: shouldShowReactions,
|
||||
hasQuotedMessage: hasQuotedMessage,
|
||||
textPadding: widget.textPadding,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
attachmentPadding: widget.attachmentPadding,
|
||||
avatarWidth: avatarWidth,
|
||||
bottomRowPadding: bottomRowPadding,
|
||||
isFailedState: isFailedState,
|
||||
isPinned: isPinned,
|
||||
messageWidget: widget,
|
||||
showBottomRow: showBottomRow,
|
||||
showPinHighlight: widget.showPinHighlight,
|
||||
showReactionPickerIndicator:
|
||||
widget.showReactionPickerIndicator,
|
||||
showReactions: showReactions,
|
||||
showUserAvatar: widget.showUserAvatar,
|
||||
streamChat: _streamChat,
|
||||
translateUserAvatar: widget.translateUserAvatar,
|
||||
deletedBottomRowBuilder: widget.deletedBottomRowBuilder,
|
||||
onThreadTap: widget.onThreadTap,
|
||||
shape: widget.shape,
|
||||
borderSide: widget.borderSide,
|
||||
borderRadiusGeometry: widget.borderRadiusGeometry,
|
||||
textBuilder: widget.textBuilder,
|
||||
onLinkTap: widget.onLinkTap,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
onQuotedMessageTap: widget.onQuotedMessageTap,
|
||||
bottomRowBuilder: widget.bottomRowBuilder,
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
userAvatarBuilder: widget.userAvatarBuilder,
|
||||
usernameBuilder: widget.usernameBuilder,
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
var _bottomRowBuilderWithDefaultWidget =
|
||||
widget.bottomRowBuilderWithDefaultWidget;
|
||||
|
||||
_bottomRowBuilderWithDefaultWidget ??=
|
||||
(context, message, defaultWidget) {
|
||||
final _bottomRowBuilder = widget.bottomRowBuilder;
|
||||
if (_bottomRowBuilder != null) {
|
||||
return _bottomRowBuilder(context, message);
|
||||
}
|
||||
|
||||
return defaultWidget.copyWith(
|
||||
onThreadTap: widget.onThreadTap,
|
||||
usernameBuilder: widget.usernameBuilder,
|
||||
deletedBottomRowBuilder: widget.deletedBottomRowBuilder,
|
||||
);
|
||||
};
|
||||
|
||||
return MessageWidgetContent(
|
||||
streamChatTheme: _streamChatTheme,
|
||||
showUsername: showUsername,
|
||||
showTimeStamp: showTimeStamp,
|
||||
showThreadReplyIndicator: showThreadReplyIndicator,
|
||||
showSendingIndicator: showSendingIndicator,
|
||||
showInChannel: showInChannel,
|
||||
isGiphy: isGiphy,
|
||||
isOnlyEmoji: isOnlyEmoji,
|
||||
hasUrlAttachments: hasUrlAttachments,
|
||||
messageTheme: widget.messageTheme,
|
||||
reverse: widget.reverse,
|
||||
message: widget.message,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
shouldShowReactions: shouldShowReactions,
|
||||
hasQuotedMessage: hasQuotedMessage,
|
||||
textPadding: widget.textPadding,
|
||||
attachmentBuilders: widget.attachmentBuilders,
|
||||
attachmentPadding: widget.attachmentPadding,
|
||||
avatarWidth: avatarWidth,
|
||||
bottomRowPadding: bottomRowPadding,
|
||||
isFailedState: isFailedState,
|
||||
isPinned: isPinned,
|
||||
messageWidget: widget,
|
||||
showBottomRow: showBottomRow,
|
||||
showPinHighlight: widget.showPinHighlight,
|
||||
showReactionPickerIndicator:
|
||||
widget.showReactionPickerIndicator,
|
||||
showReactions: showReactions,
|
||||
showUserAvatar: widget.showUserAvatar,
|
||||
streamChat: _streamChat,
|
||||
translateUserAvatar: widget.translateUserAvatar,
|
||||
shape: widget.shape,
|
||||
borderSide: widget.borderSide,
|
||||
borderRadiusGeometry: widget.borderRadiusGeometry,
|
||||
textBuilder: widget.textBuilder,
|
||||
onLinkTap: widget.onLinkTap,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
onQuotedMessageTap: widget.onQuotedMessageTap,
|
||||
bottomRowBuilderWithDefaultWidget:
|
||||
_bottomRowBuilderWithDefaultWidget,
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
userAvatarBuilder: widget.userAvatarBuilder,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,18 @@ import 'package:stream_chat_flutter/src/message_widget/message_widget_content_co
|
||||
import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// Signature for the builder function that will be called when the message
|
||||
/// bottom row is built. Includes the [Message].
|
||||
typedef BottomRowBuilder = Widget Function(BuildContext, Message);
|
||||
|
||||
/// Signature for the builder function that will be called when the message
|
||||
/// bottom row is built. Includes the [Message] and the default [BottomRow].
|
||||
typedef BottomRowBuilderWithDefaultWidget = Widget Function(
|
||||
BuildContext,
|
||||
Message,
|
||||
BottomRow,
|
||||
);
|
||||
|
||||
/// {@template messageWidgetContent}
|
||||
/// The main content of a [StreamMessageWidget].
|
||||
///
|
||||
@@ -51,12 +63,28 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
this.onMentionTap,
|
||||
this.onLinkTap,
|
||||
this.textBuilder,
|
||||
this.bottomRowBuilder,
|
||||
this.onThreadTap,
|
||||
this.deletedBottomRowBuilder,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.bottomRowBuilder,
|
||||
this.bottomRowBuilderWithDefaultWidget,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.onThreadTap,
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.deletedBottomRowBuilder,
|
||||
this.userAvatarBuilder,
|
||||
this.usernameBuilder,
|
||||
});
|
||||
@Deprecated('''
|
||||
Use [bottomRowBuilderWithDefaultWidget] instead.
|
||||
Will be removed in the next major version.
|
||||
''') this.usernameBuilder,
|
||||
}) : assert(
|
||||
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
|
||||
'You can only use one of the two bottom row builders',
|
||||
);
|
||||
|
||||
/// {@macro reverse}
|
||||
final bool reverse;
|
||||
@@ -152,7 +180,10 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
final double bottomRowPadding;
|
||||
|
||||
/// {@macro bottomRowBuilder}
|
||||
final Widget Function(BuildContext, Message)? bottomRowBuilder;
|
||||
final BottomRowBuilder? bottomRowBuilder;
|
||||
|
||||
/// {@macro bottomRowBuilderWithDefaultWidget}
|
||||
final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget;
|
||||
|
||||
/// {@macro showInChannelIndicator}
|
||||
final bool showInChannel;
|
||||
@@ -207,30 +238,7 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
right: reverse ? bottomRowPadding : 0,
|
||||
bottom: isPinned && showPinHighlight ? 6.0 : 0.0,
|
||||
),
|
||||
child: bottomRowBuilder?.call(
|
||||
context,
|
||||
message,
|
||||
) ??
|
||||
BottomRow(
|
||||
message: message,
|
||||
reverse: reverse,
|
||||
messageTheme: messageTheme,
|
||||
hasUrlAttachments: hasUrlAttachments,
|
||||
isOnlyEmoji: isOnlyEmoji,
|
||||
isDeleted: message.isDeleted,
|
||||
isGiphy: isGiphy,
|
||||
showInChannel: showInChannel,
|
||||
showSendingIndicator: showSendingIndicator,
|
||||
showThreadReplyIndicator: showThreadReplyIndicator,
|
||||
showTimeStamp: showTimeStamp,
|
||||
showUsername: showUsername,
|
||||
streamChatTheme: streamChatTheme,
|
||||
onThreadTap: onThreadTap,
|
||||
deletedBottomRowBuilder: deletedBottomRowBuilder,
|
||||
streamChat: streamChat,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
usernameBuilder: usernameBuilder,
|
||||
),
|
||||
child: _buildBottomRow(context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
@@ -457,4 +465,39 @@ class MessageWidgetContent extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomRow(BuildContext context) {
|
||||
final defaultWidget = BottomRow(
|
||||
message: message,
|
||||
reverse: reverse,
|
||||
messageTheme: messageTheme,
|
||||
hasUrlAttachments: hasUrlAttachments,
|
||||
isOnlyEmoji: isOnlyEmoji,
|
||||
isDeleted: message.isDeleted,
|
||||
isGiphy: isGiphy,
|
||||
showInChannel: showInChannel,
|
||||
showSendingIndicator: showSendingIndicator,
|
||||
showThreadReplyIndicator: showThreadReplyIndicator,
|
||||
showTimeStamp: showTimeStamp,
|
||||
showUsername: showUsername,
|
||||
streamChatTheme: streamChatTheme,
|
||||
onThreadTap: onThreadTap,
|
||||
deletedBottomRowBuilder: deletedBottomRowBuilder,
|
||||
streamChat: streamChat,
|
||||
hasNonUrlAttachments: hasNonUrlAttachments,
|
||||
usernameBuilder: usernameBuilder,
|
||||
);
|
||||
|
||||
if (bottomRowBuilder != null) {
|
||||
return bottomRowBuilder!(context, message);
|
||||
} else if (bottomRowBuilderWithDefaultWidget != null) {
|
||||
return bottomRowBuilderWithDefaultWidget!(
|
||||
context,
|
||||
message,
|
||||
defaultWidget,
|
||||
);
|
||||
}
|
||||
|
||||
return defaultWidget;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-39
@@ -2,6 +2,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reaction_bubble.dart';
|
||||
import 'package:stream_chat_flutter/src/message_widget/reactions/reactions_align.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@template streamMessageReactionsModal}
|
||||
@@ -39,35 +40,13 @@ class StreamMessageReactionsModal extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final user = StreamChat.of(context).currentUser;
|
||||
final _userPermissions = StreamChannel.of(context).channel.ownCapabilities;
|
||||
final orientation = MediaQuery.of(context).orientation;
|
||||
|
||||
final hasReactionPermission =
|
||||
_userPermissions.contains(PermissionType.sendReaction);
|
||||
|
||||
final roughMaxSize = size.width * 2 / 3;
|
||||
var messageTextLength = message.text!.length;
|
||||
if (message.quotedMessage != null) {
|
||||
var quotedMessageLength = message.quotedMessage!.text!.length + 40;
|
||||
if (message.quotedMessage!.attachments.isNotEmpty) {
|
||||
quotedMessageLength += 40;
|
||||
}
|
||||
if (quotedMessageLength > messageTextLength) {
|
||||
messageTextLength = quotedMessageLength;
|
||||
}
|
||||
}
|
||||
final roughSentenceSize = messageTextLength *
|
||||
(messageTheme.messageTextStyle?.fontSize ?? 1) *
|
||||
1.2;
|
||||
final divFactor = message.attachments.isNotEmpty
|
||||
? 1
|
||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||
|
||||
final numberOfReactions =
|
||||
StreamChatConfiguration.of(context).reactionIcons.length;
|
||||
final shiftFactor =
|
||||
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
|
||||
final fontSize = messageTheme.messageTextStyle?.fontSize;
|
||||
|
||||
final child = Center(
|
||||
child: SingleChildScrollView(
|
||||
@@ -79,22 +58,26 @@ class StreamMessageReactionsModal extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
if ((showReactions ?? hasReactionPermission) &&
|
||||
(message.status == MessageSendingStatus.sent))
|
||||
Align(
|
||||
alignment: Alignment(
|
||||
user!.id == message.user!.id
|
||||
? (divFactor >= 1.0
|
||||
? -0.2 - shiftFactor
|
||||
: (1.2 - divFactor))
|
||||
: (divFactor >= 1.0
|
||||
? shiftFactor + 0.2
|
||||
: -(1.2 - divFactor)),
|
||||
0,
|
||||
),
|
||||
child: StreamReactionPicker(
|
||||
message: message,
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Align(
|
||||
alignment: Alignment(
|
||||
calculateReactionsHorizontalAlignment(
|
||||
user,
|
||||
message,
|
||||
constraints,
|
||||
fontSize,
|
||||
orientation,
|
||||
),
|
||||
0,
|
||||
),
|
||||
child: StreamReactionPicker(
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 10),
|
||||
IgnorePointer(
|
||||
child: messageWidget,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// This method calculates the align that the modal of reactions should have.
|
||||
/// This is an approximation based on the size of the message and the
|
||||
/// available space in the screen.
|
||||
double calculateReactionsHorizontalAlignment(
|
||||
User? user,
|
||||
Message message,
|
||||
BoxConstraints constraints,
|
||||
double? fontSize,
|
||||
Orientation orientation,
|
||||
) {
|
||||
final maxWidth = constraints.maxWidth;
|
||||
|
||||
final roughSentenceSize = message.roughMessageSize(fontSize);
|
||||
final hasAttachments = message.attachments.isNotEmpty;
|
||||
final isReply = message.quotedMessageId != null;
|
||||
final isAttachment = hasAttachments && !isReply;
|
||||
|
||||
// divFactor is the percentage of the available space that the message takes.
|
||||
// When the divFactor is bigger than 0.5 that means that the messages is
|
||||
// bigger than 50% of the available space and the modal should have an offset
|
||||
// in the direction that the message grows. When the divFactor is smaller
|
||||
// than 0.5 then the offset should be to he side opposite of the message
|
||||
// growth.
|
||||
// In resume, when divFactor > 0.5 then result > 0, when divFactor < 0.5
|
||||
// then result < 0.
|
||||
var divFactor = 0.5;
|
||||
|
||||
// When in portrait, attachments normally take 75% of the screen, when in
|
||||
// landscape, attachments normally take 50% of the screen.
|
||||
if (isAttachment) {
|
||||
if (orientation == Orientation.portrait) {
|
||||
divFactor = 0.75;
|
||||
} else {
|
||||
divFactor = 0.5;
|
||||
}
|
||||
} else {
|
||||
divFactor = roughSentenceSize == 0 ? 0.5 : (roughSentenceSize / maxWidth);
|
||||
}
|
||||
|
||||
final signal = user?.id == message.user?.id ? 1 : -1;
|
||||
final result = signal * (1 - divFactor * 2.0);
|
||||
|
||||
return _capResult(result);
|
||||
}
|
||||
|
||||
// Ensure reactions don't get pushed past the edge of the screen.
|
||||
//
|
||||
// This happens if divFactor is really big. When this happens, we can simply
|
||||
// move the model all the way to the end of screen.
|
||||
double _capResult(double result) {
|
||||
if (result > 1.0) {
|
||||
return 1;
|
||||
} else if (result < -1.0) {
|
||||
return -1;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1088,7 +1088,12 @@ class StreamSvgIcon extends StatelessWidget {
|
||||
key: key,
|
||||
width: width,
|
||||
height: height,
|
||||
color: color,
|
||||
colorFilter: color != null
|
||||
? ColorFilter.mode(
|
||||
color!,
|
||||
BlendMode.srcIn,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +149,8 @@ class StreamThreadHeader extends StatelessWidget
|
||||
final theme = Theme.of(context);
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
toolbarTextStyle: theme.textTheme.bodyText2,
|
||||
titleTextStyle: theme.textTheme.headline6,
|
||||
toolbarTextStyle: theme.textTheme.bodyMedium,
|
||||
titleTextStyle: theme.textTheme.titleLarge,
|
||||
systemOverlayStyle: theme.brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark,
|
||||
|
||||
+1
-1
@@ -409,7 +409,7 @@ class StreamChannelListErrorWidget extends StatelessWidget {
|
||||
TextSpan(text: context.translations.loadingChannelsError),
|
||||
],
|
||||
),
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onPressed,
|
||||
|
||||
+22
-20
@@ -122,7 +122,8 @@ class StreamMessageSearchListTile extends StatelessWidget {
|
||||
final title = this.title ??
|
||||
MessageSearchListTileTitle(
|
||||
messageResponse: messageResponse,
|
||||
textStyle: channelPreviewTheme.titleStyle,
|
||||
textStyle: channelPreviewTheme.titleStyle
|
||||
?.copyWith(overflow: TextOverflow.ellipsis),
|
||||
);
|
||||
|
||||
final subtitle = this.subtitle ??
|
||||
@@ -177,27 +178,28 @@ class MessageSearchListTileTitle extends StatelessWidget {
|
||||
final channel = messageResponse.channel;
|
||||
final channelName = channel?.extraData['name'];
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
user.id == StreamChat.of(context).currentUser?.id
|
||||
? context.translations.youText
|
||||
: user.name,
|
||||
style: textStyle,
|
||||
),
|
||||
if (channelName != null) ...[
|
||||
Text(
|
||||
' ${context.translations.inText} ',
|
||||
style: textStyle?.copyWith(
|
||||
fontWeight: FontWeight.normal,
|
||||
return Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: user.id == StreamChat.of(context).currentUser?.id
|
||||
? context.translations.youText
|
||||
: user.name,
|
||||
),
|
||||
if (channelName != null) ...[
|
||||
TextSpan(
|
||||
text: ' ${context.translations.inText} ',
|
||||
style: textStyle?.copyWith(
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
channelName as String,
|
||||
style: textStyle,
|
||||
),
|
||||
TextSpan(
|
||||
text: channelName as String,
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
style: textStyle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -366,7 +366,7 @@ class StreamPhotoGallery extends StatelessWidget {
|
||||
color: chatThemeData.colorTheme.disabled,
|
||||
),
|
||||
emptyTitle: Text(
|
||||
context.translations.noUsersLabel,
|
||||
context.translations.noPhotoOrVideoLabel,
|
||||
style: chatThemeData.textTheme.headline,
|
||||
),
|
||||
),
|
||||
@@ -377,7 +377,7 @@ class StreamPhotoGallery extends StatelessWidget {
|
||||
return StreamScrollViewLoadMoreError.grid(
|
||||
onTap: controller.retry,
|
||||
error: Text(
|
||||
context.translations.loadingUsersError,
|
||||
context.translations.genericErrorText,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
@@ -400,7 +400,7 @@ class StreamPhotoGallery extends StatelessWidget {
|
||||
return errorBuilder?.call(context, error) ??
|
||||
Center(
|
||||
child: StreamScrollViewErrorWidget(
|
||||
errorTitle: Text(context.translations.loadingUsersError),
|
||||
errorTitle: Text(context.translations.genericErrorText),
|
||||
onRetryPressed: controller.refresh,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
this.avatarTheme,
|
||||
this.createdAtStyle,
|
||||
this.linkBackgroundColor,
|
||||
this.urlAttachmentTitleMaxLine,
|
||||
});
|
||||
|
||||
/// Text style for message text
|
||||
@@ -59,6 +60,9 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
/// Background color for messages with url attachments.
|
||||
final Color? linkBackgroundColor;
|
||||
|
||||
/// Max number of lines in Url link title
|
||||
final int? urlAttachmentTitleMaxLine;
|
||||
|
||||
/// Copy with a theme
|
||||
StreamMessageThemeData copyWith({
|
||||
TextStyle? messageTextStyle,
|
||||
@@ -73,6 +77,7 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
Color? reactionsBorderColor,
|
||||
Color? reactionsMaskColor,
|
||||
Color? linkBackgroundColor,
|
||||
int? urlAttachmentTitleMaxLine,
|
||||
}) {
|
||||
return StreamMessageThemeData(
|
||||
messageTextStyle: messageTextStyle ?? this.messageTextStyle,
|
||||
@@ -89,6 +94,8 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
|
||||
reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor,
|
||||
linkBackgroundColor: linkBackgroundColor ?? this.linkBackgroundColor,
|
||||
urlAttachmentTitleMaxLine:
|
||||
urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,6 +155,7 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
reactionsBorderColor: other.reactionsBorderColor,
|
||||
reactionsMaskColor: other.reactionsMaskColor,
|
||||
linkBackgroundColor: other.linkBackgroundColor,
|
||||
urlAttachmentTitleMaxLine: other.urlAttachmentTitleMaxLine,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,7 +175,8 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
reactionsBorderColor == other.reactionsBorderColor &&
|
||||
reactionsMaskColor == other.reactionsMaskColor &&
|
||||
avatarTheme == other.avatarTheme &&
|
||||
linkBackgroundColor == other.linkBackgroundColor;
|
||||
linkBackgroundColor == other.linkBackgroundColor &&
|
||||
urlAttachmentTitleMaxLine == other.urlAttachmentTitleMaxLine;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -182,7 +191,8 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
reactionsBorderColor.hashCode ^
|
||||
reactionsMaskColor.hashCode ^
|
||||
avatarTheme.hashCode ^
|
||||
linkBackgroundColor.hashCode;
|
||||
linkBackgroundColor.hashCode ^
|
||||
urlAttachmentTitleMaxLine.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
@@ -199,6 +209,10 @@ class StreamMessageThemeData with Diagnosticable {
|
||||
..add(ColorProperty('reactionsBackgroundColor', reactionsBackgroundColor))
|
||||
..add(ColorProperty('reactionsBorderColor', reactionsBorderColor))
|
||||
..add(ColorProperty('reactionsMaskColor', reactionsMaskColor))
|
||||
..add(ColorProperty('linkBackgroundColor', linkBackgroundColor));
|
||||
..add(ColorProperty('linkBackgroundColor', linkBackgroundColor))
|
||||
..add(DiagnosticsProperty(
|
||||
'urlAttachmentTitleMaxLine',
|
||||
urlAttachmentTitleMaxLine,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:diacritic/diacritic.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -12,6 +14,16 @@ extension StringExtension on String {
|
||||
String capitalize() =>
|
||||
isNotEmpty ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : '';
|
||||
|
||||
/// Returns the biggest line of a text.
|
||||
String biggestLine() {
|
||||
if (contains('\n')) {
|
||||
return split('\n')
|
||||
.reduce((curr, next) => curr.length > next.length ? curr : next);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the string contains only emoji's or not.
|
||||
///
|
||||
/// Emojis guidelines
|
||||
@@ -353,6 +365,33 @@ extension MessageX on Message {
|
||||
return copyWith(text: messageTextToRender);
|
||||
}
|
||||
|
||||
/// Returns an approximation of message size
|
||||
double roughMessageSize(double? fontSize) {
|
||||
var messageTextLength = min(text!.biggestLine().length, 65);
|
||||
|
||||
if (quotedMessage != null) {
|
||||
var quotedMessageLength =
|
||||
(min(quotedMessage!.text?.biggestLine().length ?? 0, 65)) + 8;
|
||||
|
||||
if (quotedMessage!.attachments.isNotEmpty) {
|
||||
quotedMessageLength += 8;
|
||||
}
|
||||
|
||||
if (quotedMessageLength > messageTextLength * 1.2) {
|
||||
messageTextLength = quotedMessageLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Quoted message have a smaller font, so it is necessary to reduce the
|
||||
// size of the multiplier to count for the smaller font.
|
||||
var multiplier = 0.55;
|
||||
if (quotedMessage != null) {
|
||||
multiplier = 0.45;
|
||||
}
|
||||
|
||||
return messageTextLength * (fontSize ?? 1) * multiplier;
|
||||
}
|
||||
|
||||
/// It returns the message with the translated text if available locally
|
||||
Message translate(String language) =>
|
||||
copyWith(text: i18n?['${language}_text'] ?? text);
|
||||
|
||||
@@ -21,6 +21,9 @@ class _IVideoService {
|
||||
///
|
||||
/// Thumbnails are not supported on Web at this time.
|
||||
///
|
||||
/// If no [video] path is supplied, or if a thumbnail cannot be generated,
|
||||
/// returns [generatePlaceholderThumbnail]. A stock placeholder image.
|
||||
///
|
||||
/// For desktop, you can specify the position of the video to generate
|
||||
/// the thumbnail.
|
||||
///
|
||||
@@ -29,14 +32,14 @@ class _IVideoService {
|
||||
/// creates lower quality of the thumbnail image, but it gets ignored for
|
||||
/// PNG format.
|
||||
Future<Uint8List?> generateVideoThumbnail({
|
||||
required String video,
|
||||
String? video,
|
||||
ImageFormat imageFormat = ImageFormat.PNG,
|
||||
int maxHeight = 0,
|
||||
int maxWidth = 0,
|
||||
int timeMs = 0,
|
||||
int quality = 10,
|
||||
}) async {
|
||||
if (kIsWeb) {
|
||||
if (kIsWeb || video == null) {
|
||||
final placeholder = await generatePlaceholderThumbnail();
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:video_thumbnail/video_thumbnail.dart';
|
||||
|
||||
/// {@template streamVideoThumbnailImage}
|
||||
/// Displays a video thumbnail for video attachments in a message.
|
||||
/// Displays a video thumbnail for video attachments.
|
||||
///
|
||||
/// [thumbUrl] is used if provided.
|
||||
///
|
||||
/// Else [video] (path to local or remote video) is used to generate
|
||||
/// a thumbnail from the video asset.
|
||||
///
|
||||
/// WARNING! a local path does not work on web.
|
||||
///
|
||||
/// If both [thumbUrl] and [video] are null, or if a thumbnail can't be
|
||||
/// generated, a stock default image will be used.
|
||||
/// {@endtemplate}
|
||||
class StreamVideoThumbnailImage extends StatefulWidget {
|
||||
/// {@macro streamVideoThumbnailImage}
|
||||
const StreamVideoThumbnailImage({
|
||||
super.key,
|
||||
required this.video,
|
||||
this.video,
|
||||
this.thumbUrl,
|
||||
this.constraints,
|
||||
this.fit = BoxFit.cover,
|
||||
@@ -24,7 +34,7 @@ class StreamVideoThumbnailImage extends StatefulWidget {
|
||||
});
|
||||
|
||||
/// Video path or url
|
||||
final String video;
|
||||
final String? video;
|
||||
|
||||
/// Video thumbnail url
|
||||
final String? thumbUrl;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:stream_chat_flutter/src/video/vlc/vlc_stub.dart'
|
||||
if (dart.library.io) 'vlc_manager_desktop.dart'
|
||||
if (dar.library.html) 'vlc_manager_web.dart';
|
||||
if (dart.library.html) 'vlc_manager_web.dart';
|
||||
|
||||
/// {@template vlcManager}
|
||||
/// An abstract class for the purpose of ensuring Flutter applications that
|
||||
|
||||
Reference in New Issue
Block a user