Merge pull request #1584 from GetStream/release/v6.2.0

This commit is contained in:
Sahil Kumar
2023-06-02 15:58:20 +05:30
committed by GitHub
90 changed files with 635 additions and 480 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
name: Dart Code Metrics
env:
flutter_version: "3.7.0"
flutter_version: "3.10.0"
folders: "lib, test"
on:
@@ -2,7 +2,7 @@ name: stream_flutter_workflow
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
flutter_version: "3.7.0"
flutter_version: "3.10.0"
on:
pull_request:
+16
View File
@@ -0,0 +1,16 @@
# Reporting a Vulnerability
At Stream we are committed to the security of our Software. We appreciate your efforts in disclosing vulnerabilities responsibly and we will make every effort to acknowledge your contributions.
Report security vulnerabilities at the following email address:
```
[security@getstream.io](mailto:security@getstream.io)
```
Alternatively it is also possible to open a new issue in the affected repository, tagging it with the `security` tag.
A team member will acknowledge the vulnerability and will follow-up with more detailed information. A representative of the security team will be in touch if more information is needed.
# Information to include in a report
While we appreciate any information that you are willing to provide, please make sure to include the following:
* Which repository is affected
* Which branch, if relevant
* Be as descriptive as possible, the team will replicate the vulnerability before working on a fix.
+2 -2
View File
@@ -26,9 +26,9 @@ scripts:
- Note: you can also rely on your IDEs Dart Analysis / Issues window.
format:
run: flutter format --set-exit-if-changed .
run: dart format --set-exit-if-changed .
description: |
Run `flutter format --set-exit-if-changed .` in all packages.
Run `dart format --set-exit-if-changed .` in all packages.
metrics:
run: |
+11
View File
@@ -1,3 +1,14 @@
## 6.2.0
🐞 Fixed
- [[#1422]](https://github.com/GetStream/stream-chat-flutter/issues/1422) Fixed `User.createdAt` property using
currentTime when the ws connection is not established.
✅ Added
- Added support for `ChatPersistenceClient.isConnected` for checking if the client is connected to the database.
## 6.1.0
🐞 Fixed
@@ -35,8 +35,8 @@ class User extends Equatable {
this.role,
String? name,
String? image,
DateTime? createdAt,
DateTime? updatedAt,
this.createdAt,
this.updatedAt,
this.lastActive,
Map<String, Object?> extraData = const {},
this.online = false,
@@ -44,8 +44,7 @@ class User extends Equatable {
this.banExpires,
this.teams = const [],
this.language,
}) : createdAt = createdAt ?? DateTime.now(),
updatedAt = updatedAt ?? DateTime.now(),
}) :
// For backwards compatibility, set 'name', 'image' in [extraData].
extraData = {
...extraData,
@@ -104,11 +103,11 @@ class User extends Equatable {
/// Date of user creation.
@JsonKey(includeToJson: false)
final DateTime createdAt;
final DateTime? createdAt;
/// Date of last user update.
@JsonKey(includeToJson: false)
final DateTime updatedAt;
final DateTime? updatedAt;
/// Date of last user connection.
@JsonKey(includeToJson: false)
@@ -14,6 +14,9 @@ import 'package:stream_chat/src/core/util/extension.dart';
/// A simple client used for persisting chat data locally.
abstract class ChatPersistenceClient {
/// Whether the connection is established.
bool get isConnected;
/// Creates a new connection to the client
Future<void> connect(String userId);
+1 -1
View File
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
/// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names
const PACKAGE_VERSION = '6.1.0';
const PACKAGE_VERSION = '6.2.0';
+1 -1
View File
@@ -1,7 +1,7 @@
name: stream_chat
homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications.
version: 6.1.0
version: 6.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -198,8 +198,8 @@ void main() {
expect(user.banned, false);
expect(user.teams, []);
expect(user.lastActive, null);
expect(user.createdAt, isNotNull);
expect(user.updatedAt, isNotNull);
expect(user.createdAt, null);
expect(user.updatedAt, null);
});
test('default values, parse json', () {
@@ -214,8 +214,8 @@ void main() {
expect(user.banned, false);
expect(user.teams, []);
expect(user.lastActive, null);
expect(user.createdAt, isNotNull);
expect(user.updatedAt, isNotNull);
expect(user.createdAt, null);
expect(user.updatedAt, null);
});
});
}
@@ -12,6 +12,9 @@ import 'package:stream_chat/src/db/chat_persistence_client.dart';
import 'package:test/test.dart';
class TestPersistenceClient extends ChatPersistenceClient {
@override
bool get isConnected => throw UnimplementedError();
@override
Future<void> connect(String userId) => throw UnimplementedError();
+40
View File
@@ -1,3 +1,43 @@
## 6.2.0
🐞 Fixed
- [[#1546]](https://github.com/GetStream/stream-chat-flutter/issues/1546)
Fixed `StreamMessageInputTheme.linkHighlightColor` returning null for default theme.
- [[#1548]](https://github.com/GetStream/stream-chat-flutter/issues/1548) Fixed `StreamMessageInput` urlRegex only
matching the lowercase `http(s)|ftp`.
- [[#1542]](https://github.com/GetStream/stream-chat-flutter/issues/1542) Handle error thrown in `StreamMessageInput`
when unable to fetch a link preview.
- [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540) Use `CircularProgressIndicator.adaptive`
instead of material indicator.
- [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490) Fixed `editMessageInputBuilder` property not
used in `MessageActionsModal.editMessage` option.
- [[#1544]](https://github.com/GetStream/stream-chat-flutter/issues/1544) Fixed error thrown when unable to fetch
image/data in Message link preview.
- [[#1482]](https://github.com/GetStream/stream-chat-flutter/issues/1482) Fixed `StreaChannelListTile` not showing
unread indicator when `currentUser` is not present in the initial member list.
- [[#1487]](https://github.com/GetStream/stream-chat-flutter/issues/1487) Use localized title
for `WebOrDesktopAttachmentPickerOption` in `StreamMessageInput`.
- [[#1250]](https://github.com/GetStream/stream-chat-flutter/issues/1250) Fixed bottomRow widgetSpans getting resized
twice when `textScaling` is enabled.
- [[#1498]](https://github.com/GetStream/stream-chat-flutter/issues/1498) Fixed `MessageInput` autocomplete not working
on non-mobile platforms.
- [[#1576]](https://github.com/GetStream/stream-chat-flutter/issues/1576) Temporary fix for `StreamMessageListView`
getting broken when loaded at a particular message and a new message is added.
✅ Added
- Added support for `StreamMessageThemeData.urlAttachmentTextMaxLine` to specify the `.maxLines` for the url attachment
text. [#1543](https://github.com/GetStream/stream-chat-flutter/issues/1543)
🔄 Changed
- Updated `shimmer` dependency to `^3.0.0`.
- Updated `image_gallery_saver` dependency to `^2.0.1`.
- Deprecated `ChannelPreview` in favor of `StreamChannelListTile`.
- Updated `stream_chat_flutter_core` dependency
to [`6.2.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
## 6.1.0
🐞 Fixed
+1 -1
View File
@@ -196,7 +196,7 @@ import 'package:stream_chat_persistence/stream_chat_persistence.dart';
final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.INFO,
connectionMode: ConnectionMode.background,
connectionMode: ConnectionMode.regular,
);
final client = StreamChatClient(
@@ -128,12 +128,12 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) => Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => Scaffold(
appBar: const StreamChannelHeader(
builder: (context) => const Scaffold(
appBar: StreamChannelHeader(
showBackButton: false,
),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -91,10 +91,10 @@ class ChannelPage extends StatelessWidget {
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
return Scaffold(
appBar: const StreamChannelHeader(),
return const Scaffold(
appBar: StreamChannelHeader(),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -126,10 +126,10 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const StreamChannelHeader(),
return const Scaffold(
appBar: StreamChannelHeader(),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -168,10 +168,10 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const StreamChannelHeader(),
return const Scaffold(
appBar: StreamChannelHeader(),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -28,7 +28,7 @@ dependencies:
cupertino_icons: ^1.0.4
flutter:
sdk: flutter
responsive_builder: ^0.6.4
responsive_builder: ^0.7.0
stream_chat_flutter:
path: ../
stream_chat_localizations:
@@ -200,7 +200,7 @@ class _FileTypeImage extends StatelessWidget {
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
),
),
@@ -210,7 +210,7 @@ class _FileTypeImage extends StatelessWidget {
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
),
),
@@ -59,6 +59,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
color: StreamChatTheme.of(context).colorTheme.barsBg,
elevation: 2,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16),
@@ -115,7 +116,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
width: constraints?.maxHeight,
height: constraints?.maxWidth,
child: const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
),
imageUrl: imageUrl,
@@ -241,10 +242,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
const SizedBox(height: 4),
const Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: StreamVisibleFootnote(),
),
child: StreamVisibleFootnote(),
),
],
),
@@ -1,5 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// {@template streamUrlAttachment}
@@ -64,14 +65,33 @@ class StreamUrlAttachment extends StatelessWidget {
),
child: Stack(
children: [
CachedNetworkImage(
width: double.infinity,
imageUrl: urlAttachment.imageUrl!,
fit: BoxFit.cover,
AspectRatio(
// Default aspect ratio for Open Graph images.
// https://www.kapwing.com/resources/what-is-an-og-image-make-and-format-og-images-for-your-blog-or-webpage
aspectRatio: 1.91 / 1,
child: CachedNetworkImage(
imageUrl: urlAttachment.imageUrl!,
fit: BoxFit.cover,
placeholder: (context, __) {
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.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
errorWidget: (_, __, ___) => const AttachmentError(),
),
),
Positioned(
left: 0,
bottom: -1,
bottom: 0,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
@@ -83,7 +103,8 @@ class StreamUrlAttachment extends StatelessWidget {
padding: const EdgeInsets.only(
top: 8,
left: 8,
right: 8,
right: 12,
bottom: 4,
),
child: Text(
hostDisplayName,
@@ -99,20 +120,40 @@ class StreamUrlAttachment extends StatelessWidget {
padding: textPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
children: <Widget>[
if (urlAttachment.title != null)
Text(
urlAttachment.title!.trim(),
maxLines: messageTheme.urlAttachmentTitleMaxLine ?? 1,
overflow: TextOverflow.ellipsis,
style: messageTheme.urlAttachmentTitleStyle,
),
Builder(builder: (context) {
final maxLines = messageTheme.urlAttachmentTitleMaxLine;
TextOverflow? overflow;
if (maxLines != null && maxLines > 0) {
overflow = TextOverflow.ellipsis;
}
return Text(
urlAttachment.title!.trim(),
maxLines: maxLines,
overflow: overflow,
style: messageTheme.urlAttachmentTitleStyle,
);
}),
if (urlAttachment.text != null)
Text(
urlAttachment.text!,
style: messageTheme.urlAttachmentTextStyle,
),
],
Builder(builder: (context) {
final maxLines = messageTheme.urlAttachmentTextMaxLine;
TextOverflow? overflow;
if (maxLines != null && maxLines > 0) {
overflow = TextOverflow.ellipsis;
}
return Text(
urlAttachment.text!,
maxLines: maxLines,
overflow: overflow,
style: messageTheme.urlAttachmentTextStyle,
);
}),
].insertBetween(const SizedBox(height: 4)),
),
),
],
@@ -345,9 +345,11 @@ class AttachmentActionsModal extends StatelessWidget {
child: Stack(
fit: StackFit.expand,
children: [
CircularProgressIndicator(
CircularProgressIndicator.adaptive(
strokeWidth: 8,
color: theme.colorTheme.accentPrimary,
valueColor: AlwaysStoppedAnimation<Color>(
theme.colorTheme.accentPrimary,
),
),
Center(
child: Text(
@@ -507,10 +507,12 @@ class _StreamAutocompleteState extends State<StreamAutocomplete> {
final anchor = widget.optionsAlignment._toAnchor();
final shouldShowOptions = _shouldShowOptions;
final optionViewBuilder = shouldShowOptions
? _currentTrigger!.optionsViewBuilder(
context,
_currentQuery!,
_messageEditingController,
? TextFieldTapRegion(
child: _currentTrigger!.optionsViewBuilder(
context,
_currentQuery!,
_messageEditingController,
),
)
: null;
@@ -143,7 +143,7 @@ class _ConnectingTitleState extends StatelessWidget {
height: 16,
width: 16,
child: Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
),
const SizedBox(width: 10),
@@ -244,7 +244,7 @@ class _ConnectingTitleState extends StatelessWidget {
height: 16,
width: 16,
child: Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
),
const SizedBox(width: 10),
@@ -24,6 +24,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// The UI is rendered based on the first ancestor of type [StreamChatTheme].
/// Modify it to change the widget's appearance.
/// {@endtemplate}
@Deprecated('Use StreamChannelListTile instead.')
class ChannelPreview extends StatelessWidget {
/// {@macro channelPreview}
const ChannelPreview({
@@ -337,7 +337,7 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
final controller = videoPackages[attachment.id]!;
if (!controller.initialized) {
return const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
);
}
return InkWell(
@@ -51,7 +51,7 @@ class StreamUploadProgressIndicator extends StatelessWidget {
SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(
child: CircularProgressIndicator.adaptive(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation(progressIndicatorColor),
),
@@ -400,8 +400,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
builder: (context) => EditMessageSheet(
message: widget.message,
channel: channel,
message: widget.message,
editMessageInputBuilder: widget.editMessageInputBuilder,
),
);
}
@@ -792,19 +792,19 @@ Widget webOrDesktopAttachmentPickerBuilder({
key: 'image-picker',
type: AttachmentPickerType.images,
icon: StreamSvgIcon.pictures(size: 36).toIconThemeSvgIcon(),
title: 'Upload a photo',
title: context.translations.uploadAPhotoLabel,
),
WebOrDesktopAttachmentPickerOption(
key: 'video-picker',
type: AttachmentPickerType.videos,
icon: StreamSvgIcon.record(size: 36).toIconThemeSvgIcon(),
title: 'Upload a video',
title: context.translations.uploadAVideoLabel,
),
WebOrDesktopAttachmentPickerOption(
key: 'file-picker',
type: AttachmentPickerType.files,
icon: StreamSvgIcon.files(size: 36).toIconThemeSvgIcon(),
title: 'Upload a file',
title: context.translations.uploadAFileLabel,
),
},
onOptionTap: (context, controller, option) async {
@@ -303,7 +303,7 @@ class _ParseAttachments extends StatelessWidget {
width: size.width,
height: size.height,
child: const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
);
},
@@ -394,7 +394,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> {
width: 32,
child: _controller.value.isInitialized
? VideoPlayer(_controller)
: const CircularProgressIndicator(),
: const CircularProgressIndicator.adaptive(),
);
}
}
@@ -1042,6 +1042,7 @@ class StreamMessageInputState extends State<StreamMessageInput>
CancelableOperation? _enrichUrlOperation;
final _urlRegex = RegExp(
r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+',
caseSensitive: false,
);
void _checkContainsUrl(String value, BuildContext context) async {
@@ -1103,8 +1104,12 @@ class StreamMessageInputState extends State<StreamMessageInput>
var response = _ogAttachmentCache[url];
if (response == null) {
final client = StreamChat.of(context).client;
response = await client.enrichUrl(url);
_ogAttachmentCache[url] = response;
try {
response = await client.enrichUrl(url);
_ogAttachmentCache[url] = response;
} catch (e, stk) {
return Future.error(e, stk);
}
}
return response;
}
@@ -1367,10 +1372,10 @@ class StreamMessageInputState extends State<StreamMessageInput>
}
final streamChannel = StreamChannel.of(context);
final channel = streamChannel.channel;
var message = _effectiveController.value;
if (!streamChannel.channel.ownCapabilities
.contains(PermissionType.sendLinks) &&
if (!channel.ownCapabilities.contains(PermissionType.sendLinks) &&
_urlRegex.allMatches(message.text ?? '').any((element) =>
element.group(0)?.split('.').last.isValidTLD() == true)) {
showInfoBottomSheet(
@@ -1396,8 +1401,8 @@ class StreamMessageInputState extends State<StreamMessageInput>
final skipEnrichUrl = _effectiveController.ogAttachment == null;
var shouldKeepFocus = widget.shouldKeepFocusAfterMessage;
shouldKeepFocus ??= !_commandEnabled;
widget.onQuotedMessageCleared?.call();
_effectiveController.reset();
@@ -1406,12 +1411,35 @@ class StreamMessageInputState extends State<StreamMessageInput>
message = await widget.preMessageSending!(message);
}
final channel = streamChannel.channel;
message = message.replaceMentionsWithId();
// If the channel is not up to date, we should reload it before sending
// the message.
if (!channel.state!.isUpToDate) {
await streamChannel.reloadChannel();
// We need to wait for the frame to be rendered with the updated channel
// state before sending the message.
await WidgetsBinding.instance.endOfFrame;
}
message = message.replaceMentionsWithId();
await _sendOrUpdateMessage(
message: message,
skipEnrichUrl: skipEnrichUrl,
);
if (shouldKeepFocus) {
FocusScope.of(context).requestFocus(_effectiveFocusNode);
} else {
FocusScope.of(context).unfocus();
}
}
Future<void> _sendOrUpdateMessage({
required Message message,
bool skipEnrichUrl = false,
}) async {
final channel = StreamChannel.of(context).channel;
try {
Future sendingFuture;
@@ -1427,12 +1455,6 @@ class StreamMessageInputState extends State<StreamMessageInput>
);
}
if (shouldKeepFocus) {
FocusScope.of(context).requestFocus(_effectiveFocusNode);
} else {
FocusScope.of(context).unfocus();
}
final resp = await sendingFuture;
if (resp.message?.type == 'error') {
_effectiveController.message = message;
@@ -52,7 +52,7 @@ class LoadingIndicator extends StatelessWidget {
const Center(
child: Padding(
padding: EdgeInsets.all(8),
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
);
},
@@ -432,7 +432,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
messageFilter: widget.messageFilter,
loadingBuilder: widget.loadingBuilder ??
(context) => const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
),
emptyBuilder: widget.emptyBuilder ??
(context) => Center(
@@ -566,21 +566,33 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
reverse: widget.reverse,
shrinkWrap: widget.shrinkWrap,
itemCount: itemCount,
findChildIndexCallback: (Key key) {
final indexedKey = key as IndexedKey;
final valueKey = indexedKey.key as ValueKey<String>?;
if (valueKey != null) {
final index = messagesIndex[valueKey.value];
if (index != null) {
// The calculation is as follows:
// * Add 2 to the index retrieved to account for the footer and the bottom loader.
// * Multiply the result by 2 to account for the separators between each pair of items.
// * Subtract 1 to adjust for the 0-based indexing of the list view.
return ((index + 2) * 2) - 1;
}
}
return null;
},
// Commented out as it is not working as expected.
// The list view gets broken in the following case:
// * The list view is loaded at a particular message (eg: Last Read, or a quoted message)
// and a new message is added to the list view.
//
// Issues faced:
// * https://github.com/GetStream/stream-chat-flutter/issues/1576
// * https://github.com/GetStream/stream-chat-flutter/issues/1414
//
// Related issues: https://github.com/flutter/flutter/issues/107123
//
// findChildIndexCallback: (Key key) {
// final indexedKey = key as IndexedKey;
// final valueKey = indexedKey.key as ValueKey<String>?;
// if (valueKey != null) {
// final index = messagesIndex[valueKey.value];
// if (index != null) {
// // The calculation is as follows:
// // * Add 2 to the index retrieved to account for the footer and the bottom loader.
// // * Multiply the result by 2 to account for the separators between each pair of items.
// // * Subtract 1 to adjust for the 0-based indexing of the list view.
// return ((index + 2) * 2) - 1;
// }
// }
// return null;
// },
// Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages)
// eg: |Type| rev(|Index(item)|) rev(|Index(separator)|) |Index(item)| |Index(separator)|
@@ -899,7 +911,10 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
final hasUrlAttachment =
message.attachments.any((it) => it.ogScrapeUrl != null);
final borderSide = isOnlyEmoji || hasUrlAttachment ? BorderSide.none : null;
final isEphemeral = message.isEphemeral;
final borderSide =
isOnlyEmoji || hasUrlAttachment || isEphemeral ? BorderSide.none : null;
final defaultMessageWidget = StreamMessageWidget(
showReplyMessage: false,
@@ -980,24 +995,7 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
FloatingActionButton(
backgroundColor: _streamTheme.colorTheme.barsBg,
onPressed: () async {
if (unreadCount > 0) {
streamChannel!.channel.markRead();
}
if (!_upToDate) {
_bottomPaginationActive = false;
initialAlignment = 0;
initialIndex = 0;
await streamChannel!.reloadChannel();
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController!.jumpTo(index: 0);
});
} else {
_showScrollToBottom.value = false;
_scrollController!.jumpTo(
index: 0,
);
}
return scrollToBottomDefaultTapAction(unreadCount);
},
child: widget.reverse
? StreamSvgIcon.down(
@@ -1101,10 +1099,13 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
final showThreadReplyIndicator = !_isThreadConversation && hasReplies;
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
final isEphemeral = message.isEphemeral;
final hasUrlAttachment =
message.attachments.any((it) => it.ogScrapeUrl != null);
final borderSide = isOnlyEmoji || hasUrlAttachment ? BorderSide.none : null;
final borderSide =
isOnlyEmoji || hasUrlAttachment || isEphemeral ? BorderSide.none : null;
final currentUser = StreamChat.of(context).currentUser;
final members = StreamChannel.of(context).channel.state?.members ?? [];
@@ -153,8 +153,6 @@ class BottomRow extends StatelessWidget {
}
}
final children = <WidgetSpan>[];
final threadParticipants = message.threadParticipants?.take(2);
final showThreadParticipants = threadParticipants?.isNotEmpty == true;
final replyCount = message.replyCount;
@@ -183,75 +181,69 @@ class BottomRow extends StatelessWidget {
const usernameKey = Key('username');
children.addAll([
final children = [
if (showUsername)
WidgetSpan(
child: usernameBuilder?.call(context, message) ??
Username(
key: usernameKey,
message: message,
messageTheme: messageTheme,
),
),
usernameBuilder?.call(context, message) ??
Username(
key: usernameKey,
message: message,
messageTheme: messageTheme,
),
if (showTimeStamp)
WidgetSpan(
child: Text(
Jiffy(message.createdAt.toLocal()).jm,
style: messageTheme.createdAtStyle,
),
Text(
Jiffy(message.createdAt.toLocal()).jm,
style: messageTheme.createdAtStyle,
),
if (showSendingIndicator)
WidgetSpan(
child: sendingIndicatorBuilder?.call(context, message) ??
SendingIndicatorBuilder(
messageTheme: messageTheme,
message: message,
hasNonUrlAttachments: hasNonUrlAttachments,
streamChat: streamChat,
streamChatTheme: streamChatTheme,
),
),
]);
sendingIndicatorBuilder?.call(context, message) ??
SendingIndicatorBuilder(
messageTheme: messageTheme,
message: message,
hasNonUrlAttachments: hasNonUrlAttachments,
streamChat: streamChat,
streamChatTheme: streamChatTheme,
),
];
final showThreadTail = !(hasUrlAttachments || isGiphy || isOnlyEmoji) &&
(showThreadReplyIndicator || showInChannel);
final threadIndicatorWidgets = <WidgetSpan>[
final threadIndicatorWidgets = [
if (showThreadTail)
WidgetSpan(
child: Padding(
padding: EdgeInsets.only(
bottom: context.textScaleFactor *
((messageTheme.repliesStyle?.fontSize ?? 1) / 2),
),
child: CustomPaint(
size: const Size(16, 32) * context.textScaleFactor,
painter: ThreadReplyPainter(
context: context,
color: messageTheme.messageBorderColor,
reverse: reverse,
// Added builder to use the nearest context to get the right
// textScaleFactor value.
Builder(
builder: (context) {
return Padding(
padding: EdgeInsets.only(
bottom: context.textScaleFactor *
((messageTheme.repliesStyle?.fontSize ?? 1) / 2),
),
),
),
child: CustomPaint(
size: const Size(16, 32) * context.textScaleFactor,
painter: ThreadReplyPainter(
context: context,
color: messageTheme.messageBorderColor,
reverse: reverse,
),
),
);
},
),
if (showInChannel || showThreadReplyIndicator) ...[
if (showThreadParticipants)
WidgetSpan(
child: SizedBox.fromSize(
size: Size((threadParticipants!.length * 8.0) + 8, 16),
child: ThreadParticipants(
threadParticipants: threadParticipants,
streamChatTheme: streamChatTheme,
),
SizedBox.fromSize(
size: Size((threadParticipants!.length * 8.0) + 8, 16),
child: ThreadParticipants(
threadParticipants: threadParticipants,
streamChatTheme: streamChatTheme,
),
),
WidgetSpan(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: _onThreadTap,
child: Text(msg, style: messageTheme.repliesStyle),
),
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: _onThreadTap,
child: Text(msg, style: messageTheme.repliesStyle),
),
),
],
@@ -266,8 +258,21 @@ class BottomRow extends StatelessWidget {
return Text.rich(
TextSpan(
children: [
...children,
].insertBetween(const WidgetSpan(child: SizedBox(width: 8))),
...children.insertBetween(const SizedBox(width: 8)).map((child) {
final mediaQueryData = MediaQuery.of(context);
return WidgetSpan(
child: MediaQuery(
// Hardcoding the textScaleFactor to 1 to avoid the multiple
// resizing of the text. This is needed because the
// textScaleFactor is already applied to the textSpan.
//
// issue: https://github.com/GetStream/stream-chat-flutter/issues/1250
data: mediaQueryData.copyWith(textScaleFactor: 1),
child: child,
),
);
}),
],
),
maxLines: 1,
textAlign: reverse ? TextAlign.right : TextAlign.left,
@@ -217,17 +217,9 @@ class StreamMessageWidget extends StatefulWidget {
);
},
'giphy': (context, message, attachments) {
final border = RoundedRectangleBorder(
side: attachmentBorderSide ??
BorderSide(
color: StreamChatTheme.of(context).colorTheme.borders,
),
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
);
return WrapAttachmentWidget(
attachmentWidget: Column(
children: attachments.map((attachment) {
final attachmentWidget = Column(
children: [
...attachments.map((attachment) {
final mediaQueryData = MediaQuery.of(context);
return StreamGiphyAttachment(
attachment: attachment,
@@ -240,14 +232,25 @@ class StreamMessageWidget extends StatefulWidget {
onShowMessage: onShowMessage,
onReplyMessage: onReplyTap,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap(message, attachment);
}
? () => onAttachmentTap(message, attachment)
: null,
);
}).toList(),
),
}),
],
);
// If the message is ephemeral, we don't want to show the border.
if (message.isEphemeral) return attachmentWidget;
final color = StreamChatTheme.of(context).colorTheme.borders;
final border = RoundedRectangleBorder(
side: attachmentBorderSide ?? BorderSide(color: color),
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
);
return WrapAttachmentWidget(
attachmentShape: border,
attachmentWidget: attachmentWidget,
);
},
'file': (context, message, attachments) {
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
/// {@endtemplate}
typedef ReactionIconBuilder = Widget Function(
BuildContext context,
// ignore: avoid_positional_boolean_parameters
bool isHighlighted,
double iconSize,
);
@@ -196,8 +196,7 @@ class StreamChannelListTile extends StatelessWidget {
initialData: channelState.members,
comparator: const ListEquality().equals,
builder: (context, members) {
if (members.isEmpty ||
!members.any((it) => it.user!.id == currentUser.id)) {
if (members.isEmpty) {
return const Offstage();
}
return unreadIndicatorBuilder?.call(context) ??
@@ -380,41 +380,3 @@ class StreamChannelListSeparator extends StatelessWidget {
);
}
}
/// A widget that is used to display an error screen
/// when [StreamChannelListController] fails to load initial channels.
class StreamChannelListErrorWidget extends StatelessWidget {
/// Creates a new instance of [StreamChannelListErrorWidget] widget.
const StreamChannelListErrorWidget({
super.key,
this.onPressed,
});
/// The callback to invoke when the user taps on the retry button.
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
const WidgetSpan(
child: Padding(
padding: EdgeInsets.only(right: 2),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: context.translations.loadingChannelsError),
],
),
style: Theme.of(context).textTheme.titleLarge,
),
TextButton(
onPressed: onPressed,
child: Text(context.translations.retryLabel),
),
],
);
}
@@ -202,9 +202,9 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
}
@override
ImageStreamCompleter loadBuffer(
ImageStreamCompleter loadImage(
MediaThumbnailProvider key,
DecoderBufferCallback decode,
ImageDecoderCallback decode,
) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
@@ -221,7 +221,7 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
Future<ui.Codec> _loadAsync(
MediaThumbnailProvider key,
DecoderBufferCallback decode,
ImageDecoderCallback decode,
) async {
assert(key == this, '$key is not $this');
final bytes = await media.thumbnailDataWithSize(
@@ -66,7 +66,7 @@ class StreamColorTheme {
this.appBg = const Color(0xff070A0D),
this.barsBg = const Color(0xff101418),
this.linkBg = const Color(0xff00193D),
this.accentPrimary = const Color(0xff005FFF),
this.accentPrimary = const Color(0xff337eff),
this.accentError = const Color(0xffFF3742),
this.accentInfo = const Color(0xff20E070),
this.borderTop = const Effect(
@@ -153,6 +153,7 @@ class StreamMessageInputThemeData with Diagnosticable {
sendButtonColor: sendButtonColor ?? this.sendButtonColor,
actionButtonIdleColor:
actionButtonIdleColor ?? this.actionButtonIdleColor,
linkHighlightColor: linkHighlightColor ?? this.linkHighlightColor,
expandButtonColor: expandButtonColor ?? this.expandButtonColor,
inputTextStyle: inputTextStyle ?? this.inputTextStyle,
sendButtonIdleColor: sendButtonIdleColor ?? this.sendButtonIdleColor,
@@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
@@ -27,6 +29,7 @@ class StreamMessageThemeData with Diagnosticable {
this.urlAttachmentTitleStyle,
this.urlAttachmentTextStyle,
this.urlAttachmentTitleMaxLine,
this.urlAttachmentTextMaxLine,
}) : urlAttachmentBackgroundColor =
urlAttachmentBackgroundColor ?? linkBackgroundColor;
@@ -82,6 +85,9 @@ class StreamMessageThemeData with Diagnosticable {
/// Max number of lines in Url link title.
final int? urlAttachmentTitleMaxLine;
/// Max number of lines in Url link text.
final int? urlAttachmentTextMaxLine;
/// Copy with a theme
StreamMessageThemeData copyWith({
TextStyle? messageTextStyle,
@@ -102,6 +108,7 @@ class StreamMessageThemeData with Diagnosticable {
TextStyle? urlAttachmentTitleStyle,
TextStyle? urlAttachmentTextStyle,
int? urlAttachmentTitleMaxLine,
int? urlAttachmentTextMaxLine,
}) {
return StreamMessageThemeData(
messageTextStyle: messageTextStyle ?? this.messageTextStyle,
@@ -128,6 +135,8 @@ class StreamMessageThemeData with Diagnosticable {
urlAttachmentTextStyle ?? this.urlAttachmentTextStyle,
urlAttachmentTitleMaxLine:
urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine,
urlAttachmentTextMaxLine:
urlAttachmentTextMaxLine ?? this.urlAttachmentTextMaxLine,
);
}
@@ -178,6 +187,16 @@ class StreamMessageThemeData with Diagnosticable {
b.urlAttachmentTitleStyle,
t,
),
urlAttachmentTitleMaxLine: lerpDouble(
a.urlAttachmentTitleMaxLine,
b.urlAttachmentTitleMaxLine,
t,
)?.round(),
urlAttachmentTextMaxLine: lerpDouble(
a.urlAttachmentTextMaxLine,
b.urlAttachmentTextMaxLine,
t,
)?.round(),
);
}
@@ -206,6 +225,7 @@ class StreamMessageThemeData with Diagnosticable {
urlAttachmentTitleStyle: other.urlAttachmentTitleStyle,
urlAttachmentTextStyle: other.urlAttachmentTextStyle,
urlAttachmentTitleMaxLine: other.urlAttachmentTitleMaxLine,
urlAttachmentTextMaxLine: other.urlAttachmentTextMaxLine,
);
}
@@ -229,7 +249,8 @@ class StreamMessageThemeData with Diagnosticable {
urlAttachmentHostStyle == other.urlAttachmentHostStyle &&
urlAttachmentTitleStyle == other.urlAttachmentTitleStyle &&
urlAttachmentTextStyle == other.urlAttachmentTextStyle &&
urlAttachmentTitleMaxLine == other.urlAttachmentTitleMaxLine;
urlAttachmentTitleMaxLine == other.urlAttachmentTitleMaxLine &&
urlAttachmentTextMaxLine == other.urlAttachmentTextMaxLine;
@override
int get hashCode =>
@@ -248,7 +269,8 @@ class StreamMessageThemeData with Diagnosticable {
urlAttachmentHostStyle.hashCode ^
urlAttachmentTitleStyle.hashCode ^
urlAttachmentTextStyle.hashCode ^
urlAttachmentTitleMaxLine.hashCode;
urlAttachmentTitleMaxLine.hashCode ^
urlAttachmentTextMaxLine.hashCode;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@@ -284,6 +306,10 @@ class StreamMessageThemeData with Diagnosticable {
..add(DiagnosticsProperty(
'urlAttachmentTitleMaxLine',
urlAttachmentTitleMaxLine,
))
..add(DiagnosticsProperty(
'urlAttachmentTextMaxLine',
urlAttachmentTextMaxLine,
));
}
}
@@ -195,15 +195,12 @@ class StreamChatThemeData {
width: 32,
),
),
messageLinksStyle: TextStyle(
color: accentColor,
),
messageLinksStyle: TextStyle(color: accentColor),
urlAttachmentBackgroundColor: colorTheme.linkBg,
urlAttachmentHostStyle: textTheme.bodyBold.copyWith(color: accentColor),
urlAttachmentTitleStyle:
textTheme.body.copyWith(fontWeight: FontWeight.w700),
urlAttachmentTextStyle:
textTheme.body.copyWith(fontWeight: FontWeight.w400),
urlAttachmentTitleStyle: textTheme.footnoteBold,
urlAttachmentTextStyle: textTheme.footnote,
urlAttachmentTitleMaxLine: 1,
),
otherMessageTheme: StreamMessageThemeData(
reactionsBackgroundColor: colorTheme.borders,
@@ -215,9 +212,7 @@ class StreamChatThemeData {
messageAuthorStyle:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor),
messageLinksStyle: TextStyle(
color: accentColor,
),
messageLinksStyle: TextStyle(color: accentColor),
messageBackgroundColor: colorTheme.barsBg,
messageBorderColor: colorTheme.borders,
avatarTheme: StreamAvatarThemeData(
@@ -229,10 +224,9 @@ class StreamChatThemeData {
),
urlAttachmentBackgroundColor: colorTheme.linkBg,
urlAttachmentHostStyle: textTheme.bodyBold.copyWith(color: accentColor),
urlAttachmentTitleStyle:
textTheme.body.copyWith(fontWeight: FontWeight.w700),
urlAttachmentTextStyle:
textTheme.body.copyWith(fontWeight: FontWeight.w400),
urlAttachmentTitleStyle: textTheme.footnoteBold,
urlAttachmentTextStyle: textTheme.footnote,
urlAttachmentTitleMaxLine: 1,
),
messageInputTheme: StreamMessageInputThemeData(
borderRadius: BorderRadius.circular(20),
@@ -4,7 +4,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
bool get isWeb => CurrentPlatform.isWeb;
/// Returns true if the app is running in a mobile device.
bool get isMobileDevice => true;
bool get isMobileDevice => CurrentPlatform.isIos || CurrentPlatform.isAndroid;
/// Returns true if the app is running in a desktop device.
bool get isDesktopDevice =>
@@ -22,7 +22,7 @@ bool get isDesktopVideoPlayerSupported =>
bool get isMobileDeviceOrWeb => isWeb || isMobileDevice;
/// Returns true if the app is running in a desktop or web.
bool get isDesktopDeviceOrWeb => false;
bool get isDesktopDeviceOrWeb => isWeb || isDesktopDevice;
/// Returns true if the app is running in a flutter test environment.
bool get isTestEnvironment => CurrentPlatform.isFlutterTest;
@@ -90,6 +90,7 @@ typedef ChannelInfoCallback = void Function(Channel);
/// {@template channelPreviewBuilder}
/// Builder used to create a custom [ChannelPreview] for a [Channel]
/// {@endtemplate}
@Deprecated('Use StreamChannelListViewIndexedWidgetBuilder instead')
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
/// {@template viewInfoCallback}
@@ -348,6 +349,7 @@ typedef KeyEventPredicate = bool Function(FocusNode, KeyEvent);
/// {@template userItemBuilder}
/// Builder used to create a custom [ListUserItem] from a [User]
/// {@endtemplate}
// ignore: avoid_positional_boolean_parameters
typedef UserItemBuilder = Widget Function(BuildContext, User, bool);
/// The action to perform when the "scroll to bottom" button is pressed
+4 -4
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 6.1.0
version: 6.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -27,7 +27,7 @@ dependencies:
flutter_portal: ^1.0.0
flutter_svg: ^2.0.4
http_parser: ^4.0.0
image_gallery_saver: ^1.7.1
image_gallery_saver: ^2.0.1
image_picker: ^0.8.2
jiffy: ^5.0.0
lottie: ^2.0.0
@@ -37,8 +37,8 @@ dependencies:
photo_view: ^0.14.0
rxdart: ^0.27.0
share_plus: ^6.3.0
shimmer: ^2.0.0
stream_chat_flutter_core: ^6.1.0
shimmer: ^3.0.0
stream_chat_flutter_core: ^6.2.0
synchronized: ^3.0.0
thumblr: ^0.0.4
url_launcher: ^6.1.0
@@ -22,9 +22,8 @@ void main() {
EdgeInsets? padding,
int initialScrollIndex = 0,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -24,9 +24,8 @@ void main() {
double anchor = 0,
int itemCount = defaultItemCount,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -23,9 +23,8 @@ void main() {
double anchor = 0,
int itemCount = defaultItemCount,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -21,9 +21,8 @@ void main() {
EdgeInsets? padding,
int initialIndex = 0,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -37,9 +37,8 @@ void main() {
double? minCacheExtent,
bool variableHeight = false,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -105,9 +104,8 @@ void main() {
testWidgets('List positioned with 0 at top - use default values',
(WidgetTester tester) async {
final itemPositionsListener = ItemPositionsListener.create();
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -1543,9 +1541,8 @@ void main() {
final itemPositionsListener = ItemPositionsListener.create();
final itemScrollController = ItemScrollController();
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -1600,9 +1597,8 @@ void main() {
final itemPositionsListener = ItemPositionsListener.create();
final itemScrollController = ItemScrollController();
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -1661,9 +1657,8 @@ void main() {
final itemPositionsListener = ItemPositionsListener.create();
final itemScrollController = ItemScrollController();
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -1732,9 +1727,8 @@ void main() {
testWidgets('Jump to 100 then set itemCount to 0',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
@@ -1781,9 +1775,8 @@ void main() {
testWidgets('List positioned with 100 at top then set itemCount to 100',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemCount = ValueNotifier<int>(defaultItemCount);
@@ -1823,9 +1816,8 @@ void main() {
testWidgets('List positioned with 499 at bottom then set itemCount to 100',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemCount = ValueNotifier<int>(defaultItemCount);
@@ -1943,9 +1935,8 @@ void main() {
});
testWidgets('Rebuild with scroll controller', (WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final key = ValueNotifier<Key>(const ValueKey('key'));
final itemScrollController = ItemScrollController();
@@ -1986,9 +1977,8 @@ void main() {
testWidgets('Double rebuild with scroll controller',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final outerKey = ValueNotifier<Key>(const ValueKey('outerKey'));
final innerKey = GlobalKey();
final listKey = ValueNotifier<Key>(const ValueKey(null));
@@ -2036,9 +2026,8 @@ void main() {
});
testWidgets('Key change with scroll controller', (WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final key = ValueNotifier<Key>(const ValueKey('key'));
final itemScrollController = ItemScrollController();
@@ -2070,9 +2059,8 @@ void main() {
});
testWidgets('Scroll after rebuild', (WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final key = ValueNotifier<Key>(const ValueKey('key'));
final itemScrollController = ItemScrollController();
@@ -2113,9 +2101,8 @@ void main() {
testWidgets('Scroll after rebuild when resusing state',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final containerKey = ValueNotifier<Key>(const ValueKey('key'));
final scrollKey = GlobalKey();
final itemScrollController = ItemScrollController();
@@ -2158,9 +2145,8 @@ void main() {
testWidgets('Scroll after changing scroll controller',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemScrollController0 = ItemScrollController();
final itemScrollController1 = ItemScrollController();
@@ -2205,9 +2191,8 @@ void main() {
testWidgets('Scroll after swapping scroll controllers',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemScrollController0 = ItemScrollController();
final itemScrollController1 = ItemScrollController();
@@ -25,9 +25,8 @@ void main() {
double anchor = 0,
int itemCount = defaultItemCount,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -33,9 +33,8 @@ void main() {
bool addRepaintBoundaries = true,
bool addAutomaticKeepAlives = true,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -100,9 +99,8 @@ void main() {
testWidgets('List positioned with 0 at top - use default values',
(WidgetTester tester) async {
final itemPositionsListener = ItemPositionsListener.create();
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -484,9 +482,8 @@ void main() {
testWidgets('Empty list then update to single item list',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
@@ -526,9 +523,8 @@ void main() {
testWidgets('ItemPositions: Empty list then update to 10 items list',
(WidgetTester tester) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
final itemScrollController = ItemScrollController();
final itemPositionsListener = ItemPositionsListener.create();
@@ -24,9 +24,8 @@ void main() {
EdgeInsets? padding,
int initialScrollIndex = 0,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -25,9 +25,8 @@ void main() {
int itemCount = defaultItemCount,
bool reverse = false,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -421,9 +420,8 @@ void main() {
testWidgets('test nested positioned list', (WidgetTester tester) async {
const itemCount = 50;
const key = Key('short_list');
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -21,9 +21,8 @@ void main() {
EdgeInsets? padding,
int initialIndex = 0,
}) async {
tester.binding.window.devicePixelRatioTestValue = 1.0;
tester.binding.window.physicalSizeTestValue =
const Size(screenWidth, screenHeight);
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(screenWidth, screenHeight);
await tester.pumpWidget(
MaterialApp(
@@ -39,20 +39,27 @@ void main() {
});
setUp(() {
methodChannel.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'listen') {
try {
await ServicesBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
methodChannel.name,
methodChannel.codec.encodeSuccessEnvelope('wifi'),
(_) {},
);
} catch (e) {
print(e);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
methodChannel,
(MethodCall methodCall) async {
if (methodCall.method == 'listen') {
try {
await TestDefaultBinaryMessengerBinding
.instance.defaultBinaryMessenger
.handlePlatformMessage(
methodChannel.name,
methodChannel.codec.encodeSuccessEnvelope('wifi'),
(_) {},
);
} catch (e) {
print(e);
}
}
}
});
return null;
},
);
});
testWidgets(
@@ -118,7 +125,8 @@ void main() {
);
tearDown(() {
methodChannel.setMockMethodCallHandler(null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, null);
});
/*testGoldens(
@@ -11,10 +11,13 @@ void main() {
const methodChannel =
MethodChannel('dev.fluttercommunity.plus/connectivity_status');
setUp(() {
methodChannel.setMockMethodCallHandler((MethodCall methodCall) async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel,
(MethodCall methodCall) async {
if (methodCall.method == 'listen') {
try {
await ServicesBinding.instance.defaultBinaryMessenger
await TestDefaultBinaryMessengerBinding
.instance.defaultBinaryMessenger
.handlePlatformMessage(
methodChannel.name,
methodChannel.codec.encodeSuccessEnvelope('wifi'),
@@ -24,6 +27,7 @@ void main() {
print(e);
}
}
return null;
});
});
@@ -87,7 +91,8 @@ void main() {
);
tearDown(() {
methodChannel.setMockMethodCallHandler(null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, null);
});
});
}
@@ -11,10 +11,13 @@ void main() {
const methodChannel =
MethodChannel('dev.fluttercommunity.plus/connectivity_status');
setUp(() {
methodChannel.setMockMethodCallHandler((MethodCall methodCall) async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel,
(MethodCall methodCall) async {
if (methodCall.method == 'listen') {
try {
await ServicesBinding.instance.defaultBinaryMessenger
await TestDefaultBinaryMessengerBinding
.instance.defaultBinaryMessenger
.handlePlatformMessage(
methodChannel.name,
methodChannel.codec.encodeSuccessEnvelope('wifi'),
@@ -24,6 +27,7 @@ void main() {
print(e);
}
}
return null;
});
});
@@ -93,7 +97,8 @@ void main() {
);
tearDown(() {
methodChannel.setMockMethodCallHandler(null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, null);
});
});
}
@@ -40,10 +40,12 @@ void main() {
});
setUp(() {
methodChannel.setMockMethodCallHandler((MethodCall methodCall) async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async {
if (methodCall.method == 'listen') {
try {
await ServicesBinding.instance.defaultBinaryMessenger
await TestDefaultBinaryMessengerBinding
.instance.defaultBinaryMessenger
.handlePlatformMessage(
methodChannel.name,
methodChannel.codec.encodeSuccessEnvelope('wifi'),
@@ -53,6 +55,7 @@ void main() {
print(e);
}
}
return null;
});
});
@@ -106,6 +109,7 @@ void main() {
});
tearDown(() {
methodChannel.setMockMethodCallHandler(null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, null);
});
}
@@ -40,10 +40,12 @@ void main() {
});
setUp(() {
methodChannel.setMockMethodCallHandler((MethodCall methodCall) async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, (MethodCall methodCall) async {
if (methodCall.method == 'listen') {
try {
await ServicesBinding.instance.defaultBinaryMessenger
await TestDefaultBinaryMessengerBinding
.instance.defaultBinaryMessenger
.handlePlatformMessage(
methodChannel.name,
methodChannel.codec.encodeSuccessEnvelope('wifi'),
@@ -53,6 +55,7 @@ void main() {
print(e);
}
}
return null;
});
});
@@ -110,6 +113,7 @@ void main() {
});
tearDown(() {
methodChannel.setMockMethodCallHandler(null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(methodChannel, null);
});
}
@@ -69,11 +69,11 @@ final _messageInputThemeControlMidLerp = StreamMessageInputThemeData(
borderRadius: BorderRadius.circular(20),
sendAnimationDuration: const Duration(milliseconds: 300),
inputBackgroundColor: const Color(0xff87898b),
actionButtonColor: const Color(0xff005fff),
actionButtonColor: const Color(0xff196eff),
actionButtonIdleColor: const Color(0xff7a7a7a),
sendButtonColor: const Color(0xff005fff),
sendButtonColor: const Color(0xff196eff),
sendButtonIdleColor: const Color(0xff848585),
expandButtonColor: const Color(0xff005fff),
expandButtonColor: const Color(0xff196eff),
inputTextStyle: const TextStyle(
color: Color(0xff7f7f7f),
fontSize: 14,
@@ -1,9 +1,18 @@
## 6.2.0
- Fixed `StreamMessageInputController.textPatternStyle` not matching case-insensitive patterns.
- Updated `connectivity_plus` dependency to `^4.0.0`
- Fixed `StreamChannel` shows black screen while loading in some cases.
- Updated `stream_chat` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat/changelog).
## 6.1.0
- Updated `dart` sdk environment range to support `3.0.0`.
- Updated `stream_chat` dependency to [`6.1.0`](https://pub.dev/packages/stream_chat/changelog).
- [[#1356]](https://github.com/GetStream/stream-chat-flutter/issues/1356) Channel doesn't auto display again after being
hidden.
- [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540) Use `CircularProgressIndicator.adaptive`
instead of material indicator.
## 6.0.0
@@ -41,7 +41,7 @@ bool Function(Message) defaultMessageFilter(String currentUserId) =>
/// },
/// loadingBuilder: (context) {
/// return Center(
/// child: CircularProgressIndicator(),
/// child: CircularProgressIndicator.adaptive(),
/// );
/// },
/// messageListBuilder: (context, list) {
@@ -41,7 +41,10 @@ class MessageTextFieldController extends TextEditingController {
}
return TextSpan(text: text, style: style).splitMapJoin(
RegExp(pattern.keys.map((it) => it.pattern).join('|')),
RegExp(
pattern.keys.map((it) => it.pattern).join('|'),
caseSensitive: false,
),
onMatch: (match) {
final text = match[0]!;
final key = pattern.keys.firstWhere((it) => it.hasMatch(text));
@@ -251,14 +251,14 @@ class StreamChannelState extends State<StreamChannel> {
channel.state!.truncate();
if (messageId == null) {
await channel.query(
final state = await channel.query(
messagesPagination: PaginationParams(
limit: limit,
),
preferOffline: preferOffline,
);
channel.state!.isUpToDate = true;
return null;
return state;
}
return channel.query(
@@ -445,13 +445,13 @@ class StreamChannelState extends State<StreamChannel> {
final dataLoaded = snapshot.data?.every((it) => it) == true;
if (widget.showLoading && !dataLoaded) {
return const Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator.adaptive(),
);
}
return widget.child;
},
);
if (initialMessageId != null) {
if (_futures.length > 1) {
child = Material(child: child);
}
return child;
@@ -1,7 +1,7 @@
name: stream_chat_flutter_core
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
version: 6.1.0
version: 6.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -11,13 +11,13 @@ environment:
dependencies:
collection: ^1.17.0
connectivity_plus: ^3.0.2
connectivity_plus: ^4.0.0
flutter:
sdk: flutter
freezed_annotation: ^2.0.3
meta: ^1.8.0
rxdart: ^0.27.0
stream_chat: ^6.1.0
stream_chat: ^6.2.0
dev_dependencies:
build_runner: ^2.3.3
@@ -1,3 +1,7 @@
## 5.2.0
* Updated `stream_chat_flutter` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
## 5.1.0
* Updated `dart` sdk environment range to support `3.0.0`.
@@ -577,10 +577,10 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const StreamChannelHeader(),
return const Scaffold(
appBar: StreamChannelHeader(),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -106,10 +106,10 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const StreamChannelHeader(),
return const Scaffold(
appBar: StreamChannelHeader(),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -133,10 +133,10 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const StreamChannelHeader(),
return const Scaffold(
appBar: StreamChannelHeader(),
body: Column(
children: const <Widget>[
children: <Widget>[
Expanded(
child: StreamMessageListView(),
),
@@ -1,6 +1,6 @@
name: stream_chat_localizations
description: The Official localizations for Stream Chat Flutter, a service for building chat applications
version: 5.1.0
version: 5.2.0
homepage: https://github.com/GetStream/stream-chat-flutter
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -14,7 +14,7 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
stream_chat_flutter: ^6.1.0
stream_chat_flutter: ^6.2.0
dev_dependencies:
dart_code_metrics: ^5.7.2
@@ -1,3 +1,10 @@
## 6.2.0
- Added support for `StreamChatPersistenceClient.isConnected` for checking if the client is connected to the database.
- [[#1422]](https://github.com/GetStream/stream-chat-flutter/issues/1422) Removed default values
from `UserEntity` `createdAt` and `updatedAt` fields.
- Updated `stream_chat` dependency to [`6.2.0`](https://pub.dev/packages/stream_chat/changelog).
## 6.1.0
- Updated `dart` sdk environment range to support `3.0.0`.
@@ -81,13 +81,13 @@ class ChannelQueryDao extends DatabaseAccessor<DriftChatDatabase>
final cachedChannelCids = await getCachedChannelCids(filter);
final query = select(channels)..where((c) => c.cid.isIn(cachedChannelCids));
final cachedChannels = await (query.join([
final cachedChannels = await query.join([
leftOuterJoin(users, channels.createdById.equalsExp(users.id)),
]).map((row) {
final createdByEntity = row.readTableOrNull(users);
final channelEntity = row.readTable(channels);
return channelEntity.toChannelModel(createdBy: createdByEntity?.toUser());
})).get();
}).get();
var chainedComparator = (ChannelModel a, ChannelModel b) {
final dateA = a.lastMessageAt ?? a.createdAt;
@@ -1,10 +1,8 @@
import 'package:drift/drift.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_persistence/src/db/drift_chat_database.dart';
import 'package:stream_chat_persistence/src/entity/members.dart';
import 'package:stream_chat_persistence/src/entity/users.dart';
import 'package:stream_chat_persistence/src/mapper/mapper.dart';
part 'member_dao.g.dart';
@@ -39,9 +37,9 @@ class MemberDao extends DatabaseAccessor<DriftChatDatabase>
) {
final entities = channelWithMembers.entries
.map((entry) =>
(entry.value?.map(
entry.value?.map(
(member) => member.toEntity(cid: entry.key),
)) ??
) ??
[])
.expand((it) => it)
.toList(growable: false);
@@ -49,7 +49,7 @@ class DriftChatDatabase extends _$DriftChatDatabase {
// you should bump this number whenever you change or add a table definition.
@override
int get schemaVersion => 10;
int get schemaVersion => 11;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -3651,18 +3651,14 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> {
const VerificationMeta('createdAt');
@override
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
'created_at', aliasedName, false,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
'created_at', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
static const VerificationMeta _updatedAtMeta =
const VerificationMeta('updatedAt');
@override
late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>(
'updated_at', aliasedName, false,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
'updated_at', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
static const VerificationMeta _lastActiveMeta =
const VerificationMeta('lastActive');
@override
@@ -3773,9 +3769,9 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> {
language: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}language']),
createdAt: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']),
updatedAt: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!,
.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at']),
lastActive: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}last_active']),
online: attachedDatabase.typeMapping
@@ -3808,10 +3804,10 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
final String? language;
/// Date of user creation
final DateTime createdAt;
final DateTime? createdAt;
/// Date of last user update
final DateTime updatedAt;
final DateTime? updatedAt;
/// Date of last user connection
final DateTime? lastActive;
@@ -3828,8 +3824,8 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
{required this.id,
this.role,
this.language,
required this.createdAt,
required this.updatedAt,
this.createdAt,
this.updatedAt,
this.lastActive,
required this.online,
required this.banned,
@@ -3844,8 +3840,12 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
if (!nullToAbsent || language != null) {
map['language'] = Variable<String>(language);
}
map['created_at'] = Variable<DateTime>(createdAt);
map['updated_at'] = Variable<DateTime>(updatedAt);
if (!nullToAbsent || createdAt != null) {
map['created_at'] = Variable<DateTime>(createdAt);
}
if (!nullToAbsent || updatedAt != null) {
map['updated_at'] = Variable<DateTime>(updatedAt);
}
if (!nullToAbsent || lastActive != null) {
map['last_active'] = Variable<DateTime>(lastActive);
}
@@ -3865,8 +3865,8 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
id: serializer.fromJson<String>(json['id']),
role: serializer.fromJson<String?>(json['role']),
language: serializer.fromJson<String?>(json['language']),
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
createdAt: serializer.fromJson<DateTime?>(json['createdAt']),
updatedAt: serializer.fromJson<DateTime?>(json['updatedAt']),
lastActive: serializer.fromJson<DateTime?>(json['lastActive']),
online: serializer.fromJson<bool>(json['online']),
banned: serializer.fromJson<bool>(json['banned']),
@@ -3880,8 +3880,8 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
'id': serializer.toJson<String>(id),
'role': serializer.toJson<String?>(role),
'language': serializer.toJson<String?>(language),
'createdAt': serializer.toJson<DateTime>(createdAt),
'updatedAt': serializer.toJson<DateTime>(updatedAt),
'createdAt': serializer.toJson<DateTime?>(createdAt),
'updatedAt': serializer.toJson<DateTime?>(updatedAt),
'lastActive': serializer.toJson<DateTime?>(lastActive),
'online': serializer.toJson<bool>(online),
'banned': serializer.toJson<bool>(banned),
@@ -3893,8 +3893,8 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
{String? id,
Value<String?> role = const Value.absent(),
Value<String?> language = const Value.absent(),
DateTime? createdAt,
DateTime? updatedAt,
Value<DateTime?> createdAt = const Value.absent(),
Value<DateTime?> updatedAt = const Value.absent(),
Value<DateTime?> lastActive = const Value.absent(),
bool? online,
bool? banned,
@@ -3903,8 +3903,8 @@ class UserEntity extends DataClass implements Insertable<UserEntity> {
id: id ?? this.id,
role: role.present ? role.value : this.role,
language: language.present ? language.value : this.language,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
createdAt: createdAt.present ? createdAt.value : this.createdAt,
updatedAt: updatedAt.present ? updatedAt.value : this.updatedAt,
lastActive: lastActive.present ? lastActive.value : this.lastActive,
online: online ?? this.online,
banned: banned ?? this.banned,
@@ -3948,8 +3948,8 @@ class UsersCompanion extends UpdateCompanion<UserEntity> {
final Value<String> id;
final Value<String?> role;
final Value<String?> language;
final Value<DateTime> createdAt;
final Value<DateTime> updatedAt;
final Value<DateTime?> createdAt;
final Value<DateTime?> updatedAt;
final Value<DateTime?> lastActive;
final Value<bool> online;
final Value<bool> banned;
@@ -4010,8 +4010,8 @@ class UsersCompanion extends UpdateCompanion<UserEntity> {
{Value<String>? id,
Value<String?>? role,
Value<String?>? language,
Value<DateTime>? createdAt,
Value<DateTime>? updatedAt,
Value<DateTime?>? createdAt,
Value<DateTime?>? updatedAt,
Value<DateTime?>? lastActive,
Value<bool>? online,
Value<bool>? banned,
@@ -15,10 +15,10 @@ class Users extends Table {
TextColumn get language => text().nullable()();
/// Date of user creation
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get createdAt => dateTime().nullable()();
/// Date of last user update
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get updatedAt => dateTime().nullable()();
/// Date of last user connection
DateTimeColumn get lastActive => dateTime().nullable()();
@@ -58,7 +58,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
bool get _debugIsConnected {
assert(() {
if (db == null) {
if (!isConnected) {
throw StateError('''
$runtimeType hasn't been connected yet or used after `disconnect`
was called. Consider calling `connect` to create a connection.
@@ -79,6 +79,9 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
webUseIndexedDbIfSupported: _webUseIndexedDbIfSupported,
);
@override
bool get isConnected => db != null;
@override
Future<void> connect(
String userId, {
@@ -405,7 +408,7 @@ class StreamChatPersistenceClient extends ChatPersistenceClient {
@override
Future<void> disconnect({bool flush = false}) async {
_logger.info('disconnect');
if (db != null) {
if (isConnected) {
_logger.info('Disconnecting');
if (flush) {
_logger.info('Flushing');
@@ -1,7 +1,7 @@
name: stream_chat_persistence
homepage: https://github.com/GetStream/stream-chat-flutter
description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter.
version: 6.1.0
version: 6.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -18,7 +18,7 @@ dependencies:
path: ^1.8.2
path_provider: ^2.0.1
sqlite3_flutter_libs: ^0.5.0
stream_chat: ^6.1.0
stream_chat: ^6.2.0
dev_dependencies:
build_runner: ^2.3.3
@@ -132,7 +132,7 @@ void main() {
// Should match lastMessageAt date
expect(
updatedChannel.lastMessageAt,
isSameDateAs(insertedChannel.lastMessageAt!),
isSameDateAs(insertedChannel.lastMessageAt),
);
}
});
@@ -177,7 +177,7 @@ void main() {
// Should match lastMessageAt date
expect(
updatedChannel.lastMessageAt,
isSameDateAs(insertedChannel.lastMessageAt!),
isSameDateAs(insertedChannel.lastMessageAt),
);
}
});
@@ -220,7 +220,7 @@ void main() {
// Should match lastMessageAt date
expect(
updatedChannel.lastMessageAt,
isSameDateAs(insertedChannel.lastMessageAt!),
isSameDateAs(insertedChannel.lastMessageAt),
);
}
});
@@ -66,7 +66,7 @@ void main() {
expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt));
expect(
fetchedMember.inviteAcceptedAt,
isSameDateAs(member.inviteAcceptedAt!),
isSameDateAs(member.inviteAcceptedAt),
);
}
});
@@ -93,7 +93,7 @@ void main() {
expect(fetchedMember.updatedAt, isSameDateAs(member.updatedAt));
expect(
fetchedMember.inviteAcceptedAt,
isSameDateAs(member.inviteAcceptedAt!),
isSameDateAs(member.inviteAcceptedAt),
);
}
@@ -37,8 +37,8 @@ void main() {
expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt));
expect(channelModel.memberCount, entity.memberCount);
expect(channelModel.cid, entity.cid);
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt!));
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt!));
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt));
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt));
expect(channelModel.extraData, entity.extraData);
expect(channelModel.createdBy!.id, entity.createdById);
});
@@ -77,8 +77,8 @@ void main() {
expect(channelModel.updatedAt, isSameDateAs(entity.updatedAt));
expect(channelModel.memberCount, entity.memberCount);
expect(channelModel.cid, entity.cid);
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt!));
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt!));
expect(channelModel.lastMessageAt, isSameDateAs(entity.lastMessageAt));
expect(channelModel.deletedAt, isSameDateAs(entity.deletedAt));
expect(channelModel.extraData, entity.extraData);
expect(channelModel.createdBy!.id, entity.createdById);
});
@@ -115,8 +115,8 @@ void main() {
expect(channelEntity.updatedAt, isSameDateAs(model.updatedAt));
expect(channelEntity.memberCount, model.memberCount);
expect(channelEntity.cid, model.cid);
expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt!));
expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt!));
expect(channelEntity.lastMessageAt, isSameDateAs(model.lastMessageAt));
expect(channelEntity.deletedAt, isSameDateAs(model.deletedAt));
expect(channelEntity.extraData, model.extraData);
expect(channelEntity.createdById, model.createdBy!.id);
});
@@ -29,8 +29,8 @@ void main() {
expect(member.createdAt, isSameDateAs(entity.createdAt));
expect(member.updatedAt, isSameDateAs(entity.updatedAt));
expect(member.channelRole, entity.channelRole);
expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt!));
expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt!));
expect(member.inviteAcceptedAt, isSameDateAs(entity.inviteAcceptedAt));
expect(member.inviteRejectedAt, isSameDateAs(entity.inviteRejectedAt));
expect(member.invited, entity.invited);
expect(member.banned, entity.banned);
expect(member.shadowBanned, entity.shadowBanned);
@@ -59,8 +59,8 @@ void main() {
expect(entity.createdAt, isSameDateAs(member.createdAt));
expect(entity.updatedAt, isSameDateAs(member.updatedAt));
expect(entity.channelRole, member.channelRole);
expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt!));
expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt!));
expect(entity.inviteAcceptedAt, isSameDateAs(member.inviteAcceptedAt));
expect(entity.inviteRejectedAt, isSameDateAs(member.inviteRejectedAt));
expect(entity.invited, member.invited);
expect(entity.banned, member.banned);
expect(entity.shadowBanned, member.shadowBanned);
@@ -96,11 +96,11 @@ void main() {
expect(message.updatedAt, isSameDateAs(entity.updatedAt));
expect(message.extraData, entity.extraData);
expect(message.user!.id, entity.userId);
expect(message.deletedAt, isSameDateAs(entity.deletedAt!));
expect(message.deletedAt, isSameDateAs(entity.deletedAt));
expect(message.text, entity.messageText);
expect(message.pinned, entity.pinned);
expect(message.pinExpires, isSameDateAs(entity.pinExpires!));
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt!));
expect(message.pinExpires, isSameDateAs(entity.pinExpires));
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt));
expect(message.pinnedBy!.id, entity.pinnedByUserId);
expect(message.reactionCounts, entity.reactionCounts);
expect(message.reactionScores, entity.reactionScores);
@@ -191,11 +191,11 @@ void main() {
expect(entity.updatedAt, isSameDateAs(message.updatedAt));
expect(entity.extraData, message.extraData);
expect(entity.userId, message.user!.id);
expect(entity.deletedAt, isSameDateAs(message.deletedAt!));
expect(entity.deletedAt, isSameDateAs(message.deletedAt));
expect(entity.messageText, message.text);
expect(entity.pinned, message.pinned);
expect(entity.pinExpires, isSameDateAs(message.pinExpires!));
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt!));
expect(entity.pinExpires, isSameDateAs(message.pinExpires));
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt));
expect(entity.pinnedByUserId, message.pinnedBy!.id);
expect(entity.reactionCounts, message.reactionCounts);
expect(entity.reactionScores, message.reactionScores);
@@ -89,11 +89,11 @@ void main() {
expect(message.updatedAt, isSameDateAs(entity.updatedAt));
expect(message.extraData, entity.extraData);
expect(message.user!.id, entity.userId);
expect(message.deletedAt, isSameDateAs(entity.deletedAt!));
expect(message.deletedAt, isSameDateAs(entity.deletedAt));
expect(message.text, entity.messageText);
expect(message.pinned, entity.pinned);
expect(message.pinExpires, isSameDateAs(entity.pinExpires!));
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt!));
expect(message.pinExpires, isSameDateAs(entity.pinExpires));
expect(message.pinnedAt, isSameDateAs(entity.pinnedAt));
expect(message.pinnedBy!.id, entity.pinnedByUserId);
expect(message.reactionCounts, entity.reactionCounts);
expect(message.reactionScores, entity.reactionScores);
@@ -179,11 +179,11 @@ void main() {
expect(entity.updatedAt, isSameDateAs(message.updatedAt));
expect(entity.extraData, message.extraData);
expect(entity.userId, message.user!.id);
expect(entity.deletedAt, isSameDateAs(message.deletedAt!));
expect(entity.deletedAt, isSameDateAs(message.deletedAt));
expect(entity.messageText, message.text);
expect(entity.pinned, message.pinned);
expect(entity.pinExpires, isSameDateAs(message.pinExpires!));
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt!));
expect(entity.pinExpires, isSameDateAs(message.pinExpires));
expect(entity.pinnedAt, isSameDateAs(message.pinnedAt));
expect(entity.pinnedByUserId, message.pinnedBy!.id);
expect(entity.reactionCounts, message.reactionCounts);
expect(entity.reactionScores, message.reactionScores);
@@ -27,7 +27,7 @@ void main() {
expect(user.language, entity.language);
expect(user.createdAt, isSameDateAs(entity.createdAt));
expect(user.updatedAt, isSameDateAs(entity.updatedAt));
expect(user.lastActive, isSameDateAs(entity.lastActive!));
expect(user.lastActive, isSameDateAs(entity.lastActive));
expect(user.online, entity.online);
expect(user.banned, entity.banned);
expect(user.extraData, entity.extraData);
@@ -52,7 +52,7 @@ void main() {
expect(entity.language, user.language);
expect(entity.createdAt, isSameDateAs(user.createdAt));
expect(entity.updatedAt, isSameDateAs(user.updatedAt));
expect(entity.lastActive, isSameDateAs(user.lastActive!));
expect(entity.lastActive, isSameDateAs(user.lastActive));
expect(entity.online, user.online);
expect(entity.banned, user.banned);
expect(entity.extraData, user.extraData);
@@ -1,21 +1,22 @@
import 'package:flutter_test/flutter_test.dart';
Matcher isSameDateAs(DateTime targetDate) =>
Matcher isSameDateAs(DateTime? targetDate) =>
_IsSameDateAs(targetDate: targetDate);
class _IsSameDateAs extends Matcher {
const _IsSameDateAs({required this.targetDate});
final DateTime targetDate;
final DateTime? targetDate;
@override
bool matches(covariant DateTime date, Map matchState) =>
date.year == targetDate.year &&
date.month == targetDate.month &&
date.day == targetDate.day &&
date.hour == targetDate.hour &&
date.minute == targetDate.minute &&
date.second == targetDate.second;
bool matches(covariant DateTime date, Map matchState) {
return date.year == targetDate?.year &&
date.month == targetDate?.month &&
date.day == targetDate?.day &&
date.hour == targetDate?.hour &&
date.minute == targetDate?.minute &&
date.second == targetDate?.second;
}
@override
Description describe(Description description) =>
@@ -16,9 +16,9 @@ void main() {
const userId = 'testUserId';
test('successfully connects with the Database', () async {
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
expect(client.db, isNull);
expect(client.isConnected, false);
await client.connect(userId, databaseProvider: testDatabaseProvider);
expect(client.db, isNotNull);
expect(client.isConnected, true);
expect(client.db, isA<DriftChatDatabase>());
expect(client.db!.userId, userId);
@@ -29,10 +29,9 @@ void main() {
test('throws if already connected', () async {
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
expect(client.db, isNull);
expect(client.isConnected, false);
await client.connect(userId, databaseProvider: testDatabaseProvider);
expect(client.db, isNotNull);
expect(client.db, isNotNull);
expect(client.isConnected, true);
expect(client.db, isA<DriftChatDatabase>());
expect(client.db!.userId, userId);
expect(
@@ -50,9 +49,9 @@ void main() {
const userId = 'testUserId';
final client = StreamChatPersistenceClient(logLevel: Level.ALL);
await client.connect(userId, databaseProvider: testDatabaseProvider);
expect(client.db, isNotNull);
expect(client.isConnected, true);
await client.disconnect(flush: true);
expect(client.db, isNull);
expect(client.isConnected, false);
});
test('client function throws stateError if db is not yet connected', () {