diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index f5cc5482..35989a72 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -912,7 +912,10 @@ class Channel { void _initState(ChannelState channelState) { state = ChannelClientState(this, channelState); - client.state.channels[cid!] = this; + + if (cid != null) { + client.state.channels[cid!] = this; + } if (!_initializedCompleter.isCompleted) { _initializedCompleter.complete(true); } diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 04bb8ae1..12820f8c 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -798,11 +798,13 @@ class StreamChatClient { for (final channelState in channelStates) { final channel = channels[channelState.channel!.cid]; if (channel != null) { - channel.state!.updateChannelState(channelState); + channel.state?.updateChannelState(channelState); newChannels.add(channel); } else { final newChannel = Channel.fromState(this, channelState); - channels[newChannel.cid!] = newChannel; + if (newChannel.cid != null) { + channels[newChannel.cid!] = newChannel; + } newChannels.add(newChannel); } } @@ -1513,7 +1515,7 @@ class ClientState { /// The current list of channels in memory Map get channels => _channelsController.value!; - set channels(Map? v) { + set channels(Map v) { if (v != null) _channelsController.add(v); } diff --git a/packages/stream_chat/lib/src/models/attachment.dart b/packages/stream_chat/lib/src/models/attachment.dart index 1bba82e7..09ea4f58 100644 --- a/packages/stream_chat/lib/src/models/attachment.dart +++ b/packages/stream_chat/lib/src/models/attachment.dart @@ -33,7 +33,7 @@ class Attachment extends Equatable { this.authorIcon, this.assetUrl, List? actions, - this.extraData, + this.extraData = const {}, this.file, UploadState? uploadState, }) : id = id ?? const Uuid().v4(), @@ -110,8 +110,11 @@ class Attachment extends Equatable { late final UploadState uploadState; /// Map of custom channel extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; /// The attachment ID. /// diff --git a/packages/stream_chat/lib/src/models/attachment.g.dart b/packages/stream_chat/lib/src/models/attachment.g.dart index d7e76e74..c42aaf5f 100644 --- a/packages/stream_chat/lib/src/models/attachment.g.dart +++ b/packages/stream_chat/lib/src/models/attachment.g.dart @@ -31,8 +31,9 @@ Attachment _$AttachmentFromJson(Map json) { .toList() ?? [], extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, file: json['file'] == null ? null : AttachmentFile.fromJson(json['file'] as Map), @@ -71,7 +72,7 @@ Map _$AttachmentToJson(Attachment instance) { val['actions'] = instance.actions.map((e) => e.toJson()).toList(); writeNotNull('file', instance.file?.toJson()); val['upload_state'] = instance.uploadState.toJson(); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; val['id'] = instance.id; return val; } diff --git a/packages/stream_chat/lib/src/models/attachment_file.dart b/packages/stream_chat/lib/src/models/attachment_file.dart index d72cecbd..8dbb70ad 100644 --- a/packages/stream_chat/lib/src/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/models/attachment_file.dart @@ -59,10 +59,10 @@ String? _toString(Uint8List? bytes) { class AttachmentFile { /// Creates a new [AttachmentFile] instance. const AttachmentFile({ + required this.size, this.path, this.name, this.bytes, - this.size, }) : assert( path != null || bytes != null, 'Either path or bytes should be != null', diff --git a/packages/stream_chat/lib/src/models/channel_model.dart b/packages/stream_chat/lib/src/models/channel_model.dart index 95145296..98d86539 100644 --- a/packages/stream_chat/lib/src/models/channel_model.dart +++ b/packages/stream_chat/lib/src/models/channel_model.dart @@ -21,7 +21,7 @@ class ChannelModel { DateTime? updatedAt, this.deletedAt, this.memberCount = 0, - this.extraData, + this.extraData = const {}, this.team, }) : assert( (cid != null && cid.contains(':')) || (id != null && type != null), @@ -83,8 +83,11 @@ class ChannelModel { final int memberCount; /// Map of custom channel extraData - @JsonKey(includeIfNull: false) - final Map? extraData; + @JsonKey( + includeIfNull: false, + defaultValue: {}, + ) + final Map extraData; /// The team the channel belongs to @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) @@ -108,9 +111,8 @@ class ChannelModel { ]; /// Shortcut for channel name - String get name => extraData?.containsKey('name') == true - ? extraData!['name'] as String - : cid; + String get name => + extraData.containsKey('name') ? extraData['name'] as String : cid; /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( diff --git a/packages/stream_chat/lib/src/models/channel_model.g.dart b/packages/stream_chat/lib/src/models/channel_model.g.dart index 47f23071..78e93910 100644 --- a/packages/stream_chat/lib/src/models/channel_model.g.dart +++ b/packages/stream_chat/lib/src/models/channel_model.g.dart @@ -32,8 +32,9 @@ ChannelModel _$ChannelModelFromJson(Map json) { : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, team: json['team'] as String?, ); } @@ -59,7 +60,7 @@ Map _$ChannelModelToJson(ChannelModel instance) { writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; writeNotNull('team', readonly(instance.team)); return val; } diff --git a/packages/stream_chat/lib/src/models/event.dart b/packages/stream_chat/lib/src/models/event.dart index 1d070230..1675197d 100644 --- a/packages/stream_chat/lib/src/models/event.dart +++ b/packages/stream_chat/lib/src/models/event.dart @@ -193,7 +193,7 @@ class EventChannel extends ChannelModel { updatedAt: updatedAt, deletedAt: deletedAt, memberCount: memberCount, - extraData: extraData, + extraData: extraData ?? {}, ); /// Create a new instance from a json diff --git a/packages/stream_chat/lib/src/models/event.g.dart b/packages/stream_chat/lib/src/models/event.g.dart index 2715b476..b76c675c 100644 --- a/packages/stream_chat/lib/src/models/event.g.dart +++ b/packages/stream_chat/lib/src/models/event.g.dart @@ -90,8 +90,9 @@ EventChannel _$EventChannelFromJson(Map json) { : DateTime.parse(json['deleted_at'] as String), memberCount: json['member_count'] as int? ?? 0, extraData: (json['extra_data'] as Map?)?.map( - (k, e) => MapEntry(k, e as Object), - ), + (k, e) => MapEntry(k, e as Object), + ) ?? + {}, ); } @@ -116,7 +117,7 @@ Map _$EventChannelToJson(EventChannel instance) { writeNotNull('updated_at', readonly(instance.updatedAt)); writeNotNull('deleted_at', readonly(instance.deletedAt)); writeNotNull('member_count', readonly(instance.memberCount)); - writeNotNull('extra_data', instance.extraData); + val['extra_data'] = instance.extraData; val['members'] = instance.members?.map((e) => e.toJson()).toList(); return val; } diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart index 176bbbd3..0ad06cc0 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_title.dart @@ -6,12 +6,12 @@ import '../utils.dart'; class AttachmentTitle extends StatelessWidget { const AttachmentTitle({ - Key key, - @required this.attachment, - @required this.messageTheme, + Key? key, + required this.attachment, + required this.messageTheme, }) : super(key: key); - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final Attachment attachment; @override @@ -19,7 +19,7 @@ class AttachmentTitle extends StatelessWidget { return GestureDetector( onTap: () { if (attachment.titleLink != null) { - launchURL(context, attachment.titleLink); + launchURL(context, attachment.titleLink!); } }, child: Padding( @@ -28,17 +28,18 @@ class AttachmentTitle extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - attachment.title, - overflow: TextOverflow.ellipsis, - style: messageTheme.messageText.copyWith( - color: StreamChatTheme.of(context).colorTheme.accentBlue, - fontWeight: FontWeight.bold, + if (attachment.title != null) + Text( + attachment.title!, + overflow: TextOverflow.ellipsis, + style: messageTheme?.messageText?.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentBlue, + fontWeight: FontWeight.bold, + ), ), - ), if (attachment.titleLink != null || attachment.ogScrapeUrl != null) Text( - Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) + Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl!) .authority .split('.') .reversed @@ -46,7 +47,7 @@ class AttachmentTitle extends StatelessWidget { .toList() .reversed .join('.'), - style: messageTheme.messageText, + style: messageTheme?.messageText, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index 2425b966..2aa71516 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -8,55 +8,52 @@ typedef FailedBuilder = Widget Function(BuildContext, String); class AttachmentUploadStateBuilder extends StatelessWidget { final Message message; final Attachment attachment; - final FailedBuilder failedBuilder; - final WidgetBuilder successBuilder; - final InProgressBuilder inProgressBuilder; - final WidgetBuilder preparingBuilder; + final FailedBuilder? failedBuilder; + final WidgetBuilder? successBuilder; + final InProgressBuilder? inProgressBuilder; + final WidgetBuilder? preparingBuilder; const AttachmentUploadStateBuilder({ - Key key, - @required this.message, - @required this.attachment, + Key? key, + required this.message, + required this.attachment, this.failedBuilder, this.successBuilder, this.inProgressBuilder, this.preparingBuilder, - }) : assert(message != null), - assert(attachment != null), - super(key: key); + }) : super(key: key); @override Widget build(BuildContext context) { - if (message.status == null || message.status == MessageSendingStatus.sent) { + if (message.status == MessageSendingStatus.sent) { return Offstage(); } final messageId = message.id; final attachmentId = attachment.id; - var inProgress = inProgressBuilder; - inProgress ??= (context, int sent, int total) { - return _InProgressState( - sent: sent, - total: total, - attachmentId: attachmentId, - ); - }; + final inProgress = inProgressBuilder ?? + (context, int sent, int total) { + return _InProgressState( + sent: sent, + total: total, + attachmentId: attachmentId, + ); + }; - var failed = failedBuilder; - failed ??= (context, error) { - return _FailedState( - error: error, - messageId: messageId, - attachmentId: attachmentId, - ); - }; + final failed = failedBuilder ?? + (context, error) { + return _FailedState( + error: error, + messageId: messageId, + attachmentId: attachmentId, + ); + }; - var success = successBuilder; - success ??= (context) => _SuccessState(); + final success = successBuilder ?? (context) => _SuccessState(); - var preparing = preparingBuilder; - preparing ??= (context) => _PreparingState(attachmentId: attachmentId); + final preparing = preparingBuilder ?? + (context) => _PreparingState(attachmentId: attachmentId); return attachment.uploadState.when( preparing: () => preparing(context), @@ -68,13 +65,13 @@ class AttachmentUploadStateBuilder extends StatelessWidget { } class _IconButton extends StatelessWidget { - final Widget icon; + final Widget? icon; final double iconSize; - final VoidCallback onPressed; - final Color fillColor; + final VoidCallback? onPressed; + final Color? fillColor; const _IconButton({ - Key key, + Key? key, this.icon, this.iconSize = 24.0, this.onPressed, @@ -95,7 +92,9 @@ class _IconButton extends StatelessWidget { onPressed: onPressed, fillColor: fillColor ?? StreamChatTheme.of(context).colorTheme.overlayDark, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), child: icon, ), ); @@ -106,8 +105,8 @@ class _PreparingState extends StatelessWidget { final String attachmentId; const _PreparingState({ - Key key, - @required this.attachmentId, + Key? key, + required this.attachmentId, }) : super(key: key); @override @@ -144,10 +143,10 @@ class _InProgressState extends StatelessWidget { final String attachmentId; const _InProgressState({ - Key key, - @required this.sent, - @required this.total, - @required this.attachmentId, + Key? key, + required this.sent, + required this.total, + required this.attachmentId, }) : super(key: key); @override @@ -179,15 +178,15 @@ class _InProgressState extends StatelessWidget { } class _FailedState extends StatelessWidget { - final String error; + final String? error; final String messageId; final String attachmentId; const _FailedState({ - Key key, + Key? key, this.error, - @required this.messageId, - @required this.attachmentId, + required this.messageId, + required this.attachmentId, }) : super(key: key); @override @@ -203,7 +202,7 @@ class _FailedState extends StatelessWidget { color: theme.colorTheme.white, ), onPressed: () { - return channel.retryAttachmentUpload(messageId, attachmentId); + channel.retryAttachmentUpload(messageId, attachmentId); }, ), Center( @@ -213,7 +212,10 @@ class _FailedState extends StatelessWidget { color: theme.colorTheme.overlayDark.withOpacity(0.6), ), child: Padding( - padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 12, + ), child: Text( 'UPLOAD ERROR', style: theme.textTheme.footnote.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart index bd58462d..d80109c2 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget.dart @@ -13,15 +13,9 @@ extension AttachmentSourceX on AttachmentSource { /// Its prototype depends on the AttachmentSource defined. // ignore: missing_return T when({ - @required T Function() local, - @required T Function() network, + required T Function() local, + required T Function() network, }) { - assert(() { - if (local == null || network == null) { - throw 'check for all possible cases'; - } - return true; - }()); switch (this) { case AttachmentSource.local: return local(); @@ -32,30 +26,32 @@ extension AttachmentSourceX on AttachmentSource { } abstract class AttachmentWidget extends StatelessWidget { - final Size size; + final Size? size; + final AttachmentSource? _source; final Message message; final Attachment attachment; - final AttachmentSource _source; - AttachmentSource get source => _source ?? attachment.file != null - ? AttachmentSource.local - : AttachmentSource.network; + AttachmentSource get source => + _source ?? + (attachment.file != null + ? AttachmentSource.local + : AttachmentSource.network); const AttachmentWidget({ - Key key, - @required this.message, - @required this.attachment, + Key? key, + required this.message, + required this.attachment, this.size, - AttachmentSource source, + AttachmentSource? source, }) : _source = source, super(key: key); } class AttachmentError extends StatelessWidget { - final Size size; + final Size? size; const AttachmentError({ - Key key, + Key? key, this.size, }) : super(key: key); diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 1003c767..929cddc2 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -11,19 +11,24 @@ import '../upload_progress_indicator.dart'; import 'attachment_widget.dart'; class FileAttachment extends AttachmentWidget { - final Widget title; - final Widget trailing; - final VoidCallback onAttachmentTap; + final Widget? title; + final Widget? trailing; + final VoidCallback? onAttachmentTap; const FileAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.title, this.trailing, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video'; @@ -31,6 +36,7 @@ class FileAttachment extends AttachmentWidget { @override Widget build(BuildContext context) { + final colorTheme = StreamChatTheme.of(context).colorTheme; return Material( child: GestureDetector( onTap: onAttachmentTap, @@ -38,10 +44,10 @@ class FileAttachment extends AttachmentWidget { width: size?.width ?? 100, height: 56.0, decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, + color: colorTheme.white, borderRadius: BorderRadius.circular(12), border: Border.all( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + color: colorTheme.greyWhisper, ), ), child: Row( @@ -60,7 +66,7 @@ class FileAttachment extends AttachmentWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - attachment?.title ?? 'File', + attachment.title ?? 'File', style: StreamChatTheme.of(context).textTheme.bodyBold, maxLines: 1, overflow: TextOverflow.ellipsis, @@ -93,34 +99,51 @@ class FileAttachment extends AttachmentWidget { type: MaterialType.transparency, shape: _getDefaultShape(context), child: source.when( - local: () => Image.memory( - attachment.file.bytes, - fit: BoxFit.cover, - errorBuilder: (_, obj, trace) { - return getFileTypeImage(attachment.extraData['other']); - }, - ), - network: () => CachedNetworkImage( - imageUrl: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - fit: BoxFit.cover, - errorWidget: (_, obj, trace) { - return getFileTypeImage(attachment.extraData['other']); - }, - placeholder: (_, __) { - return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( + local: () { + if (attachment.file?.bytes == null) { + return getFileTypeImage(attachment.extraData['other'] as String?); + } + return Image.memory( + attachment.file!.bytes!, + fit: BoxFit.cover, + errorBuilder: (_, obj, trace) { + return getFileTypeImage( + attachment.extraData['other'] as String?); + }, + ); + }, + network: () { + if ((attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl) == + null) { + return getFileTypeImage(attachment.extraData['other'] as String?); + } + return CachedNetworkImage( + imageUrl: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl!, + fit: BoxFit.cover, + errorWidget: (_, obj, trace) { + return getFileTypeImage( + attachment.extraData['other'] as String?); + }, + placeholder: (_, __) { + final image = Image.asset( 'images/placeholder.png', fit: BoxFit.cover, package: 'stream_chat_flutter', - ), - ); - }, - ), + ); + + final colorTheme = StreamChatTheme.of(context).colorTheme; + return Shimmer.fromColors( + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, + ); + }, + ); + }, ), ); } @@ -132,7 +155,7 @@ class FileAttachment extends AttachmentWidget { shape: _getDefaultShape(context), child: source.when( local: () => VideoThumbnailImage( - video: attachment.file.path, + video: attachment.file?.path, placeholderBuilder: (_) { return Center( child: Container( @@ -158,14 +181,14 @@ class FileAttachment extends AttachmentWidget { ), ); } - return getFileTypeImage(attachment.extraData['mime_type']); + return getFileTypeImage(attachment.extraData['mime_type'] as String?); } Widget _buildButton({ - Widget icon, + Widget? icon, double iconSize = 24.0, - VoidCallback onPressed, - Color fillColor, + VoidCallback? onPressed, + Color? fillColor, }) { return Container( height: iconSize, @@ -189,7 +212,7 @@ class FileAttachment extends AttachmentWidget { final channel = StreamChannel.of(context).channel; final attachmentId = attachment.id; var trailingWidget = trailing; - trailingWidget ??= attachment.uploadState?.when( + trailingWidget ??= attachment.uploadState.when( preparing: () => Padding( padding: const EdgeInsets.all(8.0), child: _buildButton( @@ -220,7 +243,7 @@ class FileAttachment extends AttachmentWidget { icon: StreamSvgIcon.retry(color: theme.colorTheme.white), fillColor: theme.colorTheme.overlayDark, onPressed: () => channel.retryAttachmentUpload( - message?.id, + message.id, attachmentId, ), ), @@ -236,9 +259,7 @@ class FileAttachment extends AttachmentWidget { }, ); - if (message != null && - (message.status == null || - message.status == MessageSendingStatus.sent)) { + if (message.status == MessageSendingStatus.sent) { trailingWidget = IconButton( icon: StreamSvgIcon.cloudDownload(color: theme.colorTheme.black), padding: const EdgeInsets.all(8), @@ -262,11 +283,8 @@ class FileAttachment extends AttachmentWidget { final textStyle = theme.textTheme.footnote.copyWith( color: theme.colorTheme.grey, ); - return attachment.uploadState?.when( + return attachment.uploadState.when( preparing: () { - if (message == null) { - return Text('${fileSize(size, 2)}', style: textStyle); - } return UploadProgressIndicator( uploaded: 0, total: double.maxFinite.toInt(), diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index 8b8bb4c1..cb8d3556 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -9,17 +9,15 @@ import '../stream_svg_icon.dart'; import 'attachment_widget.dart'; class GiphyAttachment extends AttachmentWidget { - final MessageTheme messageTheme; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; - final VoidCallback onAttachmentTap; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; + final VoidCallback? onAttachmentTap; const GiphyAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, - this.messageTheme, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, @@ -29,10 +27,10 @@ class GiphyAttachment extends AttachmentWidget { Widget build(BuildContext context) { final imageUrl = attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl; - if (imageUrl == null && source == AttachmentSource.network) { + if (imageUrl == null) { return AttachmentError(); } - if (attachment.actions != null) { + if (attachment.actions.isNotEmpty) { return _buildSendingAttachment(context, imageUrl); } return _buildSentAttachment(context, imageUrl); @@ -73,7 +71,7 @@ class GiphyAttachment extends AttachmentWidget { if (attachment.title != null) Flexible( child: Text( - attachment.title, + attachment.title!, style: TextStyle( color: StreamChatTheme.of(context) .colorTheme @@ -199,10 +197,11 @@ class GiphyAttachment extends AttachmentWidget { child: Text( 'Send', style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - fontWeight: FontWeight.bold), + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -259,8 +258,7 @@ class GiphyAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), @@ -268,7 +266,7 @@ class GiphyAttachment extends AttachmentWidget { }, ), ); - if (res != null) onReturnAction(res); + if (res != null) onReturnAction?.call(res); } Widget _buildSentAttachment(BuildContext context, String imageUrl) { @@ -282,14 +280,13 @@ class GiphyAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), ); })); - if (res != null) onReturnAction(res); + if (res != null) onReturnAction!(res); }, child: Stack( children: [ @@ -297,16 +294,17 @@ class GiphyAttachment extends AttachmentWidget { height: size?.height, width: size?.width, placeholder: (_, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); + + final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, ); }, imageUrl: imageUrl, diff --git a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart index aa8d1246..6ac48f59 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/image_attachment.dart @@ -10,35 +10,40 @@ import 'attachment_title.dart'; import 'attachment_widget.dart'; class ImageAttachment extends AttachmentWidget { - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final bool showTitle; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; - final VoidCallback onAttachmentTap; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; + final VoidCallback? onAttachmentTap; const ImageAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.messageTheme, this.showTitle = false, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); @override Widget build(BuildContext context) { return source.when( local: () { - if (attachment.localUri == null) { + if (attachment.localUri == null || attachment.file?.bytes == null) { return AttachmentError(size: size); } return _buildImageAttachment( context, Image.memory( - attachment.file.bytes, + attachment.file!.bytes!, height: size?.height, width: size?.width, fit: BoxFit.cover, @@ -85,15 +90,16 @@ class ImageAttachment extends AttachmentWidget { height: size?.height, width: size?.width, placeholder: (_, __) { + final image = Image.asset( + 'images/placeholder.png', + fit: BoxFit.cover, + package: 'stream_chat_flutter', + ); + final colorTheme = StreamChatTheme.of(context).colorTheme; return Shimmer.fromColors( - baseColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - highlightColor: - StreamChatTheme.of(context).colorTheme.whiteSmoke, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), + baseColor: colorTheme.greyGainsboro, + highlightColor: colorTheme.whiteSmoke, + child: image, ); }, imageUrl: imageUrl, @@ -109,7 +115,7 @@ class ImageAttachment extends AttachmentWidget { Widget _buildImageAttachment(BuildContext context, Widget imageWidget) { return ConstrainedBox( - constraints: BoxConstraints.loose(size), + constraints: BoxConstraints.loose(size!), child: Column( children: [ Expanded( @@ -127,8 +133,7 @@ class ImageAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), @@ -136,7 +141,7 @@ class ImageAttachment extends AttachmentWidget { }, ), ); - if (result != null) onReturnAction(result); + if (result != null) onReturnAction?.call(result); }, child: imageWidget, ), @@ -152,7 +157,7 @@ class ImageAttachment extends AttachmentWidget { ), if (showTitle && attachment.title != null) Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index d2ffc335..05c511b5 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -8,21 +8,26 @@ import 'attachment_upload_state_builder.dart'; import 'attachment_widget.dart'; class VideoAttachment extends AttachmentWidget { - final MessageTheme messageTheme; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; - final VoidCallback onAttachmentTap; + final MessageTheme? messageTheme; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; + final VoidCallback? onAttachmentTap; const VideoAttachment({ - Key key, - @required Message message, - @required Attachment attachment, - Size size, + Key? key, + required Message message, + required Attachment attachment, + Size? size, this.messageTheme, this.onShowMessage, this.onReturnAction, this.onAttachmentTap, - }) : super(key: key, message: message, attachment: attachment, size: size); + }) : super( + key: key, + message: message, + attachment: attachment, + size: size, + ); @override Widget build(BuildContext context) { @@ -34,7 +39,7 @@ class VideoAttachment extends AttachmentWidget { return _buildVideoAttachment( context, VideoThumbnailImage( - video: attachment.file.path, + video: attachment.file?.path, height: size?.height, width: size?.width, fit: BoxFit.cover, @@ -62,7 +67,7 @@ class VideoAttachment extends AttachmentWidget { Widget _buildVideoAttachment(BuildContext context, Widget videoWidget) { return ConstrainedBox( - constraints: BoxConstraints.loose(size), + constraints: BoxConstraints.loose(size ?? Size.infinite), child: Column( children: [ Expanded( @@ -77,15 +82,14 @@ class VideoAttachment extends AttachmentWidget { channel: channel, child: FullScreenMedia( mediaAttachments: [attachment], - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), ), ), ); - if (res != null) onReturnAction(res); + if (res != null) onReturnAction?.call(res); }, child: Stack( children: [ @@ -112,7 +116,7 @@ class VideoAttachment extends AttachmentWidget { ), if (attachment.title != null) Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, child: AttachmentTitle( messageTheme: messageTheme, attachment: attachment, diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart index 0c8eac7d..642b8b00 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal.dart @@ -12,7 +12,7 @@ import 'extension.dart'; /// Callback to download an attachment asset typedef AttachmentDownloader = Future Function( Attachment attachment, { - ProgressCallback progressCallback, + ProgressCallback? progressCallback, }); /// Widget that shows the options in the gallery view @@ -21,25 +21,25 @@ class AttachmentActionsModal extends StatelessWidget { final Message message; /// Current page index - final currentIndex; + final int currentIndex; /// Callback to show the message - final VoidCallback onShowMessage; + final VoidCallback? onShowMessage; /// Callback to download images - final AttachmentDownloader imageDownloader; + final AttachmentDownloader? imageDownloader; /// Callback to provide download files - final AttachmentDownloader fileDownloader; + final AttachmentDownloader? fileDownloader; /// Returns a new [AttachmentActionsModal] const AttachmentActionsModal({ - @required this.currentIndex, - this.message, + required this.currentIndex, + required this.message, this.onShowMessage, this.imageDownloader, this.fileDownloader, - }) : assert(currentIndex != null, 'currentIndex cannot be null'); + }); @override Widget build(BuildContext context) { @@ -99,11 +99,16 @@ class AttachmentActionsModal extends StatelessWidget { () { final attachment = message.attachments[currentIndex]; final isImage = attachment.type == 'image'; - final saveFile = fileDownloader ?? _downloadAttachment; - final saveImage = imageDownloader ?? _downloadAttachment; + final Future Function(Attachment, + {void Function(int, int) progressCallback}) + saveFile = fileDownloader ?? _downloadAttachment; + final Future Function(Attachment, + {void Function(int, int) progressCallback}) + saveImage = imageDownloader ?? _downloadAttachment; final downloader = isImage ? saveImage : saveFile; - final progressNotifier = ValueNotifier<_DownloadProgress>( + final progressNotifier = + ValueNotifier<_DownloadProgress?>( _DownloadProgress.initial(), ); @@ -134,7 +139,7 @@ class AttachmentActionsModal extends StatelessWidget { ); }, ), - if (StreamChat.of(context).user.id == message.user.id) + if (StreamChat.of(context).user?.id == message.user?.id) _buildButton( context, 'Delete', @@ -164,8 +169,10 @@ class AttachmentActionsModal extends StatelessWidget { color: theme.colorTheme.accentRed, ), ] - .map((e) => - Align(alignment: Alignment.centerRight, child: e)) + .map((e) => Align( + alignment: Alignment.centerRight, + child: e, + )) .insertBetween( Container( height: 1, @@ -184,9 +191,9 @@ class AttachmentActionsModal extends StatelessWidget { context, String title, StreamSvgIcon icon, - VoidCallback onTap, { - Color color, - Key key, + VoidCallback? onTap, { + Color? color, + Key? key, }) { return Material( key: key, @@ -215,16 +222,16 @@ class AttachmentActionsModal extends StatelessWidget { Widget _buildDownloadProgressDialog( BuildContext context, - ValueNotifier<_DownloadProgress> progressNotifier, + ValueNotifier<_DownloadProgress?> progressNotifier, ) { final theme = StreamChatTheme.of(context); return WillPopScope( onWillPop: () => Future.value(false), child: ValueListenableBuilder( valueListenable: progressNotifier, - builder: (_, _DownloadProgress progress, __) { + builder: (_, _DownloadProgress? progress, __) { // Pop the dialog in case the progress is null or it's completed. - if (progress == null || progress?.toProgressIndicatorValue == 1.0) { + if (progress == null || progress.toProgressIndicatorValue == 1.0) { Future.delayed( const Duration(milliseconds: 500), Navigator.of(context).maybePop, @@ -291,23 +298,23 @@ class AttachmentActionsModal extends StatelessWidget { ); } - Future _downloadAttachment( + Future _downloadAttachment( Attachment attachment, { - ProgressCallback progressCallback, + ProgressCallback? progressCallback, }) async { - String filePath; + String? filePath; final appDocDir = await getTemporaryDirectory(); await Dio().download( - attachment.assetUrl ?? attachment.imageUrl ?? attachment.thumbUrl, + attachment.assetUrl ?? attachment.imageUrl ?? attachment.thumbUrl!, (Headers responseHeaders) { - final contentType = responseHeaders[Headers.contentTypeHeader]; - final mimeType = contentType.first?.split('/')?.last; + 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); + final result = await ImageGallerySaver.saveFile(filePath!); return (result as Map)['filePath']; } } diff --git a/packages/stream_chat_flutter/lib/src/back_button.dart b/packages/stream_chat_flutter/lib/src/back_button.dart index f49fa98c..3d38364a 100644 --- a/packages/stream_chat_flutter/lib/src/back_button.dart +++ b/packages/stream_chat_flutter/lib/src/back_button.dart @@ -6,17 +6,17 @@ import '../stream_chat_flutter.dart'; class StreamBackButton extends StatelessWidget { const StreamBackButton({ - Key key, + Key? key, this.onPressed, this.showUnreads = false, this.cid, }) : super(key: key); - final VoidCallback onPressed; + final VoidCallback? onPressed; final bool showUnreads; /// Channel cid used to retrieve unread count - final String cid; + final String? cid; @override Widget build(BuildContext context) { @@ -34,7 +34,7 @@ class StreamBackButton extends StatelessWidget { hoverElevation: 0, onPressed: () { if (onPressed != null) { - onPressed(); + onPressed!(); } else { Navigator.maybePop(context); } diff --git a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart index 2f3a26b2..c4218632 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -5,7 +5,7 @@ import 'channel_info.dart'; import 'option_list_tile.dart'; class ChannelBottomSheet extends StatefulWidget { - final VoidCallback onViewInfoTap; + final VoidCallback? onViewInfoTap; ChannelBottomSheet({this.onViewInfoTap}); @@ -18,13 +18,13 @@ class _ChannelBottomSheetState extends State { @override Widget build(BuildContext context) { - var channel = StreamChannel.of(context).channel; + final channel = StreamChannel.of(context).channel; - var members = channel.state.members; + final members = channel.state?.members ?? []; - var userAsMember = - members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id); - var isOwner = userAsMember.role == 'owner'; + final userAsMember = members + .firstWhere((e) => e.user?.id == StreamChat.of(context).user?.id); + final isOwner = userAsMember.role == 'owner'; return Material( color: StreamChatTheme.of(context).colorTheme.white, @@ -73,8 +73,8 @@ class _ChannelBottomSheetState extends State { UserAvatar( user: members .firstWhere( - (e) => e.user.id != userAsMember.user.id) - .user, + (e) => e.user?.id != userAsMember.user?.id) + .user!, constraints: BoxConstraints( maxHeight: 64.0, maxWidth: 64.0, @@ -88,10 +88,11 @@ class _ChannelBottomSheetState extends State { ), Text( members - .firstWhere( - (e) => e.user.id != userAsMember.user.id) - .user - .name, + .firstWhere( + (e) => e.user?.id != userAsMember.user?.id) + .user + ?.name ?? + '', style: StreamChatTheme.of(context).textTheme.footnoteBold, maxLines: 1, @@ -113,7 +114,7 @@ class _ChannelBottomSheetState extends State { child: Column( children: [ UserAvatar( - user: members[index].user, + user: members[index].user!, constraints: BoxConstraints.tightFor( height: 64.0, width: 64.0, @@ -126,7 +127,7 @@ class _ChannelBottomSheetState extends State { height: 6.0, ), Text( - members[index].user.name, + members[index].user?.name ?? '', style: StreamChatTheme.of(context) .textTheme .footnoteBold, @@ -220,7 +221,7 @@ class _ChannelBottomSheetState extends State { color: StreamChatTheme.of(context).colorTheme.accentRed, ), ); - var channel = StreamChannel.of(context).channel; + final channel = StreamChannel.of(context).channel; if (res == true) { await channel.delete(); Navigator.pop(context); @@ -240,7 +241,10 @@ class _ChannelBottomSheetState extends State { ); if (res == true) { final channel = StreamChannel.of(context).channel; - await channel.removeMembers([StreamChat.of(context).user.id]); + final user = StreamChat.of(context).user; + if (user != null) { + await channel.removeMembers([user.id]); + } Navigator.pop(context); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index c2927a6e..82270e3b 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -58,13 +58,13 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when the header is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// Callback to call when the image is tapped. - final VoidCallback onImageTap; + final VoidCallback? onImageTap; /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; @@ -72,21 +72,21 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { final bool showConnectionStateTile; /// Title widget - final Widget title; + final Widget? title; /// Subtitle widget - final Widget subtitle; + final Widget? subtitle; /// Leading widget - final Widget leading; + final Widget? leading; /// AppBar actions /// By default it shows the [ChannelImage] - final List actions; + final List? actions; /// Creates a channel header ChannelHeader({ - Key key, + Key? key, this.showBackButton = true, this.onBackPressed, this.onTitleTap, @@ -151,12 +151,12 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { .channelTheme .channelHeaderTheme .avatarTheme - .borderRadius, + ?.borderRadius, constraints: StreamChatTheme.of(context) .channelTheme .channelHeaderTheme .avatarTheme - .constraints, + ?.constraints, onTap: onImageTap, ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_image.dart b/packages/stream_chat_flutter/lib/src/channel_image.dart index 4b3edce3..041e0e03 100644 --- a/packages/stream_chat_flutter/lib/src/channel_image.dart +++ b/packages/stream_chat_flutter/lib/src/channel_image.dart @@ -46,7 +46,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class ChannelImage extends StatelessWidget { /// Instantiate a new ChannelImage const ChannelImage({ - Key key, + Key? key, this.channel, this.constraints, this.onTap, @@ -56,20 +56,20 @@ class ChannelImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// The channel to show the image of - final Channel channel; + final Channel? channel; /// The diameter of the image - final BoxConstraints constraints; + final BoxConstraints? constraints; /// The function called when the image is tapped - final VoidCallback onTap; + final VoidCallback? onTap; final bool selected; - final Color selectionColor; + final Color? selectionColor; final double selectionThickness; @@ -81,30 +81,30 @@ class ChannelImage extends StatelessWidget { stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { - String image; - if (snapshot.data?.containsKey('image') == true) { - image = snapshot.data['image']; - } else if (channel.state.members?.length == 2) { - final otherMember = channel.state.members - .firstWhere((member) => member.user.id != streamChat.user.id); + String? image; + if (snapshot.data!.containsKey('image') == true) { + image = snapshot.data!['image']; + } else if (channel.state?.members.length == 2) { + final otherMember = channel.state?.members + .firstWhere((member) => member.user?.id != streamChat.user?.id); return StreamBuilder( - stream: streamChat.client.state.usersStream - .map((users) => users[otherMember.userId]), - initialData: otherMember.user, + stream: streamChat.client.state.usersStream.map( + (users) => users[otherMember?.userId] ?? otherMember!.user!), + initialData: otherMember!.user, builder: (context, snapshot) { return UserAvatar( borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .borderRadius, - user: snapshot.data ?? otherMember.user, + ?.borderRadius, + user: snapshot.data ?? otherMember.user!, constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .constraints, - onTap: onTap != null ? (_) => onTap() : null, + ?.constraints, + onTap: onTap != null ? (_) => onTap!() : null, selected: selected, selectionColor: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, @@ -112,25 +112,25 @@ class ChannelImage extends StatelessWidget { ); }); } else { - final images = channel.state.members + final images = channel.state?.members .where((member) => - member.user.id != streamChat.user.id && - member.user.extraData['image'] != null) + member.user?.id != streamChat.user?.id && + member.user?.extraData['image'] != null) .take(4) - .map((e) => e.user.extraData['image'] as String) + .map((e) => e.user?.extraData['image'] as String?) .toList(); return GroupImage( - images: images, + images: images ?? [], borderRadius: borderRadius ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .borderRadius, + ?.borderRadius, constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .constraints, + ?.constraints, onTap: onTap, selected: selected, selectionColor: selectionColor ?? @@ -144,13 +144,13 @@ class ChannelImage extends StatelessWidget { StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .borderRadius, + ?.borderRadius, child: Container( constraints: constraints ?? StreamChatTheme.of(context) .channelPreviewTheme .avatarTheme - .constraints, + ?.constraints, decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -165,7 +165,7 @@ class ChannelImage extends StatelessWidget { return Center( child: Text( snapshot.data?.containsKey('name') ?? false - ? snapshot.data['name'][0] + ? snapshot.data!['name'][0] : '', style: TextStyle( color: StreamChatTheme.of(context) @@ -178,8 +178,10 @@ class ChannelImage extends StatelessWidget { }, fit: BoxFit.cover, ) - : StreamChatTheme.of(context) - .defaultChannelImage(context, channel), + : StreamChatTheme.of(context).defaultChannelImage( + context, + channel, + ), Material( color: Colors.transparent, child: InkWell( @@ -197,14 +199,15 @@ class ChannelImage extends StatelessWidget { StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .borderRadius) + + ?.borderRadius ?? + BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .constraints, + ?.constraints, color: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index 1d6f6fe2..dfcaa749 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -8,14 +9,14 @@ class ChannelInfo extends StatelessWidget { final Channel channel; /// The style of the text displayed - final TextStyle textStyle; + final TextStyle? textStyle; /// If true the typing indicator will be rendered if a user is typing final bool showTypingIndicator; const ChannelInfo({ - Key key, - @required this.channel, + Key? key, + required this.channel, this.textStyle, this.showTypingIndicator = true, }) : super(key: key); @@ -24,8 +25,8 @@ class ChannelInfo extends StatelessWidget { Widget build(BuildContext context) { final client = StreamChat.of(context).client; return StreamBuilder>( - stream: channel.state.membersStream, - initialData: channel.state.members, + stream: channel.state?.membersStream, + initialData: channel.state?.members, builder: (context, snapshot) { return ConnectionStatusBuilder( statusBuilder: (context, status) { @@ -45,12 +46,13 @@ class ChannelInfo extends StatelessWidget { ); } - Widget _buildConnectedTitleState(BuildContext context, List members) { + Widget _buildConnectedTitleState( + BuildContext context, List? members) { var alternativeWidget; - if (channel.memberCount != null && channel.memberCount > 2) { + if (channel.memberCount != null && channel.memberCount! > 2) { var text = '${channel.memberCount} Members'; - final watcherCount = channel.state.watcherCount ?? 0; + final watcherCount = channel.state?.watcherCount ?? 0; if (watcherCount > 0) text += ' $watcherCount Online'; alternativeWidget = Text( text, @@ -60,20 +62,19 @@ class ChannelInfo extends StatelessWidget { .subtitle, ); } else { - final otherMember = members.firstWhere( - (element) => element.userId != StreamChat.of(context).user.id, - orElse: () => null, + final otherMember = members?.firstWhereOrNull( + (element) => element.userId != StreamChat.of(context).user?.id, ); if (otherMember != null) { - if (otherMember.user.online) { + if (otherMember.user?.online == true) { alternativeWidget = Text( 'Online', style: textStyle, ); } else { alternativeWidget = Text( - 'Last seen ${Jiffy(otherMember.user.lastActive).fromNow()}', + 'Last seen ${Jiffy(otherMember.user?.lastActive).fromNow()}', style: textStyle, ); } @@ -112,7 +113,9 @@ class ChannelInfo extends StatelessWidget { } Widget _buildDisconnectedTitleState( - BuildContext context, StreamChatClient client) { + BuildContext context, + StreamChatClient client, + ) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -131,11 +134,11 @@ class ChannelInfo extends StatelessWidget { ), onPressed: () async { await client.disconnect(); - return client.connect(); + await client.connect(); }, child: Text( 'Try Again', - style: textStyle.copyWith( + style: textStyle?.copyWith( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ), diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index 88b329e6..f3889e22 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -10,7 +10,7 @@ import 'connection_status_builder.dart'; import 'info_tile.dart'; import 'stream_chat.dart'; -typedef _TitleBuilder = Widget Function( +typedef TitleBuilder = Widget Function( BuildContext context, ConnectionStatus status, StreamChatClient client, @@ -50,7 +50,7 @@ typedef _TitleBuilder = Widget Function( class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { /// Instantiates a ChannelListHeader const ChannelListHeader({ - Key key, + Key? key, this.client, this.titleBuilder, this.onUserAvatarTap, @@ -63,32 +63,32 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { }) : super(key: key); /// Pass this if you don't have a [StreamChatClient] in your widget tree. - final StreamChatClient client; + final StreamChatClient? client; /// Use this to build your own title as per different [ConnectionStatus] - final _TitleBuilder titleBuilder; + final TitleBuilder? titleBuilder; /// Callback to call when pressing the user avatar button. /// By default it calls Scaffold.of(context).openDrawer() - final Function(User) onUserAvatarTap; + final Function(User)? onUserAvatarTap; /// Callback to call when pressing the new chat button. - final VoidCallback onNewChatButtonTap; + final VoidCallback? onNewChatButtonTap; final bool showConnectionStateTile; - final VoidCallback preNavigationCallback; + final VoidCallback? preNavigationCallback; /// Subtitle widget - final Widget subtitle; + final Widget? subtitle; /// Leading widget /// By default it shows the logged in user avatar - final Widget leading; + final Widget? leading; /// AppBar actions /// By default it shows the new chat button - final List actions; + final List? actions; @override Widget build(BuildContext context) { @@ -123,25 +123,27 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { centerTitle: true, leading: leading ?? Center( - child: UserAvatar( - user: user, - showOnlineStatus: false, - onTap: onUserAvatarTap ?? - (_) { - if (preNavigationCallback != null) { - preNavigationCallback(); - } - Scaffold.of(context).openDrawer(); - }, - borderRadius: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - .borderRadius, - constraints: StreamChatTheme.of(context) - .channelListHeaderTheme - .avatarTheme - .constraints, - ), + child: user != null + ? UserAvatar( + user: user, + showOnlineStatus: false, + onTap: onUserAvatarTap ?? + (_) { + if (preNavigationCallback != null) { + preNavigationCallback!(); + } + Scaffold.of(context).openDrawer(); + }, + borderRadius: StreamChatTheme.of(context) + .channelListHeaderTheme + .avatarTheme + ?.borderRadius, + constraints: StreamChatTheme.of(context) + .channelListHeaderTheme + .avatarTheme + ?.constraints, + ) + : Offstage(), ), actions: actions ?? [ @@ -181,7 +183,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { Builder( builder: (context) { if (titleBuilder != null) { - return titleBuilder(context, status, _client); + return titleBuilder!(context, status, _client); } switch (status) { case ConnectionStatus.connected: @@ -225,40 +227,46 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { SizedBox(width: 10), Text( 'Searching for Network', - style: - StreamChatTheme.of(context).channelListHeaderTheme.title.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context) + .channelListHeaderTheme + .title + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ], ); } Widget _buildDisconnectedTitleState( - BuildContext context, StreamChatClient client) { + BuildContext context, + StreamChatClient client, + ) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Offline...', - style: - StreamChatTheme.of(context).channelListHeaderTheme.title.copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + style: StreamChatTheme.of(context) + .channelListHeaderTheme + .title + ?.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), TextButton( onPressed: () async { await client.disconnect(); - return client.connect(); + await client.connect(); }, child: Text( 'Try Again', style: StreamChatTheme.of(context) .channelListHeaderTheme .title - .copyWith( + ?.copyWith( fontSize: 16, fontWeight: FontWeight.bold, color: StreamChatTheme.of(context).colorTheme.accentBlue, diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 90afbd64..a210501e 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -11,7 +12,7 @@ import 'channel_bottom_sheet.dart'; import 'channel_preview.dart'; /// Callback called when tapping on a channel -typedef ChannelTapCallback = void Function(Channel, Widget); +typedef ChannelTapCallback = void Function(Channel, Widget?); /// Builder used to create a custom [ChannelPreview] from a [Channel] typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); @@ -54,7 +55,7 @@ typedef ViewInfoCallback = void Function(Channel); class ChannelListView extends StatefulWidget { /// Instantiate a new ChannelListView ChannelListView({ - Key key, + Key? key, this.filter, this.options, this.sort, @@ -90,67 +91,67 @@ class ChannelListView extends StatefulWidget { /// /// state: if true returns the Channel state /// watch: if true listen to changes to this Channel in real time. - final Map options; + final Map? options; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be provided. /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. /// Direction can be ascending or descending. - final List> sort; + final List>? sort; /// Pagination parameters /// limit: the number of channels to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams pagination; + final PaginationParams? pagination; /// Function called when tapping on a channel /// By default it calls [Navigator.push] building a [MaterialPageRoute] /// with the widget [channelWidget] as child. - final ChannelTapCallback onChannelTap; + final ChannelTapCallback? onChannelTap; /// Function called when long pressing on a channel - final Function(Channel) onChannelLongPress; + final Function(Channel)? onChannelLongPress; /// Widget used when opening a channel - final Widget channelWidget; + final Widget? channelWidget; /// Builder used to create a custom channel preview - final ChannelPreviewBuilder channelPreviewBuilder; + final ChannelPreviewBuilder? channelPreviewBuilder; /// Builder used to create a custom item separator - final Function(BuildContext, int) separatorBuilder; + final Function(BuildContext, int)? separatorBuilder; /// The function called when the image is tapped - final Function(Channel) onImageTap; + final Function(Channel)? onImageTap; /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; /// Callback used in the default empty list widget - final VoidCallback onStartChatPressed; + final VoidCallback? onStartChatPressed; /// The number of children in the cross axis. final int crossAxisCount; /// The amount of space by which to inset the children. - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; final List selectedChannels; - final ViewInfoCallback onViewInfoTap; + final ViewInfoCallback? onViewInfoTap; /// The builder that will be used in case of error - final ErrorBuilder errorBuilder; + final ErrorBuilder? errorBuilder; /// The builder that will be used in case of loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// The builder which is used when list of channels loads - final Function(BuildContext, List) listBuilder; + final Function(BuildContext, List)? listBuilder; /// The builder used when the channel list is empty. - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; @override _ChannelListViewState createState() => _ChannelListViewState(); @@ -164,7 +165,10 @@ class _ChannelListViewState extends State { @override Widget build(BuildContext context) { Widget child = ChannelListCore( - pagination: widget.pagination, + pagination: widget.pagination ?? + const PaginationParams( + limit: 25, + ), options: widget.options, sort: widget.sort, filter: widget.filter, @@ -177,26 +181,27 @@ class _ChannelListViewState extends State { if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: () => _channelListController.loadData(), + onRefresh: () => _channelListController.loadData!(), child: child, ); } return LazyLoadScrollView( - onEndOfPage: () => _channelListController.paginateData(), + onEndOfPage: () => _channelListController.paginateData!(), child: child, ); } Widget _buildListView(BuildContext context, List channels) { - Widget child; + late Widget child; if (channels.isNotEmpty) { if (widget.crossAxisCount > 1) { child = GridView.builder( padding: widget.padding, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount), + crossAxisCount: widget.crossAxisCount, + ), itemCount: channels.length, physics: AlwaysScrollableScrollPhysics(), itemBuilder: (context, index) { @@ -211,7 +216,7 @@ class _ChannelListViewState extends State { channels.isNotEmpty ? channels.length + 1 : channels.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); + return widget.separatorBuilder!(context, index); } return _separatorBuilder(context, index); }, @@ -317,7 +322,7 @@ class _ChannelListViewState extends State { if (widget.crossAxisCount == 1) { if (i % 2 != 0) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, i); + return widget.separatorBuilder!(context, i); } return _separatorBuilder(context, i); } @@ -447,7 +452,7 @@ class _ChannelListViewState extends State { style: Theme.of(context).textTheme.headline6, ), TextButton( - onPressed: () => _channelListController.loadData(), + onPressed: () => _channelListController.loadData!(), child: Text('Retry'), ), ], @@ -459,24 +464,7 @@ class _ChannelListViewState extends State { final channelsProvider = ChannelsBloc.of(context); if (i < channels.length) { final channel = channels[i]; - ChannelTapCallback onTap; - if (widget.onChannelTap != null) { - onTap = widget.onChannelTap; - } else { - onTap = (client, _) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return StreamChannel( - channel: client, - child: widget.channelWidget, - ); - }, - ), - ); - }; - } + final onTap = _getChannelTap(context); final backgroundColor = StreamChatTheme.of(context).colorTheme.whiteSmoke; return StreamChannel( @@ -509,7 +497,7 @@ class _ChannelListViewState extends State { channel: channel, child: ChannelBottomSheet( onViewInfoTap: () { - widget.onViewInfoTap(channel); + widget.onViewInfoTap?.call(channel); }, ), ); @@ -520,9 +508,9 @@ class _ChannelListViewState extends State { if ([ 'admin', 'owner', - ].contains(channel.state.members - .firstWhere((m) => m.userId == channel.client.state.user.id, - orElse: () => null) + ].contains(channel.state!.members + .firstWhereOrNull( + (m) => m.userId == channel.client.state.user?.id) ?.role)) IconSlideAction( color: backgroundColor, @@ -567,10 +555,35 @@ class _ChannelListViewState extends State { } } - Widget _gridItemBuilder(BuildContext context, int i, List channels) { - var channel = channels[i]; + ChannelTapCallback _getChannelTap(BuildContext context) { + ChannelTapCallback onTap; + if (widget.onChannelTap != null) { + onTap = widget.onChannelTap!; + } else { + onTap = (client, _) { + if (widget.channelWidget == null) { + return; + } + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return StreamChannel( + channel: client, + child: widget.channelWidget!, + ); + }, + ), + ); + }; + } + return onTap; + } - var selected = widget.selectedChannels.contains(channel); + Widget _gridItemBuilder(BuildContext context, int i, List channels) { + final channel = channels[i]; + + final selected = widget.selectedChannels.contains(channel); return Container( key: ValueKey('CHANNEL-${channel.id}'), @@ -586,7 +599,7 @@ class _ChannelListViewState extends State { width: 64, height: 64, ), - onTap: () => widget.onChannelTap(channel, null), + onTap: () => _getChannelTap(context), ), SizedBox(height: 7), Padding( @@ -628,7 +641,7 @@ class _ChannelListViewState extends State { ), ); } - return snapshot.data + return snapshot.data! ? Center( child: Padding( padding: const EdgeInsets.all(16.0), @@ -644,7 +657,7 @@ class _ChannelListViewState extends State { return Container( height: 1, - color: effect.color.withOpacity(effect.alpha ?? 1.0), + color: effect.color!.withOpacity(effect.alpha ?? 1.0), ); } } diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 53817563..48657419 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -10,12 +10,12 @@ import '../stream_chat_flutter.dart'; class ChannelName extends StatelessWidget { /// Instantiate a new ChannelName const ChannelName({ - Key key, + Key? key, this.textStyle, }) : super(key: key); /// The style of the text displayed - final TextStyle textStyle; + final TextStyle? textStyle; @override Widget build(BuildContext context) { @@ -26,31 +26,31 @@ class ChannelName extends StatelessWidget { stream: channel.extraDataStream, initialData: channel.extraData, builder: (context, snapshot) { - return _buildName(snapshot.data, channel.state.members, client); + return _buildName(snapshot.data!, channel.state?.members, client); }, ); } Widget _buildName( Map extraData, - List members, + List? members, StreamChatState client, ) { return LayoutBuilder( builder: (context, constraints) { - String title; + String? title; if (extraData['name'] == null) { final otherMembers = - members.where((member) => member.userId != client.user.id); - if (otherMembers.length == 1) { - title = otherMembers.first.user.name; - } else if (otherMembers.isNotEmpty) { + members?.where((member) => member.userId != client.user!.id); + if (otherMembers?.length == 1) { + title = otherMembers!.first.user?.name; + } else if (otherMembers?.isNotEmpty == true) { final maxWidth = constraints.maxWidth; - final maxChars = maxWidth / textStyle.fontSize; + final maxChars = maxWidth / (textStyle?.fontSize ?? 1); var currentChars = 0; final currentMembers = []; - otherMembers.forEach((element) { - final newLength = currentChars + element.user.name.length; + otherMembers!.forEach((element) { + final newLength = currentChars + (element.user?.name.length ?? 0); if (newLength < maxChars) { currentChars = newLength; currentMembers.add(element); @@ -60,7 +60,7 @@ class ChannelName extends StatelessWidget { final exceedingMembers = otherMembers.length - currentMembers.length; title = - '${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + '${currentMembers.map((e) => e.user?.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; } else { title = 'No title'; } @@ -69,7 +69,7 @@ class ChannelName extends StatelessWidget { } return Text( - title, + title!, style: textStyle, overflow: TextOverflow.ellipsis, ); diff --git a/packages/stream_chat_flutter/lib/src/channel_preview.dart b/packages/stream_chat_flutter/lib/src/channel_preview.dart index 959342a6..f28e9368 100644 --- a/packages/stream_chat_flutter/lib/src/channel_preview.dart +++ b/packages/stream_chat_flutter/lib/src/channel_preview.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; @@ -20,35 +21,35 @@ import 'channel_name.dart'; /// Modify it to change the widget appearance. class ChannelPreview extends StatelessWidget { /// Function called when tapping this widget - final void Function(Channel) onTap; + final void Function(Channel)? onTap; /// Function called when long pressing this widget - final void Function(Channel) onLongPress; + final void Function(Channel)? onLongPress; /// Channel displayed final Channel channel; /// The function called when the image is tapped - final VoidCallback onImageTap; + final VoidCallback? onImageTap; /// Widget rendering the title - final Widget title; + final Widget? title; /// Widget rendering the subtitle - final Widget subtitle; + final Widget? subtitle; /// Widget rendering the leading element, by default it shows the [ChannelImage] - final Widget leading; + final Widget? leading; /// Widget rendering the trailing element, by default it shows the last message date - final Widget trailing; + final Widget? trailing; /// Widget rendering the sending indicator, by default it uses the [SendingIndicator] widget - final Widget sendingIndicator; + final Widget? sendingIndicator; ChannelPreview({ - @required this.channel, - Key key, + required this.channel, + Key? key, this.onTap, this.onLongPress, this.onImageTap, @@ -67,7 +68,7 @@ class ChannelPreview extends StatelessWidget { initialData: channel.isMuted, builder: (context, snapshot) { return Opacity( - opacity: snapshot.data ? 0.5 : 1, + opacity: snapshot.data! ? 0.5 : 1, child: ListTile( visualDensity: VisualDensity.compact, contentPadding: const EdgeInsets.symmetric( @@ -75,12 +76,12 @@ class ChannelPreview extends StatelessWidget { ), onTap: () { if (onTap != null) { - onTap(channel); + onTap!(channel); } }, onLongPress: () { if (onLongPress != null) { - onLongPress(channel); + onLongPress!(channel); } }, leading: leading ?? @@ -97,13 +98,13 @@ class ChannelPreview extends StatelessWidget { ), ), StreamBuilder>( - stream: channel.state.membersStream, - initialData: channel.state.members, + stream: channel.state?.membersStream, + initialData: channel.state?.members, builder: (context, snapshot) { if (!snapshot.hasData || - snapshot.data.isEmpty || - !snapshot.data.any((Member e) => - e.user.id == channel.client.state.user.id)) { + snapshot.data!.isEmpty || + !snapshot.data!.any((Member e) => + e.user!.id == channel.client.state.user?.id)) { return SizedBox(); } return UnreadIndicator( @@ -120,24 +121,24 @@ class ChannelPreview extends StatelessWidget { sendingIndicator ?? Builder( builder: (context) { - final lastMessage = channel.state.messages.lastWhere( + final lastMessage = + channel.state?.messages.lastWhereOrNull( (m) => !m.isDeleted && m.shadowed != true, - orElse: () => null, ); if (lastMessage?.user?.id == - StreamChat.of(context).user.id) { + StreamChat.of(context).user?.id) { return Padding( padding: const EdgeInsets.only(right: 4.0), child: SendingIndicator( - message: lastMessage, + message: lastMessage!, size: channelPreviewTheme.indicatorIconSize, - isMessageRead: channel.state.read + isMessageRead: channel.state!.read ?.where((element) => element.user.id != - channel.client.state.user.id) - ?.where((element) => element.lastRead + channel.client.state.user!.id) + .where((element) => element.lastRead .isAfter(lastMessage.createdAt)) - ?.isNotEmpty == + .isNotEmpty == true, ), ); @@ -154,14 +155,14 @@ class ChannelPreview extends StatelessWidget { } Widget _buildDate(BuildContext context) { - return StreamBuilder( + return StreamBuilder( stream: channel.lastMessageAtStream, initialData: channel.lastMessageAt, builder: (context, snapshot) { if (!snapshot.hasData) { return SizedBox(); } - final lastMessageAt = snapshot.data.toLocal(); + final lastMessageAt = snapshot.data!.toLocal(); String stringDate; final now = DateTime.now(); @@ -211,60 +212,58 @@ class ChannelPreview extends StatelessWidget { } Widget _buildLastMessage(BuildContext context) { - return StreamBuilder>( - stream: channel.state.messagesStream, - initialData: channel.state.messages, + return StreamBuilder?>( + stream: channel.state!.messagesStream, + initialData: channel.state!.messages, builder: (context, snapshot) { - final lastMessage = snapshot.data?.lastWhere( - (m) => m.shadowed != true && !m.isDeleted, - orElse: () => null); + final lastMessage = snapshot.data + ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted); if (lastMessage == null) { return SizedBox(); } var text = lastMessage.text; - if (lastMessage.attachments != null) { - final parts = [ - ...lastMessage.attachments.map((e) { - if (e.type == 'image') { - return '📷'; - } else if (e.type == 'video') { - return '🎬'; - } else if (e.type == 'giphy') { - return '[GIF]'; - } - return e == lastMessage.attachments.last - ? (e.title ?? 'File') - : '${e.title ?? 'File'} , '; - }).where((e) => e != null), - lastMessage.text ?? '', - ]; + final parts = [ + ...lastMessage.attachments.map((e) { + if (e.type == 'image') { + return '📷'; + } else if (e.type == 'video') { + return '🎬'; + } else if (e.type == 'giphy') { + return '[GIF]'; + } + return e == lastMessage.attachments.last + ? (e.title ?? 'File') + : '${e.title ?? 'File'} , '; + }), + lastMessage.text ?? '', + ]; - text = parts.join(' '); - } + text = parts.join(' '); return Text.rich( _getDisplayText( text, lastMessage.mentionedUsers, lastMessage.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( color: StreamChatTheme.of(context) .channelPreviewTheme .subtitle - .color, + ?.color, fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal), - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( - color: StreamChatTheme.of(context) - .channelPreviewTheme - .subtitle - .color, - fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) - ? FontStyle.italic - : FontStyle.normal, - fontWeight: FontWeight.bold), + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( + color: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + ?.color, + fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) + ? FontStyle.italic + : FontStyle.normal, + fontWeight: FontWeight.bold, + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -274,29 +273,28 @@ class ChannelPreview extends StatelessWidget { } TextSpan _getDisplayText( - String text, - List mentions, - List attachments, - TextStyle normalTextStyle, - TextStyle mentionsTextStyle) { - var textList = text.split(' '); - var resList = []; - for (var e in textList) { - if (mentions != null && - mentions.isNotEmpty && + String text, + List mentions, + List attachments, + TextStyle? normalTextStyle, + TextStyle? mentionsTextStyle, + ) { + final textList = text.split(' '); + final resList = []; + for (final e in textList) { + if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { resList.add(TextSpan( text: '$e ', style: mentionsTextStyle, )); - } else if (attachments != null && - attachments.isNotEmpty && + } else if (attachments.isNotEmpty && attachments .where((e) => e.title != null) .any((element) => element.title == e)) { resList.add(TextSpan( text: '$e ', - style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), + style: normalTextStyle?.copyWith(fontStyle: FontStyle.italic), )); } else { resList.add(TextSpan( diff --git a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart index 01632b1b..620c41ac 100644 --- a/packages/stream_chat_flutter/lib/src/connection_status_builder.dart +++ b/packages/stream_chat_flutter/lib/src/connection_status_builder.dart @@ -11,26 +11,25 @@ import 'stream_chat.dart'; class ConnectionStatusBuilder extends StatelessWidget { /// Creates a new ConnectionStatusBuilder const ConnectionStatusBuilder({ - Key key, - @required this.statusBuilder, + Key? key, + required this.statusBuilder, this.initialStatus = ConnectionStatus.disconnected, this.connectionStatusStream, this.errorBuilder, this.loadingBuilder, - }) : assert(statusBuilder != null), - super(key: key); + }) : super(key: key); /// The connection status that will be used to create the initial snapshot. final ConnectionStatus initialStatus; /// The asynchronous computation to which this builder is currently connected. - final Stream connectionStatusStream; + final Stream? connectionStatusStream; /// The builder that will be used in case of error - final Widget Function(BuildContext context, Object error) errorBuilder; + final Widget Function(BuildContext context, Object? error)? errorBuilder; /// The builder that will be used in case of loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// The builder that will be used in case of data final Widget Function(BuildContext context, ConnectionStatus status) @@ -46,15 +45,15 @@ class ConnectionStatusBuilder extends StatelessWidget { builder: (context, snapshot) { if (snapshot.hasError) { if (errorBuilder != null) { - return errorBuilder(context, snapshot.error); + return errorBuilder!(context, snapshot.error); } return Offstage(); } if (!snapshot.hasData) { - if (loadingBuilder != null) return loadingBuilder(context); + if (loadingBuilder != null) return loadingBuilder!(context); return Offstage(); } - return statusBuilder(context, snapshot.data); + return statusBuilder(context, snapshot.data!); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index 20b8151d..3674e085 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -8,8 +8,8 @@ class DateDivider extends StatelessWidget { final bool uppercase; const DateDivider({ - Key key, - @required this.dateTime, + Key? key, + required this.dateTime, this.uppercase = false, }) : super(key: key); diff --git a/packages/stream_chat_flutter/lib/src/deleted_message.dart b/packages/stream_chat_flutter/lib/src/deleted_message.dart index bd014770..c0387982 100644 --- a/packages/stream_chat_flutter/lib/src/deleted_message.dart +++ b/packages/stream_chat_flutter/lib/src/deleted_message.dart @@ -5,8 +5,8 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; class DeletedMessage extends StatelessWidget { const DeletedMessage({ - Key key, - @required this.messageTheme, + Key? key, + required this.messageTheme, this.borderRadiusGeometry, this.shape, this.borderSide, @@ -14,16 +14,16 @@ class DeletedMessage extends StatelessWidget { }) : super(key: key); /// The theme of the message - final MessageTheme messageTheme; + final MessageTheme? messageTheme; /// The border radius of the message text - final BorderRadiusGeometry borderRadiusGeometry; + final BorderRadiusGeometry? borderRadiusGeometry; /// The shape of the message text - final ShapeBorder shape; + final ShapeBorder? shape; /// The borderside of the message text - final BorderSide borderSide; + final BorderSide? borderSide; /// If true the widget will be mirrored final bool reverse; @@ -34,7 +34,7 @@ class DeletedMessage extends StatelessWidget { transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, child: Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, shape: shape ?? RoundedRectangleBorder( borderRadius: borderRadiusGeometry ?? BorderRadius.zero, @@ -61,9 +61,9 @@ class DeletedMessage extends StatelessWidget { alignment: Alignment.center, child: Text( 'Message deleted', - style: messageTheme.messageText.copyWith( + style: messageTheme?.messageText?.copyWith( fontStyle: FontStyle.italic, - color: messageTheme.createdAt.color, + color: messageTheme?.createdAt?.color, ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index daa8a69c..ef73d9ad 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -47,53 +47,53 @@ extension PlatformFileX on PlatformFile { } extension InputDecorationX on InputDecoration { - InputDecoration merge(InputDecoration other) { + InputDecoration merge(InputDecoration? other) { if (other == null) return this; return copyWith( - icon: other?.icon, - labelText: other?.labelText, + icon: other.icon, + labelText: other.labelText, labelStyle: labelStyle?.merge(other.labelStyle) ?? other.labelStyle, - helperText: other?.helperText, + helperText: other.helperText, helperStyle: helperStyle?.merge(other.helperStyle) ?? other.helperStyle, - helperMaxLines: other?.helperMaxLines, - hintText: other?.hintText, + helperMaxLines: other.helperMaxLines, + hintText: other.hintText, hintStyle: hintStyle?.merge(other.hintStyle) ?? other.hintStyle, - hintTextDirection: other?.hintTextDirection, - hintMaxLines: other?.hintMaxLines, - errorText: other?.errorText, + hintTextDirection: other.hintTextDirection, + hintMaxLines: other.hintMaxLines, + errorText: other.errorText, errorStyle: errorStyle?.merge(other.errorStyle) ?? other.errorStyle, - errorMaxLines: other?.errorMaxLines, - floatingLabelBehavior: other?.floatingLabelBehavior, - isCollapsed: other?.isCollapsed, - isDense: other?.isDense, - contentPadding: other?.contentPadding, - prefixIcon: other?.prefixIcon, - prefix: other?.prefix, - prefixText: other?.prefixText, - prefixIconConstraints: other?.prefixIconConstraints, + errorMaxLines: other.errorMaxLines, + floatingLabelBehavior: other.floatingLabelBehavior, + isCollapsed: other.isCollapsed, + isDense: other.isDense, + contentPadding: other.contentPadding, + prefixIcon: other.prefixIcon, + prefix: other.prefix, + prefixText: other.prefixText, + prefixIconConstraints: other.prefixIconConstraints, prefixStyle: prefixStyle?.merge(other.prefixStyle) ?? other.prefixStyle, - suffixIcon: other?.suffixIcon, - suffix: other?.suffix, - suffixText: other?.suffixText, + suffixIcon: other.suffixIcon, + suffix: other.suffix, + suffixText: other.suffixText, suffixStyle: suffixStyle?.merge(other.suffixStyle) ?? other.suffixStyle, - suffixIconConstraints: other?.suffixIconConstraints, - counter: other?.counter, - counterText: other?.counterText, + suffixIconConstraints: other.suffixIconConstraints, + counter: other.counter, + counterText: other.counterText, counterStyle: counterStyle?.merge(other.counterStyle) ?? other.counterStyle, - filled: other?.filled, - fillColor: other?.fillColor, - focusColor: other?.focusColor, - hoverColor: other?.hoverColor, - errorBorder: other?.errorBorder, - focusedBorder: other?.focusedBorder, - focusedErrorBorder: other?.focusedErrorBorder, - disabledBorder: other?.disabledBorder, - enabledBorder: other?.enabledBorder, - border: other?.border, - enabled: other?.enabled, - semanticCounterText: other?.semanticCounterText, - alignLabelWithHint: other?.alignLabelWithHint, + filled: other.filled, + fillColor: other.fillColor, + focusColor: other.focusColor, + hoverColor: other.hoverColor, + errorBorder: other.errorBorder, + focusedBorder: other.focusedBorder, + focusedErrorBorder: other.focusedErrorBorder, + disabledBorder: other.disabledBorder, + enabledBorder: other.enabledBorder, + border: other.border, + enabled: other.enabled, + semanticCounterText: other.semanticCounterText, + alignLabelWithHint: other.alignLabelWithHint, ); } } diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index aa35ed15..4956fb28 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -24,19 +24,18 @@ class FullScreenMedia extends StatefulWidget { final int startIndex; final String userName; - final DateTime sentAt; - final ShowMessageCallback onShowMessage; + final ShowMessageCallback? onShowMessage; /// Instantiate a new FullScreenImage const FullScreenMedia({ - Key key, - @required this.mediaAttachments, - this.message, + Key? key, + required this.mediaAttachments, + required this.message, this.startIndex = 0, - this.userName = '', - this.sentAt, + String? userName, this.onShowMessage, - }) : super(key: key); + }) : userName = userName ?? '', + super(key: key); @override _FullScreenMediaState createState() => _FullScreenMediaState(); @@ -46,10 +45,10 @@ class _FullScreenMediaState extends State with SingleTickerProviderStateMixin { bool _optionsShown = true; - AnimationController _controller; - PageController _pageController; + late final AnimationController _controller; + late final PageController _pageController; - int _currentPage; + late int _currentPage; final videoPackages = {}; @@ -101,10 +100,11 @@ class _FullScreenMediaState extends State attachment.assetUrl ?? attachment.thumbUrl; return PhotoView( - imageProvider: - imageUrl == null && attachment.localUri != null - ? Image.memory(attachment.file.bytes).image - : CachedNetworkImageProvider(imageUrl), + imageProvider: (imageUrl == null && + attachment.localUri != null && + attachment.file?.bytes != null) + ? Image.memory(attachment.file!.bytes!).image + : CachedNetworkImageProvider(imageUrl!), maxScale: PhotoViewComputedScale.covered, minScale: PhotoViewComputedScale.contained, heroAttributes: PhotoViewHeroAttributes( @@ -112,12 +112,12 @@ class _FullScreenMediaState extends State ), backgroundDecoration: BoxDecoration( color: ColorTween( - begin: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .color, - end: Colors.black) - .lerp(_controller.value), + begin: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .color, + end: Colors.black, + ).lerp(_controller.value), ), onTapUp: (a, b, c) { setState(() { @@ -131,7 +131,7 @@ class _FullScreenMediaState extends State }, ); } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]; + final controller = videoPackages[attachment.id]!; if (!controller.initialized) { return Center( child: CircularProgressIndicator(), @@ -153,7 +153,7 @@ class _FullScreenMediaState extends State vertical: 50.0, ), child: Chewie( - controller: controller.chewieController, + controller: controller.chewieController!, ), ), ); @@ -171,9 +171,8 @@ class _FullScreenMediaState extends State children: [ ImageHeader( userName: widget.userName, - sentAt: widget.message.createdAt == null - ? '' - : 'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}', + sentAt: + 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', onBackPressed: () { Navigator.of(context).pop(); }, @@ -181,8 +180,10 @@ class _FullScreenMediaState extends State urls: widget.mediaAttachments, currentIndex: _currentPage, onShowMessage: () { - widget.onShowMessage( - widget.message, StreamChannel.of(context).channel); + widget.onShowMessage?.call( + widget.message, + StreamChannel.of(context).channel, + ); }, ), if (widget.message.type != 'ephemeral') @@ -194,9 +195,11 @@ class _FullScreenMediaState extends State mediaSelectedCallBack: (val) { setState(() { _currentPage = val; - _pageController.animateToPage(val, - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut); + _pageController.animateToPage( + val, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); Navigator.pop(context); }); }, @@ -210,7 +213,7 @@ class _FullScreenMediaState extends State } String getDay(DateTime dateTime) { - var now = DateTime.now(); + final now = DateTime.now(); if (DateTime(dateTime.year, dateTime.month, dateTime.day) == DateTime(now.year, now.month, now.day)) { @@ -238,11 +241,11 @@ class VideoPackage { final bool _showControls; final bool _autoInitialize; final VideoPlayerController _videoPlayerController; - ChewieController _chewieController; + ChewieController? _chewieController; VideoPlayerController get videoPlayer => _videoPlayerController; - ChewieController get chewieController => _chewieController; + ChewieController? get chewieController => _chewieController; bool get initialized => _videoPlayerController.value.isInitialized; @@ -250,12 +253,11 @@ class VideoPackage { Attachment attachment, { bool showControls = false, bool autoInitialize = true, - }) : assert(attachment != null), - _showControls = showControls, + }) : _showControls = showControls, _autoInitialize = autoInitialize, _videoPlayerController = attachment.localUri != null - ? VideoPlayerController.file(File.fromUri(attachment.localUri)) - : VideoPlayerController.network(attachment.assetUrl); + ? VideoPlayerController.file(File.fromUri(attachment.localUri!)) + : VideoPlayerController.network(attachment.assetUrl!); Future initialize() { return _videoPlayerController.initialize().then((_) { @@ -278,6 +280,6 @@ class VideoPackage { Future dispose() { _chewieController?.dispose(); - return _videoPlayerController?.dispose(); + return _videoPlayerController.dispose(); } } diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 4cfd655a..d6ec45e0 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -5,8 +5,8 @@ import '../stream_chat_flutter.dart'; class GroupImage extends StatelessWidget { const GroupImage({ - Key key, - @required this.images, + Key? key, + required this.images, this.constraints, this.onTap, this.borderRadius, @@ -15,12 +15,12 @@ class GroupImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); - final List images; - final BoxConstraints constraints; - final VoidCallback onTap; + final List images; + final BoxConstraints? constraints; + final VoidCallback? onTap; final bool selected; - final BorderRadius borderRadius; - final Color selectionColor; + final BorderRadius? borderRadius; + final Color? selectionColor; final double selectionThickness; @override @@ -35,13 +35,13 @@ class GroupImage extends StatelessWidget { StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .borderRadius, + ?.borderRadius, child: Container( constraints: constraints ?? StreamChatTheme.of(context) .ownMessageTheme .avatarTheme - .constraints, + ?.constraints, decoration: BoxDecoration( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -64,7 +64,7 @@ class GroupImage extends StatelessWidget { child: Transform.scale( scale: 1.2, child: CachedNetworkImage( - imageUrl: url, + imageUrl: url!, fit: BoxFit.cover, ), ), @@ -89,7 +89,7 @@ class GroupImage extends StatelessWidget { child: Transform.scale( scale: 1.2, child: CachedNetworkImage( - imageUrl: url, + imageUrl: url!, fit: BoxFit.cover, ), ), @@ -107,7 +107,8 @@ class GroupImage extends StatelessWidget { if (selected) { avatar = ClipRRect( borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) + + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? + BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( color: selectionColor ?? diff --git a/packages/stream_chat_flutter/lib/src/image_footer.dart b/packages/stream_chat_flutter/lib/src/image_footer.dart index 6833a52f..a25d1be3 100644 --- a/packages/stream_chat_flutter/lib/src/image_footer.dart +++ b/packages/stream_chat_flutter/lib/src/image_footer.dart @@ -5,8 +5,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:share_plus/share_plus.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -15,13 +15,13 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class ImageFooter extends StatefulWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when the header is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// Callback to call when the image is tapped. - final VoidCallback onImageTap; + final VoidCallback? onImageTap; final int currentPage; final int totalPages; @@ -29,18 +29,18 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { final List mediaAttachments; final Message message; - final ValueChanged mediaSelectedCallBack; + final ValueChanged? mediaSelectedCallBack; /// Creates a channel header ImageFooter({ - Key key, + Key? key, + required this.message, this.onBackPressed, this.onTitleTap, this.onImageTap, this.currentPage = 0, this.totalPages = 0, - this.mediaAttachments, - this.message, + this.mediaAttachments = const [], this.mediaSelectedCallBack, }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); @@ -53,14 +53,11 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { } class _ImageFooterState extends State { - TextEditingController _searchController; final TextEditingController _messageController = TextEditingController(); final FocusNode _messageFocusNode = FocusNode(); final List _selectedChannels = []; - Function modalSetStateCallback; - @override void initState() { super.initState(); @@ -69,13 +66,6 @@ class _ImageFooterState extends State { }); } - @override - void dispose() { - _searchController?.clear(); - _searchController?.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { final showShareButton = !kIsWeb; @@ -106,10 +96,10 @@ class _ImageFooterState extends State { widget.mediaAttachments[widget.currentPage]; final url = attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl; + attachment.thumbUrl!; final type = attachment.type == 'image' ? 'jpg' - : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + : url.split('?').first.split('.').last; final request = await HttpClient().getUrl(Uri.parse(url)); final response = await request.close(); @@ -227,7 +217,7 @@ class _ImageFooterState extends State { final attachment = widget.mediaAttachments[index]; if (attachment.type == 'video') { media = InkWell( - onTap: () => widget.mediaSelectedCallBack(index), + onTap: () => widget.mediaSelectedCallBack!(index), child: FittedBox( fit: BoxFit.cover, child: VideoThumbnailImage( @@ -238,13 +228,13 @@ class _ImageFooterState extends State { ); } else { media = InkWell( - onTap: () => widget.mediaSelectedCallBack(index), + onTap: () => widget.mediaSelectedCallBack!(index), child: AspectRatio( aspectRatio: 1.0, child: CachedNetworkImage( imageUrl: attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl!, fit: BoxFit.cover, ), ), @@ -254,32 +244,33 @@ class _ImageFooterState extends State { return Stack( children: [ media, - Padding( - padding: EdgeInsets.all(8.0), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.6), - boxShadow: [ - BoxShadow( - blurRadius: 8.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.3), - ), - ], - ), - padding: const EdgeInsets.all(2), - child: UserAvatar( - user: widget.message.user, - constraints: - BoxConstraints.tight(Size(24, 24)), - showOnlineStatus: false, + if (widget.message.user != null) + Padding( + padding: EdgeInsets.all(8.0), + child: Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.6), + boxShadow: [ + BoxShadow( + blurRadius: 8.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.3), + ), + ], + ), + padding: const EdgeInsets.all(2), + child: UserAvatar( + user: widget.message.user!, + constraints: + BoxConstraints.tight(Size(24, 24)), + showOnlineStatus: false, + ), ), ), - ), ], ); }, @@ -302,7 +293,7 @@ class _ImageFooterState extends State { _messageController.clear(); - for (var channel in _selectedChannels) { + for (final channel in _selectedChannels) { final message = Message( text: text, attachments: [attachments[widget.currentPage]], diff --git a/packages/stream_chat_flutter/lib/src/image_group.dart b/packages/stream_chat_flutter/lib/src/image_group.dart index 39c2883a..85e28497 100644 --- a/packages/stream_chat_flutter/lib/src/image_group.dart +++ b/packages/stream_chat_flutter/lib/src/image_group.dart @@ -1,23 +1,23 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class ImageGroup extends StatelessWidget { const ImageGroup({ - Key key, - @required this.images, - @required this.message, - @required this.messageTheme, - @required this.size, + Key? key, + required this.images, + required this.message, + required this.messageTheme, + required this.size, this.onShowMessage, }) : super(key: key); final List images; final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final Size size; - final ShowMessageCallback onShowMessage; + final ShowMessageCallback? onShowMessage; @override Widget build(BuildContext context) { @@ -106,9 +106,9 @@ class ImageGroup extends StatelessWidget { } void _onTap( - BuildContext context, [ + BuildContext context, int index, - ]) { + ) { final channel = StreamChannel.of(context).channel; Navigator.push( @@ -119,8 +119,7 @@ class ImageGroup extends StatelessWidget { child: FullScreenMedia( mediaAttachments: images, startIndex: index, - userName: message.user.name, - sentAt: message.createdAt, + userName: message.user?.name, message: message, onShowMessage: onShowMessage, ), diff --git a/packages/stream_chat_flutter/lib/src/image_header.dart b/packages/stream_chat_flutter/lib/src/image_header.dart index 023c8eaa..1cec9c36 100644 --- a/packages/stream_chat_flutter/lib/src/image_header.dart +++ b/packages/stream_chat_flutter/lib/src/image_header.dart @@ -11,16 +11,16 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when pressing the show message button. - final VoidCallback onShowMessage; + final VoidCallback? onShowMessage; /// Callback to call when the header is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// Callback to call when the image is tapped. - final VoidCallback onImageTap; + final VoidCallback? onImageTap; final Message message; @@ -32,9 +32,9 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// Creates a channel header ImageHeader({ - Key key, - this.message, - this.urls, + Key? key, + required this.message, + this.urls = const [], this.currentIndex, this.showBackButton = true, this.onBackPressed, @@ -109,7 +109,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { void _showMessageActionModalBottomSheet(BuildContext context) async { final channel = StreamChannel.of(context).channel; - var result = await showDialog( + final result = await showDialog( context: context, barrierColor: StreamChatTheme.of(context).colorTheme.overlay, builder: (context) { diff --git a/packages/stream_chat_flutter/lib/src/info_tile.dart b/packages/stream_chat_flutter/lib/src/info_tile.dart index 72a077cc..deb30877 100644 --- a/packages/stream_chat_flutter/lib/src/info_tile.dart +++ b/packages/stream_chat_flutter/lib/src/info_tile.dart @@ -6,15 +6,15 @@ class InfoTile extends StatelessWidget { final String message; final Widget child; final bool showMessage; - final Alignment tileAnchor; - final Alignment childAnchor; - final TextStyle textStyle; - final Color backgroundColor; + final Alignment? tileAnchor; + final Alignment? childAnchor; + final TextStyle? textStyle; + final Color? backgroundColor; InfoTile({ - this.message, - this.child, - this.showMessage, + required this.message, + required this.child, + required this.showMessage, this.tileAnchor, this.childAnchor, this.textStyle, diff --git a/packages/stream_chat_flutter/lib/src/media_list_view.dart b/packages/stream_chat_flutter/lib/src/media_list_view.dart index ddaf2c24..3ba266bc 100644 --- a/packages/stream_chat_flutter/lib/src/media_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/media_list_view.dart @@ -21,10 +21,10 @@ extension on Duration { class MediaListView extends StatefulWidget { final List selectedIds; - final void Function(AssetEntity media) onSelect; + final void Function(AssetEntity media)? onSelect; const MediaListView({ - Key key, + Key? key, this.selectedIds = const [], this.onSelect, }) : super(key: key); @@ -58,7 +58,7 @@ class _MediaListViewState extends State { child: InkWell( onTap: () { if (widget.onSelect != null) { - widget.onSelect(media); + widget.onSelect!(media); } }, child: Stack( @@ -147,7 +147,7 @@ class _MediaListViewState extends State { final assetList = await PhotoManager.getAssetPathList( hasAll: true, ).then((value) { - if (value?.isNotEmpty == true) { + if (value.isNotEmpty == true) { return value.singleWhere((element) => element.isAll); } }); @@ -169,29 +169,29 @@ class _MediaListViewState extends State { class MediaThumbnailProvider extends ImageProvider { const MediaThumbnailProvider({ - @required this.media, - }) : assert(media != null); + required this.media, + }); final AssetEntity media; @override ImageStreamCompleter load(key, decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode), + codec: _loadAsync(key, decode) as Future, scale: 1.0, informationCollector: () sync* { - yield ErrorDescription('Id: ${media?.id}'); + yield ErrorDescription('Id: ${media.id}'); }, ); } - Future _loadAsync( + Future _loadAsync( MediaThumbnailProvider key, DecoderCallback decode) async { assert(key == this); final bytes = await media.thumbData; if (bytes?.isNotEmpty != true) return null; - return await decode(bytes); + return await decode(bytes!); } @override @@ -203,12 +203,12 @@ class MediaThumbnailProvider extends ImageProvider { bool operator ==(dynamic other) { if (other.runtimeType != runtimeType) return false; final MediaThumbnailProvider typedOther = other; - return media?.id == typedOther.media?.id; + return media.id == typedOther.media.id; } @override - int get hashCode => media?.id?.hashCode ?? 0; + int get hashCode => media.id.hashCode; @override - String toString() => '$runtimeType("${media?.id}")'; + String toString() => '$runtimeType("${media.id}")'; } diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index 7d8b0ada..37e4e9ad 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -9,16 +9,16 @@ class MentionTile extends StatelessWidget { final Member member; /// Widget to display as title - final Widget title; + final Widget? title; /// Widget to display below [title] - final Widget subtitle; + final Widget? subtitle; /// Widget at the start of the tile - final Widget leading; + final Widget? leading; /// Widget at the end of tile - final Widget trailing; + final Widget? trailing; MentionTile( this.member, { @@ -46,7 +46,7 @@ class MentionTile extends StatelessWidget { 40, ), ), - user: member.user, + user: member.user!, ), SizedBox( width: 8.0, @@ -60,7 +60,7 @@ class MentionTile extends StatelessWidget { children: [ title ?? Text( - '${member.user.name}', + '${member.user!.name}', maxLines: 1, overflow: TextOverflow.ellipsis, style: StreamChatTheme.of(context).textTheme.bodyBold, @@ -87,7 +87,10 @@ class MentionTile extends StatelessWidget { ), trailing ?? Padding( - padding: const EdgeInsets.only(right: 18.0, left: 8.0), + padding: const EdgeInsets.only( + right: 18.0, + left: 8.0, + ), child: StreamSvgIcon.mentions( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), diff --git a/packages/stream_chat_flutter/lib/src/message_action.dart b/packages/stream_chat_flutter/lib/src/message_action.dart index 5cedc962..6d4bd93c 100644 --- a/packages/stream_chat_flutter/lib/src/message_action.dart +++ b/packages/stream_chat_flutter/lib/src/message_action.dart @@ -4,13 +4,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Class describing a message action class MessageAction { /// leading widget - final Widget leading; + final Widget? leading; /// title widget - final Widget title; + final Widget? title; /// callback called on tap - final OnMessageTap onTap; + final OnMessageTap? onTap; /// returns a new instance of a [MessageAction] MessageAction({ diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 0fdabf7d..5d97bdf6 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -16,13 +16,13 @@ import 'stream_chat.dart'; import 'stream_chat_theme.dart'; class MessageActionsModal extends StatefulWidget { - final Widget Function(BuildContext, Message) editMessageInputBuilder; - final OnMessageTap onThreadReplyTap; - final OnMessageTap onReplyTap; + final Widget Function(BuildContext, Message)? editMessageInputBuilder; + final OnMessageTap? onThreadReplyTap; + final OnMessageTap? onReplyTap; final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final bool showReactions; - final OnMessageTap onCopyTap; + final OnMessageTap? onCopyTap; final bool showDeleteMessage; final bool showCopyMessage; final bool showEditMessage; @@ -31,18 +31,18 @@ class MessageActionsModal extends StatefulWidget { final bool showThreadReplyMessage; final bool showFlagButton; final bool reverse; - final ShapeBorder messageShape; - final ShapeBorder attachmentShape; + final ShapeBorder? messageShape; + final ShapeBorder? attachmentShape; final DisplayWidget showUserAvatar; - final BorderRadius attachmentBorderRadiusGeometry; + final BorderRadius? attachmentBorderRadiusGeometry; /// List of custom actions final List customActions; const MessageActionsModal({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.showReactions = true, this.showDeleteMessage = true, this.showEditMessage = true, @@ -80,24 +80,26 @@ class _MessageActionsModalState extends State { final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; - var messageTextLength = widget.message.text.length; + var messageTextLength = widget.message.text!.length; if (widget.message.quotedMessage != null) { - var quotedMessageLength = widget.message.quotedMessage.text.length + 40; - if (widget.message.quotedMessage.attachments?.isNotEmpty == true) { + 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.messageText.fontSize * 1.2; - final divFactor = widget.message.attachments?.isNotEmpty == true + final roughSentenceSize = messageTextLength * + (widget.messageTheme?.messageText?.fontSize ?? 1) * + 1.2; + final divFactor = widget.message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); final hasFileAttachment = - widget.message.attachments?.any((it) => it.type == 'file') == true; + widget.message.attachments.any((it) => it.type == 'file') == true; return GestureDetector( behavior: HitTestBehavior.translucent, @@ -134,11 +136,10 @@ class _MessageActionsModalState extends State { children: [ if (widget.showReactions && (widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null)) + MessageSendingStatus.sent)) Align( alignment: Alignment( - user.id == widget.message.user.id + user?.id == widget.message.user?.id ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor)) @@ -159,8 +160,8 @@ class _MessageActionsModalState extends State { attachmentBorderRadiusGeometry: widget.attachmentBorderRadiusGeometry, message: widget.message.copyWith( - text: widget.message.text.length > 200 - ? '${widget.message.text.substring(0, 200)}...' + text: widget.message.text!.length > 200 + ? '${widget.message.text!.substring(0, 200)}...' : widget.message.text, ), messageTheme: widget.messageTheme, @@ -177,15 +178,14 @@ class _MessageActionsModalState extends State { padding: const EdgeInsets.all(0), textPadding: EdgeInsets.symmetric( vertical: 8.0, - horizontal: widget.message.text.isOnlyEmoji + horizontal: widget.message.text!.isOnlyEmoji ? 0 : 16.0, ), showReactionPickerIndicator: widget.showReactions && (widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null), + MessageSendingStatus.sent), showInChannelIndicator: false, showSendingIndicator: false, shape: widget.messageShape, @@ -212,14 +212,12 @@ class _MessageActionsModalState extends State { CrossAxisAlignment.stretch, children: [ if (widget.showReplyMessage && - widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null) + widget.message.status == + MessageSendingStatus.sent) _buildReplyButton(context), if (widget.showThreadReplyMessage && (widget.message.status == - MessageSendingStatus.sent || - widget.message.status == null) && + MessageSendingStatus.sent) && widget.message.parentId == null) _buildThreadReplyButton(context), if (widget.showResendMessage) @@ -315,7 +313,7 @@ class _MessageActionsModalState extends State { okText: 'OK', ); } catch (err) { - if (json.decode(err?.body ?? {})['code'] == 4) { + if (err is ApiError && json.decode(err.body ?? '{}')['code'] == 4) { await showInfoDialog( context, icon: StreamSvgIcon.flag( @@ -337,7 +335,7 @@ class _MessageActionsModalState extends State { setState(() { _showActions = false; }); - var answer = await showConfirmationDialog( + final answer = await showConfirmationDialog( context, title: 'Delete message', icon: StreamSvgIcon.flag( @@ -349,7 +347,7 @@ class _MessageActionsModalState extends State { cancelText: 'CANCEL', ); - if (answer) { + if (answer == true) { try { Navigator.pop(context); await StreamChannel.of(context).channel.deleteMessage(widget.message); @@ -381,7 +379,7 @@ class _MessageActionsModalState extends State { onTap: () { Navigator.pop(context); if (widget.onReplyTap != null) { - widget.onReplyTap(widget.message); + widget.onReplyTap!(widget.message); } }, child: Padding( @@ -578,7 +576,7 @@ class _MessageActionsModalState extends State { ), ), widget.editMessageInputBuilder != null - ? widget.editMessageInputBuilder(context, widget.message) + ? widget.editMessageInputBuilder!(context, widget.message) : MessageInput( editMessage: widget.message, preMessageSending: (m) { @@ -599,7 +597,7 @@ class _MessageActionsModalState extends State { onTap: () { Navigator.pop(context); if (widget.onThreadReplyTap != null) { - widget.onThreadReplyTap(widget.message); + widget.onThreadReplyTap!(widget.message); } }, child: Padding( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 70307936..12ee9e93 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; @@ -19,6 +20,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:substring_highlight/substring_highlight.dart'; +import 'package:video_compress/video_compress.dart'; import '../stream_chat_flutter.dart'; import 'attachment/attachment.dart'; @@ -109,7 +111,7 @@ const _kMaxAttachmentSize = 20971520; // 20MB in Bytes class MessageInput extends StatefulWidget { /// Instantiate a new MessageInput MessageInput({ - Key key, + Key? key, this.onMessageSent, this.preMessageSending, this.parentMessage, @@ -135,20 +137,20 @@ class MessageInput extends StatefulWidget { }) : super(key: key); /// Message to edit - final Message editMessage; + final Message? editMessage; /// Message to start with - final Message initialMessage; + final Message? initialMessage; /// Function called after sending the message - final void Function(Message) onMessageSent; + final void Function(Message)? onMessageSent; /// Function called right before sending the message /// Use this to transform the message - final FutureOr Function(Message) preMessageSending; + final FutureOr Function(Message)? preMessageSending; /// Parent message in case of a thread - final Message parentMessage; + final Message? parentMessage; /// Maximum Height for the TextField to grow before it starts scrolling final double maxHeight; @@ -166,25 +168,25 @@ class MessageInput extends StatefulWidget { final bool hideSendAsDm; /// The text controller of the TextField - final TextEditingController textEditingController; + final TextEditingController? textEditingController; /// List of action widgets - final List actions; + final List? actions; /// The location of the custom actions final ActionsLocation actionsLocation; /// Map that defines a thumbnail builder for an attachment type - final Map attachmentThumbnailBuilders; + final Map? attachmentThumbnailBuilders; /// The focus node associated to the TextField - final FocusNode focusNode; + final FocusNode? focusNode; /// - final Message quotedMessage; + final Message? quotedMessage; /// - final VoidCallback onQuotedMessageCleared; + final VoidCallback? onQuotedMessageCleared; /// The location of the send button final SendButtonLocation sendButtonLocation; @@ -193,20 +195,20 @@ class MessageInput extends StatefulWidget { final bool autofocus; /// Send button widget in an idle state - final Widget idleSendButton; + final Widget? idleSendButton; /// Send button widget in an active state - final Widget activeSendButton; + final Widget? activeSendButton; /// Customize the tile for the mentions overlay - final MentionTileBuilder mentionsTileBuilder; + final MentionTileBuilder? mentionsTileBuilder; @override MessageInputState createState() => MessageInputState(); /// Use this method to get the current [StreamChatState] instance static MessageInputState of(BuildContext context) { - MessageInputState messageInputState; + MessageInputState? messageInputState; messageInputState = context.findAncestorStateOfType(); @@ -224,15 +226,15 @@ class MessageInputState extends State { final List _mentionedUsers = []; final _imagePicker = ImagePicker(); - FocusNode _focusNode; + late final FocusNode _focusNode; bool _inputEnabled = true; bool _messageIsPresent = false; bool _animateContainer = true; bool _commandEnabled = false; - OverlayEntry _commandsOverlay, _mentionsOverlay, _emojiOverlay; - Iterable _emojiNames; + OverlayEntry? _commandsOverlay, _mentionsOverlay, _emojiOverlay; + late Iterable _emojiNames; - Command _chosenCommand; + Command? _chosenCommand; bool _actionsShrunk = false; bool _sendAsDm = false; bool _openFilePickerSection = false; @@ -242,7 +244,7 @@ class MessageInputState extends State { KeyboardVisibilityController(); /// The editing controller passed to the input TextField - TextEditingController textEditingController; + late final TextEditingController textEditingController; bool get _hasQuotedMessage => widget.quotedMessage != null; @@ -250,7 +252,8 @@ class MessageInputState extends State { void initState() { super.initState(); _focusNode = widget.focusNode ?? FocusNode(); - _emojiNames = Emoji.all().map((e) => e.name); + _emojiNames = + Emoji.all().where((it) => it.name != null).map((e) => e.name!); if (!kIsWeb) { _keyboardListener = @@ -264,7 +267,7 @@ class MessageInputState extends State { textEditingController = widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null || widget.initialMessage != null) { - _parseExistingMessage(widget.editMessage ?? widget.initialMessage); + _parseExistingMessage(widget.editMessage ?? widget.initialMessage!); } textEditingController.addListener(() { @@ -447,7 +450,7 @@ class MessageInputState extends State { firstChild: sendButton, secondChild: widget.idleSendButton ?? _buildIdleSendButton(context), duration: - StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration, + StreamChatTheme.of(context).messageInputTheme.sendAnimationDuration!, alignment: Alignment.center, ); } @@ -490,9 +493,9 @@ class MessageInputState extends State { widget.editMessage == null && StreamChannel.of(context) .channel - ?.config + .config ?.commands - ?.isNotEmpty == + .isNotEmpty == true) _buildCommandButton(), ...widget.actions ?? [], @@ -571,7 +574,7 @@ class MessageInputState extends State { return InputDecoration( isDense: true, hintText: _getHint(), - hintStyle: theme.messageInputTheme.inputTextStyle.copyWith( + hintStyle: theme.messageInputTheme.inputTextStyle!.copyWith( color: theme.colorTheme.grey, ), border: OutlineInputBorder( @@ -621,7 +624,7 @@ class MessageInputState extends State { size: 16.0, ), Text( - _chosenCommand?.name?.toUpperCase() ?? '', + _chosenCommand?.name.toUpperCase() ?? '', style: StreamChatTheme.of(context) .textTheme .footnoteBold @@ -676,9 +679,9 @@ class MessageInputState extends State { ).merge(passedDecoration); } - Timer _debounce; + Timer? _debounce; - String _previousValue; + String? _previousValue; void _onChanged(BuildContext context, String s) { if (s == _previousValue) { @@ -686,14 +689,14 @@ class MessageInputState extends State { } _previousValue = s; - if (_debounce?.isActive == true) _debounce.cancel(); + if (_debounce?.isActive == true) _debounce!.cancel(); _debounce = Timer( const Duration(milliseconds: 350), () { if (!mounted) { return; } - StreamChannel.of(context).channel.keyStroke()?.catchError((e) {}); + StreamChannel.of(context).channel.keyStroke().catchError((e) {}); setState(() { _messageIsPresent = s.trim().isNotEmpty; @@ -721,7 +724,7 @@ class MessageInputState extends State { } String _getHint() { - if (_commandEnabled && _chosenCommand.name == 'giphy') { + if (_commandEnabled && _chosenCommand!.name == 'giphy') { return 'Search GIFs'; } if (_attachments.isNotEmpty) { @@ -742,7 +745,7 @@ class MessageInputState extends State { final textToSelection = textEditingController.text .substring(0, textEditingController.value.selection.start); final splits = textToSelection.split(':'); - final query = splits[splits.length - 2]?.toLowerCase(); + final query = splits[splits.length - 2].toLowerCase(); final emoji = Emoji.byName(query); if (textToSelection.endsWith(':') && emoji != null) { @@ -751,7 +754,7 @@ class MessageInputState extends State { _emojiOverlay = _buildEmojiOverlay(); if (_emojiOverlay != null) { - Overlay.of(context).insert(_emojiOverlay); + Overlay.of(context)!.insert(_emojiOverlay!); } } } @@ -767,7 +770,7 @@ class MessageInputState extends State { .contains('@')) { _mentionsOverlay = _buildMentionsOverlayEntry(); if (_mentionsOverlay != null) { - Overlay.of(context).insert(_mentionsOverlay); + Overlay.of(context)!.insert(_mentionsOverlay!); } } } @@ -778,8 +781,8 @@ class MessageInputState extends State { .channel .config ?.commands - ?.where((element) => element.name == s.substring(1)) - ?.toList() ?? + .where((element) => element.name == s.substring(1)) + .toList() ?? []; if (matchedCommandsList.length == 1) { @@ -789,32 +792,32 @@ class MessageInputState extends State { setState(() { _commandEnabled = true; }); - _commandsOverlay.remove(); + _commandsOverlay!.remove(); _commandsOverlay = null; } else { _commandsOverlay = _buildCommandsOverlayEntry(); if (_commandsOverlay != null) { - Overlay.of(context).insert(_commandsOverlay); + Overlay.of(context)!.insert(_commandsOverlay!); } } } } - OverlayEntry _buildCommandsOverlayEntry() { + OverlayEntry? _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); final commands = StreamChannel.of(context) .channel .config ?.commands - ?.where((c) => c.name.contains(text.replaceFirst('/', ''))) - ?.toList() ?? + .where((c) => c.name.contains(text.replaceFirst('/', ''))) + .toList() ?? []; if (commands.isEmpty) { return null; } - RenderBox renderBox = context.findRenderObject(); + final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry(builder: (context) { @@ -951,7 +954,6 @@ class MessageInputState extends State { .colorTheme .black .withOpacity(0.2)); - break; case 1: return _attachmentContainsFile ? StreamChatTheme.of(context).colorTheme.accentBlue @@ -964,17 +966,14 @@ class MessageInputState extends State { .colorTheme .black .withOpacity(0.2)); - break; case 2: return _attachmentContainsFile && _attachments.isNotEmpty ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); - break; case 3: return _attachmentContainsFile && _attachments.isNotEmpty ? StreamChatTheme.of(context).colorTheme.black.withOpacity(0.2) : StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5); - break; default: return Colors.black; } @@ -1106,10 +1105,10 @@ class MessageInputState extends State { } void _addAttachment(AssetEntity medium) async { - final mediaFile = await medium.originFile.timeout( + final mediaFile = await (medium.originFile.timeout( Duration(seconds: 5), onTimeout: () => medium.originFile, - ); + ) as FutureOr); var file = AttachmentFile( path: mediaFile.path, @@ -1117,11 +1116,12 @@ class MessageInputState extends State { bytes: mediaFile.readAsBytesSync(), ); - if (file.size > _kMaxAttachmentSize) { - if (medium?.type == AssetType.video) { - final mediaInfo = await VideoService.compressVideo(file.path); + if (file.size! > _kMaxAttachmentSize) { + if (medium.type == AssetType.video) { + final mediaInfo = await (VideoService.compressVideo(file.path) + as FutureOr); - if (mediaInfo.filesize > _kMaxAttachmentSize) { + if (mediaInfo.filesize! > _kMaxAttachmentSize) { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); @@ -1129,8 +1129,8 @@ class MessageInputState extends State { } file = AttachmentFile( name: file.name, - size: mediaInfo.filesize, - bytes: await mediaInfo.file.readAsBytes(), + size: mediaInfo.filesize!, + bytes: await mediaInfo.file?.readAsBytes(), path: mediaInfo.path, ); } else { @@ -1159,7 +1159,6 @@ class MessageInputState extends State { size: 24.0, ), ); - break; case 'ban': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1169,7 +1168,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'flag': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1179,7 +1177,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'imgur': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1190,7 +1187,6 @@ class MessageInputState extends State { ), ), ); - break; case 'mute': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1200,7 +1196,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'unban': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1210,7 +1205,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; case 'unmute': return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1220,7 +1214,6 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; default: return CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1230,17 +1223,16 @@ class MessageInputState extends State { color: Colors.white, ), ); - break; } } - OverlayEntry _buildMentionsOverlayEntry() { + OverlayEntry? _buildMentionsOverlayEntry() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split('@'); final query = splits.last.toLowerCase(); - Future> queryMembers; + Future>? queryMembers; if (query.isNotEmpty) { queryMembers = StreamChannel.of(context) @@ -1249,16 +1241,16 @@ class MessageInputState extends State { .then((res) => res.members); } - final members = StreamChannel.of(context).channel.state.members?.where((m) { - return m.user.name.toLowerCase().contains(query); - })?.toList() ?? + final members = StreamChannel.of(context).channel.state?.members.where((m) { + return m.user?.name.toLowerCase().contains(query) == true; + }).toList() ?? []; if (members.isEmpty) { return null; } - RenderBox renderBox = context.findRenderObject(); + final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry( @@ -1298,7 +1290,9 @@ class MessageInputState extends State { SizedBox( height: 8.0, ), - ...snapshot.data.map( + ...snapshot.data! + .where((it) => it.user != null) + .map( (m) { return Material( color: StreamChatTheme.of(context) @@ -1306,9 +1300,11 @@ class MessageInputState extends State { .white, child: InkWell( onTap: () { - _mentionedUsers.add(m.user); + if (m.user != null) { + _mentionedUsers.add(m.user!); + } - splits[splits.length - 1] = m.user.name; + splits[splits.length - 1] = m.user!.name; final rejoin = splits.join('@'); textEditingController.value = @@ -1321,12 +1317,13 @@ class MessageInputState extends State { offset: rejoin.length, ), ); - _debounce.cancel(); + _debounce!.cancel(); _mentionsOverlay?.remove(); _mentionsOverlay = null; }, child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder(context, m) + ? widget.mentionsTileBuilder!( + context, m) : MentionTile(m), ), ); @@ -1349,7 +1346,7 @@ class MessageInputState extends State { ); } - OverlayEntry _buildEmojiOverlay() { + OverlayEntry? _buildEmojiOverlay() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split(':'); @@ -1368,7 +1365,7 @@ class MessageInputState extends State { return null; } - RenderBox renderBox = context.findRenderObject(); + final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; return OverlayEntry(builder: (context) { @@ -1431,19 +1428,20 @@ class MessageInputState extends State { ); } - final emoji = emojis.elementAt(i - 1); + final emoji = emojis.elementAt(i - 1)!; return ListTile( title: SubstringHighlight( - text: "${emoji.char} ${emoji.name.replaceAll('_', ' ')}", + text: "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", term: query, textStyleHighlight: - Theme.of(context).textTheme.headline6.copyWith( + Theme.of(context).textTheme.headline6!.copyWith( fontSize: 14.5, fontWeight: FontWeight.bold, ), - textStyle: Theme.of(context).textTheme.headline6.copyWith( - fontSize: 14.5, - ), + textStyle: + Theme.of(context).textTheme.headline6!.copyWith( + fontSize: 14.5, + ), ), onTap: () { _chooseEmoji(splits, emoji); @@ -1457,7 +1455,7 @@ class MessageInputState extends State { } void _chooseEmoji(List splits, Emoji emoji) { - final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char; + final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char!; textEditingController.value = TextEditingValue( text: rejoin + @@ -1485,8 +1483,8 @@ class MessageInputState extends State { Widget _buildReplyToMessage() { if (!_hasQuotedMessage) return Offstage(); - final containsUrl = widget.quotedMessage.attachments - ?.any((element) => element.ogScrapeUrl != null) == + final containsUrl = widget.quotedMessage!.attachments + .any((element) => element.ogScrapeUrl != null) == true; return Transform( transform: Matrix4.rotationY(pi), @@ -1494,7 +1492,7 @@ class MessageInputState extends State { child: QuotedMessageWidget( reverse: true, showBorder: !containsUrl, - message: widget.quotedMessage, + message: widget.quotedMessage!, messageTheme: StreamChatTheme.of(context).otherMessageTheme, padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), ), @@ -1525,7 +1523,7 @@ class MessageInputState extends State { borderRadius: BorderRadius.circular(10), clipBehavior: Clip.antiAlias, child: FileAttachment( - message: null, + message: Message(), // dummy message attachment: e, size: Size( MediaQuery.of(context).size.width * 0.65, @@ -1609,11 +1607,9 @@ class MessageInputState extends State { } Widget _buildAttachment(Attachment attachment) { - if (attachment == null) return Offstage(); - if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == true) { - return widget.attachmentThumbnailBuilders[attachment.type]( + return widget.attachmentThumbnailBuilders![attachment.type!]!( context, attachment, ); @@ -1624,7 +1620,7 @@ class MessageInputState extends State { case 'giphy': return attachment.file != null ? Image.memory( - attachment.file.bytes, + attachment.file!.bytes!, fit: BoxFit.cover, errorBuilder: (context, _, __) { return Image.asset( @@ -1636,10 +1632,11 @@ class MessageInputState extends State { : CachedNetworkImage( imageUrl: attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl!, fit: BoxFit.cover, errorWidget: (_, obj, trace) { - return getFileTypeImage(attachment.extraData['other']); + return getFileTypeImage( + attachment.extraData['other'] as String?); }, progressIndicatorBuilder: (context, _, progress) { return Shimmer.fromColors( @@ -1717,7 +1714,7 @@ class MessageInputState extends State { setState(() { _commandsOverlay = _buildCommandsOverlayEntry(); if (_commandsOverlay != null) { - Overlay.of(context).insert(_commandsOverlay); + Overlay.of(context)!.insert(_commandsOverlay!); } }); } else { @@ -1852,7 +1849,7 @@ class MessageInputState extends State { void addAttachment(Attachment attachment) { setState(() { _attachments[attachment.id] = attachment.copyWith( - uploadState: attachment.uploadState ?? UploadState.success(), + uploadState: attachment.uploadState, ); }); } @@ -1862,8 +1859,8 @@ class MessageInputState extends State { void pickFile(DefaultAttachmentTypes fileType, [bool camera = false]) async { setState(() => _inputEnabled = false); - AttachmentFile file; - String attachmentType; + AttachmentFile? file; + String? attachmentType; if (fileType == DefaultAttachmentTypes.image) { attachmentType = 'image'; @@ -1874,7 +1871,7 @@ class MessageInputState extends State { } if (camera) { - PickedFile pickedFile; + PickedFile? pickedFile; if (fileType == DefaultAttachmentTypes.image) { pickedFile = await _imagePicker.getImage(source: ImageSource.camera); } else if (fileType == DefaultAttachmentTypes.video) { @@ -1890,7 +1887,7 @@ class MessageInputState extends State { bytes: bytes, ); } else { - FileType type; + late FileType type; if (fileType == DefaultAttachmentTypes.image) { type = FileType.image; } else if (fileType == DefaultAttachmentTypes.video) { @@ -1902,8 +1899,8 @@ class MessageInputState extends State { type: type, withData: true, ); - if (res?.files?.isNotEmpty == true) { - file = res.files.single.toAttachmentFile; + if (res?.files.isNotEmpty == true) { + file = res!.files.single.toAttachmentFile; } } @@ -1911,29 +1908,28 @@ class MessageInputState extends State { if (file == null) return; - final mimeType = file.name?.mimeType ?? file.path.split('/').last.mimeType; + final mimeType = file.name?.mimeType ?? file.path!.split('/').last.mimeType; - final extraDataMap = {}; + final extraDataMap = {}; if (mimeType?.subtype != null) { - extraDataMap['mime_type'] = mimeType.subtype.toLowerCase(); + extraDataMap['mime_type'] = mimeType!.subtype.toLowerCase(); } - if (file.size != null) { - extraDataMap['file_size'] = file.size; - } + extraDataMap['file_size'] = file.size!; final attachment = Attachment( file: file, type: attachmentType, - extraData: extraDataMap.isNotEmpty ? extraDataMap : null, + extraData: extraDataMap, ); - if (file.size > _kMaxAttachmentSize) { + if (file.size! > _kMaxAttachmentSize) { if (attachmentType == 'Video') { - final mediaInfo = await VideoService.compressVideo(file.path); + final mediaInfo = await (VideoService.compressVideo(file.path) + as FutureOr); - if (mediaInfo.filesize > _kMaxAttachmentSize) { + if (mediaInfo.filesize! > _kMaxAttachmentSize) { _showErrorAlert( 'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.', ); @@ -1941,8 +1937,8 @@ class MessageInputState extends State { } file = AttachmentFile( name: file.name, - size: mediaInfo.filesize, - bytes: await mediaInfo.file.readAsBytes(), + size: mediaInfo.filesize!, + bytes: await mediaInfo.file!.readAsBytes(), path: mediaInfo.path, ); } else { @@ -1959,7 +1955,8 @@ class MessageInputState extends State { _attachments.update(attachment.id, (it) { return it.copyWith( file: file, - extraData: {...it.extraData}..update('file_size', (_) => file.size), + extraData: {...it.extraData} + ..update('file_size', ((_) => file!.size!)), ); }); }); @@ -2023,7 +2020,7 @@ class MessageInputState extends State { final shouldUnfocus = _commandEnabled; if (_commandEnabled) { - text = '/${_chosenCommand.name} ' + text; + text = '/${_chosenCommand!.name} ' + text; } final attachments = [..._attachments.values]; @@ -2031,7 +2028,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); if (widget.onQuotedMessageCleared != null) { - widget.onQuotedMessageCleared(); + widget.onQuotedMessageCleared!(); } setState(() { @@ -2047,7 +2044,7 @@ class MessageInputState extends State { Future sendingFuture; Message message; if (widget.editMessage != null) { - message = widget.editMessage.copyWith( + message = widget.editMessage!.copyWith( text: text, attachments: attachments, mentionedUsers: @@ -2066,25 +2063,25 @@ class MessageInputState extends State { if (widget.quotedMessage != null) { message = message.copyWith( - quotedMessageId: widget.quotedMessage.id, + quotedMessageId: widget.quotedMessage!.id, ); } if (widget.preMessageSending != null) { - message = await widget.preMessageSending(message); + message = await widget.preMessageSending!(message); } final streamChannel = StreamChannel.of(context); final channel = streamChannel.channel; - if (!channel.state.isUpToDate) { + if (!channel.state!.isUpToDate) { await streamChannel.reloadChannel(); } _mentionedUsers.clear(); if (widget.editMessage == null || - widget.editMessage.status == MessageSendingStatus.failed || - widget.editMessage.status == MessageSendingStatus.sending) { + widget.editMessage!.status == MessageSendingStatus.failed || + widget.editMessage!.status == MessageSendingStatus.sending) { sendingFuture = channel.sendMessage(message); } else { sendingFuture = channel.updateMessage(message); @@ -2099,12 +2096,12 @@ class MessageInputState extends State { _parseExistingMessage(message); } if (widget.onMessageSent != null) { - widget.onMessageSent(resp.message); + widget.onMessageSent!(resp.message); } }); } - StreamSubscription _keyboardListener; + StreamSubscription? _keyboardListener; void _showErrorAlert(String description) { showModalBottomSheet( @@ -2178,14 +2175,12 @@ class MessageInputState extends State { } void _parseExistingMessage(Message message) { - textEditingController.text = message.text; + textEditingController.text = message.text!; _messageIsPresent = true; - if (message.attachments != null) { - for (final attachment in message.attachments) { - _attachments[attachment.id] = attachment.copyWith( - uploadState: attachment.uploadState ?? UploadState.success(), - ); - } + for (final attachment in message.attachments) { + _attachments[attachment.id] = attachment.copyWith( + uploadState: attachment.uploadState, + ); } } @@ -2266,12 +2261,12 @@ class _PickerWidget extends StatefulWidget { final void Function(AssetEntity) onMediaSelected; const _PickerWidget({ - Key key, - @required this.filePickerIndex, - @required this.containsFile, - @required this.selectedMedias, - @required this.onAddMoreFilesClick, - @required this.onMediaSelected, + Key? key, + required this.filePickerIndex, + required this.containsFile, + required this.selectedMedias, + required this.onAddMoreFilesClick, + required this.onMediaSelected, }) : super(key: key); @override @@ -2279,7 +2274,7 @@ class _PickerWidget extends StatefulWidget { } class __PickerWidgetState extends State<_PickerWidget> { - Future requestPermission; + Future? requestPermission; @override void initState() { @@ -2299,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> { return const Center(child: CircularProgressIndicator()); } - if (snapshot.data) { + if (snapshot.data!) { if (widget.containsFile) { return GestureDetector( onTap: () { diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 9af5bc4a..f444224d 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -26,14 +26,14 @@ typedef MessageBuilder = Widget Function( ); typedef ParentMessageBuilder = Widget Function( BuildContext, - Message, + Message?, ); typedef SystemMessageBuilder = Widget Function( BuildContext, Message, ); -typedef ThreadBuilder = Widget Function(BuildContext context, Message parent); -typedef ThreadTapCallback = void Function(Message, Widget); +typedef ThreadBuilder = Widget Function(BuildContext context, Message? parent); +typedef ThreadTapCallback = void Function(Message, Widget?); typedef OnMessageSwiped = void Function(Message); typedef OnMessageTap = void Function(Message); @@ -41,13 +41,13 @@ typedef ReplyTapCallback = void Function(Message); class MessageDetails { /// True if the message belongs to the current user - bool isMyMessage; + bool? isMyMessage; /// True if the user message is the same of the previous message - bool isLastUser; + bool? isLastUser; /// True if the user message is the same of the next message - bool isNextUser; + bool? isNextUser; /// The message Message message; @@ -61,11 +61,11 @@ class MessageDetails { List messages, this.index, ) { - isMyMessage = message.user.id == StreamChat.of(context).user.id; + isMyMessage = message.user?.id == StreamChat.of(context).user?.id; isLastUser = index + 1 < messages.length && - message.user.id == messages[index + 1]?.user?.id; + message.user?.id == messages[index + 1].user?.id; isNextUser = - index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id; + index - 1 >= 0 && message.user!.id == messages[index - 1].user?.id; } } @@ -112,7 +112,7 @@ class MessageDetails { class MessageListView extends StatefulWidget { /// Instantiate a new MessageListView MessageListView({ - Key key, + Key? key, this.showScrollToBottom = true, this.messageBuilder, this.parentMessageBuilder, @@ -145,52 +145,52 @@ class MessageListView extends StatefulWidget { }) : super(key: key); /// Function used to build a custom message widget - final MessageBuilder messageBuilder; + final MessageBuilder? messageBuilder; /// Function used to build a custom system message widget - final SystemMessageBuilder systemMessageBuilder; + final SystemMessageBuilder? systemMessageBuilder; /// Function used to build a custom parent message widget - final ParentMessageBuilder parentMessageBuilder; + final ParentMessageBuilder? parentMessageBuilder; /// Function used to build a custom thread widget - final ThreadBuilder threadBuilder; + final ThreadBuilder? threadBuilder; /// Function called when tapping on a thread /// By default it calls [Navigator.push] using the widget built using [threadBuilder] - final ThreadTapCallback onThreadTap; + final ThreadTapCallback? onThreadTap; /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero final bool showScrollToBottom; /// Parent message in case of a thread - final Message parentMessage; + final Message? parentMessage; /// Builder used to render date dividers - final Widget Function(DateTime) dateDividerBuilder; + final Widget Function(DateTime)? dateDividerBuilder; /// Index of an item to initially align within the viewport. - final int initialScrollIndex; + final int? initialScrollIndex; /// Determines where the leading edge of the item at [initialScrollIndex] /// should be placed. - final double initialAlignment; + final double? initialAlignment; /// Controller for jumping or scrolling to an item. - final ItemScrollController scrollController; + final ItemScrollController? scrollController; /// Provides a listenable iterable of [itemPositions] of items that are on /// screen and their locations. - final ItemPositionsListener itemPositionListener; + final ItemPositionsListener? itemPositionListener; /// The ScrollPhysics used by the ListView final ScrollPhysics scrollPhysics; /// Called when message item gets swiped - final OnMessageSwiped onMessageSwiped; + final OnMessageSwiped? onMessageSwiped; /// - final ReplyTapCallback onReplyTap; + final ReplyTapCallback? onReplyTap; /// If true the list will highlight the initialMessage if there is any. /// @@ -198,64 +198,64 @@ class MessageListView extends StatefulWidget { final bool highlightInitialMessage; /// Color used while highlighting initial message - final Color messageHighlightColor; + final Color? messageHighlightColor; - final ShowMessageCallback onShowMessage; + final ShowMessageCallback? onShowMessage; final bool showConnectionStateTile; /// Function called when messages are fetched - final Widget Function(BuildContext, List) messageListBuilder; + final Widget Function(BuildContext, List)? messageListBuilder; /// Function used to build a loading widget - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// Function used to build an empty widget - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; /// Callback triggered when an error occurs while performing the given request. /// This parameter can be used to display an error message to users in the event /// of a connection failure. - final ErrorBuilder errorWidgetBuilder; + final ErrorBuilder? errorWidgetBuilder; /// Predicate used to filter messages - final bool Function(Message) messageFilter; + final bool Function(Message)? messageFilter; /// Attachment builders for the default message widget /// Please change this in the [MessageWidget] if you are using a custom implementation - final Map customAttachmentBuilders; + final Map? customAttachmentBuilders; /// Called when any message is tapped except a system message (use [onSystemMessageTap] instead) - final OnMessageTap onMessageTap; + final OnMessageTap? onMessageTap; /// Called when system message is tapped - final OnMessageTap onSystemMessageTap; + final OnMessageTap? onSystemMessageTap; /// Customize onTap on attachment - final void Function(Message message, Attachment attachment) onAttachmentTap; + final void Function(Message message, Attachment attachment)? onAttachmentTap; /// Customize the MessageWidget textBuilder - final void Function(BuildContext context, Message message) textBuilder; + final void Function(BuildContext context, Message message)? textBuilder; @override _MessageListViewState createState() => _MessageListViewState(); } class _MessageListViewState extends State { - ItemScrollController _scrollController; - Function _onThreadTap; + ItemScrollController? _scrollController; + Function? _onThreadTap; bool _showScrollToBottom = false; - ItemPositionsListener _itemPositionListener; - int _messageListLength; - StreamChannelState streamChannel; + late final ItemPositionsListener _itemPositionListener; + int? _messageListLength; + StreamChannelState? streamChannel; - int get _initialIndex { + int? get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - if (streamChannel.initialMessageId != null) { - final messages = streamChannel.channel.state.messages; + if (streamChannel!.initialMessageId != null) { + final messages = streamChannel!.channel.state!.messages; final totalMessages = messages.length; final messageIndex = messages.indexWhere((e) { - return e.id == streamChannel.initialMessageId; + return e.id == streamChannel!.initialMessageId; }); final index = totalMessages - messageIndex; if (index != 0) return index - 1; @@ -264,24 +264,24 @@ class _MessageListViewState extends State { return 0; } - double get _initialAlignment { + double? get _initialAlignment { if (widget.initialAlignment != null) return widget.initialAlignment; return 0; } bool _isInitialMessage(String id) { - return streamChannel.initialMessageId == id; + return streamChannel!.initialMessageId == id; } - bool get _upToDate => streamChannel.channel.state.isUpToDate; + bool get _upToDate => streamChannel!.channel.state!.isUpToDate; bool get _isThreadConversation => widget.parentMessage != null; bool _topPaginationActive = false; bool _bottomPaginationActive = false; - int initialIndex; - double initialAlignment; + int? initialIndex; + double? initialAlignment; List messages = []; @@ -343,9 +343,9 @@ class _MessageListViewState extends State { if (_messageListLength != null) { if (_bottomPaginationActive || (_inBetweenList && _upToDate)) { - if (_itemPositionListener.itemPositions.value?.isNotEmpty == true) { + if (_itemPositionListener.itemPositions.value.isNotEmpty == true) { final first = _itemPositionListener.itemPositions.value.first; - final diff = newMessagesListLength - _messageListLength; + final diff = newMessagesListLength - _messageListLength!; if (diff > 0) { initialIndex = first.index + diff; initialAlignment = first.itemLeadingEdge; @@ -413,7 +413,7 @@ class _MessageListViewState extends State { _inBetweenList = true; }, child: ScrollablePositionedList.separated( - key: ValueKey(initialIndex + initialAlignment), + key: ValueKey(initialIndex! + initialAlignment!), itemPositionsListener: _itemPositionListener, addAutomaticKeepAlives: true, initialScrollIndex: initialIndex ?? 0, @@ -427,7 +427,7 @@ class _MessageListViewState extends State { if (i == messages.length) return Offstage(); if (i == 0) return SizedBox(height: 30); if (i == messages.length + 1) { - final replyCount = widget.parentMessage.replyCount; + final replyCount = widget.parentMessage!.replyCount; return Container( decoration: BoxDecoration( gradient: @@ -454,7 +454,7 @@ class _MessageListViewState extends State { Units.DAY, )) { final divider = widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( + ? widget.dateDividerBuilder!( nextMessage.createdAt.toLocal(), ) : DateDivider( @@ -472,8 +472,8 @@ class _MessageListViewState extends State { ); final isNextUserSame = - message.user.id == nextMessage.user?.id; - final isThread = message.replyCount > 0; + message.user!.id == nextMessage.user?.id; + final isThread = message.replyCount! > 0; final isDeleted = message.isDeleted; if (timeDiff >= 1 || !isNextUserSame || @@ -486,12 +486,12 @@ class _MessageListViewState extends State { itemBuilder: (context, i) { if (i == messages.length + 2) { if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder( + return widget.parentMessageBuilder!( context, widget.parentMessage, ); } else { - return buildParentMessage(widget.parentMessage); + return buildParentMessage(widget.parentMessage!); } } if (i == messages.length + 1) { @@ -528,7 +528,7 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (context) => widget.messageBuilder( + builder: (context) => widget.messageBuilder!( context, MessageDetails( context, @@ -555,7 +555,7 @@ class _MessageListViewState extends State { child: ValueListenableBuilder>( valueListenable: _itemPositionListener.itemPositions, builder: (context, values, _) { - final items = _itemPositionListener.itemPositions?.value; + final items = _itemPositionListener.itemPositions.value; if (items.isEmpty || messages.isEmpty) { return SizedBox(); } @@ -571,7 +571,7 @@ class _MessageListViewState extends State { } return widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( + ? widget.dateDividerBuilder!( messages[index].createdAt.toLocal(), ) : DateDivider( @@ -585,8 +585,8 @@ class _MessageListViewState extends State { } Future _paginateData( - StreamChannelState channel, QueryDirection direction) { - return _messageListController.paginateData(direction: direction); + StreamChannelState? channel, QueryDirection direction) { + return _messageListController.paginateData!(direction: direction); } ItemPosition _getTopElement(Iterable values) { @@ -599,8 +599,8 @@ class _MessageListViewState extends State { Widget _buildScrollToBottom() { return StreamBuilder>( stream: Rx.combineLatest2( - streamChannel.channel.state.isUpToDateStream, - streamChannel.channel.state.unreadCountStream, + streamChannel!.channel.state!.isUpToDateStream, + streamChannel!.channel.state!.unreadCountStream, (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), ), builder: (_, snapshot) { @@ -609,15 +609,15 @@ class _MessageListViewState extends State { } else if (!snapshot.hasData) { return Offstage(); } - final isUpToDate = snapshot.data.item1; + final isUpToDate = snapshot.data!.item1; final showScrollToBottom = !isUpToDate || _showScrollToBottom; if (!showScrollToBottom) { return Offstage(); } - final unreadCount = snapshot.data.item2; + final unreadCount = snapshot.data!.item2; final showUnreadCount = unreadCount > 0 && - streamChannel.channel.state.members.any( - (e) => e.userId == streamChannel.channel.client.state.user.id); + streamChannel!.channel.state!.members.any((e) => + e.userId == streamChannel!.channel.client.state.user!.id); return Positioned( bottom: 8, right: 8, @@ -630,15 +630,15 @@ class _MessageListViewState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.white, onPressed: () { if (unreadCount > 0) { - streamChannel.channel.markRead(); + streamChannel!.channel.markRead(); } if (!_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } else { setState(() => _showScrollToBottom = false); - _scrollController.scrollTo( + _scrollController!.scrollTo( index: 0, duration: Duration(seconds: 1), curve: Curves.easeInOut, @@ -676,12 +676,12 @@ class _MessageListViewState extends State { } Widget _buildLoadingIndicator( - StreamChannelState streamChannel, + StreamChannelState? streamChannel, QueryDirection direction, ) { final stream = direction == QueryDirection.top - ? streamChannel.queryTopMessages - : streamChannel.queryBottomMessages; + ? streamChannel!.queryTopMessages + : streamChannel!.queryBottomMessages; return StreamBuilder( key: Key('LOADING-INDICATOR'), stream: stream, @@ -698,7 +698,7 @@ class _MessageListViewState extends State { ), ); } - if (!snapshot.data) { + if (!snapshot.data!) { if (!_isThreadConversation && direction == QueryDirection.top) { return Container( height: 52, @@ -721,13 +721,13 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState streamChannel, + StreamChannelState? streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('TOP-MESSAGE'), - builder: (_) => widget.messageBuilder( + builder: (_) => widget.messageBuilder!( context, MessageDetails( context, @@ -748,13 +748,13 @@ class _MessageListViewState extends State { BuildContext context, Message message, List messages, - StreamChannelState streamChannel, + StreamChannelState? streamChannel, ) { Widget messageWidget; if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('BOTTOM-MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder( + builder: (_) => widget.messageBuilder!( context, MessageDetails( context, @@ -774,10 +774,10 @@ class _MessageListViewState extends State { onVisibilityChanged: (visibility) { final isVisible = visibility.visibleBounds != Rect.zero; if (isVisible) { - final channel = streamChannel.channel; + final channel = streamChannel!.channel; if (_upToDate && channel.config?.readEvents == true && - channel.state.unreadCount > 0) { + channel.state!.unreadCount! > 0) { streamChannel.channel.markRead(); } } @@ -792,8 +792,8 @@ class _MessageListViewState extends State { Widget buildParentMessage( Message message, ) { - final isMyMessage = message.user.id == StreamChat.of(context).user.id; - final isOnlyEmoji = message.text.isOnlyEmoji; + final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; + final isOnlyEmoji = message.text!.isOnlyEmoji; return MessageWidget( showThreadReplyIndicator: false, @@ -809,7 +809,7 @@ class _MessageListViewState extends State { showUsername: !isMyMessage, padding: const EdgeInsets.all(8.0), showSendingIndicator: false, - onThreadTap: _onThreadTap, + onThreadTap: _onThreadTap as void Function(Message)?, borderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular(2), @@ -832,18 +832,19 @@ class _MessageListViewState extends State { break; case ReturnActionType.reply: FocusScope.of(context).unfocus(); - widget.onMessageSwiped(message); + widget.onMessageSwiped!(message); break; } }, customAttachmentBuilders: widget.customAttachmentBuilders, onMessageTap: (message) { if (widget.onMessageTap != null) { - widget.onMessageTap(message); + widget.onMessageTap!(message); } FocusScope.of(context).unfocus(); }, - textBuilder: widget.textBuilder, + textBuilder: + widget.textBuilder as Widget Function(BuildContext, Message)?, ); } @@ -860,18 +861,18 @@ class _MessageListViewState extends State { message: message, onMessageTap: (message) { if (widget.onSystemMessageTap != null) { - widget.onSystemMessageTap(message); + widget.onSystemMessageTap!(message); } FocusScope.of(context).unfocus(); }, ); } - final userId = StreamChat.of(context).user.id; - final isMyMessage = message.user.id == userId; + final userId = StreamChat.of(context).user!.id; + final isMyMessage = message.user!.id == userId; final nextMessage = index - 2 >= 0 ? messages[index - 2] : null; final isNextUserSame = - nextMessage != null && message.user.id == nextMessage.user.id; + nextMessage != null && message.user!.id == nextMessage.user!.id; num timeDiff = 0; if (nextMessage != null) { @@ -881,27 +882,26 @@ class _MessageListViewState extends State { ); } - final channel = streamChannel.channel; + final channel = streamChannel!.channel; final readList = channel.state?.read?.where((read) { if (read.user.id == userId) return false; return (read.lastRead.isAfter(message.createdAt) || read.lastRead.isAtSameMomentAs(message.createdAt)); - })?.toList() ?? + }).toList() ?? []; final allRead = readList.length >= (channel.memberCount ?? 0) - 1; final hasFileAttachment = - message.attachments?.any((it) => it.type == 'file') == true; + message.attachments.any((it) => it.type == 'file') == true; final isThreadMessage = - message?.parentId != null && message?.showInChannel == true; + message.parentId != null && message.showInChannel == true; - final hasReplies = message.replyCount > 0; + final hasReplies = message.replyCount! > 0; final attachmentBorderRadius = hasFileAttachment ? 12.0 : 14.0; - final showTimeStamp = message.createdAt != null && - (!isThreadMessage || _isThreadConversation) && + final showTimeStamp = (!isThreadMessage || _isThreadConversation) && !hasReplies && (timeDiff >= 1 || !isNextUserSame); @@ -921,10 +921,10 @@ class _MessageListViewState extends State { final showInChannelIndicator = !_isThreadConversation && isThreadMessage; final showThreadReplyIndicator = !_isThreadConversation && hasReplies; - final isOnlyEmoji = message.text.isOnlyEmoji; + final isOnlyEmoji = message.text!.isOnlyEmoji; final hasUrlAttachment = - message.attachments?.any((it) => it.ogScrapeUrl != null) == true; + message.attachments.any((it) => it.ogScrapeUrl != null) == true; final borderSide = isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment) @@ -954,8 +954,8 @@ class _MessageListViewState extends State { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } else { - await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { - WidgetsBinding.instance.addPostFrameCallback((_) { + await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { + WidgetsBinding.instance!.addPostFrameCallback((_) { if (messages.map((e) => e.id).contains(quotedMessageId)) { scrollToIndex(); } @@ -968,7 +968,7 @@ class _MessageListViewState extends State { showThreadReplyMessage: !isThreadMessage, showFlagButton: !isMyMessage, borderSide: borderSide, - onThreadTap: _onThreadTap, + onThreadTap: _onThreadTap as void Function(Message)?, onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), @@ -1008,19 +1008,20 @@ class _MessageListViewState extends State { break; case ReturnActionType.reply: FocusScope.of(context).unfocus(); - widget.onMessageSwiped(message); + widget.onMessageSwiped!(message); break; } }, customAttachmentBuilders: widget.customAttachmentBuilders, onMessageTap: (message) { if (widget.onMessageTap != null) { - widget.onMessageTap(message); + widget.onMessageTap!(message); } FocusScope.of(context).unfocus(); }, onAttachmentTap: widget.onAttachmentTap, - textBuilder: widget.textBuilder, + textBuilder: + widget.textBuilder as Widget Function(BuildContext, Message)?, ); if (!message.isDeleted && @@ -1033,7 +1034,7 @@ class _MessageListViewState extends State { child: Swipeable( onSwipeEnd: () { FocusScope.of(context).unfocus(); - widget.onMessageSwiped(message); + widget.onMessageSwiped!(message); }, backgroundIcon: StreamSvgIcon.reply( color: StreamChatTheme.of(context).colorTheme.accentBlue, @@ -1049,7 +1050,7 @@ class _MessageListViewState extends State { final colorTheme = StreamChatTheme.of(context).colorTheme; final highlightColor = widget.messageHighlightColor ?? colorTheme.highlight; - child = TweenAnimationBuilder( + child = TweenAnimationBuilder( tween: ColorTween( begin: highlightColor, end: colorTheme.white.withOpacity(0), @@ -1071,7 +1072,7 @@ class _MessageListViewState extends State { return child; } - StreamSubscription _messageNewListener; + StreamSubscription? _messageNewListener; @override void initState() { @@ -1085,13 +1086,14 @@ class _MessageListViewState extends State { initialAlignment = _initialAlignment; _messageNewListener = - streamChannel.channel.on(EventType.messageNew).listen((event) { + streamChannel!.channel.on(EventType.messageNew).listen((event) { if (_upToDate) { _bottomPaginationActive = false; _topPaginationActive = false; } - if (event.message.user.id == streamChannel.channel.client.state.user.id) { - WidgetsBinding.instance.addPostFrameCallback((_) { + if (event.message!.user!.id == + streamChannel!.channel.client.state.user!.id) { + WidgetsBinding.instance!.addPostFrameCallback((_) { _scrollController?.jumpTo( index: 0, ); @@ -1100,7 +1102,7 @@ class _MessageListViewState extends State { }); if (_isThreadConversation) { - streamChannel.getReplies(widget.parentMessage.id); + streamChannel!.getReplies(widget.parentMessage!.id); } _getOnThreadTap(); @@ -1110,10 +1112,10 @@ class _MessageListViewState extends State { void _getOnThreadTap() { if (widget.onThreadTap != null) { _onThreadTap = (Message message) { - widget.onThreadTap( + widget.onThreadTap!( message, widget.threadBuilder != null - ? widget.threadBuilder(context, message) + ? widget.threadBuilder!(context, message) : null); }; } else if (widget.threadBuilder != null) { @@ -1122,14 +1124,14 @@ class _MessageListViewState extends State { context, MaterialPageRoute(builder: (_) { return StreamBuilder( - stream: streamChannel.channel.state.messagesStream.map( + stream: streamChannel!.channel.state!.messagesStream.map( (messages) => - messages.firstWhere((m) => m.id == message.id)), + messages!.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) { return StreamChannel( - channel: streamChannel.channel, - child: widget.threadBuilder(context, snapshot.data), + channel: streamChannel!.channel, + child: widget.threadBuilder!(context, snapshot.data), ); }); }), @@ -1141,7 +1143,7 @@ class _MessageListViewState extends State { @override void dispose() { if (!_upToDate) { - streamChannel.reloadChannel(); + streamChannel!.reloadChannel(); } _messageNewListener?.cancel(); super.dispose(); diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index e2171078..ed9534ac 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -14,19 +14,19 @@ import 'stream_chat_theme.dart'; class MessageReactionsModal extends StatelessWidget { final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; final bool reverse; final bool showReactions; final DisplayWidget showUserAvatar; - final ShapeBorder messageShape; - final ShapeBorder attachmentShape; - final void Function(User) onUserAvatarTap; - final BorderRadius attachmentBorderRadiusGeometry; + final ShapeBorder? messageShape; + final ShapeBorder? attachmentShape; + final void Function(User)? onUserAvatarTap; + final BorderRadius? attachmentBorderRadiusGeometry; const MessageReactionsModal({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.showReactions = true, this.messageShape, this.attachmentShape, @@ -42,10 +42,10 @@ class MessageReactionsModal extends StatelessWidget { final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; - var messageTextLength = message.text.length; + var messageTextLength = message.text!.length; if (message.quotedMessage != null) { - var quotedMessageLength = message.quotedMessage.text.length + 40; - if (message.quotedMessage.attachments?.isNotEmpty == true) { + var quotedMessageLength = message.quotedMessage!.text!.length + 40; + if (message.quotedMessage!.attachments.isNotEmpty == true) { quotedMessageLength += 40; } if (quotedMessageLength > messageTextLength) { @@ -53,8 +53,8 @@ class MessageReactionsModal extends StatelessWidget { } } final roughSentenceSize = - messageTextLength * messageTheme.messageText.fontSize * 1.2; - final divFactor = message.attachments?.isNotEmpty == true + messageTextLength * (messageTheme?.messageText?.fontSize ?? 1) * 1.2; + final divFactor = message.attachments.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); @@ -64,7 +64,7 @@ class MessageReactionsModal extends StatelessWidget { curve: Curves.easeInOutBack, builder: (context, val, snapshot) { final hasFileAttachment = - message.attachments?.any((it) => it.type == 'file') == true; + message.attachments.any((it) => it.type == 'file') == true; return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), @@ -92,11 +92,10 @@ class MessageReactionsModal extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (showReactions && - (message.status == MessageSendingStatus.sent || - message.status == null)) + (message.status == MessageSendingStatus.sent)) Align( alignment: Alignment( - user.id == message.user.id + user!.id == message.user!.id ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor)) @@ -115,8 +114,8 @@ class MessageReactionsModal extends StatelessWidget { key: Key('MessageWidget'), reverse: reverse, message: message.copyWith( - text: message.text.length > 200 - ? '${message.text.substring(0, 200)}...' + text: message.text!.length > 200 + ? '${message.text!.substring(0, 200)}...' : message.text, ), messageTheme: messageTheme, @@ -138,12 +137,11 @@ class MessageReactionsModal extends StatelessWidget { showInChannelIndicator: false, textPadding: EdgeInsets.symmetric( vertical: 8.0, - horizontal: message.text.isOnlyEmoji ? 0 : 16.0, + horizontal: + message.text!.isOnlyEmoji ? 0 : 16.0, ), showReactionPickerIndicator: showReactions && - (message.status == - MessageSendingStatus.sent || - message.status == null), + (message.status == MessageSendingStatus.sent), ), ), if (message.latestReactions?.isNotEmpty == true) ...[ @@ -188,10 +186,10 @@ class MessageReactionsModal extends StatelessWidget { spacing: 16, runSpacing: 16, alignment: WrapAlignment.start, - children: message.latestReactions + children: message.latestReactions! .map((e) => _buildReaction( e, - currentUser, + currentUser!, context, )) .toList(), @@ -209,7 +207,7 @@ class MessageReactionsModal extends StatelessWidget { User currentUser, BuildContext context, ) { - final isCurrentUser = reaction.user.id == currentUser.id; + final isCurrentUser = reaction.user?.id == currentUser.id; return ConstrainedBox( constraints: BoxConstraints.loose(Size( 64, @@ -225,7 +223,7 @@ class MessageReactionsModal extends StatelessWidget { children: [ UserAvatar( onTap: onUserAvatarTap, - user: reaction.user, + user: reaction.user!, constraints: BoxConstraints.tightFor( height: 64, width: 64, @@ -246,8 +244,10 @@ class MessageReactionsModal extends StatelessWidget { child: ReactionBubble( reactions: [reaction], flipTail: !reverse, - borderColor: messageTheme.reactionsBorderColor, - backgroundColor: messageTheme.reactionsBackgroundColor, + borderColor: messageTheme?.reactionsBorderColor ?? + Colors.transparent, + backgroundColor: messageTheme?.reactionsBackgroundColor ?? + Colors.transparent, maskColor: StreamChatTheme.of(context).colorTheme.white, tailCirclesSpacing: 1, highlightOwnReactions: false, @@ -258,7 +258,7 @@ class MessageReactionsModal extends StatelessWidget { ), const SizedBox(height: 8), Text( - reaction.user.name.split(' ')[0], + reaction.user!.name.split(' ')[0], style: StreamChatTheme.of(context).textTheme.footnoteBold, textAlign: TextAlign.center, ), diff --git a/packages/stream_chat_flutter/lib/src/message_search_item.dart b/packages/stream_chat_flutter/lib/src/message_search_item.dart index 4cf7b5ed..87b55aaa 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -12,8 +12,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class MessageSearchItem extends StatelessWidget { /// Instantiate a new MessageSearchItem const MessageSearchItem({ - Key key, - @required this.getMessageResponse, + Key? key, + required this.getMessageResponse, this.onTap, this.showOnlineStatus = true, }) : super(key: key); @@ -22,7 +22,7 @@ class MessageSearchItem extends StatelessWidget { final GetMessageResponse getMessageResponse; /// Function called when tapping this widget - final VoidCallback onTap; + final VoidCallback? onTap; /// If true the [MessageSearchItem] will show the current online Status final bool showOnlineStatus; @@ -31,8 +31,8 @@ class MessageSearchItem extends StatelessWidget { Widget build(BuildContext context) { final message = getMessageResponse.message; final channel = getMessageResponse.channel; - final channelName = channel.extraData['name']; - final user = message.user; + final channelName = channel?.extraData['name']; + final user = message.user!; return ListTile( onTap: onTap, leading: UserAvatar( @@ -46,7 +46,7 @@ class MessageSearchItem extends StatelessWidget { title: Row( children: [ Text( - user.id == StreamChat.of(context).user.id ? 'You' : user.name, + user.id == StreamChat.of(context).user?.id ? 'You' : user.name, style: StreamChatTheme.of(context).channelPreviewTheme.title, ), if (channelName != null) ...[ @@ -55,12 +55,12 @@ class MessageSearchItem extends StatelessWidget { style: StreamChatTheme.of(context) .channelPreviewTheme .title - .copyWith( + ?.copyWith( fontWeight: FontWeight.normal, ), ), Text( - channelName, + channelName as String, style: StreamChatTheme.of(context).channelPreviewTheme.title, ), ], @@ -96,14 +96,10 @@ class MessageSearchItem extends StatelessWidget { } Widget _buildSubtitle(BuildContext context, Message message) { - if (message == null) { - return SizedBox(); - } - var text = message.text; if (message.isDeleted) { text = 'This message was deleted.'; - } else if (message.attachments != null) { + } else if (message.attachments.isNotEmpty) { final parts = [ ...message.attachments.map((e) { if (e.type == 'image') { @@ -116,7 +112,7 @@ class MessageSearchItem extends StatelessWidget { return e == message.attachments.last ? (e.title ?? 'File') : '${e.title ?? 'File'} , '; - }).where((e) => e != null), + }), message.text ?? '', ]; @@ -125,15 +121,15 @@ class MessageSearchItem extends StatelessWidget { return Text.rich( _getDisplayText( - text, + text!, message.mentionedUsers, message.attachments, - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( fontStyle: (message.isSystem || message.isDeleted) ? FontStyle.italic : FontStyle.normal, ), - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( + StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( fontStyle: (message.isSystem || message.isDeleted) ? FontStyle.italic : FontStyle.normal, @@ -149,26 +145,24 @@ class MessageSearchItem extends StatelessWidget { String text, List mentions, List attachments, - TextStyle normalTextStyle, - TextStyle mentionsTextStyle) { + TextStyle? normalTextStyle, + TextStyle? mentionsTextStyle) { var textList = text.split(' '); var resList = []; for (var e in textList) { - if (mentions != null && - mentions.isNotEmpty && + if (mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) { resList.add(TextSpan( text: '$e ', style: mentionsTextStyle, )); - } else if (attachments != null && - attachments.isNotEmpty && + } else if (attachments.isNotEmpty && attachments .where((e) => e.title != null) .any((element) => element.title == e)) { resList.add(TextSpan( text: '$e ', - style: normalTextStyle.copyWith(fontStyle: FontStyle.italic), + style: normalTextStyle?.copyWith(fontStyle: FontStyle.italic), )); } else { resList.add(TextSpan( diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index d7ece34e..4eff2f8f 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -10,11 +10,15 @@ typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); /// Builder used to create a custom [ListUserItem] from a [User] typedef MessageSearchItemBuilder = Widget Function( - BuildContext, GetMessageResponse); + BuildContext, + GetMessageResponse, +); /// Builder used when [MessageSearchListView] is empty typedef EmptyMessageSearchBuilder = Widget Function( - BuildContext context, String searchQuery); + BuildContext context, + String searchQuery, +); /// /// It shows the list of searched messages. @@ -47,9 +51,9 @@ typedef EmptyMessageSearchBuilder = Widget Function( class MessageSearchListView extends StatefulWidget { /// Instantiate a new MessageSearchListView const MessageSearchListView({ - Key key, + Key? key, + required this.filters, this.messageQuery, - this.filters, this.sortOptions, this.paginationParams, this.messageFilters, @@ -66,7 +70,7 @@ class MessageSearchListView extends StatefulWidget { }) : super(key: key); /// Message String to search on - final String messageQuery; + final String? messageQuery; /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. @@ -77,27 +81,27 @@ class MessageSearchListView extends StatefulWidget { /// Sorting is based on field and direction, multiple sorting options can be provided. /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. /// Direction can be ascending or descending. - final List sortOptions; + final List? sortOptions; /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams paginationParams; + final PaginationParams? paginationParams; /// The message query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Map messageFilters; + final Map? messageFilters; /// Builder used to create a custom item preview - final MessageSearchItemBuilder itemBuilder; + final MessageSearchItemBuilder? itemBuilder; /// Function called when tapping on a [MessageSearchItem] - final MessageSearchItemTapCallback onItemTap; + final MessageSearchItemTapCallback? onItemTap; /// Builder used to create a custom item separator - final IndexedWidgetBuilder separatorBuilder; + final IndexedWidgetBuilder? separatorBuilder; /// Set it to false to hide total results text final bool showResultCount; @@ -108,16 +112,16 @@ class MessageSearchListView extends StatefulWidget { final bool showErrorTile; /// The builder that is used when the search messages are fetched - final Widget Function(List) childBuilder; + final Widget Function(List)? childBuilder; /// The builder used when the channel list is empty. - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; /// The builder that will be used in case of error - final ErrorBuilder errorBuilder; + final ErrorBuilder? errorBuilder; /// The builder that will be used in case of loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; @override _MessageSearchListViewState createState() => _MessageSearchListViewState(); @@ -202,11 +206,11 @@ class _MessageSearchListViewState extends State { Widget _listItemBuilder( BuildContext context, GetMessageResponse getMessageResponse) { if (widget.itemBuilder != null) { - return widget.itemBuilder(context, getMessageResponse); + return widget.itemBuilder!(context, getMessageResponse); } return MessageSearchItem( getMessageResponse: getMessageResponse, - onTap: () => widget.onItemTap(getMessageResponse), + onTap: () => widget.onItemTap!(getMessageResponse), ); } @@ -235,7 +239,7 @@ class _MessageSearchListViewState extends State { height: 100, padding: EdgeInsets.all(32), child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), + child: snapshot.data! ? CircularProgressIndicator() : Container(), ), ); }); @@ -249,7 +253,7 @@ class _MessageSearchListViewState extends State { itemCount: items.isNotEmpty ? items.length + 1 : items.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); + return widget.separatorBuilder!(context, index); } return _separatorBuilder(context, index); }, @@ -262,13 +266,13 @@ class _MessageSearchListViewState extends State { ); if (widget.pullToRefresh) { child = RefreshIndicator( - onRefresh: () => _messageSearchListController.loadData(), + onRefresh: () => _messageSearchListController.loadData!(), child: child, ); } child = LazyLoadScrollView( - onEndOfPage: () => _messageSearchListController.paginateData(), + onEndOfPage: () => _messageSearchListController.paginateData!(), child: child, ); diff --git a/packages/stream_chat_flutter/lib/src/message_text.dart b/packages/stream_chat_flutter/lib/src/message_text.dart index 3afa60f9..eb80b89b 100644 --- a/packages/stream_chat_flutter/lib/src/message_text.dart +++ b/packages/stream_chat_flutter/lib/src/message_text.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; @@ -7,43 +8,45 @@ import 'utils.dart'; class MessageText extends StatelessWidget { const MessageText({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.onMentionTap, this.onLinkTap, }) : super(key: key); final Message message; - final void Function(User) onMentionTap; - final void Function(String) onLinkTap; - final MessageTheme messageTheme; + final void Function(User)? onMentionTap; + final void Function(String)? onLinkTap; + final MessageTheme? messageTheme; @override Widget build(BuildContext context) { - final text = _replaceMentions(message.text).replaceAll('\n', '\\\n'); + final text = _replaceMentions(message.text)!.replaceAll('\n', '\\\n'); return MarkdownBody( data: text, onTapLink: ( String link, - String href, + String? href, String title, ) { if (link.startsWith('@')) { - final mentionedUser = message.mentionedUsers.firstWhere( + final mentionedUser = message.mentionedUsers.firstWhereOrNull( (u) => '@${u.name}' == link, - orElse: () => null, ); + if (mentionedUser == null) { + return; + } if (onMentionTap != null) { - onMentionTap(mentionedUser); + onMentionTap!(mentionedUser); } else { print('tap on ${mentionedUser.name}'); } } else { if (onLinkTap != null) { - onLinkTap(link); + onLinkTap!(link); } else { launchURL(context, link); } @@ -52,23 +55,23 @@ class MessageText extends StatelessWidget { styleSheet: MarkdownStyleSheet.fromTheme( Theme.of(context).copyWith( textTheme: Theme.of(context).textTheme.apply( - bodyColor: messageTheme.messageText.color, - decoration: messageTheme.messageText.decoration, - decorationColor: messageTheme.messageText.decorationColor, - decorationStyle: messageTheme.messageText.decorationStyle, - fontFamily: messageTheme.messageText.fontFamily, + bodyColor: messageTheme!.messageText!.color, + decoration: messageTheme!.messageText!.decoration, + decorationColor: messageTheme!.messageText!.decorationColor, + decorationStyle: messageTheme!.messageText!.decorationStyle, + fontFamily: messageTheme!.messageText!.fontFamily, ), ), ).copyWith( - a: messageTheme.messageLinks, - p: messageTheme.messageText, + a: messageTheme!.messageLinks, + p: messageTheme!.messageText, ), ); } - String _replaceMentions(String text) { - message.mentionedUsers?.map((u) => u.name)?.toSet()?.forEach((userName) { - text = text.replaceAll( + String? _replaceMentions(String? text) { + message.mentionedUsers.map((u) => u.name).toSet().forEach((userName) { + text = text!.replaceAll( '@$userName', '[@$userName](@${userName.replaceAll(' ', '')})'); }); return text; diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 09feeb74..d39240ab 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -26,7 +26,7 @@ typedef AttachmentBuilder = Widget Function( Message, List, ); -typedef OnQuotedMessageTap = void Function(String); +typedef OnQuotedMessageTap = void Function(String?); /// The display behaviour of a widget enum DisplayWidget { @@ -51,46 +51,46 @@ enum DisplayWidget { /// Modify it to change the widget appearance. class MessageWidget extends StatefulWidget { /// Function called on mention tap - final void Function(User) onMentionTap; + final void Function(User)? onMentionTap; /// The function called when tapping on replies - final void Function(Message) onThreadTap; - final void Function(Message) onReplyTap; - final Widget Function(BuildContext, Message) editMessageInputBuilder; - final Widget Function(BuildContext, Message) textBuilder; + final void Function(Message)? onThreadTap; + final void Function(Message)? onReplyTap; + final Widget Function(BuildContext, Message)? editMessageInputBuilder; + final Widget Function(BuildContext, Message)? textBuilder; /// Function called on long press - final void Function(BuildContext, Message) onMessageActions; + final void Function(BuildContext, Message)? onMessageActions; /// The message final Message message; /// The message theme - final MessageTheme messageTheme; + final MessageTheme? messageTheme; /// If true the widget will be mirrored final bool reverse; /// The shape of the message text - final ShapeBorder shape; + final ShapeBorder? shape; /// The shape of an attachment - final ShapeBorder attachmentShape; + final ShapeBorder? attachmentShape; /// The borderside of the message text - final BorderSide borderSide; + final BorderSide? borderSide; /// The borderside of an attachment - final BorderSide attachmentBorderSide; + final BorderSide? attachmentBorderSide; /// The border radius of the message text - final BorderRadiusGeometry borderRadiusGeometry; + final BorderRadiusGeometry? borderRadiusGeometry; /// The border radius of an attachment - final BorderRadiusGeometry attachmentBorderRadiusGeometry; + final BorderRadiusGeometry? attachmentBorderRadiusGeometry; /// The padding of the widget - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// The internal padding of the message text final EdgeInsetsGeometry textPadding; @@ -116,18 +116,18 @@ class MessageWidget extends StatefulWidget { final bool showInChannelIndicator; /// The function called when tapping on UserAvatar - final void Function(User) onUserAvatarTap; + final void Function(User)? onUserAvatarTap; /// The function called when tapping on a link - final void Function(String) onLinkTap; + final void Function(String)? onLinkTap; /// Used in [MessageReactionsModal] and [MessageActionsModal] final bool showReactionPickerIndicator; - final List readList; + final List? readList; - final ShowMessageCallback onShowMessage; - final ValueChanged onReturnAction; + final ShowMessageCallback? onShowMessage; + final ValueChanged? onReturnAction; /// If true show the users username next to the timestamp of the message final bool showUsername; @@ -147,22 +147,22 @@ class MessageWidget extends StatefulWidget { final bool translateUserAvatar; /// Function called when quotedMessage is tapped - final OnQuotedMessageTap onQuotedMessageTap; + final OnQuotedMessageTap? onQuotedMessageTap; /// Function called when message is tapped - final void Function(Message) onMessageTap; + final void Function(Message)? onMessageTap; /// List of custom actions shown on message long tap final List customActions; // Customize onTap on attachment - final void Function(Message message, Attachment attachment) onAttachmentTap; + final void Function(Message message, Attachment attachment)? onAttachmentTap; /// MessageWidget({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.reverse = false, this.translateUserAvatar = true, this.shape, @@ -197,7 +197,7 @@ class MessageWidget extends StatefulWidget { this.editMessageInputBuilder, this.textBuilder, this.onReturnAction, - Map customAttachmentBuilders, + Map? customAttachmentBuilders, this.readList, this.padding, this.textPadding = const EdgeInsets.symmetric( @@ -222,7 +222,7 @@ class MessageWidget extends StatefulWidget { child: wrapAttachmentWidget( context, Material( - color: messageTheme.messageBackgroundColor, + color: messageTheme?.messageBackgroundColor, child: ImageGroup( size: Size( MediaQuery.of(context).size.width * 0.8, @@ -236,7 +236,8 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ), ); } @@ -255,13 +256,14 @@ class MessageWidget extends StatefulWidget { onReturnAction: onReturnAction, onAttachmentTap: onAttachmentTap != null ? () { - onAttachmentTap?.call(message, attachments[0]); + onAttachmentTap.call(message, attachments[0]); } : null, ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }, 'video': (context, message, attachments) { @@ -286,7 +288,7 @@ class MessageWidget extends StatefulWidget { onReturnAction: onReturnAction, onAttachmentTap: onAttachmentTap != null ? () { - onAttachmentTap?.call(message, attachment); + onAttachmentTap(message, attachment); } : null, ); @@ -294,11 +296,12 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }, 'giphy': (context, message, attachments) { - var border = RoundedRectangleBorder( + final border = RoundedRectangleBorder( side: BorderSide.none, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, ); @@ -309,7 +312,6 @@ class MessageWidget extends StatefulWidget { children: attachments.map((attachment) { return GiphyAttachment( attachment: attachment, - messageTheme: messageTheme, message: message, size: Size( MediaQuery.of(context).size.width * 0.8, @@ -322,7 +324,8 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }, 'file': (context, message, attachments) { @@ -349,7 +352,8 @@ class MessageWidget extends StatefulWidget { ), border, reverse, - attachmentBorderRadiusGeometry ?? BorderRadius.zero, + attachmentBorderRadiusGeometry as BorderRadius? ?? + BorderRadius.zero, ); }) .insertBetween(SizedBox( @@ -381,7 +385,7 @@ class _MessageWidgetState extends State bool get showInChannel => widget.showInChannelIndicator; - bool get hasQuotedMessage => widget.message?.quotedMessage != null; + bool get hasQuotedMessage => widget.message.quotedMessage != null; bool get isSendFailed => widget.message.status == MessageSendingStatus.failed; @@ -394,17 +398,17 @@ class _MessageWidgetState extends State bool get isFailedState => isSendFailed || isUpdateFailed || isDeleteFailed; bool get isGiphy => - widget.message.attachments?.any((element) => element.type == 'giphy') == + widget.message.attachments.any((element) => element.type == 'giphy') == true; bool get hasNonUrlAttachments => widget.message.attachments - ?.where((it) => it.ogScrapeUrl == null) - ?.isNotEmpty == + .where((it) => it.ogScrapeUrl == null) + .isNotEmpty == true; bool get hasUrlAttachments => - widget.message.attachments?.any((it) => it.ogScrapeUrl != null) == true; + widget.message.attachments.any((it) => it.ogScrapeUrl != null) == true; bool get showBottomRow => showThreadReplyIndicator || @@ -415,12 +419,13 @@ class _MessageWidgetState extends State isDeleted; @override - bool get wantKeepAlive => widget.message.attachments?.isNotEmpty == true; + bool get wantKeepAlive => widget.message.attachments.isNotEmpty == true; @override Widget build(BuildContext context) { super.build(context); - final avatarWidth = widget.messageTheme.avatarTheme.constraints.maxWidth; + final avatarWidth = + widget.messageTheme?.avatarTheme?.constraints.maxWidth ?? 40; var leftPadding = widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; @@ -429,7 +434,7 @@ class _MessageWidgetState extends State child: Portal( child: InkWell( onTap: () { - widget.onMessageTap(widget.message); + widget.onMessageTap!(widget.message); }, onLongPress: widget.message.isDeleted && !isFailedState ? null @@ -460,7 +465,8 @@ class _MessageWidgetState extends State mainAxisSize: MainAxisSize.min, children: [ if (widget.showUserAvatar == - DisplayWidget.show) ...[ + DisplayWidget.show && + widget.message.user != null) ...[ _buildUserAvatar(), SizedBox(width: 4), ], @@ -533,13 +539,14 @@ class _MessageWidgetState extends State ), shape: widget.shape ?? RoundedRectangleBorder( - side: - widget.borderSide ?? - BorderSide( - color: widget + side: widget + .borderSide ?? + BorderSide( + color: widget .messageTheme - .messageBorderColor, - ), + ?.messageBorderColor ?? + Colors.grey, + ), borderRadius: widget .borderRadiusGeometry ?? BorderRadius.zero, @@ -616,14 +623,14 @@ class _MessageWidgetState extends State Widget _buildQuotedMessage() { final isMyMessage = - widget.message.user.id == StreamChat.of(context).user.id; - final onTap = widget.message?.quotedMessage?.isDeleted != true && + widget.message.user?.id == StreamChat.of(context).user?.id; + final onTap = widget.message.quotedMessage?.isDeleted != true && widget.onQuotedMessageTap != null - ? () => widget.onQuotedMessageTap(widget.message.quotedMessageId) + ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) : null; return QuotedMessageWidget( onTap: onTap, - message: widget.message.quotedMessage, + message: widget.message.quotedMessage!, messageTheme: isMyMessage ? StreamChatTheme.of(context).otherMessageTheme : StreamChatTheme.of(context).ownMessageTheme, @@ -660,12 +667,12 @@ class _MessageWidgetState extends State var children = []; - final threadParticipants = widget.message?.threadParticipants?.take(2); + final threadParticipants = widget.message.threadParticipants?.take(2); final showThreadParticipants = threadParticipants?.isNotEmpty == true; final replyCount = widget.message.replyCount; var msg = 'Thread Reply'; - if (showThreadReplyIndicator && replyCount > 1) { + if (showThreadReplyIndicator && replyCount! > 1) { msg = '$replyCount Thread Replies'; } @@ -674,9 +681,9 @@ class _MessageWidgetState extends State var message = widget.message; if (showInChannel) { final channel = StreamChannel.of(context); - message = await channel.getMessage(widget.message.parentId); + message = await channel.getMessage(widget.message.parentId!); } - return widget.onThreadTap(message); + return widget.onThreadTap!(message); } catch (e, stk) { print(e); print(stk); @@ -690,7 +697,7 @@ class _MessageWidgetState extends State if (showInChannel || showThreadReplyIndicator) ...[ if (showThreadParticipants) SizedBox.fromSize( - size: Size((threadParticipants.length * 8.0) + 8, 16), + size: Size((threadParticipants!.length * 8.0) + 8, 16), child: _buildThreadParticipantsIndicator(threadParticipants), ), InkWell( @@ -700,16 +707,16 @@ class _MessageWidgetState extends State ], if (showUsername) Text( - widget.message.user.name, + widget.message.user!.name, maxLines: 1, key: usernameKey, - style: widget.messageTheme.messageAuthor, + style: widget.messageTheme?.messageAuthor, overflow: TextOverflow.ellipsis, ), if (showTimeStamp) Text( Jiffy(widget.message.createdAt.toLocal()).jm, - style: widget.messageTheme.createdAt, + style: widget.messageTheme?.createdAt, ), if (showSendingIndicator) _buildSendingIndicator(), ]); @@ -724,13 +731,13 @@ class _MessageWidgetState extends State Container( margin: EdgeInsets.only( bottom: context.textScaleFactor * - (widget.messageTheme.replies.fontSize / 2), + ((widget.messageTheme?.replies?.fontSize ?? 1) / 2), ), child: CustomPaint( size: Size(16, 32) * context.textScaleFactor, painter: _ThreadReplyPainter( context: context, - color: widget.messageTheme.messageBorderColor, + color: widget.messageTheme?.messageBorderColor, ), ), ), @@ -758,7 +765,7 @@ class _MessageWidgetState extends State var urlAttachment = widget.message.attachments .firstWhere((element) => element.ogScrapeUrl != null); - var host = Uri.parse(urlAttachment.ogScrapeUrl).host; + var host = Uri.parse(urlAttachment.ogScrapeUrl!).host; var splitList = host.split('.'); var hostName = splitList.length == 3 ? splitList[1] : splitList[0]; var hostDisplayName = urlAttachment.authorName?.capitalize() ?? @@ -768,7 +775,7 @@ class _MessageWidgetState extends State return UrlAttachment( urlAttachment: urlAttachment, hostDisplayName: hostDisplayName, - textPadding: widget.textPadding, + textPadding: widget.textPadding as EdgeInsets, ); } @@ -801,15 +808,16 @@ class _MessageWidgetState extends State Widget _buildReactionIndicator( BuildContext context, ) { - final ownId = StreamChat.of(context).user.id; + final ownId = StreamChat.of(context).user!.id; final reactionsMap = {}; widget.message.latestReactions?.forEach((element) { - if (!reactionsMap.containsKey(element.type) || element.user.id == ownId) { + if (!reactionsMap.containsKey(element.type) || + element.user!.id == ownId) { reactionsMap[element.type] = element; } }); final reactionsList = reactionsMap.values.toList() - ..sort((a, b) => a.user.id == ownId ? 1 : -1); + ..sort((a, b) => a.user!.id == ownId ? 1 : -1); return AnimatedSwitcher( duration: Duration(milliseconds: 300), @@ -822,9 +830,13 @@ class _MessageWidgetState extends State key: ValueKey('${widget.message.id}.reactions'), reverse: widget.reverse, flipTail: widget.reverse, - backgroundColor: widget.messageTheme.reactionsBackgroundColor, - borderColor: widget.messageTheme.reactionsBorderColor, - maskColor: widget.messageTheme.reactionsMaskColor, + backgroundColor: + widget.messageTheme?.reactionsBackgroundColor ?? + Colors.transparent, + borderColor: widget.messageTheme?.reactionsBorderColor ?? + Colors.transparent, + maskColor: widget.messageTheme?.reactionsMaskColor ?? + Colors.transparent, reactions: reactionsList, ), ) @@ -845,9 +857,9 @@ class _MessageWidgetState extends State onCopyTap: (message) => Clipboard.setData(ClipboardData(text: message.text)), attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry, + widget.attachmentBorderRadiusGeometry as BorderRadius?, showUserAvatar: - widget.message.user.id == channel.client.state.user.id + widget.message.user!.id == channel.client.state.user!.id ? DisplayWidget.gone : DisplayWidget.show, messageTheme: widget.messageTheme, @@ -864,11 +876,11 @@ class _MessageWidgetState extends State widget.showResendMessage && (isSendFailed || isUpdateFailed), showCopyMessage: widget.showCopyMessage && !isFailedState && - widget.message.text?.trim()?.isNotEmpty == true, + widget.message.text?.trim().isNotEmpty == true, showEditMessage: widget.showEditMessage && !isDeleteFailed && widget.message.attachments - ?.any((element) => element.type == 'giphy') != + .any((element) => element.type == 'giphy') != true, showReactions: widget.showReactions, showReplyMessage: widget.showReplyMessage && @@ -894,9 +906,9 @@ class _MessageWidgetState extends State channel: channel, child: MessageReactionsModal( attachmentBorderRadiusGeometry: - widget.attachmentBorderRadiusGeometry, + widget.attachmentBorderRadiusGeometry as BorderRadius?, showUserAvatar: - widget.message.user.id == channel.client.state.user.id + widget.message.user!.id == channel.client.state.user!.id ? DisplayWidget.gone : DisplayWidget.show, onUserAvatarTap: widget.onUserAvatarTap, @@ -914,7 +926,7 @@ class _MessageWidgetState extends State ShapeBorder _getDefaultAttachmentShape(BuildContext context) { final hasFiles = - widget.message.attachments?.any((it) => it.type == 'file') == true; + widget.message.attachments.any((it) => it.type == 'file') == true; return RoundedRectangleBorder( side: hasFiles ? widget.attachmentBorderSide ?? @@ -940,13 +952,13 @@ class _MessageWidgetState extends State final attachmentGroups = >{}; widget.message.attachments - .where((element) => element.ogScrapeUrl == null) + .where((element) => element.ogScrapeUrl == null && element.type != null) .forEach((e) { if (attachmentGroups[e.type] == null) { - attachmentGroups[e.type] = []; + attachmentGroups[e.type!] = []; } - attachmentGroups[e.type].add(e); + attachmentGroups[e.type]?.add(e); }); final attachmentList = []; @@ -954,7 +966,7 @@ class _MessageWidgetState extends State attachmentGroups.forEach((type, attachments) { final attachmentBuilder = widget.attachmentBuilders[type]; - if (attachmentBuilder == null) return SizedBox(); + if (attachmentBuilder == null) return; final attachmentWidget = attachmentBuilder( context, widget.message, @@ -967,10 +979,9 @@ class _MessageWidgetState extends State padding: widget.attachmentPadding, child: Column( mainAxisSize: MainAxisSize.min, - children: attachmentList?.insertBetween(SizedBox( - height: widget.attachmentPadding.vertical / 2, - )) ?? - [], + children: attachmentList.insertBetween(SizedBox( + height: widget.attachmentPadding.vertical / 2, + )), ), ); } @@ -982,7 +993,7 @@ class _MessageWidgetState extends State } if (widget.onMessageActions != null) { - widget.onMessageActions(context, widget.message); + widget.onMessageActions!(context, widget.message); } else { _showMessageActionModalBottomSheet(context); } @@ -990,7 +1001,7 @@ class _MessageWidgetState extends State } Widget _buildSendingIndicator() { - final style = widget.messageTheme.createdAt; + final style = widget.messageTheme?.createdAt; final message = widget.message; if (hasNonUrlAttachments && @@ -1002,8 +1013,8 @@ class _MessageWidgetState extends State }).length; if (uploadRemaining == 0) { return StreamSvgIcon.check( - size: style.fontSize, - color: IconTheme.of(context).color.withOpacity(0.5), + size: style!.fontSize, + color: IconTheme.of(context).color!.withOpacity(0.5), ); } return Text( @@ -1015,14 +1026,14 @@ class _MessageWidgetState extends State Widget child = SendingIndicator( message: message, isMessageRead: isMessageRead, - size: style.fontSize, + size: style!.fontSize, ); if (isMessageRead) { child = Row( children: [ - if (StreamChannel.of(context).channel.memberCount > 2) + if (StreamChannel.of(context).channel.memberCount! > 2) Text( - widget.readList.length.toString(), + widget.readList!.length.toString(), style: style.copyWith( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -1042,21 +1053,23 @@ class _MessageWidgetState extends State offset: Offset( 0, widget.translateUserAvatar - ? widget.messageTheme.avatarTheme.constraints.maxHeight / 2 + ? (widget.messageTheme?.avatarTheme?.constraints.maxHeight ?? + 40) / + 2 : 0, ), child: UserAvatar( - user: widget.message.user, + user: widget.message.user!, onTap: widget.onUserAvatarTap, - constraints: widget.messageTheme.avatarTheme.constraints, - borderRadius: widget.messageTheme.avatarTheme.borderRadius, + constraints: widget.messageTheme?.avatarTheme!.constraints, + borderRadius: widget.messageTheme?.avatarTheme!.borderRadius, showOnlineStatus: false, ), ), ); Widget _buildTextBubble() { - if (widget.message.text.trim().isEmpty) return Offstage(); + if (widget.message.text!.trim().isEmpty) return Offstage(); return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, @@ -1066,15 +1079,15 @@ class _MessageWidgetState extends State Padding( padding: isOnlyEmoji ? EdgeInsets.zero : widget.textPadding, child: widget.textBuilder != null - ? widget.textBuilder(context, widget.message) + ? widget.textBuilder!(context, widget.message) : MessageText( onLinkTap: widget.onLinkTap, message: widget.message, onMentionTap: widget.onMentionTap, messageTheme: isOnlyEmoji - ? widget.messageTheme.copyWith( + ? widget.messageTheme?.copyWith( messageText: - widget.messageTheme.messageText.copyWith( + widget.messageTheme?.messageText!.copyWith( fontSize: 42, )) : widget.messageTheme, @@ -1086,11 +1099,11 @@ class _MessageWidgetState extends State ); } - bool get isOnlyEmoji => widget.message.text.isOnlyEmoji; + bool get isOnlyEmoji => widget.message.text!.isOnlyEmoji; - Color _getBackgroundColor() { + Color? _getBackgroundColor() { if (hasQuotedMessage) { - return widget.messageTheme.messageBackgroundColor; + return widget.messageTheme?.messageBackgroundColor; } if (hasUrlAttachments) { @@ -1105,7 +1118,7 @@ class _MessageWidgetState extends State return Colors.transparent; } - return widget.messageTheme.messageBackgroundColor; + return widget.messageTheme?.messageBackgroundColor; } void retryMessage(BuildContext context) { @@ -1127,15 +1140,15 @@ class _MessageWidgetState extends State } class _ThreadReplyPainter extends CustomPainter { - final Color color; - final BuildContext context; + final Color? color; + final BuildContext? context; - const _ThreadReplyPainter({this.context, @required this.color}); + const _ThreadReplyPainter({this.context, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() - ..color = color ?? StreamChatTheme.of(context).colorTheme.greyGainsboro + ..color = color ?? StreamChatTheme.of(context!).colorTheme.greyGainsboro ..style = PaintingStyle.stroke ..strokeWidth = 1 ..strokeCap = StrokeCap.round; diff --git a/packages/stream_chat_flutter/lib/src/option_list_tile.dart b/packages/stream_chat_flutter/lib/src/option_list_tile.dart index 61ce939e..03af584b 100644 --- a/packages/stream_chat_flutter/lib/src/option_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/option_list_tile.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; class OptionListTile extends StatelessWidget { - final String title; - final Widget leading; - final Widget trailing; - final VoidCallback onTap; - final Color titleColor; - final Color tileColor; - final Color separatorColor; - final TextStyle titleTextStyle; + final String? title; + final Widget? leading; + final Widget? trailing; + final VoidCallback? onTap; + final Color? titleColor; + final Color? tileColor; + final Color? separatorColor; + final TextStyle? titleTextStyle; OptionListTile({ this.title, @@ -47,7 +47,7 @@ class OptionListTile extends StatelessWidget { Expanded( flex: 4, child: Text( - title, + title!, style: titleTextStyle ?? (titleColor == null ? StreamChatTheme.of(context).textTheme.bodyBold diff --git a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart index 4d2b2e38..bc77e97a 100644 --- a/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/quoted_message_widget.dart @@ -22,8 +22,8 @@ class _VideoAttachmentThumbnail extends StatefulWidget { final Attachment attachment; const _VideoAttachmentThumbnail({ - Key key, - @required this.attachment, + Key? key, + required this.attachment, this.size = const Size(32, 32), }) : super(key: key); @@ -33,12 +33,12 @@ class _VideoAttachmentThumbnail extends StatefulWidget { } class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { - VideoPlayerController _controller; + late VideoPlayerController _controller; @override void initState() { super.initState(); - _controller = VideoPlayerController.network(widget.attachment.assetUrl) + _controller = VideoPlayerController.network(widget.attachment.assetUrl!) ..initialize().then((_) { setState(() {}); //when your thumbnail will show. }); @@ -67,7 +67,7 @@ class QuotedMessageWidget extends StatelessWidget { final Message message; /// The message theme - final MessageTheme messageTheme; + final MessageTheme? messageTheme; /// If true the widget will be mirrored final bool reverse; @@ -79,18 +79,18 @@ class QuotedMessageWidget extends StatelessWidget { final int textLimit; /// Map that defines a thumbnail builder for an attachment type - final Map + final Map? attachmentThumbnailBuilders; final EdgeInsetsGeometry padding; - final GestureTapCallback onTap; + final GestureTapCallback? onTap; /// QuotedMessageWidget({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, this.reverse = false, this.showBorder = false, this.textLimit = 170, @@ -99,13 +99,12 @@ class QuotedMessageWidget extends StatelessWidget { this.onTap, }) : super(key: key); - bool get _hasAttachments => message.attachments?.isNotEmpty == true; + bool get _hasAttachments => message.attachments.isNotEmpty == true; bool get _containsScrapeUrl => - message.attachments?.any((element) => element.ogScrapeUrl != null) == - true; + message.attachments.any((element) => element.ogScrapeUrl != null) == true; - bool get _containsText => message?.text?.isNotEmpty == true; + bool get _containsText => message.text?.isNotEmpty == true; @override Widget build(BuildContext context) { @@ -119,7 +118,7 @@ class QuotedMessageWidget extends StatelessWidget { children: [ Flexible(child: _buildMessage(context)), SizedBox(width: 8), - _buildUserAvatar(), + if (message.user != null) _buildUserAvatar(), ], ), ), @@ -127,17 +126,17 @@ class QuotedMessageWidget extends StatelessWidget { } Widget _buildMessage(BuildContext context) { - final isOnlyEmoji = message.text.isOnlyEmoji; + final isOnlyEmoji = message.text!.isOnlyEmoji; var msg = _hasAttachments && !_containsText - ? message.copyWith(text: message.attachments.last?.title ?? '') + ? message.copyWith(text: message.attachments.last.title ?? '') : message; - if (msg.text.length > textLimit) { - msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...'); + if (msg.text!.length > textLimit) { + msg = msg.copyWith(text: '${msg.text!.substring(0, textLimit - 3)}...'); } final children = [ if (_hasAttachments) _parseAttachments(context), - if (msg.text.isNotEmpty) + if (msg.text!.isNotEmpty) Flexible( child: Transform( transform: Matrix4.rotationY(reverse ? pi : 0), @@ -145,12 +144,12 @@ class QuotedMessageWidget extends StatelessWidget { child: MessageText( message: msg, messageTheme: isOnlyEmoji && _containsText - ? messageTheme.copyWith( - messageText: messageTheme.messageText.copyWith( + ? messageTheme?.copyWith( + messageText: messageTheme?.messageText?.copyWith( fontSize: 32, )) - : messageTheme.copyWith( - messageText: messageTheme.messageText.copyWith( + : messageTheme?.copyWith( + messageText: messageTheme?.messageText?.copyWith( fontSize: 12, )), ), @@ -193,7 +192,7 @@ class QuotedMessageWidget extends StatelessWidget { image: DecorationImage( fit: BoxFit.cover, image: CachedNetworkImageProvider( - attachment.imageUrl, + attachment.imageUrl!, ), ), ), @@ -211,16 +210,16 @@ class QuotedMessageWidget extends StatelessWidget { ); child = _buildUrlAttachment(attachment); } else { - QuotedMessageAttachmentThumbnailBuilder attachmentBuilder; + QuotedMessageAttachmentThumbnailBuilder? attachmentBuilder; attachment = message.attachments.last; - if (attachmentThumbnailBuilders?.containsKey(attachment?.type) == true) { - attachmentBuilder = attachmentThumbnailBuilders[attachment?.type]; + if (attachmentThumbnailBuilders?.containsKey(attachment.type) == true) { + attachmentBuilder = attachmentThumbnailBuilders![attachment.type]; } - attachmentBuilder = _defaultAttachmentBuilder[attachment?.type]; + attachmentBuilder = _defaultAttachmentBuilder[attachment.type]; if (attachmentBuilder == null) { child = Offstage(); } - child = attachmentBuilder(context, attachment); + child = attachmentBuilder!(context, attachment); } child = AbsorbPointer(child: child); return Transform( @@ -247,7 +246,7 @@ class QuotedMessageWidget extends StatelessWidget { transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, child: UserAvatar( - user: message.user, + user: message.user!, constraints: BoxConstraints.tightFor( height: 24, width: 24, @@ -277,19 +276,20 @@ class QuotedMessageWidget extends StatelessWidget { 'giphy': (_, attachment) { final size = Size(32, 32); return CachedNetworkImage( - height: size?.height, - width: size?.width, + height: size.height, + width: size.width, placeholder: (_, __) { return Container( - width: size?.width, - height: size?.height, + width: size.width, + height: size.height, child: Center( child: CircularProgressIndicator(), ), ); }, - imageUrl: - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl!, errorWidget: (context, url, error) { return AttachmentError(size: size); }, @@ -300,16 +300,16 @@ class QuotedMessageWidget extends StatelessWidget { return Container( height: 32, width: 32, - child: getFileTypeImage(attachment.extraData['mime_type']), + child: getFileTypeImage(attachment.extraData['mime_type'] as String?), ); }, }; } - Color _getBackgroundColor(BuildContext context) { + Color? _getBackgroundColor(BuildContext context) { if (_containsScrapeUrl) { return StreamChatTheme.of(context).colorTheme.blueAlice; } - return messageTheme.messageBackgroundColor; + return messageTheme?.messageBackgroundColor; } } diff --git a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart index 51ec7aa5..bcf3127c 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_bubble.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -9,11 +10,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ReactionBubble extends StatelessWidget { const ReactionBubble({ - Key key, - @required this.reactions, - @required this.borderColor, - @required this.backgroundColor, - @required this.maskColor, + Key? key, + required this.reactions, + required this.borderColor, + required this.backgroundColor, + required this.maskColor, this.reverse = false, this.flipTail = false, this.highlightOwnReactions = true, @@ -108,9 +109,8 @@ class ReactionBubble extends StatelessWidget { Reaction reaction, BuildContext context, ) { - final reactionIcon = reactionIcons.firstWhere( + final reactionIcon = reactionIcons.firstWhereOrNull( (r) => r.type == reaction.type, - orElse: () => null, ); return Padding( @@ -123,7 +123,7 @@ class ReactionBubble extends StatelessWidget { width: 16, height: 16, color: (!highlightOwnReactions || - reaction.user.id == StreamChat.of(context).user.id) + reaction.user?.id == StreamChat.of(context).user?.id) ? StreamChatTheme.of(context).colorTheme.accentBlue : StreamChatTheme.of(context) .colorTheme @@ -134,7 +134,7 @@ class ReactionBubble extends StatelessWidget { Icons.help_outline_rounded, size: 16, color: (!highlightOwnReactions || - reaction.user.id == StreamChat.of(context).user.id) + reaction.user?.id == StreamChat.of(context).user?.id) ? StreamChatTheme.of(context).colorTheme.accentBlue : StreamChatTheme.of(context) .colorTheme diff --git a/packages/stream_chat_flutter/lib/src/reaction_icon.dart b/packages/stream_chat_flutter/lib/src/reaction_icon.dart index 99b93328..66e19fe7 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_icon.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_icon.dart @@ -3,7 +3,7 @@ class ReactionIcon { final String assetName; ReactionIcon({ - this.type, - this.assetName, + required this.type, + required this.assetName, }); } diff --git a/packages/stream_chat_flutter/lib/src/reaction_picker.dart b/packages/stream_chat_flutter/lib/src/reaction_picker.dart index a33e7b52..7cd01eb7 100644 --- a/packages/stream_chat_flutter/lib/src/reaction_picker.dart +++ b/packages/stream_chat_flutter/lib/src/reaction_picker.dart @@ -16,13 +16,13 @@ import 'extension.dart'; class ReactionPicker extends StatefulWidget { const ReactionPicker({ - Key key, - @required this.message, - @required this.messageTheme, + Key? key, + required this.message, + required this.messageTheme, }) : super(key: key); final Message message; - final MessageTheme messageTheme; + final MessageTheme? messageTheme; @override _ReactionPickerState createState() => _ReactionPickerState(); @@ -98,7 +98,8 @@ class _ReactionPickerState extends State if (ownReactionIndex != -1) { removeReaction( context, - widget.message.ownReactions[ownReactionIndex], + widget + .message.ownReactions![ownReactionIndex], ); } else { sendReaction( @@ -129,7 +130,7 @@ class _ReactionPickerState extends State .accentBlue : Theme.of(context) .iconTheme - .color + .color! .withOpacity(.5), ), ); @@ -181,7 +182,7 @@ class _ReactionPickerState extends State @override void dispose() { for (var a in animations) { - a?.dispose(); + a.dispose(); } super.dispose(); } diff --git a/packages/stream_chat_flutter/lib/src/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/sending_indicator.dart index 3fca2fa9..68fc5a0d 100644 --- a/packages/stream_chat_flutter/lib/src/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/sending_indicator.dart @@ -5,11 +5,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class SendingIndicator extends StatelessWidget { final Message message; final bool isMessageRead; - final double size; + final double? size; const SendingIndicator({ - Key key, - this.message, + Key? key, + required this.message, this.isMessageRead = false, this.size = 12, }) : super(key: key); @@ -22,10 +22,10 @@ class SendingIndicator extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.accentBlue, ); } - if (message.status == MessageSendingStatus.sent || message.status == null) { + if (message.status == MessageSendingStatus.sent) { return StreamSvgIcon.check( size: size, - color: IconTheme.of(context).color.withOpacity(0.5), + color: IconTheme.of(context).color!.withOpacity(0.5), ); } if (message.status == MessageSendingStatus.sending || diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index 470591e7..d3a196a1 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -7,7 +8,6 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'dart:ui' as ui; /// Widget used to provide information about the chat to the widget tree /// @@ -32,8 +32,8 @@ import 'dart:ui' as ui; /// Use [StreamChat.of] to get the current [StreamChatState] instance. class StreamChat extends StatefulWidget { final StreamChatClient client; - final Widget child; - final StreamChatThemeData streamChatThemeData; + final Widget? child; + final StreamChatThemeData? streamChatThemeData; /// The amount of time that will pass before disconnecting the client in the background final Duration backgroundKeepAlive; @@ -41,12 +41,12 @@ class StreamChat extends StatefulWidget { /// Handler called whenever the [client] receives a new [Event] while the app /// is in background. Can be used to display various notifications depending /// upon the [Event.type] - final EventHandler onBackgroundEventReceived; + final EventHandler? onBackgroundEventReceived; StreamChat({ - Key key, - @required this.client, - @required this.child, + Key? key, + required this.client, + required this.child, this.streamChatThemeData, this.onBackgroundEventReceived, this.backgroundKeepAlive = const Duration(minutes: 1), @@ -59,7 +59,7 @@ class StreamChat extends StatefulWidget { /// Use this method to get the current [StreamChatState] instance static StreamChatState of(BuildContext context) { - StreamChatState streamChatState; + StreamChatState? streamChatState; streamChatState = context.findAncestorStateOfType(); @@ -96,7 +96,7 @@ class StreamChatState extends State { client: client, onBackgroundEventReceived: widget.onBackgroundEventReceived, backgroundKeepAlive: widget.backgroundKeepAlive, - child: widget.child, + child: widget.child!, ), ); }, @@ -107,17 +107,18 @@ class StreamChatState extends State { StreamChatThemeData _getTheme( BuildContext context, - StreamChatThemeData themeData, + StreamChatThemeData? themeData, ) { - final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); - return defaultTheme.merge(themeData) ?? themeData; + final appBrightness = Theme.of(context).brightness; + final defaultTheme = StreamChatThemeData(brightness: appBrightness); + return defaultTheme.merge(themeData); } /// The current user - User get user => widget.client.state.user; + User? get user => widget.client.state.user; /// The current user as a stream - Stream get userStream => widget.client.state.userStream; + Stream get userStream => widget.client.state.userStream; @override void initState() { diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index e54c0fbe..9b28d1f6 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -2,10 +2,11 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; import 'package:stream_chat_flutter/src/channel_preview.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/reaction_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Inherited widget providing the [StreamChatThemeData] to the widget tree @@ -13,9 +14,9 @@ class StreamChatTheme extends InheritedWidget { final StreamChatThemeData data; StreamChatTheme({ - Key key, - @required this.data, - Widget child, + Key? key, + required this.data, + required Widget child, }) : super( key: key, child: child, @@ -31,13 +32,12 @@ class StreamChatTheme extends InheritedWidget { final streamChatTheme = context.dependOnInheritedWidgetOfExactType(); - if (streamChatTheme == null) { - throw Exception( - 'You must have a StreamChatTheme widget at the top of your widget tree', - ); - } + assert( + streamChatTheme != null, + 'You must have a StreamChatTheme widget at the top of your widget tree', + ); - return streamChatTheme.data; + return streamChatTheme!.data; } } @@ -80,49 +80,96 @@ class StreamChatThemeData { final List reactionIcons; /// Create a theme from scratch - const StreamChatThemeData({ - this.textTheme, - this.colorTheme, - this.channelListHeaderTheme, - this.channelPreviewTheme, - this.channelTheme, - this.otherMessageTheme, - this.ownMessageTheme, - this.messageInputTheme, - this.defaultChannelImage, - this.defaultUserImage, - this.primaryIconTheme, - this.reactionIcons, + factory StreamChatThemeData({ + Brightness? brightness, + TextTheme? textTheme, + ColorTheme? colorTheme, + ChannelListHeaderTheme? channelListHeaderTheme, + ChannelPreviewTheme? channelPreviewTheme, + ChannelTheme? channelTheme, + MessageTheme? otherMessageTheme, + MessageTheme? ownMessageTheme, + MessageInputTheme? messageInputTheme, + Widget Function(BuildContext, Channel)? defaultChannelImage, + Widget Function(BuildContext, User)? defaultUserImage, + IconThemeData? primaryIconTheme, + List? reactionIcons, + }) { + brightness ??= colorTheme?.brightness ?? Brightness.light; + final isDark = brightness == Brightness.dark; + textTheme ??= isDark ? TextTheme.dark() : TextTheme.light(); + colorTheme ??= isDark ? ColorTheme.dark() : ColorTheme.light(); + + final defaultData = fromColorAndTextTheme( + colorTheme, + textTheme, + ); + + final customizedData = defaultData.copyWith( + channelListHeaderTheme: channelListHeaderTheme, + channelPreviewTheme: channelPreviewTheme, + channelTheme: channelTheme, + otherMessageTheme: otherMessageTheme, + ownMessageTheme: ownMessageTheme, + messageInputTheme: messageInputTheme, + defaultChannelImage: defaultChannelImage, + defaultUserImage: defaultUserImage, + primaryIconTheme: primaryIconTheme, + reactionIcons: reactionIcons, + ); + + return defaultData.merge(customizedData); + } + + factory StreamChatThemeData.light() => + StreamChatThemeData(brightness: Brightness.light); + + factory StreamChatThemeData.dark() => + StreamChatThemeData(brightness: Brightness.dark); + + const StreamChatThemeData.raw({ + required this.textTheme, + required this.colorTheme, + required this.channelListHeaderTheme, + required this.channelPreviewTheme, + required this.channelTheme, + required this.otherMessageTheme, + required this.ownMessageTheme, + required this.messageInputTheme, + required this.defaultChannelImage, + required this.defaultUserImage, + required this.primaryIconTheme, + required this.reactionIcons, }); /// Create a theme from a Material [Theme] factory StreamChatThemeData.fromTheme(ThemeData theme) { - final defaultTheme = getDefaultTheme(theme); + final defaultTheme = StreamChatThemeData(brightness: theme.brightness); final customizedTheme = StreamChatThemeData.fromColorAndTextTheme( defaultTheme.colorTheme.copyWith( accentBlue: theme.accentColor, ), defaultTheme.textTheme, ); - return defaultTheme.merge(customizedTheme) ?? customizedTheme; + return defaultTheme.merge(customizedTheme); } /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. StreamChatThemeData copyWith({ - TextTheme textTheme, - ColorTheme colorTheme, - ChannelPreviewTheme channelPreviewTheme, - ChannelTheme channelTheme, - MessageTheme ownMessageTheme, - MessageTheme otherMessageTheme, - MessageInputTheme messageInputTheme, - Widget Function(BuildContext, Channel) defaultChannelImage, - Widget Function(BuildContext, User) defaultUserImage, - IconThemeData primaryIconTheme, - ChannelListHeaderTheme channelListHeaderTheme, - List reactionIcons, + TextTheme? textTheme, + ColorTheme? colorTheme, + ChannelPreviewTheme? channelPreviewTheme, + ChannelTheme? channelTheme, + MessageTheme? ownMessageTheme, + MessageTheme? otherMessageTheme, + MessageInputTheme? messageInputTheme, + Widget Function(BuildContext, Channel)? defaultChannelImage, + Widget Function(BuildContext, User)? defaultUserImage, + IconThemeData? primaryIconTheme, + ChannelListHeaderTheme? channelListHeaderTheme, + List? reactionIcons, }) => - StreamChatThemeData( + StreamChatThemeData.raw( channelListHeaderTheme: channelListHeaderTheme ?? this.channelListHeaderTheme, textTheme: textTheme ?? this.textTheme, @@ -138,28 +185,21 @@ class StreamChatThemeData { reactionIcons: reactionIcons ?? this.reactionIcons, ); - StreamChatThemeData merge(StreamChatThemeData other) { + StreamChatThemeData merge(StreamChatThemeData? other) { if (other == null) return this; return copyWith( channelListHeaderTheme: - channelListHeaderTheme?.merge(other.channelListHeaderTheme) ?? - other.channelListHeaderTheme, - textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme, - colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme, + channelListHeaderTheme.merge(other.channelListHeaderTheme), + textTheme: textTheme.merge(other.textTheme), + colorTheme: colorTheme.merge(other.colorTheme), primaryIconTheme: other.primaryIconTheme, defaultChannelImage: other.defaultChannelImage, defaultUserImage: other.defaultUserImage, - channelPreviewTheme: - channelPreviewTheme?.merge(other.channelPreviewTheme) ?? - other.channelPreviewTheme, - channelTheme: - channelTheme?.merge(other.channelTheme) ?? other.channelTheme, - ownMessageTheme: ownMessageTheme?.merge(other.ownMessageTheme) ?? - other.ownMessageTheme, - otherMessageTheme: otherMessageTheme?.merge(other.otherMessageTheme) ?? - other.otherMessageTheme, - messageInputTheme: messageInputTheme?.merge(other.messageInputTheme) ?? - other.messageInputTheme, + channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme), + channelTheme: channelTheme.merge(other.channelTheme), + ownMessageTheme: ownMessageTheme.merge(other.ownMessageTheme), + otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme), + messageInputTheme: messageInputTheme.merge(other.messageInputTheme), reactionIcons: other.reactionIcons, ); } @@ -169,7 +209,7 @@ class StreamChatThemeData { TextTheme textTheme, ) { final accentColor = colorTheme.accentBlue; - return StreamChatThemeData( + return StreamChatThemeData.raw( textTheme: textTheme, colorTheme: colorTheme, primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), @@ -314,22 +354,6 @@ class StreamChatThemeData { ], ); } - - /// Get the default Stream Chat theme - static StreamChatThemeData getDefaultTheme(ThemeData theme) { - final isDark = theme.brightness == Brightness.dark; - final textTheme = isDark ? TextTheme.dark() : TextTheme.light(); - final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light(); - return fromColorAndTextTheme( - colorTheme, - textTheme, - ); - } -} - -enum TextThemeType { - light, - dark, } class TextTheme { @@ -427,17 +451,17 @@ class TextTheme { }); TextTheme copyWith({ - TextThemeType type = TextThemeType.light, - TextStyle body, - TextStyle title, - TextStyle headlineBold, - TextStyle headline, - TextStyle bodyBold, - TextStyle footnoteBold, - TextStyle footnote, - TextStyle captionBold, + Brightness brightness = Brightness.light, + TextStyle? body, + TextStyle? title, + TextStyle? headlineBold, + TextStyle? headline, + TextStyle? bodyBold, + TextStyle? footnoteBold, + TextStyle? footnote, + TextStyle? captionBold, }) { - return type == TextThemeType.light + return brightness == Brightness.light ? TextTheme.light( body: body ?? this.body, title: title ?? this.title, @@ -460,28 +484,21 @@ class TextTheme { ); } - TextTheme merge(TextTheme other) { + TextTheme merge(TextTheme? other) { if (other == null) return this; return copyWith( - body: body?.merge(other.body) ?? other.body, - title: title?.merge(other.title) ?? other.title, - headlineBold: - headlineBold?.merge(other.headlineBold) ?? other.headlineBold, - headline: headline?.merge(other.headline) ?? other.headline, - bodyBold: bodyBold?.merge(other.bodyBold) ?? other.bodyBold, - footnoteBold: - footnoteBold?.merge(other.footnoteBold) ?? other.footnoteBold, - footnote: footnote?.merge(other.footnote) ?? other.footnote, - captionBold: captionBold?.merge(other.captionBold) ?? other.captionBold, + body: body.merge(other.body), + title: title.merge(other.title), + headlineBold: headlineBold.merge(other.headlineBold), + headline: headline.merge(other.headline), + bodyBold: bodyBold.merge(other.bodyBold), + footnoteBold: footnoteBold.merge(other.footnoteBold), + footnote: footnote.merge(other.footnote), + captionBold: captionBold.merge(other.captionBold), ); } } -enum ColorThemeType { - light, - dark, -} - class ColorTheme { final Color black; final Color grey; @@ -502,6 +519,7 @@ class ColorTheme { final Color overlay; final Color overlayDark; final Gradient bgGradient; + final Brightness brightness; ColorTheme.light({ this.black = const Color(0xff000000), @@ -536,7 +554,7 @@ class ColorTheme { sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), this.modalShadow = const Effect( sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), - }); + }) : brightness = Brightness.light; ColorTheme.dark({ this.black = const Color(0xffffffff), @@ -551,13 +569,32 @@ class ColorTheme { this.accentRed = const Color(0xffFF3742), this.accentGreen = const Color(0xff20E070), this.borderTop = const Effect( - sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0), + sigmaX: 0, + sigmaY: -1, + color: Color(0xff141924), + blur: 0.0, + ), this.borderBottom = const Effect( - sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0, alpha: 1.0), + sigmaX: 0, + sigmaY: 1, + color: Color(0xff141924), + blur: 0.0, + alpha: 1.0, + ), this.shadowIconButton = const Effect( - sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), + sigmaX: 0, + sigmaY: 2, + color: Color(0xff000000), + alpha: 0.5, + blur: 4.0, + ), this.modalShadow = const Effect( - sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), + sigmaX: 0, + sigmaY: 0, + color: Color(0xff000000), + alpha: 1, + blur: 8.0, + ), this.highlight = const Color(0xff302d22), this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6), @@ -570,31 +607,31 @@ class ColorTheme { ], stops: [0, 1], ), - }); + }) : brightness = Brightness.dark; ColorTheme copyWith({ - ColorThemeType type = ColorThemeType.light, - Color black, - Color grey, - Color greyGainsboro, - Color greyWhisper, - Color whiteSmoke, - Color whiteSnow, - Color white, - Color blueAlice, - Color accentBlue, - Color accentRed, - Color accentGreen, - Effect borderTop, - Effect borderBottom, - Effect shadowIconButton, - Effect modalShadow, - Color highlight, - Color overlay, - Color overlayDark, - Gradient bgGradient, + Brightness brightness = Brightness.light, + Color? black, + Color? grey, + Color? greyGainsboro, + Color? greyWhisper, + Color? whiteSmoke, + Color? whiteSnow, + Color? white, + Color? blueAlice, + Color? accentBlue, + Color? accentRed, + Color? accentGreen, + Effect? borderTop, + Effect? borderBottom, + Effect? shadowIconButton, + Effect? modalShadow, + Color? highlight, + Color? overlay, + Color? overlayDark, + Gradient? bgGradient, }) { - return type == ColorThemeType.light + return brightness == Brightness.light ? ColorTheme.light( black: black ?? this.black, grey: grey ?? this.grey, @@ -639,7 +676,7 @@ class ColorTheme { ); } - ColorTheme merge(ColorTheme other) { + ColorTheme merge(ColorTheme? other) { if (other == null) return this; return copyWith( black: other.black, @@ -671,65 +708,77 @@ class ChannelTheme { final ChannelHeaderTheme channelHeaderTheme; ChannelTheme({ - this.channelHeaderTheme, + required this.channelHeaderTheme, }); /// Creates a copy of [ChannelTheme] with specified attributes overridden. ChannelTheme copyWith({ - ChannelHeaderTheme channelHeaderTheme, + ChannelHeaderTheme? channelHeaderTheme, }) => ChannelTheme( channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme, ); - ChannelTheme merge(ChannelTheme other) { + ChannelTheme merge(ChannelTheme? other) { if (other == null) return this; return copyWith( - channelHeaderTheme: channelHeaderTheme?.merge(other.channelHeaderTheme) ?? - other.channelHeaderTheme, + channelHeaderTheme: channelHeaderTheme.merge(other.channelHeaderTheme), ); } } class AvatarTheme { - final BoxConstraints constraints; - final BorderRadius borderRadius; + final BoxConstraints? _constraints; + final BorderRadius? _borderRadius; + + BoxConstraints get constraints { + return _constraints ?? + BoxConstraints.tightFor( + height: 32, + width: 32, + ); + } + + BorderRadius get borderRadius { + return _borderRadius ?? BorderRadius.circular(20); + } AvatarTheme({ - this.constraints, - this.borderRadius, - }); + BoxConstraints? constraints, + BorderRadius? borderRadius, + }) : _constraints = constraints, + _borderRadius = borderRadius; AvatarTheme copyWith({ - BoxConstraints constraints, - BorderRadius borderRadius, + BoxConstraints? constraints, + BorderRadius? borderRadius, }) => AvatarTheme( - constraints: constraints ?? this.constraints, - borderRadius: borderRadius ?? this.borderRadius, + constraints: constraints ?? _constraints, + borderRadius: borderRadius ?? _borderRadius, ); - AvatarTheme merge(AvatarTheme other) { + AvatarTheme merge(AvatarTheme? other) { if (other == null) return this; return copyWith( - constraints: other.constraints, - borderRadius: other.borderRadius, + constraints: other._constraints, + borderRadius: other._borderRadius, ); } } class MessageTheme { - final TextStyle messageText; - final TextStyle messageAuthor; - final TextStyle messageLinks; - final TextStyle createdAt; - final TextStyle replies; - final Color messageBackgroundColor; - final Color messageBorderColor; - final Color reactionsBackgroundColor; - final Color reactionsBorderColor; - final Color reactionsMaskColor; - final AvatarTheme avatarTheme; + final TextStyle? messageText; + final TextStyle? messageAuthor; + final TextStyle? messageLinks; + final TextStyle? createdAt; + final TextStyle? replies; + final Color? messageBackgroundColor; + final Color? messageBorderColor; + final Color? reactionsBackgroundColor; + final Color? reactionsBorderColor; + final Color? reactionsMaskColor; + final AvatarTheme? avatarTheme; const MessageTheme({ this.replies, @@ -746,17 +795,17 @@ class MessageTheme { }); MessageTheme copyWith({ - TextStyle messageText, - TextStyle messageAuthor, - TextStyle messageLinks, - TextStyle createdAt, - TextStyle replies, - Color messageBackgroundColor, - Color messageBorderColor, - AvatarTheme avatarTheme, - Color reactionsBackgroundColor, - Color reactionsBorderColor, - Color reactionsMaskColor, + TextStyle? messageText, + TextStyle? messageAuthor, + TextStyle? messageLinks, + TextStyle? createdAt, + TextStyle? replies, + Color? messageBackgroundColor, + Color? messageBorderColor, + AvatarTheme? avatarTheme, + Color? reactionsBackgroundColor, + Color? reactionsBorderColor, + Color? reactionsMaskColor, }) => MessageTheme( messageText: messageText ?? this.messageText, @@ -774,7 +823,7 @@ class MessageTheme { reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, ); - MessageTheme merge(MessageTheme other) { + MessageTheme merge(MessageTheme? other) { if (other == null) return this; return copyWith( messageText: messageText?.merge(other.messageText) ?? other.messageText, @@ -795,12 +844,12 @@ class MessageTheme { } class ChannelPreviewTheme { - final TextStyle title; - final TextStyle subtitle; - final TextStyle lastMessageAt; - final AvatarTheme avatarTheme; - final Color unreadCounterColor; - final double indicatorIconSize; + final TextStyle? title; + final TextStyle? subtitle; + final TextStyle? lastMessageAt; + final AvatarTheme? avatarTheme; + final Color? unreadCounterColor; + final double? indicatorIconSize; const ChannelPreviewTheme({ this.title, @@ -812,12 +861,12 @@ class ChannelPreviewTheme { }); ChannelPreviewTheme copyWith({ - TextStyle title, - TextStyle subtitle, - TextStyle lastMessageAt, - AvatarTheme avatarTheme, - Color unreadCounterColor, - double indicatorIconSize, + TextStyle? title, + TextStyle? subtitle, + TextStyle? lastMessageAt, + AvatarTheme? avatarTheme, + Color? unreadCounterColor, + double? indicatorIconSize, }) => ChannelPreviewTheme( title: title ?? this.title, @@ -828,7 +877,7 @@ class ChannelPreviewTheme { indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, ); - ChannelPreviewTheme merge(ChannelPreviewTheme other) { + ChannelPreviewTheme merge(ChannelPreviewTheme? other) { if (other == null) return this; return copyWith( title: title?.merge(other.title) ?? other.title, @@ -842,10 +891,10 @@ class ChannelPreviewTheme { } class ChannelHeaderTheme { - final TextStyle title; - final TextStyle subtitle; - final AvatarTheme avatarTheme; - final Color color; + final TextStyle? title; + final TextStyle? subtitle; + final AvatarTheme? avatarTheme; + final Color? color; const ChannelHeaderTheme({ this.title, @@ -855,10 +904,10 @@ class ChannelHeaderTheme { }); ChannelHeaderTheme copyWith({ - TextStyle title, - TextStyle subtitle, - AvatarTheme avatarTheme, - Color color, + TextStyle? title, + TextStyle? subtitle, + AvatarTheme? avatarTheme, + Color? color, }) => ChannelHeaderTheme( title: title ?? this.title, @@ -867,7 +916,7 @@ class ChannelHeaderTheme { color: color ?? this.color, ); - ChannelHeaderTheme merge(ChannelHeaderTheme other) { + ChannelHeaderTheme merge(ChannelHeaderTheme? other) { if (other == null) return this; return copyWith( title: title?.merge(other.title) ?? other.title, @@ -881,13 +930,13 @@ class ChannelHeaderTheme { /// Theme dedicated to the [ChannelListHeader] class ChannelListHeaderTheme { /// Style of the title text - final TextStyle title; + final TextStyle? title; /// Theme dedicated to the userAvatar - final AvatarTheme avatarTheme; + final AvatarTheme? avatarTheme; /// Background color of the appbar - final Color color; + final Color? color; /// Returns a new [ChannelListHeaderTheme] const ChannelListHeaderTheme({ @@ -898,9 +947,9 @@ class ChannelListHeaderTheme { /// Returns a new [ChannelListHeaderTheme] replacing some of its properties ChannelListHeaderTheme copyWith({ - TextStyle title, - AvatarTheme avatarTheme, - Color color, + TextStyle? title, + AvatarTheme? avatarTheme, + Color? color, }) => ChannelListHeaderTheme( title: title ?? this.title, @@ -909,7 +958,7 @@ class ChannelListHeaderTheme { ); /// Merges [this] [ChannelListHeaderTheme] with the [other] - ChannelListHeaderTheme merge(ChannelListHeaderTheme other) { + ChannelListHeaderTheme merge(ChannelListHeaderTheme? other) { if (other == null) return this; return copyWith( title: title?.merge(other.title) ?? other.title, @@ -922,40 +971,40 @@ class ChannelListHeaderTheme { /// Defines the theme dedicated to the [MessageInput] widget class MessageInputTheme { /// Duration of the [MessageInput] send button animation - final Duration sendAnimationDuration; + final Duration? sendAnimationDuration; /// Background color of [MessageInput] send button - final Color sendButtonColor; + final Color? sendButtonColor; /// Background color of [MessageInput] action buttons - final Color actionButtonColor; + final Color? actionButtonColor; /// Background color of [MessageInput] send button - final Color sendButtonIdleColor; + final Color? sendButtonIdleColor; /// Background color of [MessageInput] action buttons - final Color actionButtonIdleColor; + final Color? actionButtonIdleColor; /// Background color of [MessageInput] expand button - final Color expandButtonColor; + final Color? expandButtonColor; /// Background color of [MessageInput] - final Color inputBackground; + final Color? inputBackground; /// TextStyle of [MessageInput] - final TextStyle inputTextStyle; + final TextStyle? inputTextStyle; /// InputDecoration of [MessageInput] - final InputDecoration inputDecoration; + final InputDecoration? inputDecoration; /// Border gradient when the [MessageInput] is not focused - final Gradient idleBorderGradient; + final Gradient? idleBorderGradient; /// Border gradient when the [MessageInput] is focused - final Gradient activeBorderGradient; + final Gradient? activeBorderGradient; /// Border radius of [MessageInput] - final BorderRadius borderRadius; + final BorderRadius? borderRadius; /// Returns a new [MessageInputTheme] const MessageInputTheme({ @@ -975,18 +1024,18 @@ class MessageInputTheme { /// Returns a new [MessageInputTheme] replacing some of its properties MessageInputTheme copyWith({ - Duration sendAnimationDuration, - Color inputBackground, - Color actionButtonColor, - Color sendButtonColor, - Color actionButtonIdleColor, - Color sendButtonIdleColor, - Color expandButtonColor, - TextStyle inputTextStyle, - InputDecoration inputDecoration, - Gradient activeBorderGradient, - Gradient idleBorderGradient, - BorderRadius borderRadius, + Duration? sendAnimationDuration, + Color? inputBackground, + Color? actionButtonColor, + Color? sendButtonColor, + Color? actionButtonIdleColor, + Color? sendButtonIdleColor, + Color? expandButtonColor, + TextStyle? inputTextStyle, + InputDecoration? inputDecoration, + Gradient? activeBorderGradient, + Gradient? idleBorderGradient, + BorderRadius? borderRadius, }) => MessageInputTheme( sendAnimationDuration: @@ -1006,7 +1055,7 @@ class MessageInputTheme { ); /// Merges [this] [MessageInputTheme] with the [other] - MessageInputTheme merge(MessageInputTheme other) { + MessageInputTheme merge(MessageInputTheme? other) { if (other == null) return this; return copyWith( sendAnimationDuration: other.sendAnimationDuration, @@ -1027,11 +1076,11 @@ class MessageInputTheme { } class Effect { - final double sigmaX; - final double sigmaY; - final Color color; - final double alpha; - final double blur; + final double? sigmaX; + final double? sigmaY; + final Color? color; + final double? alpha; + final double? blur; const Effect({ this.sigmaX, @@ -1042,17 +1091,17 @@ class Effect { }); Effect copyWith({ - double sigmaX, - double sigmaY, - Color color, - double alpha, - double blur, + double? sigmaX, + double? sigmaY, + Color? color, + double? alpha, + double? blur, }) => Effect( sigmaX: sigmaX ?? this.sigmaX, sigmaY: sigmaY ?? this.sigmaY, color: color ?? this.color, - alpha: color ?? this.alpha, + alpha: color as double? ?? this.alpha, blur: blur ?? this.blur, ); } diff --git a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart index 35e98960..95b3c70e 100644 --- a/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart +++ b/packages/stream_chat_flutter/lib/src/stream_neumorphic_button.dart @@ -5,8 +5,8 @@ class StreamNeumorphicButton extends StatelessWidget { final Color backgroundColor; const StreamNeumorphicButton({ - Key key, - @required this.child, + Key? key, + required this.child, this.backgroundColor = Colors.white, }) : super(key: key); @@ -21,7 +21,7 @@ class StreamNeumorphicButton extends StatelessWidget { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.grey[700], + color: Colors.grey.shade700, offset: Offset(0, 1.0), blurRadius: 0.5, spreadRadius: 0, diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index 374b9ba1..8dc38ba2 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class StreamSvgIcon extends StatelessWidget { - final String assetName; - final double width; - final double height; - final Color color; + final String? assetName; + final double? width; + final double? height; + final Color? color; const StreamSvgIcon({ this.assetName, @@ -30,8 +30,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.settings({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'settings.svg', @@ -42,8 +42,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.down({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_down.svg', @@ -54,8 +54,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.attach({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_attach.svg', @@ -66,8 +66,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.smile({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_smile.svg', @@ -78,8 +78,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.mentions({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'mentions.svg', @@ -90,8 +90,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.record({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_record.svg', @@ -102,8 +102,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.camera({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_camera.svg', @@ -114,8 +114,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.files({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'files.svg', @@ -126,8 +126,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.pictures({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'pictures.svg', @@ -138,8 +138,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.left({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_left.svg', @@ -150,8 +150,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.user({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_user.svg', @@ -162,8 +162,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.userAdd({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_User_add.svg', @@ -174,8 +174,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.check({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_check.svg', @@ -186,8 +186,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.checkAll({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_check_all.svg', @@ -198,8 +198,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.checkSend({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_check_send.svg', @@ -210,8 +210,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.penWrite({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_pen-write.svg', @@ -222,8 +222,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.contacts({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_contacts.svg', @@ -234,8 +234,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.close({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_close.svg', @@ -246,8 +246,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.search({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_search.svg', @@ -258,8 +258,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.right({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_right.svg', @@ -270,8 +270,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.mute({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_mute.svg', @@ -282,8 +282,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.userRemove({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_User_deselect.svg', @@ -294,8 +294,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.lightning({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_lightning-command runner.svg', @@ -306,8 +306,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.emptyCircleLeft({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_empty_circle_left.svg', @@ -318,8 +318,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.message({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_message.svg', @@ -330,8 +330,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.thread({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_Thread_Reply.svg', @@ -342,8 +342,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.reply({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_curve_line_left_up_big.svg', @@ -354,8 +354,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.edit({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_edit.svg', @@ -366,8 +366,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.download({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_download.svg', @@ -378,8 +378,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.cloudDownload({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_cloud_download.svg', @@ -390,8 +390,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.copy({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_copy.svg', @@ -402,8 +402,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.delete({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_delete.svg', @@ -414,8 +414,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.eye({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_eye-off.svg', @@ -426,8 +426,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.arrowRight({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_arrow_right.svg', @@ -438,8 +438,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.closeSmall({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_close_sml.svg', @@ -450,8 +450,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconCurveLineLeftUp({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_curve_line_left_up.svg', @@ -462,8 +462,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconMoon({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_moon.svg', @@ -474,8 +474,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconShare({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_SHARE.svg', @@ -486,8 +486,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconGrid({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_grid.svg', @@ -498,8 +498,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconSendMessage({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_send_message.svg', @@ -510,8 +510,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconMenuPoint({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_menu_point_v.svg', @@ -522,8 +522,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconSave({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_save.svg', @@ -534,8 +534,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.shareArrow({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'share_arrow.svg', @@ -546,8 +546,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetype7z({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_7z.svg', @@ -558,8 +558,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeCsv({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_CSV.svg', @@ -570,8 +570,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeDoc({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_DOC.svg', @@ -582,8 +582,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeDocx({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_DOCX.svg', @@ -594,8 +594,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeGeneric({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_Generic.svg', @@ -606,8 +606,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeHtml({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_html.svg', @@ -618,8 +618,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeMd({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_MD.svg', @@ -630,8 +630,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeOdt({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_ODT.svg', @@ -642,8 +642,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypePdf({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_PDF.svg', @@ -654,8 +654,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypePpt({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_PPT.svg', @@ -666,8 +666,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypePptx({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_PPTX.svg', @@ -678,8 +678,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeRar({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_RAR.svg', @@ -690,8 +690,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeRtf({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_RTF.svg', @@ -702,8 +702,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeTar({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_TAR.svg', @@ -714,8 +714,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeTxt({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_TXT.svg', @@ -726,8 +726,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeXls({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_XLS.svg', @@ -738,8 +738,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeXlsx({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_XLSX.svg', @@ -750,8 +750,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.filetypeZip({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'filetype_ZIP.svg', @@ -762,8 +762,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconGroup({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_group.svg', @@ -774,8 +774,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconNotification({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_notification.svg', @@ -786,8 +786,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconUserDelete({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_user_delete.svg', @@ -798,8 +798,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.error({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_error.svg', @@ -810,8 +810,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.circleUp({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_circle_up.svg', @@ -822,8 +822,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconUserSettings({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'Icon_user_settings.svg', @@ -834,8 +834,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.giphyIcon({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'giphy_icon.svg', @@ -846,8 +846,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.imgur({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'imgur.svg', @@ -858,8 +858,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.volumeUp({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'volume-up.svg', @@ -870,8 +870,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.flag({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'flag.svg', @@ -882,8 +882,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.iconFlag({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_flag.svg', @@ -894,8 +894,8 @@ class StreamSvgIcon extends StatelessWidget { } factory StreamSvgIcon.retry({ - double size, - Color color, + double? size, + Color? color, }) { return StreamSvgIcon( assetName: 'icon_retry.svg', diff --git a/packages/stream_chat_flutter/lib/src/swipeable.dart b/packages/stream_chat_flutter/lib/src/swipeable.dart index 8280bf24..90146e4c 100644 --- a/packages/stream_chat_flutter/lib/src/swipeable.dart +++ b/packages/stream_chat_flutter/lib/src/swipeable.dart @@ -8,15 +8,15 @@ import 'stream_chat_theme.dart'; class Swipeable extends StatefulWidget { final Widget child; final Widget backgroundIcon; - final VoidCallback onSwipeStart; - final VoidCallback onSwipeCancel; - final VoidCallback onSwipeEnd; + final VoidCallback? onSwipeStart; + final VoidCallback? onSwipeCancel; + final VoidCallback? onSwipeEnd; final double threshold; /// const Swipeable({ - @required this.child, - @required this.backgroundIcon, + required this.child, + required this.backgroundIcon, this.onSwipeStart, this.onSwipeCancel, this.onSwipeEnd, @@ -29,11 +29,11 @@ class Swipeable extends StatefulWidget { class _SwipeableState extends State with TickerProviderStateMixin { double _dragExtent = 0.0; - AnimationController _moveController; - AnimationController _iconMoveController; - Animation _moveAnimation; - Animation _iconTransitionAnimation; - Animation _iconFadeAnimation; + late AnimationController _moveController; + late AnimationController _iconMoveController; + late Animation _moveAnimation; + late Animation _iconTransitionAnimation; + late Animation _iconFadeAnimation; bool _pastThreshold = false; final _animationDuration = const Duration(milliseconds: 200); @@ -67,18 +67,18 @@ class _SwipeableState extends State with TickerProviderStateMixin { void _handleDragStart(DragStartDetails details) { if (widget.onSwipeStart != null) { - widget.onSwipeStart(); + widget.onSwipeStart!(); } } void _handleDragUpdate(DragUpdateDetails details) { - final delta = details.primaryDelta; + final delta = details.primaryDelta!; _dragExtent += delta; if (_dragExtent.isNegative) return; var movePastThresholdPixels = widget.threshold; - var newPos = _dragExtent.abs() / context.size.width; + var newPos = _dragExtent.abs() / context.size!.width; if (_dragExtent.abs() > movePastThresholdPixels) { // how many "thresholds" past the threshold we are. 1 = the threshold 2 @@ -90,7 +90,7 @@ class _SwipeableState extends State with TickerProviderStateMixin { var reducedThreshold = math.pow(n, 0.3); var adjustedPixelPos = movePastThresholdPixels * reducedThreshold; - newPos = adjustedPixelPos / context.size.width; + newPos = adjustedPixelPos / context.size!.width; if (_dragExtent > 0 && !_pastThreshold) { _iconMoveController.value = 1; @@ -100,7 +100,7 @@ class _SwipeableState extends State with TickerProviderStateMixin { // Send a cancel event if the user has swiped back underneath the // threshold if (_pastThreshold && widget.onSwipeCancel != null) { - widget.onSwipeCancel(); + widget.onSwipeCancel!(); } _pastThreshold = false; } @@ -115,7 +115,7 @@ class _SwipeableState extends State with TickerProviderStateMixin { _iconMoveController.animateTo(0.0, duration: _animationDuration); _dragExtent = 0.0; if (_pastThreshold && widget.onSwipeEnd != null) { - widget.onSwipeEnd(); + widget.onSwipeEnd!(); } } diff --git a/packages/stream_chat_flutter/lib/src/system_message.dart b/packages/stream_chat_flutter/lib/src/system_message.dart index ac3176ce..cdb9294f 100644 --- a/packages/stream_chat_flutter/lib/src/system_message.dart +++ b/packages/stream_chat_flutter/lib/src/system_message.dart @@ -7,11 +7,11 @@ class SystemMessage extends StatelessWidget { final Message message; /// The function called when tapping on the message when the message is not failed - final void Function(Message) onMessageTap; + final void Function(Message)? onMessageTap; const SystemMessage({ - Key key, - @required this.message, + Key? key, + required this.message, this.onMessageTap, }) : super(key: key); @@ -22,11 +22,11 @@ class SystemMessage extends StatelessWidget { behavior: HitTestBehavior.opaque, onTap: () { if (onMessageTap != null) { - onMessageTap(message); + onMessageTap!(message); } }, child: Text( - message.text, + message.text!, textAlign: TextAlign.center, softWrap: true, style: theme.textTheme.captionBold.copyWith( diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 449d4584..9dc8f98c 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'back_button.dart'; import 'channel_name.dart'; @@ -61,30 +61,30 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] - final VoidCallback onBackPressed; + final VoidCallback? onBackPressed; /// Callback to call when the title is tapped. - final VoidCallback onTitleTap; + final VoidCallback? onTitleTap; /// The message parent of this thread final Message parent; /// Title widget - final Widget title; + final Widget? title; /// Subtitle widget - final Widget subtitle; + final Widget? subtitle; /// Leading widget - final Widget leading; + final Widget? leading; /// AppBar actions - final List actions; + final List? actions; /// Instantiate a new ThreadHeader ThreadHeader({ - Key key, - @required this.parent, + Key? key, + required this.parent, this.showBackButton = true, this.onBackPressed, this.title, diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 8a53c3e8..aecc3bf1 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -6,7 +6,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class TypingIndicator extends StatelessWidget { /// Instantiate a new TypingIndicator const TypingIndicator({ - Key key, + Key? key, this.channel, this.alternativeWidget, this.style, @@ -15,13 +15,13 @@ class TypingIndicator extends StatelessWidget { }) : super(key: key); /// Style of the text widget - final TextStyle style; + final TextStyle? style; /// List of typing users - final Channel channel; + final Channel? channel; /// Widget built when no typings is happening - final Widget alternativeWidget; + final Widget? alternativeWidget; /// The padding of this widget final EdgeInsets padding; @@ -31,7 +31,7 @@ class TypingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { final channelState = - channel?.state ?? StreamChannel.of(context).channel.state; + channel?.state ?? StreamChannel.of(context).channel.state!; return StreamBuilder>( initialData: channelState.typingEvents, stream: channelState.typingEventsStream, @@ -53,7 +53,7 @@ class TypingIndicator extends StatelessWidget { height: 4, ), Text( - ' ${snapshot.data[0].name}${snapshot.data.length == 1 ? '' : ' and ${snapshot.data.length - 1} more'} ${snapshot.data.length == 1 ? 'is' : 'are'} typing', + ' ${snapshot.data![0].name}${snapshot.data!.length == 1 ? '' : ' and ${snapshot.data!.length - 1} more'} ${snapshot.data!.length == 1 ? 'is' : 'are'} typing', maxLines: 1, style: style, ), diff --git a/packages/stream_chat_flutter/lib/src/unread_indicator.dart b/packages/stream_chat_flutter/lib/src/unread_indicator.dart index 0c30e9a1..d55cd4f1 100644 --- a/packages/stream_chat_flutter/lib/src/unread_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/unread_indicator.dart @@ -4,23 +4,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class UnreadIndicator extends StatelessWidget { const UnreadIndicator({ - Key key, + Key? key, this.cid, }) : super(key: key); /// Channel cid used to retrieve unread count - final String cid; + final String? cid; @override Widget build(BuildContext context) { final client = StreamChat.of(context).client; return IgnorePointer( - child: StreamBuilder( + child: StreamBuilder( stream: cid != null - ? client.state.channels[cid].state.unreadCountStream + ? client.state.channels[cid]?.state?.unreadCountStream : client.state.totalUnreadCountStream, initialData: cid != null - ? client.state.channels[cid].state.unreadCount + ? client.state.channels[cid]?.state?.unreadCount : client.state.totalUnreadCount, builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data == 0) { @@ -40,7 +40,7 @@ class UnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot.data > 99 ? '99+' : snapshot.data}', + '${snapshot.data! > 99 ? '99+' : snapshot.data}', style: TextStyle( fontSize: 11, color: Colors.white, diff --git a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart index 53c121e2..ec06de8a 100644 --- a/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/upload_progress_indicator.dart @@ -5,20 +5,27 @@ import 'stream_chat_theme.dart'; class UploadProgressIndicator extends StatelessWidget { final int uploaded; final int total; - final Color progressIndicatorColor; + late final Color progressIndicatorColor; final EdgeInsetsGeometry padding; final bool showBackground; - final TextStyle textStyle; + final TextStyle? textStyle; - const UploadProgressIndicator({ - Key key, - @required this.uploaded, - @required this.total, - this.progressIndicatorColor = const Color(0xffb2b2b2), - this.padding = const EdgeInsets.only(top: 5, bottom: 5, right: 11, left: 5), + UploadProgressIndicator({ + Key? key, + required this.uploaded, + required this.total, + Color? progressIndicatorColor, + this.padding = const EdgeInsets.only( + top: 5, + bottom: 5, + right: 11, + left: 5, + ), this.showBackground = true, this.textStyle, - }) : super(key: key); + }) : progressIndicatorColor = + progressIndicatorColor ?? const Color(0xffb2b2b2), + super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter/lib/src/url_attachment.dart b/packages/stream_chat_flutter/lib/src/url_attachment.dart index 9dc5cdc8..d9beab20 100644 --- a/packages/stream_chat_flutter/lib/src/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/url_attachment.dart @@ -9,9 +9,9 @@ class UrlAttachment extends StatelessWidget { final EdgeInsets textPadding; UrlAttachment({ - @required this.urlAttachment, - @required this.hostDisplayName, - @required this.textPadding, + required this.urlAttachment, + required this.hostDisplayName, + required this.textPadding, }); @override @@ -19,7 +19,7 @@ class UrlAttachment extends StatelessWidget { return GestureDetector( onTap: () => launchURL( context, - urlAttachment.ogScrapeUrl, + urlAttachment.ogScrapeUrl!, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -35,7 +35,7 @@ class UrlAttachment extends StatelessWidget { children: [ CachedNetworkImage( width: double.infinity, - imageUrl: urlAttachment.imageUrl, + imageUrl: urlAttachment.imageUrl!, fit: BoxFit.cover, ), Positioned( @@ -78,7 +78,7 @@ class UrlAttachment extends StatelessWidget { children: [ if (urlAttachment.title != null) Text( - urlAttachment.title.trim(), + urlAttachment.title!.trim(), maxLines: 1, overflow: TextOverflow.ellipsis, style: StreamChatTheme.of(context) @@ -88,7 +88,7 @@ class UrlAttachment extends StatelessWidget { ), if (urlAttachment.text != null) Text( - urlAttachment.text, + urlAttachment.text!, style: StreamChatTheme.of(context) .textTheme .body diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index 41860a7f..96753b99 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -6,8 +6,8 @@ import '../stream_chat_flutter.dart'; class UserAvatar extends StatelessWidget { const UserAvatar({ - Key key, - @required this.user, + Key? key, + required this.user, this.constraints, this.onlineIndicatorConstraints, this.onTap, @@ -22,19 +22,19 @@ class UserAvatar extends StatelessWidget { final User user; final Alignment onlineIndicatorAlignment; - final BoxConstraints constraints; - final BorderRadius borderRadius; - final BoxConstraints onlineIndicatorConstraints; - final void Function(User) onTap; - final void Function(User) onLongPress; + final BoxConstraints? constraints; + final BorderRadius? borderRadius; + final BoxConstraints? onlineIndicatorConstraints; + final void Function(User)? onTap; + final void Function(User)? onLongPress; final bool showOnlineStatus; final bool selected; - final Color selectionColor; + final Color? selectionColor; final double selectionThickness; @override Widget build(BuildContext context) { - final hasImage = user.extraData?.containsKey('image') == true && + final hasImage = user.extraData.containsKey('image') && user.extraData['image'] != null && user.extraData['image'] != ''; final streamChatTheme = StreamChatTheme.of(context); @@ -44,17 +44,17 @@ class UserAvatar extends StatelessWidget { child: ClipRRect( clipBehavior: Clip.antiAlias, borderRadius: borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme.borderRadius, + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, child: Container( constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, decoration: BoxDecoration( color: streamChatTheme.colorTheme.accentBlue, ), child: hasImage ? CachedNetworkImage( filterQuality: FilterQuality.high, - imageUrl: user.extraData['image'], + imageUrl: user.extraData['image'] as String, errorWidget: (_, __, ___) { return streamChatTheme.defaultUserImage(context, user); }, @@ -68,11 +68,12 @@ class UserAvatar extends StatelessWidget { if (selected) { avatar = ClipRRect( borderRadius: (borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) + + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ?? + BorderRadius.zero) + BorderRadius.circular(selectionThickness), child: Container( constraints: constraints ?? - streamChatTheme.ownMessageTheme.avatarTheme.constraints, + streamChatTheme.ownMessageTheme.avatarTheme?.constraints, color: selectionColor ?? StreamChatTheme.of(context).colorTheme.accentBlue, child: Padding( @@ -83,12 +84,12 @@ class UserAvatar extends StatelessWidget { ); } return GestureDetector( - onTap: onTap != null ? () => onTap(user) : null, - onLongPress: onLongPress != null ? () => onLongPress(user) : null, + onTap: onTap != null ? () => onTap!(user) : null, + onLongPress: onLongPress != null ? () => onLongPress!(user) : null, child: Stack( children: [ avatar, - if (showOnlineStatus && user.online == true) + if (showOnlineStatus && user.online) Positioned.fill( child: Align( alignment: onlineIndicatorAlignment, diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index cb82e311..258855d8 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/user_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'stream_chat_theme.dart'; @@ -19,8 +19,8 @@ import 'stream_chat_theme.dart'; class UserItem extends StatelessWidget { /// Instantiate a new UserItem const UserItem({ - Key key, - @required this.user, + Key? key, + required this.user, this.onTap, this.onLongPress, this.onImageTap, @@ -29,16 +29,16 @@ class UserItem extends StatelessWidget { }) : super(key: key); /// Function called when tapping this widget - final void Function(User) onTap; + final void Function(User)? onTap; /// Function called when long pressing this widget - final void Function(User) onLongPress; + final void Function(User)? onLongPress; /// User displayed final User user; /// The function called when the image is tapped - final void Function(User) onImageTap; + final void Function(User)? onImageTap; /// If true the [UserItem] will show a trailing checkmark final bool selected; @@ -51,12 +51,12 @@ class UserItem extends StatelessWidget { return ListTile( onTap: () { if (onTap != null) { - onTap(user); + onTap!(user); } }, onLongPress: () { if (onLongPress != null) { - onLongPress(user); + onLongPress!(user); } }, leading: UserAvatar( @@ -64,7 +64,7 @@ class UserItem extends StatelessWidget { showOnlineStatus: true, onTap: (user) { if (onImageTap != null) { - onImageTap(user); + onImageTap!(user); } }, constraints: BoxConstraints.tightFor( diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index c300971d..2f0e13b9 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -5,7 +5,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'user_item.dart'; /// Callback called when tapping on a user -typedef UserTapCallback = void Function(User, Widget); +typedef UserTapCallback = void Function(User, Widget?); /// Builder used to create a custom [ListUserItem] from a [User] typedef UserItemBuilder = Widget Function(BuildContext, User, bool); @@ -44,7 +44,7 @@ typedef UserItemBuilder = Widget Function(BuildContext, User, bool); class UserListView extends StatefulWidget { /// Instantiate a new UserListView const UserListView({ - Key key, + Key? key, this.filter, this.options, this.sort, @@ -72,51 +72,51 @@ class UserListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - final Filter filter; + final Filter? filter; /// Query channels options. /// /// state: if true returns the Channel state /// watch: if true listen to changes to this Channel in real time. - final Map options; + final Map? options; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be provided. /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. /// Direction can be ascending or descending. - final List sort; + final List? sort; /// Pagination parameters /// limit: the number of users to return (max is 30) /// offset: the offset (max is 1000) /// message_limit: how many messages should be included to each channel - final PaginationParams pagination; + final PaginationParams? pagination; /// Function called when tapping on a channel /// By default it calls [Navigator.push] building a [MaterialPageRoute] /// with the widget [userWidget] as child. - final UserTapCallback onUserTap; + final UserTapCallback? onUserTap; /// Function called when long pressing on a channel - final Function(User) onUserLongPress; + final Function(User)? onUserLongPress; /// Widget used when opening a channel - final Widget userWidget; + final Widget? userWidget; /// Builder used to create a custom user preview - final UserItemBuilder userItemBuilder; + final UserItemBuilder? userItemBuilder; /// Builder used to create a custom item separator - final Function(BuildContext, int) separatorBuilder; + final Function(BuildContext, int)? separatorBuilder; /// The function called when the image is tapped - final Function(User) onImageTap; + final Function(User)? onImageTap; /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; /// Sets a blue trailing checkMark in [ListUserItem] for all the [selectedUsers] - final Set selectedUsers; + final Set? selectedUsers; /// Set it to true to group users by their first character /// @@ -127,16 +127,17 @@ class UserListView extends StatefulWidget { final int crossAxisCount; /// The builder that will be used in case of error - final Widget Function(Error error) errorBuilder; + final Widget Function(Error error)? errorBuilder; /// The builder that will be used to build the list - final Widget Function(BuildContext context, List users) listBuilder; + final Widget Function(BuildContext context, List users)? + listBuilder; /// The builder that will be used for loading - final WidgetBuilder loadingBuilder; + final WidgetBuilder? loadingBuilder; /// The builder used when the channel list is empty. - final WidgetBuilder emptyBuilder; + final WidgetBuilder? emptyBuilder; @override _UserListViewState createState() => _UserListViewState(); @@ -151,9 +152,9 @@ class _UserListViewState extends State @override Widget build(BuildContext context) { var child = UserListCore( - errorBuilder: widget.errorBuilder ?? + errorBuilder: widget.errorBuilder as Widget Function(Object)? ?? (err) { - return _buildError(err); + return _buildError(err as Error); }, emptyBuilder: widget.emptyBuilder ?? (context) { @@ -193,7 +194,7 @@ class _UserListViewState extends State return child; } else { return RefreshIndicator( - onRefresh: () => _userListController.loadData(), + onRefresh: () => _userListController.loadData!(), child: child, ); } @@ -241,7 +242,7 @@ class _UserListViewState extends State child: Text(message), ), TextButton( - onPressed: () => _userListController.loadData(), + onPressed: () => _userListController.loadData!(), child: Text('Retry'), ), ], @@ -276,7 +277,7 @@ class _UserListViewState extends State itemCount: items.isNotEmpty ? items.length + 1 : items.length, separatorBuilder: (_, index) { if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); + return widget.separatorBuilder!(context, index); } return _separatorBuilder(context, index); }, @@ -296,7 +297,7 @@ class _UserListViewState extends State ); return LazyLoadScrollView( - onEndOfPage: () => _userListController.paginateData(), + onEndOfPage: () => _userListController.paginateData!(), child: child, ); } @@ -329,10 +330,10 @@ class _UserListViewState extends State return Container( key: ValueKey('USER-${user.id}'), child: widget.userItemBuilder != null - ? widget.userItemBuilder(context, user, selected) + ? widget.userItemBuilder!(context, user, selected) : UserItem( user: user, - onTap: (user) => widget.onUserTap(user, widget.userWidget), + onTap: (user) => widget.onUserTap!(user, widget.userWidget), onLongPress: widget.onUserLongPress, onImageTap: widget.onImageTap, selected: selected, @@ -356,7 +357,7 @@ class _UserListViewState extends State return Container( key: ValueKey('USER-${user.id}'), child: widget.userItemBuilder != null - ? widget.userItemBuilder(context, user, selected) + ? widget.userItemBuilder!(context, user, selected) : Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -374,7 +375,7 @@ class _UserListViewState extends State width: 12, ), onTap: (user) => - widget.onUserTap(user, widget.userWidget), + widget.onUserTap!(user, widget.userWidget), onLongPress: widget.onUserLongPress, ), SizedBox(height: 4), @@ -424,7 +425,7 @@ class _UserListViewState extends State height: 100, padding: EdgeInsets.all(32), child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), + child: snapshot.data! ? CircularProgressIndicator() : Container(), ), ); }); diff --git a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart index af2797ca..492baef5 100644 --- a/packages/stream_chat_flutter/lib/src/user_reaction_display.dart +++ b/packages/stream_chat_flutter/lib/src/user_reaction_display.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class UserReactionDisplay extends StatelessWidget { const UserReactionDisplay({ - Key key, - @required this.reactionToEmoji, - @required this.message, + Key? key, + required this.reactionToEmoji, + required this.message, this.size = 30, }) : super(key: key); @@ -23,12 +23,13 @@ class UserReactionDisplay extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: reactionToEmoji.keys.map((reactionType) { - var firstUserReaction = message.latestReactions.firstWhere( - (element) => element.type == reactionType, orElse: () { - return null; - }); + var firstUserReaction = message.latestReactions! + .firstWhere((element) => element.type == reactionType, + orElse: () { + return null; + } as Reaction Function()?); - if (firstUserReaction == null) { + if (firstUserReaction.user == null) { return IconButton( iconSize: size, icon: Container(), @@ -39,7 +40,7 @@ class UserReactionDisplay extends StatelessWidget { return IconButton( iconSize: size, icon: UserAvatar( - user: firstUserReaction.user, + user: firstUserReaction.user!, constraints: BoxConstraints( maxHeight: size - 5, maxWidth: size - 5, diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 8950a77c..ebb2a22e 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -7,8 +7,8 @@ import 'package:url_launcher/url_launcher.dart'; import '../stream_chat_flutter.dart'; import 'stream_svg_icon.dart'; -Future launchURL(BuildContext context, String url) async { - if (await canLaunch(url)) { +Future launchURL(BuildContext context, String? url) async { + if (url != null && await canLaunch(url)) { await launch(url); } else { // ignore: deprecated_member_use @@ -20,13 +20,13 @@ Future launchURL(BuildContext context, String url) async { } } -Future showConfirmationDialog( +Future showConfirmationDialog( BuildContext context, { - String title, - Widget icon, - String question, - String okText, - String cancelText, + String? title, + Widget? icon, + String? question, + String? okText, + String? cancelText, }) { return showModalBottomSheet( backgroundColor: StreamChatTheme.of(context).colorTheme.white, @@ -46,17 +46,17 @@ Future showConfirmationDialog( if (icon != null) icon, SizedBox(height: 26.0), Text( - title, + title!, style: StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox(height: 7.0), Text( - question, + question!, textAlign: TextAlign.center, ), SizedBox(height: 36.0), Container( - color: effect.color.withOpacity(effect.alpha ?? 1), + color: effect.color!.withOpacity(effect.alpha ?? 1), height: 1, ), Row( @@ -69,7 +69,7 @@ Future showConfirmationDialog( Navigator.of(context).pop(false); }, child: Text( - cancelText, + cancelText!, style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -90,7 +90,7 @@ Future showConfirmationDialog( Navigator.pop(context, true); }, child: Text( - okText, + okText!, style: StreamChatTheme.of(context) .textTheme .bodyBold @@ -110,17 +110,17 @@ Future showConfirmationDialog( }); } -Future showInfoDialog( +Future showInfoDialog( BuildContext context, { - String title, - Widget icon, - String details, - String okText, - StreamChatThemeData theme, + String? title, + Widget? icon, + String? details, + String? okText, + StreamChatThemeData? theme, }) { return showModalBottomSheet( - backgroundColor: theme?.colorTheme?.white ?? - StreamChatTheme.of(context).colorTheme.white, + backgroundColor: + theme?.colorTheme.white ?? StreamChatTheme.of(context).colorTheme.white, context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -140,19 +140,19 @@ Future showInfoDialog( height: 26.0, ), Text( - title, - style: theme?.textTheme?.headlineBold ?? + title!, + style: theme?.textTheme.headlineBold ?? StreamChatTheme.of(context).textTheme.headlineBold, ), SizedBox( height: 7.0, ), - Text(details), + Text(details!), SizedBox( height: 36.0, ), Container( - color: theme?.colorTheme?.black?.withOpacity(.08) ?? + color: theme?.colorTheme.black.withOpacity(.08) ?? StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), height: 1.0, ), @@ -162,9 +162,9 @@ Future showInfoDialog( Navigator.of(context).pop(); }, child: Text( - okText, + okText!, style: TextStyle( - color: theme?.colorTheme?.black?.withOpacity(0.5) ?? + color: theme?.colorTheme.black.withOpacity(0.5) ?? StreamChatTheme.of(context).colorTheme.accentBlue, fontWeight: FontWeight.w400, ), @@ -183,7 +183,7 @@ String getRandomPicUrl(User user) => 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; /// Get websiteName from [hostName] -String getWebsiteName(String hostName) { +String? getWebsiteName(String hostName) { switch (hostName) { case 'reddit': return 'Reddit'; @@ -292,62 +292,44 @@ String fileSize(dynamic size, [int round = 2]) { } /// -StreamSvgIcon getFileTypeImage(String type) { +StreamSvgIcon getFileTypeImage(String? type) { switch (type) { case '7z': return StreamSvgIcon.filetype7z(); - break; case 'csv': return StreamSvgIcon.filetypeCsv(); - break; case 'doc': return StreamSvgIcon.filetypeDoc(); - break; case 'docx': return StreamSvgIcon.filetypeDocx(); - break; case 'html': return StreamSvgIcon.filetypeHtml(); - break; case 'md': return StreamSvgIcon.filetypeMd(); - break; case 'odt': return StreamSvgIcon.filetypeOdt(); - break; case 'pdf': return StreamSvgIcon.filetypePdf(); - break; case 'ppt': return StreamSvgIcon.filetypePpt(); - break; case 'pptx': return StreamSvgIcon.filetypePptx(); - break; case 'rar': return StreamSvgIcon.filetypeRar(); - break; case 'rtf': return StreamSvgIcon.filetypeRtf(); - break; case 'tar': return StreamSvgIcon.filetypeTar(); - break; case 'txt': return StreamSvgIcon.filetypeTxt(); - break; case 'xls': return StreamSvgIcon.filetypeXls(); - break; case 'xlsx': return StreamSvgIcon.filetypeXlsx(); - break; case 'zip': return StreamSvgIcon.filetypeZip(); - break; default: return StreamSvgIcon.filetypeGeneric(); - break; } } diff --git a/packages/stream_chat_flutter/lib/src/video_service.dart b/packages/stream_chat_flutter/lib/src/video_service.dart index 30d06c55..a4d0d1ff 100644 --- a/packages/stream_chat_flutter/lib/src/video_service.dart +++ b/packages/stream_chat_flutter/lib/src/video_service.dart @@ -4,7 +4,6 @@ import 'dart:typed_data'; import 'package:synchronized/synchronized.dart'; import 'package:video_compress/video_compress.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; -import 'package:meta/meta.dart'; class IVideoService { static final IVideoService instance = IVideoService._(); @@ -27,10 +26,10 @@ class IVideoService { /// ); /// debugPrint(info.toJson()); /// ``` - Future compressVideo(String path) async { + Future compressVideo(String? path) async { return _lock.synchronized(() { return VideoCompress.compressVideo( - path, + path!, ); }); } @@ -39,8 +38,8 @@ class IVideoService { /// The video can be a local video file, or an URL repreents iOS or Android native supported video format. /// Speicify the maximum height or width for the thumbnail or 0 for same resolution as the original video. /// The lower quality value creates lower quality of the thumbnail image, but it gets ignored for PNG format. - Future generateVideoThumbnail({ - @required String video, + Future generateVideoThumbnail({ + required String video, ImageFormat imageFormat = ImageFormat.PNG, int maxHeight = 0, int maxWidth = 0, diff --git a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart index 80607b8e..df8373eb 100644 --- a/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video_thumbnail_image.dart @@ -9,17 +9,17 @@ import 'stream_svg_icon.dart'; import 'video_service.dart'; class VideoThumbnailImage extends StatefulWidget { - final String video; - final double width; - final double height; - final BoxFit fit; + final String? video; + final double? width; + final double? height; + final BoxFit? fit; final ImageFormat format; - final Widget Function(BuildContext, Object) errorBuilder; - final WidgetBuilder placeholderBuilder; + final Widget Function(BuildContext, Object?)? errorBuilder; + final WidgetBuilder? placeholderBuilder; const VideoThumbnailImage({ - Key key, - @required this.video, + Key? key, + required this.video, this.width, this.height, this.fit, @@ -33,12 +33,12 @@ class VideoThumbnailImage extends StatefulWidget { } class _VideoThumbnailImageState extends State { - Future thumbnailFuture; + late Future thumbnailFuture; @override void initState() { thumbnailFuture = VideoService.generateVideoThumbnail( - video: widget.video, + video: widget.video!, imageFormat: widget.format, ); super.initState(); @@ -48,7 +48,7 @@ class _VideoThumbnailImageState extends State { void didUpdateWidget(covariant VideoThumbnailImage oldWidget) { if (oldWidget.video != widget.video || oldWidget.format != widget.format) { thumbnailFuture = VideoService.generateVideoThumbnail( - video: widget.video, + video: widget.video!, imageFormat: widget.format, ); } @@ -57,13 +57,13 @@ class _VideoThumbnailImageState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( + return FutureBuilder( future: thumbnailFuture, builder: (context, snapshot) { return AnimatedSwitcher( duration: const Duration(milliseconds: 350), child: Builder( - key: ValueKey>(snapshot), + key: ValueKey>(snapshot), builder: (_) { if (snapshot.hasError) { return widget.errorBuilder?.call(context, snapshot.error) ?? @@ -90,7 +90,7 @@ class _VideoThumbnailImageState extends State { ); } return Image.memory( - snapshot.data, + snapshot.data!, fit: widget.fit, height: widget.height, width: widget.width, diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 4114051e..942df52d 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -8,7 +8,7 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues publish_to: none environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: @@ -19,12 +19,12 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 jiffy: ^4.1.0 flutter_svg: ^0.21.0+1 - flutter_portal: ^0.4.0-nullsafety.0 + flutter_portal: ^0.4.0 cached_network_image: ^3.0.0 shimmer: ^2.0.0-nullsafety.0 flutter_markdown: ^0.6.1 url_launcher: ^6.0.3 - emojis: + emojis: git: git@github.com:Parkar99/emojis.git video_player: ^2.1.1 chewie: ^1.0.0 @@ -36,7 +36,7 @@ dependencies: http_parser: ^4.0.0 meta: ^1.3.0 lottie: ^1.0.1 - substring_highlight: + substring_highlight: git: git@github.com:ArturAntin/substring_highlight.git flutter_slidable: ^0.6.0-nullsafety.0 image_gallery_saver: ^1.6.9 @@ -48,6 +48,7 @@ dependencies: characters: ^1.1.0 path_provider: ^2.0.1 video_thumbnail: ^0.3.3 + collection: ^1.15.0 dependency_overrides: stream_chat: @@ -66,7 +67,7 @@ flutter: dev_dependencies: flutter_test: sdk: flutter - mockito: ^5.0.3 + mocktail: ^0.1.2 pedantic: ^1.11.0 golden_toolkit: ^0.9.0 diff --git a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart index 1ef02cd9..361d7cfc 100644 --- a/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_actions_modal_test.dart @@ -2,19 +2,19 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; class MockAttachmentDownloader extends Mock { - ProgressCallback progressCallback; + ProgressCallback? progressCallback; Completer completer = Completer(); Future call( Attachment attachment, { - ProgressCallback progressCallback, + ProgressCallback? progressCallback, }) { this.progressCallback = progressCallback; return completer.future; @@ -22,17 +22,22 @@ class MockAttachmentDownloader extends Mock { } void main() { + setUpAll(() { + registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox())); + registerFallbackValue(Message()); + }); + testWidgets( 'it should show all the actions', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -70,11 +75,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id2')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id2')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -112,11 +117,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -153,11 +158,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final mockObserver = MockNavigatorObserver(); @@ -190,7 +195,7 @@ void main() { ), ); await tester.tap(find.text('Reply')); - verify(mockObserver.didPop(any, any)); + verify(() => mockObserver.didPop(any(), any())); }, ); @@ -200,11 +205,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final onShowMessage = MockVoidCallback(); await tester.pumpWidget( @@ -234,7 +239,7 @@ void main() { ), ); await tester.tap(find.text('Show in Chat')); - verify(onShowMessage.call()).called(1); + verify(() => onShowMessage.call()).called(1); }, ); @@ -245,11 +250,11 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(mockChannel.updateMessage(any)).thenAnswer((_) { - return; + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async { + return UpdateMessageResponse(); }); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final message = Message( text: 'test', @@ -287,11 +292,11 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(mockChannel.updateMessage(message.copyWith( - attachments: [ - message.attachments[1], - ], - ))).called(1); + verify(() => mockChannel.updateMessage(message.copyWith( + attachments: [ + message.attachments[1], + ], + ))).called(1); }, ); @@ -302,11 +307,11 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(mockChannel.updateMessage(any)).thenAnswer((_) { - return; + when(() => mockChannel.updateMessage(any())).thenAnswer((_) async { + return UpdateMessageResponse(); }); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final message = Message( text: 'test', @@ -340,9 +345,9 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(mockChannel.updateMessage(message.copyWith( - attachments: [], - ))).called(1); + verify(() => mockChannel.updateMessage(message.copyWith( + attachments: [], + ))).called(1); }, ); @@ -353,11 +358,11 @@ void main() { final clientState = MockClientState(); final mockChannel = MockChannel(); - when(mockChannel.deleteMessage(any)).thenAnswer((_) { - return; + when(() => mockChannel.deleteMessage(any())).thenAnswer((_) async { + return EmptyResponse(); }); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final message = Message( user: User( @@ -390,7 +395,7 @@ void main() { ), ); await tester.tap(find.text('Delete')); - verify(mockChannel.deleteMessage(message)).called(1); + verify(() => mockChannel.deleteMessage(message)).called(1); }, ); @@ -400,8 +405,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final imageDownloader = MockAttachmentDownloader(); @@ -435,15 +440,15 @@ void main() { await tester.tap(find.text('Save Image')); - imageDownloader.progressCallback(0, 100); + imageDownloader.progressCallback!(0, 100); await tester.pump(); expect(find.text('0%'), findsOneWidget); - imageDownloader.progressCallback(50, 100); + imageDownloader.progressCallback!(50, 100); await tester.pump(); expect(find.text('50%'), findsOneWidget); - imageDownloader.progressCallback(100, 100); + imageDownloader.progressCallback!(100, 100); imageDownloader.completer.complete('path'); await tester.pump(); expect(find.byKey(Key('completedIcon')), findsOneWidget); @@ -457,8 +462,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final fileDownloader = MockAttachmentDownloader(); @@ -492,15 +497,15 @@ void main() { await tester.tap(find.text('Save Video')); - fileDownloader.progressCallback(0, 100); + fileDownloader.progressCallback!(0, 100); await tester.pump(); expect(find.text('0%'), findsOneWidget); - fileDownloader.progressCallback(50, 100); + fileDownloader.progressCallback!(50, 100); await tester.pump(); expect(find.text('50%'), findsOneWidget); - fileDownloader.progressCallback(100, 100); + fileDownloader.progressCallback!(100, 100); fileDownloader.completer.complete('path'); await tester.pump(); expect(find.byKey(Key('completedIcon')), findsOneWidget); diff --git a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart index b547e80f..b1eb70e2 100644 --- a/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment_widgets_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -12,10 +12,10 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(channel.state).thenReturn(channelState); + when(() => channel.state).thenReturn(channelState); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/back_button_test.dart b/packages/stream_chat_flutter/test/src/back_button_test.dart index 3ee88ed1..a9c5f724 100644 --- a/packages/stream_chat_flutter/test/src/back_button_test.dart +++ b/packages/stream_chat_flutter/test/src/back_button_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -19,7 +19,7 @@ void main() { return Material( child: Center( child: StreamChatTheme( - data: StreamChatThemeData.getDefaultTheme(theme), + data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton(), ), ), @@ -51,7 +51,7 @@ void main() { home: Material( child: Center( child: StreamChatTheme( - data: StreamChatThemeData.getDefaultTheme(theme), + data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton(), ), ), @@ -82,7 +82,7 @@ void main() { return Material( child: Center( child: StreamChatTheme( - data: StreamChatThemeData.getDefaultTheme(theme), + data: StreamChatThemeData.fromTheme(theme), child: StreamBackButton( onPressed: () => customCallbackWasCalled = true, ), @@ -121,7 +121,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); + when(() => client.state).thenReturn(clientState); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(0)); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/channel_header_test.dart b/packages/stream_chat_flutter/test/src/channel_header_test.dart index 1a5cb79d..c284cad5 100644 --- a/packages/stream_chat_flutter/test/src/channel_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_header_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -16,28 +16,33 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), @@ -72,35 +77,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(client.wsConnectionStatusStream) + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -131,36 +139,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(client.wsConnectionStatusStream) + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -194,33 +204,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -263,34 +278,35 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(client.wsConnectionStatusStream) + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget(MaterialApp( @@ -328,33 +344,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); var backPressed = false; var imageTapped = false; diff --git a/packages/stream_chat_flutter/test/src/channel_image_test.dart b/packages/stream_chat_flutter/test/src/channel_image_test.dart index cea48d2f..1068bb52 100644 --- a/packages/stream_chat_flutter/test/src/channel_image_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_image_test.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/group_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -16,15 +16,15 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', 'image': 'imagetest', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', 'image': 'imagetest', }); @@ -55,17 +55,17 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), @@ -80,7 +80,7 @@ void main() { ), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id2', user: User( @@ -95,7 +95,7 @@ void main() { user: User(id: 'user-id'), ) ]); - when(clientState.usersStream).thenAnswer((i) => Stream.value({ + when(() => clientState.usersStream).thenAnswer((i) => Stream.value({ 'user-id2': User( id: 'user-id2', extraData: { @@ -103,7 +103,7 @@ void main() { }, ), })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); @@ -133,17 +133,17 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User( @@ -172,7 +172,7 @@ void main() { ), ), ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User( @@ -230,15 +230,15 @@ void main() { final channel = MockChannel(); final channelState = MockChannelState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', 'image': 'imagetest', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', 'image': 'imagetest', }); diff --git a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart index 6fc4d428..f77ea04a 100644 --- a/packages/stream_chat_flutter/test/src/channel_list_header_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_list_header_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -12,9 +12,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connected)); await tester.pumpWidget( @@ -42,9 +42,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.disconnected)); await tester.pumpWidget( @@ -71,9 +71,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( @@ -100,9 +100,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); await tester.pumpWidget( @@ -140,9 +140,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = false; @@ -174,9 +174,9 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(client.wsConnectionStatusStream) + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.wsConnectionStatusStream) .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); var tapped = 0; diff --git a/packages/stream_chat_flutter/test/src/channel_name_test.dart b/packages/stream_chat_flutter/test/src/channel_name_test.dart index bf6f7e55..586a0776 100644 --- a/packages/stream_chat_flutter/test/src/channel_name_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_name_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,40 +15,41 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), diff --git a/packages/stream_chat_flutter/test/src/channel_preview_test.dart b/packages/stream_chat_flutter/test/src/channel_preview_test.dart index d8ed9f72..b913489e 100644 --- a/packages/stream_chat_flutter/test/src/channel_preview_test.dart +++ b/packages/stream_chat_flutter/test/src/channel_preview_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,50 +15,55 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(channel.cid).thenReturn('cid'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => channel.cid).thenReturn('cid'); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test name', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test name', }); - when(clientState.channels).thenReturn({ - channel.cid: channel, + when(() => clientState.channels).thenReturn({ + channel.cid!: channel, }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); + when(() => channelState.typingEvents).thenReturn([]); + when(() => channelState.typingEventsStream) + .thenAnswer((_) => Stream.value([])); + await tester.pumpWidget(MaterialApp( home: StreamChat( client: client, diff --git a/packages/stream_chat_flutter/test/src/date_divider_test.dart b/packages/stream_chat_flutter/test/src/date_divider_test.dart index 4856a0d3..eb5e9b70 100644 --- a/packages/stream_chat_flutter/test/src/date_divider_test.dart +++ b/packages/stream_chat_flutter/test/src/date_divider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -12,8 +12,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/deleted_message_test.dart b/packages/stream_chat_flutter/test/src/deleted_message_test.dart index bb93b28b..854c2492 100644 --- a/packages/stream_chat_flutter/test/src/deleted_message_test.dart +++ b/packages/stream_chat_flutter/test/src/deleted_message_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -13,8 +13,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -45,21 +45,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.light(); @@ -98,21 +97,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.dark(); @@ -151,21 +149,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); final materialTheme = ThemeData.light(); diff --git a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart index 55adf7d3..81c342eb 100644 --- a/packages/stream_chat_flutter/test/src/full_screen_media_test.dart +++ b/packages/stream_chat_flutter/test/src/full_screen_media_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -16,51 +16,52 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); - when(channelState.typingEvents).thenAnswer((i) => [ + when(() => channelState.typingEvents).thenAnswer((i) => [ User(id: 'other-user', extraData: {'name': 'demo'}) ]); - when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + when(() => channelState.typingEventsStream) + .thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -78,7 +79,6 @@ void main() { message: Message( createdAt: DateTime.now(), ), - sentAt: DateTime.now(), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png index a05377cb..ea03e3e6 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png index 9ee602b3..cb7e4894 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png index bd4f4710..8caefa4f 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png differ diff --git a/packages/stream_chat_flutter/test/src/image_footer_test.dart b/packages/stream_chat_flutter/test/src/image_footer_test.dart index 2fe72aaa..e2809d11 100644 --- a/packages/stream_chat_flutter/test/src/image_footer_test.dart +++ b/packages/stream_chat_flutter/test/src/image_footer_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,17 +15,17 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); @@ -37,7 +37,9 @@ void main() { child: WillPopScope( onWillPop: () async => false, child: Scaffold( - body: ImageFooter(), + body: ImageFooter( + message: Message(), + ), ), ), ), diff --git a/packages/stream_chat_flutter/test/src/info_tile_test.dart b/packages/stream_chat_flutter/test/src/info_tile_test.dart index d304d48e..091c64c7 100644 --- a/packages/stream_chat_flutter/test/src/info_tile_test.dart +++ b/packages/stream_chat_flutter/test/src/info_tile_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -13,8 +13,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -43,8 +43,8 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index c744a6bb..8a6e8c33 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -1,23 +1,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; void main() { + setUpAll(() { + registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox())); + registerFallbackValue(Message()); + }); + testWidgets( 'it should show the all actions', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -55,11 +60,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( theme: themeData, @@ -102,11 +107,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; await tester.pumpWidget( @@ -156,11 +161,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; @@ -173,7 +178,7 @@ void main() { child: Container( child: MessageActionsModal( onReplyTap: (m) { - return tapped = true; + tapped = true; }, message: Message( text: 'test', @@ -201,11 +206,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; @@ -218,7 +223,7 @@ void main() { child: Container( child: MessageActionsModal( onThreadReplyTap: (m) { - return tapped = true; + tapped = true; }, message: Message( text: 'test', @@ -247,12 +252,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -298,12 +302,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -352,12 +355,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); var tapped = false; @@ -404,12 +406,13 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.sendMessage(any())) + .thenAnswer((_) async => SendMessageResponse()); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -443,7 +446,7 @@ void main() { await tester.tap(find.text('Resend')); - verify(channel.sendMessage(any)).called(1); + verify(() => channel.sendMessage(any())).called(1); }, ); @@ -454,12 +457,13 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.updateMessage(any())) + .thenAnswer((_) async => UpdateMessageResponse()); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -493,7 +497,7 @@ void main() { await tester.tap(find.text('Resend Edited Message')); - verify(channel.updateMessage(any)).called(1); + verify(() => channel.updateMessage(any())).called(1); }, ); @@ -504,12 +508,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -549,7 +552,7 @@ void main() { await tester.tap(find.text('FLAG')); await tester.pumpAndSettle(); - verify(client.flagMessage('testid')).called(1); + verify(() => client.flagMessage('testid')).called(1); }, ); @@ -560,16 +563,15 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(client.flagMessage(any)).thenThrow(ApiError( + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.flagMessage(any())).thenThrow(ApiError( '{}', 500, )); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -620,16 +622,15 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(client.flagMessage(any)).thenThrow(ApiError( + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.flagMessage(any())).thenThrow(ApiError( '{"code":4}', 400, )); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -680,12 +681,11 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( @@ -725,7 +725,7 @@ void main() { await tester.tap(find.text('DELETE')); await tester.pumpAndSettle(); - verify(channel.deleteMessage(any)).called(1); + verify(() => channel.deleteMessage(any())).called(1); }, ); @@ -736,16 +736,15 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.deleteMessage(any)).thenThrow(ApiError( + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.deleteMessage(any())).thenThrow(ApiError( '{}', 500, )); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidget( MaterialApp( diff --git a/packages/stream_chat_flutter/test/src/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input_test.dart index 38e80565..39e7ae3f 100644 --- a/packages/stream_chat_flutter/test/src/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,51 +15,52 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); - when(channelState.typingEvents).thenAnswer((i) => [ + when(() => channelState.typingEvents).thenAnswer((i) => [ User(id: 'other-user', extraData: {'name': 'demo'}) ]); - when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + when(() => channelState.typingEventsStream) + .thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart index 8745cb45..e1136ac5 100644 --- a/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -15,10 +15,10 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final message = Message( id: 'test', @@ -75,10 +75,10 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); final message = Message( id: 'test', diff --git a/packages/stream_chat_flutter/test/src/message_text_test.dart b/packages/stream_chat_flutter/test/src/message_text_test.dart index 1d2ac8be..a8aba86a 100644 --- a/packages/stream_chat_flutter/test/src/message_text_test.dart +++ b/packages/stream_chat_flutter/test/src/message_text_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -16,19 +16,19 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + final streamTheme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); diff --git a/packages/stream_chat_flutter/test/src/mocks.dart b/packages/stream_chat_flutter/test/src/mocks.dart index 50f7ee6a..842c1c2f 100644 --- a/packages/stream_chat_flutter/test/src/mocks.dart +++ b/packages/stream_chat_flutter/test/src/mocks.dart @@ -1,12 +1,20 @@ import 'package:flutter/material.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class MockClient extends Mock implements StreamChatClient {} class MockClientState extends Mock implements ClientState {} -class MockChannel extends Mock implements Channel {} +class MockChannel extends Mock implements Channel { + @override + Future get initialized async => true; + + @override + Future keyStroke([String? parentId]) async { + return; + } +} class MockChannelState extends Mock implements ChannelClientState {} diff --git a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart index d4a3b6a6..fd5d31ce 100644 --- a/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart +++ b/packages/stream_chat_flutter/test/src/reaction_bubble_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -39,10 +39,10 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData.light(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); await tester.pumpWidgetBuilder( StreamChat( client: client, @@ -55,9 +55,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -73,15 +73,15 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( color: Colors.black, child: ReactionBubble( @@ -91,9 +91,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -109,15 +109,15 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final themeData = ThemeData.light(); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( color: Colors.black, child: ReactionBubble( @@ -135,9 +135,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -153,15 +153,15 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final themeData = ThemeData.dark(); - final theme = StreamChatThemeData.getDefaultTheme(themeData); + final theme = StreamChatThemeData.fromTheme(themeData); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( color: Colors.black, child: ReactionBubble( @@ -179,9 +179,9 @@ void main() { user: User(id: 'test'), ), ], - borderColor: theme.ownMessageTheme.reactionsBorderColor, - backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, - maskColor: theme.ownMessageTheme.reactionsMaskColor, + borderColor: theme.ownMessageTheme.reactionsBorderColor!, + backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!, + maskColor: theme.ownMessageTheme.reactionsMaskColor!, ), ), ), @@ -198,13 +198,13 @@ void main() { final clientState = MockClientState(); final themeData = ThemeData(); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); await tester.pumpWidgetBuilder( StreamChat( client: client, - streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), + streamChatThemeData: StreamChatThemeData.fromTheme(themeData), child: Container( child: ReactionBubble( reactions: [ diff --git a/packages/stream_chat_flutter/test/src/simple_frame.dart b/packages/stream_chat_flutter/test/src/simple_frame.dart index c472df85..18d7ae55 100644 --- a/packages/stream_chat_flutter/test/src/simple_frame.dart +++ b/packages/stream_chat_flutter/test/src/simple_frame.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class SimpleFrame extends StatelessWidget { final Widget child; - const SimpleFrame({Key key, @required this.child}) : super(key: key); + const SimpleFrame({Key? key, required this.child}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/stream_chat_flutter/test/src/system_message_test.dart b/packages/stream_chat_flutter/test/src/system_message_test.dart index 04967c39..53741864 100644 --- a/packages/stream_chat_flutter/test/src/system_message_test.dart +++ b/packages/stream_chat_flutter/test/src/system_message_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:golden_toolkit/golden_toolkit.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -16,20 +16,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); var tapped = false; @@ -67,21 +67,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); await tester.pumpWidgetBuilder( @@ -119,21 +118,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); await tester.pumpWidgetBuilder( diff --git a/packages/stream_chat_flutter/test/src/thread_header_test.dart b/packages/stream_chat_flutter/test/src/thread_header_test.dart index 25987e31..9e64abb7 100644 --- a/packages/stream_chat_flutter/test/src/thread_header_test.dart +++ b/packages/stream_chat_flutter/test/src/thread_header_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,34 +15,38 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); + when(() => client.wsConnectionStatusStream) + .thenAnswer((_) => Stream.value(ConnectionStatus.connecting)); + when(() => clientState.totalUnreadCountStream) + .thenAnswer((i) => Stream.value(1)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -75,29 +79,29 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.initialized).thenAnswer((_) => Future.value(true)); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.unreadCount).thenReturn(1); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.unreadCount).thenReturn(1); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(1)); + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), diff --git a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart index bbdbeb3e..8698de91 100644 --- a/packages/stream_chat_flutter/test/src/typing_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/typing_indicator_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,51 +15,52 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.isMuted).thenReturn(false); - when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(channelState.membersStream).thenAnswer((i) => Stream.value([ + when(() => channelState.membersStream).thenAnswer((i) => Stream.value([ Member( userId: 'user-id', user: User(id: 'user-id'), ) ])); - when(channelState.members).thenReturn([ + when(() => channelState.members).thenReturn([ Member( userId: 'user-id', user: User(id: 'user-id'), ), ]); - when(channelState.messages).thenReturn([ + when(() => channelState.messages).thenReturn([ Message( text: 'hello', user: User(id: 'other-user'), ) ]); - when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([ Message( text: 'hello', user: User(id: 'other-user'), ) ])); - when(channelState.typingEvents).thenAnswer((i) => [ + when(() => channelState.typingEvents).thenAnswer((i) => [ User(id: 'other-user', extraData: {'name': 'demo'}) ]); - when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ - User(id: 'other-user', extraData: {'name': 'demo'}), - User(id: 'other-user', extraData: {'name': 'demo'}), - ])); + when(() => channelState.typingEventsStream) + .thenAnswer((i) => Stream.value([ + User(id: 'other-user', extraData: {'name': 'demo'}), + User(id: 'other-user', extraData: {'name': 'demo'}), + ])); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart index 87018263..9b1b5e1e 100644 --- a/packages/stream_chat_flutter/test/src/unread_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/unread_indicator_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'mocks.dart'; @@ -15,20 +15,20 @@ void main() { final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({ 'name': 'test', })); - when(channel.extraData).thenReturn({ + when(() => channel.extraData).thenReturn({ 'name': 'test', }); - when(clientState.totalUnreadCount).thenReturn(10); - when(clientState.totalUnreadCountStream) + when(() => clientState.totalUnreadCount).thenReturn(10); + when(() => clientState.totalUnreadCountStream) .thenAnswer((i) => Stream.value(10)); await tester.pumpWidget(MaterialApp( @@ -54,19 +54,20 @@ void main() { final clientState = MockClientState(); final channel = MockChannel(); final channelState = MockChannelState(); - when(channel.cid).thenReturn('cid'); + when(() => channel.cid).thenReturn('cid'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(clientState.channels).thenReturn({ - channel.cid: channel, + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.channels).thenReturn({ + channel.cid!: channel, }); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channelState.unreadCount).thenReturn(0); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(0)); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channelState.unreadCount).thenReturn(0); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(0)); await tester.pumpWidget(MaterialApp( home: StreamChat( @@ -92,20 +93,21 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final channel = MockChannel(); - when(channel.cid).thenReturn('cid'); + when(() => channel.cid).thenReturn('cid'); final channelState = MockChannelState(); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - when(client.state).thenReturn(clientState); - when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - when(clientState.channels).thenReturn({ - channel.cid: channel, + when(() => client.state).thenReturn(clientState); + when(() => clientState.user).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.channels).thenReturn({ + channel.cid!: channel, }); - when(channel.lastMessageAt).thenReturn(lastMessageAt); - when(channel.state).thenReturn(channelState); - when(channel.client).thenReturn(client); - when(channelState.unreadCount).thenReturn(100); - when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(100)); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(() => channelState.unreadCount).thenReturn(100); + when(() => channelState.unreadCountStream) + .thenAnswer((i) => Stream.value(100)); await tester.pumpWidget(MaterialApp( home: StreamChat( diff --git a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart index 5bff63dc..2cacdb91 100644 --- a/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart +++ b/packages/stream_chat_persistence/lib/src/dao/channel_query_dao.dart @@ -6,7 +6,6 @@ import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/entity/channel_queries.dart'; import 'package:stream_chat_persistence/src/entity/channels.dart'; import 'package:stream_chat_persistence/src/entity/users.dart'; - import 'package:stream_chat_persistence/src/mapper/mapper.dart'; part 'channel_query_dao.g.dart'; @@ -93,7 +92,7 @@ class ChannelQueryDao extends DatabaseAccessor final possibleSortingFields = cachedChannels.fold>( ChannelModel.topLevelFields, (previousValue, element) { - final extraData = element.extraData ?? {}; + final extraData = element.extraData; return {...previousValue, ...extraData.keys}.toList(); }); diff --git a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart index b85857bb..8220ee4e 100644 --- a/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/moor_chat_database.g.dart @@ -43,19 +43,21 @@ class ChannelEntity extends DataClass implements Insertable { /// Map of custom channel extraData final Map? extraData; - ChannelEntity( - {required this.id, - required this.type, - required this.cid, - required this.config, - required this.frozen, - this.lastMessageAt, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - required this.memberCount, - this.createdById, - this.extraData}); + + ChannelEntity({ + required this.id, + required this.type, + required this.cid, + required this.config, + required this.frozen, + this.lastMessageAt, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.memberCount, + this.createdById, + this.extraData, + }); factory ChannelEntity.fromData( Map data, GeneratedDatabase db, {String? prefix}) { diff --git a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart index 5c4ec555..71f5b708 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/channel_mapper.dart @@ -17,7 +17,7 @@ extension ChannelEntityX on ChannelEntity { cid: cid, lastMessageAt: lastMessageAt, deletedAt: deletedAt, - extraData: extraData, + extraData: extraData ?? {}, createdBy: createdBy, ); } diff --git a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart index 3f04711d..50ff7a7c 100644 --- a/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/channel_query_dao_test.dart @@ -207,8 +207,8 @@ void main() { test('should return sorted channels using custom field', () async { int sortComparator(ChannelModel a, ChannelModel b) { - final aData = a.extraData!['test_custom_field'] as int; - final bData = b.extraData!['test_custom_field'] as int; + final aData = a.extraData['test_custom_field'] as int; + final bData = b.extraData['test_custom_field'] as int; return bData.compareTo(aData); }