migrate ui

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

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -15,17 +15,17 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false); when(() => channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({ when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
@@ -37,7 +37,9 @@ void main() {
child: WillPopScope( child: WillPopScope(
onWillPop: () async => false, onWillPop: () async => false,
child: Scaffold( child: Scaffold(
body: ImageFooter(), body: ImageFooter(
message: Message(),
),
), ),
), ),
), ),
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -13,8 +13,8 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -43,8 +43,8 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -1,23 +1,28 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/src/message_actions_modal.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
void main() { void main() {
setUpAll(() {
registerFallbackValue(MaterialPageRoute(builder: (context) => SizedBox()));
registerFallbackValue(Message());
});
testWidgets( testWidgets(
'it should show the all actions', 'it should show the all actions',
(WidgetTester tester) async { (WidgetTester tester) async {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: themeData, theme: themeData,
@@ -55,11 +60,11 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: themeData, theme: themeData,
@@ -102,11 +107,11 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
var tapped = false; var tapped = false;
await tester.pumpWidget( await tester.pumpWidget(
@@ -156,11 +161,11 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
var tapped = false; var tapped = false;
@@ -173,7 +178,7 @@ void main() {
child: Container( child: Container(
child: MessageActionsModal( child: MessageActionsModal(
onReplyTap: (m) { onReplyTap: (m) {
return tapped = true; tapped = true;
}, },
message: Message( message: Message(
text: 'test', text: 'test',
@@ -201,11 +206,11 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
var tapped = false; var tapped = false;
@@ -218,7 +223,7 @@ void main() {
child: Container( child: Container(
child: MessageActionsModal( child: MessageActionsModal(
onThreadReplyTap: (m) { onThreadReplyTap: (m) {
return tapped = true; tapped = true;
}, },
message: Message( message: Message(
text: 'test', text: 'test',
@@ -247,12 +252,11 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -298,12 +302,11 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -352,12 +355,11 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
var tapped = false; var tapped = false;
@@ -404,12 +406,13 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.sendMessage(any()))
.thenAnswer((_) async => SendMessageResponse());
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -443,7 +446,7 @@ void main() {
await tester.tap(find.text('Resend')); 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 clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.updateMessage(any()))
.thenAnswer((_) async => UpdateMessageResponse());
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -493,7 +497,7 @@ void main() {
await tester.tap(find.text('Resend Edited Message')); 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 clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -549,7 +552,7 @@ void main() {
await tester.tap(find.text('FLAG')); await tester.tap(find.text('FLAG'));
await tester.pumpAndSettle(); 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 clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => client.flagMessage(any())).thenThrow(ApiError(
when(client.flagMessage(any)).thenThrow(ApiError(
'{}', '{}',
500, 500,
)); ));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -620,16 +622,15 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => client.flagMessage(any())).thenThrow(ApiError(
when(client.flagMessage(any)).thenThrow(ApiError(
'{"code":4}', '{"code":4}',
400, 400,
)); ));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -680,12 +681,11 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -725,7 +725,7 @@ void main() {
await tester.tap(find.text('DELETE')); await tester.tap(find.text('DELETE'));
await tester.pumpAndSettle(); 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 clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.deleteMessage(any())).thenThrow(ApiError(
when(channel.deleteMessage(any)).thenThrow(ApiError(
'{}', '{}',
500, 500,
)); ));
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -15,51 +15,52 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false); when(() => channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({ when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(channelState.membersStream).thenAnswer((i) => Stream.value([ when(() => channelState.membersStream).thenAnswer((i) => Stream.value([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
) )
])); ]));
when(channelState.members).thenReturn([ when(() => channelState.members).thenReturn([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
), ),
]); ]);
when(channelState.messages).thenReturn([ when(() => channelState.messages).thenReturn([
Message( Message(
text: 'hello', text: 'hello',
user: User(id: 'other-user'), user: User(id: 'other-user'),
) )
]); ]);
when(channelState.messagesStream).thenAnswer((i) => Stream.value([ when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([
Message( Message(
text: 'hello', text: 'hello',
user: User(id: 'other-user'), user: User(id: 'other-user'),
) )
])); ]));
when(channelState.typingEvents).thenAnswer((i) => [ when(() => channelState.typingEvents).thenAnswer((i) => [
User(id: 'other-user', extraData: {'name': 'demo'}) User(id: 'other-user', extraData: {'name': 'demo'})
]); ]);
when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ when(() => channelState.typingEventsStream)
User(id: 'other-user', extraData: {'name': 'demo'}), .thenAnswer((i) => Stream.value([
User(id: 'other-user', extraData: {'name': 'demo'}), User(id: 'other-user', extraData: {'name': 'demo'}),
])); User(id: 'other-user', extraData: {'name': 'demo'}),
]));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/message_reactions_modal.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -15,10 +15,10 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData(); final themeData = ThemeData();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
final message = Message( final message = Message(
id: 'test', id: 'test',
@@ -75,10 +75,10 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData(); final themeData = ThemeData();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
final message = Message( final message = Message(
id: 'test', id: 'test',
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -16,19 +16,19 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
final themeData = ThemeData(); final themeData = ThemeData();
final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); final streamTheme = StreamChatThemeData.fromTheme(themeData);
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false); when(() => channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({ when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
@@ -1,12 +1,20 @@
import 'package:flutter/material.dart'; 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'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
class MockClient extends Mock implements StreamChatClient {} class MockClient extends Mock implements StreamChatClient {}
class MockClientState extends Mock implements ClientState {} class MockClientState extends Mock implements ClientState {}
class MockChannel extends Mock implements Channel {} class MockChannel extends Mock implements Channel {
@override
Future<bool> get initialized async => true;
@override
Future<void> keyStroke([String? parentId]) async {
return;
}
}
class MockChannelState extends Mock implements ChannelClientState {} class MockChannelState extends Mock implements ChannelClientState {}
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.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/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -39,10 +39,10 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData.light(); final themeData = ThemeData.light();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
final theme = StreamChatThemeData.getDefaultTheme(themeData); final theme = StreamChatThemeData.fromTheme(themeData);
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
StreamChat( StreamChat(
client: client, client: client,
@@ -55,9 +55,9 @@ void main() {
user: User(id: 'test'), user: User(id: 'test'),
), ),
], ],
borderColor: theme.ownMessageTheme.reactionsBorderColor, borderColor: theme.ownMessageTheme.reactionsBorderColor!,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!,
maskColor: theme.ownMessageTheme.reactionsMaskColor, maskColor: theme.ownMessageTheme.reactionsMaskColor!,
), ),
), ),
), ),
@@ -73,15 +73,15 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData.dark(); final themeData = ThemeData.dark();
final theme = StreamChatThemeData.getDefaultTheme(themeData); final theme = StreamChatThemeData.fromTheme(themeData);
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
StreamChat( StreamChat(
client: client, client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
child: Container( child: Container(
color: Colors.black, color: Colors.black,
child: ReactionBubble( child: ReactionBubble(
@@ -91,9 +91,9 @@ void main() {
user: User(id: 'test'), user: User(id: 'test'),
), ),
], ],
borderColor: theme.ownMessageTheme.reactionsBorderColor, borderColor: theme.ownMessageTheme.reactionsBorderColor!,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!,
maskColor: theme.ownMessageTheme.reactionsMaskColor, maskColor: theme.ownMessageTheme.reactionsMaskColor!,
), ),
), ),
), ),
@@ -109,15 +109,15 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData.light(); final themeData = ThemeData.light();
final theme = StreamChatThemeData.getDefaultTheme(themeData); final theme = StreamChatThemeData.fromTheme(themeData);
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
StreamChat( StreamChat(
client: client, client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
child: Container( child: Container(
color: Colors.black, color: Colors.black,
child: ReactionBubble( child: ReactionBubble(
@@ -135,9 +135,9 @@ void main() {
user: User(id: 'test'), user: User(id: 'test'),
), ),
], ],
borderColor: theme.ownMessageTheme.reactionsBorderColor, borderColor: theme.ownMessageTheme.reactionsBorderColor!,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!,
maskColor: theme.ownMessageTheme.reactionsMaskColor, maskColor: theme.ownMessageTheme.reactionsMaskColor!,
), ),
), ),
), ),
@@ -153,15 +153,15 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData.dark(); final themeData = ThemeData.dark();
final theme = StreamChatThemeData.getDefaultTheme(themeData); final theme = StreamChatThemeData.fromTheme(themeData);
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
StreamChat( StreamChat(
client: client, client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
child: Container( child: Container(
color: Colors.black, color: Colors.black,
child: ReactionBubble( child: ReactionBubble(
@@ -179,9 +179,9 @@ void main() {
user: User(id: 'test'), user: User(id: 'test'),
), ),
], ],
borderColor: theme.ownMessageTheme.reactionsBorderColor, borderColor: theme.ownMessageTheme.reactionsBorderColor!,
backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor, backgroundColor: theme.ownMessageTheme.reactionsBackgroundColor!,
maskColor: theme.ownMessageTheme.reactionsMaskColor, maskColor: theme.ownMessageTheme.reactionsMaskColor!,
), ),
), ),
), ),
@@ -198,13 +198,13 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final themeData = ThemeData(); final themeData = ThemeData();
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
StreamChat( StreamChat(
client: client, client: client,
streamChatThemeData: StreamChatThemeData.getDefaultTheme(themeData), streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
child: Container( child: Container(
child: ReactionBubble( child: ReactionBubble(
reactions: [ reactions: [
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class SimpleFrame extends StatelessWidget { class SimpleFrame extends StatelessWidget {
final Widget child; final Widget child;
const SimpleFrame({Key key, @required this.child}) : super(key: key); const SimpleFrame({Key? key, required this.child}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -16,20 +16,20 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({ when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(clientState.totalUnreadCount).thenReturn(10); when(() => clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream) when(() => clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10)); .thenAnswer((i) => Stream.value(10));
var tapped = false; var tapped = false;
@@ -67,21 +67,20 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(clientState.totalUnreadCount).thenReturn(10); when(() => clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream) when(() => clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10)); .thenAnswer((i) => Stream.value(10));
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
@@ -119,21 +118,20 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(clientState.totalUnreadCount).thenReturn(10); when(() => clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream) when(() => clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10)); .thenAnswer((i) => Stream.value(10));
await tester.pumpWidgetBuilder( await tester.pumpWidgetBuilder(
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -15,34 +15,38 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.isMuted).thenReturn(false);
when(channel.isMuted).thenReturn(false); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(channelState.unreadCount).thenReturn(1); when(() => channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.unreadCountStream)
when(channelState.membersStream).thenAnswer((i) => Stream.value([ .thenAnswer((i) => Stream.value(1));
when(() => channelState.membersStream).thenAnswer((i) => Stream.value([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
) )
])); ]));
when(channelState.members).thenReturn([ when(() => channelState.members).thenReturn([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: '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( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -75,29 +79,29 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.initialized).thenAnswer((_) => Future.value(true)); when(() => channel.isMuted).thenReturn(false);
when(channel.isMuted).thenReturn(false); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
when(channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(channelState.unreadCount).thenReturn(1); when(() => channelState.unreadCount).thenReturn(1);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); when(() => channelState.unreadCountStream)
when(channelState.membersStream).thenAnswer((i) => Stream.value([ .thenAnswer((i) => Stream.value(1));
when(() => channelState.membersStream).thenAnswer((i) => Stream.value([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
) )
])); ]));
when(channelState.members).thenReturn([ when(() => channelState.members).thenReturn([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -15,51 +15,52 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.isMuted).thenReturn(false); when(() => channel.isMuted).thenReturn(false);
when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); when(() => channel.isMutedStream).thenAnswer((i) => Stream.value(false));
when(channel.extraDataStream).thenAnswer((i) => Stream.value({ when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(channelState.membersStream).thenAnswer((i) => Stream.value([ when(() => channelState.membersStream).thenAnswer((i) => Stream.value([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
) )
])); ]));
when(channelState.members).thenReturn([ when(() => channelState.members).thenReturn([
Member( Member(
userId: 'user-id', userId: 'user-id',
user: User(id: 'user-id'), user: User(id: 'user-id'),
), ),
]); ]);
when(channelState.messages).thenReturn([ when(() => channelState.messages).thenReturn([
Message( Message(
text: 'hello', text: 'hello',
user: User(id: 'other-user'), user: User(id: 'other-user'),
) )
]); ]);
when(channelState.messagesStream).thenAnswer((i) => Stream.value([ when(() => channelState.messagesStream).thenAnswer((i) => Stream.value([
Message( Message(
text: 'hello', text: 'hello',
user: User(id: 'other-user'), user: User(id: 'other-user'),
) )
])); ]));
when(channelState.typingEvents).thenAnswer((i) => [ when(() => channelState.typingEvents).thenAnswer((i) => [
User(id: 'other-user', extraData: {'name': 'demo'}) User(id: 'other-user', extraData: {'name': 'demo'})
]); ]);
when(channelState.typingEventsStream).thenAnswer((i) => Stream.value([ when(() => channelState.typingEventsStream)
User(id: 'other-user', extraData: {'name': 'demo'}), .thenAnswer((i) => Stream.value([
User(id: 'other-user', extraData: {'name': 'demo'}), User(id: 'other-user', extraData: {'name': 'demo'}),
])); User(id: 'other-user', extraData: {'name': 'demo'}),
]));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -15,20 +15,20 @@ void main() {
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channel.extraDataStream).thenAnswer((i) => Stream.value({ when(() => channel.extraDataStream).thenAnswer((i) => Stream.value({
'name': 'test', 'name': 'test',
})); }));
when(channel.extraData).thenReturn({ when(() => channel.extraData).thenReturn({
'name': 'test', 'name': 'test',
}); });
when(clientState.totalUnreadCount).thenReturn(10); when(() => clientState.totalUnreadCount).thenReturn(10);
when(clientState.totalUnreadCountStream) when(() => clientState.totalUnreadCountStream)
.thenAnswer((i) => Stream.value(10)); .thenAnswer((i) => Stream.value(10));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
@@ -54,19 +54,20 @@ void main() {
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
final channelState = MockChannelState(); final channelState = MockChannelState();
when(channel.cid).thenReturn('cid'); when(() => channel.cid).thenReturn('cid');
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(clientState.channels).thenReturn({ when(() => clientState.channels).thenReturn({
channel.cid: channel, channel.cid!: channel,
}); });
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channelState.unreadCount).thenReturn(0); when(() => channelState.unreadCount).thenReturn(0);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(0)); when(() => channelState.unreadCountStream)
.thenAnswer((i) => Stream.value(0));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -92,20 +93,21 @@ void main() {
final client = MockClient(); final client = MockClient();
final clientState = MockClientState(); final clientState = MockClientState();
final channel = MockChannel(); final channel = MockChannel();
when(channel.cid).thenReturn('cid'); when(() => channel.cid).thenReturn('cid');
final channelState = MockChannelState(); final channelState = MockChannelState();
final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); final lastMessageAt = DateTime.parse('2020-06-22 12:00:00');
when(client.state).thenReturn(clientState); when(() => client.state).thenReturn(clientState);
when(clientState.user).thenReturn(OwnUser(id: 'user-id')); when(() => clientState.user).thenReturn(OwnUser(id: 'user-id'));
when(clientState.channels).thenReturn({ when(() => clientState.channels).thenReturn({
channel.cid: channel, channel.cid!: channel,
}); });
when(channel.lastMessageAt).thenReturn(lastMessageAt); when(() => channel.lastMessageAt).thenReturn(lastMessageAt);
when(channel.state).thenReturn(channelState); when(() => channel.state).thenReturn(channelState);
when(channel.client).thenReturn(client); when(() => channel.client).thenReturn(client);
when(channelState.unreadCount).thenReturn(100); when(() => channelState.unreadCount).thenReturn(100);
when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(100)); when(() => channelState.unreadCountStream)
.thenAnswer((i) => Stream.value(100));
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: StreamChat( home: StreamChat(
@@ -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/channel_queries.dart';
import 'package:stream_chat_persistence/src/entity/channels.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/entity/users.dart';
import 'package:stream_chat_persistence/src/mapper/mapper.dart'; import 'package:stream_chat_persistence/src/mapper/mapper.dart';
part 'channel_query_dao.g.dart'; part 'channel_query_dao.g.dart';
@@ -93,7 +92,7 @@ class ChannelQueryDao extends DatabaseAccessor<MoorChatDatabase>
final possibleSortingFields = cachedChannels.fold<List<String>>( final possibleSortingFields = cachedChannels.fold<List<String>>(
ChannelModel.topLevelFields, (previousValue, element) { ChannelModel.topLevelFields, (previousValue, element) {
final extraData = element.extraData ?? {}; final extraData = element.extraData;
return {...previousValue, ...extraData.keys}.toList(); return {...previousValue, ...extraData.keys}.toList();
}); });

Some files were not shown because too many files have changed in this diff Show More