Merge pull request #1584 from GetStream/release/v6.2.0
This commit is contained in:
@@ -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
@@ -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
@@ -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: |
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
+4
-2
@@ -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),
|
||||
),
|
||||
|
||||
+2
-1
@@ -400,8 +400,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
),
|
||||
),
|
||||
builder: (context) => EditMessageSheet(
|
||||
message: widget.message,
|
||||
channel: channel,
|
||||
message: widget.message,
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+3
-3
@@ -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,
|
||||
);
|
||||
|
||||
+1
-2
@@ -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) ??
|
||||
|
||||
-38
@@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
+30
-45
@@ -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();
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
+8
-12
@@ -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();
|
||||
|
||||
+2
-3
@@ -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(
|
||||
|
||||
+4
-6
@@ -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(
|
||||
|
||||
+2
-3
@@ -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', () {
|
||||
|
||||
Reference in New Issue
Block a user