migrate ui

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

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