Merge branch 'develop' into overlay-alt
This commit is contained in:
@@ -15,10 +15,14 @@
|
|||||||
- Added `Filter.contains` and `Filter.empty`
|
- Added `Filter.contains` and `Filter.empty`
|
||||||
- Added support for `next`, `previous` value pagination in `client.search`
|
- Added support for `next`, `previous` value pagination in `client.search`
|
||||||
, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination)
|
, [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination)
|
||||||
|
- `Attachment` class now has a `fileSize` and `mimeType` property. Setting a `file` will also set the `file_size`
|
||||||
|
, `mime_type` key on `extraData`, so `attachment.fileSize`, `attachment.mimetype` and `attachment.extraData['file_size']`
|
||||||
|
, `attachment.extraData['mime_type]` is same respectively.
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
- [[#659]](https://github.com/GetStream/stream-chat-flutter/issues/659) Fixed unread count not updating correctly.
|
- [[#659]](https://github.com/GetStream/stream-chat-flutter/issues/659) Fixed unread count not updating correctly.
|
||||||
|
- Fix `Filter.empty()` json encoding.
|
||||||
|
|
||||||
## 2.2.1
|
## 2.2.1
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,20 @@ class Attachment extends Equatable {
|
|||||||
this.authorIcon,
|
this.authorIcon,
|
||||||
this.assetUrl,
|
this.assetUrl,
|
||||||
List<Action>? actions,
|
List<Action>? actions,
|
||||||
this.extraData = const {},
|
Map<String, Object?> extraData = const {},
|
||||||
this.file,
|
this.file,
|
||||||
UploadState? uploadState,
|
UploadState? uploadState,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
title = title ?? file?.name,
|
title = title ?? file?.name,
|
||||||
localUri = file?.path != null ? Uri.parse(file!.path!) : null,
|
localUri = file?.path != null ? Uri.parse(file!.path!) : null,
|
||||||
actions = actions ?? [] {
|
actions = actions ?? [],
|
||||||
|
// For backwards compatibility,
|
||||||
|
// set 'file_size', 'mime_type' in [extraData].
|
||||||
|
extraData = {
|
||||||
|
...extraData,
|
||||||
|
if (file?.size != null) 'file_size': file?.size,
|
||||||
|
if (file?.mimeType != null) 'mime_type': file?.mimeType?.mimeType,
|
||||||
|
} {
|
||||||
this.uploadState = uploadState ??
|
this.uploadState = uploadState ??
|
||||||
((assetUrl != null || imageUrl != null)
|
((assetUrl != null || imageUrl != null)
|
||||||
? const UploadState.success()
|
? const UploadState.success()
|
||||||
@@ -121,6 +128,18 @@ class Attachment extends Equatable {
|
|||||||
/// This is created locally for uniquely identifying a attachment.
|
/// This is created locally for uniquely identifying a attachment.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
/// Shortcut for file size.
|
||||||
|
///
|
||||||
|
/// {@macro fileSize}
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
int? get fileSize => extraData['file_size'] as int?;
|
||||||
|
|
||||||
|
/// Shortcut for file mimeType.
|
||||||
|
///
|
||||||
|
/// {@macro mimeType}
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
String? get mimeType => extraData['mime_type'] as String?;
|
||||||
|
|
||||||
/// Known top level fields.
|
/// Known top level fields.
|
||||||
/// Useful for [Serializer] methods.
|
/// Useful for [Serializer] methods.
|
||||||
static const topLevelFields = [
|
static const topLevelFields = [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart' show MultipartFile;
|
import 'package:dio/dio.dart' show MultipartFile;
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
import 'package:stream_chat/src/core/platform_detector/platform_detector.dart';
|
||||||
import 'package:stream_chat/src/core/util/extension.dart';
|
import 'package:stream_chat/src/core/util/extension.dart';
|
||||||
@@ -65,7 +66,7 @@ class AttachmentFile {
|
|||||||
AttachmentFile({
|
AttachmentFile({
|
||||||
required this.size,
|
required this.size,
|
||||||
this.path,
|
this.path,
|
||||||
this.name,
|
String? name,
|
||||||
this.bytes,
|
this.bytes,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
path != null || bytes != null,
|
path != null || bytes != null,
|
||||||
@@ -74,7 +75,12 @@ class AttachmentFile {
|
|||||||
assert(
|
assert(
|
||||||
!CurrentPlatform.isWeb || bytes != null,
|
!CurrentPlatform.isWeb || bytes != null,
|
||||||
'File by path is not supported in web, Please provide bytes',
|
'File by path is not supported in web, Please provide bytes',
|
||||||
);
|
),
|
||||||
|
assert(
|
||||||
|
name?.contains('.') ?? true,
|
||||||
|
'Invalid file name, should also contain file extension',
|
||||||
|
),
|
||||||
|
_name = name;
|
||||||
|
|
||||||
/// Create a new instance from a json
|
/// Create a new instance from a json
|
||||||
factory AttachmentFile.fromJson(Map<String, dynamic> json) =>
|
factory AttachmentFile.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -87,8 +93,10 @@ class AttachmentFile {
|
|||||||
/// ```
|
/// ```
|
||||||
final String? path;
|
final String? path;
|
||||||
|
|
||||||
|
final String? _name;
|
||||||
|
|
||||||
/// File name including its extension.
|
/// File name including its extension.
|
||||||
final String? name;
|
String? get name => _name ?? path?.split('/').last;
|
||||||
|
|
||||||
/// Byte data for this file. Particularly useful if you want to manipulate
|
/// Byte data for this file. Particularly useful if you want to manipulate
|
||||||
/// its data or easily upload to somewhere else.
|
/// its data or easily upload to somewhere else.
|
||||||
@@ -101,26 +109,26 @@ class AttachmentFile {
|
|||||||
/// File extension for this file.
|
/// File extension for this file.
|
||||||
String? get extension => name?.split('.').last;
|
String? get extension => name?.split('.').last;
|
||||||
|
|
||||||
|
/// The mime type of this file.
|
||||||
|
MediaType? get mimeType => name?.mimeType;
|
||||||
|
|
||||||
/// Serialize to json
|
/// Serialize to json
|
||||||
Map<String, dynamic> toJson() => _$AttachmentFileToJson(this);
|
Map<String, dynamic> toJson() => _$AttachmentFileToJson(this);
|
||||||
|
|
||||||
/// Converts this into a [MultipartFile]
|
/// Converts this into a [MultipartFile]
|
||||||
Future<MultipartFile> toMultipartFile() async {
|
Future<MultipartFile> toMultipartFile() async {
|
||||||
final filename = path?.split('/').last ?? name;
|
MultipartFile multiPartFile;
|
||||||
final mimeType = filename?.mimeType;
|
|
||||||
|
|
||||||
late MultipartFile multiPartFile;
|
|
||||||
|
|
||||||
if (CurrentPlatform.isWeb) {
|
if (CurrentPlatform.isWeb) {
|
||||||
multiPartFile = MultipartFile.fromBytes(
|
multiPartFile = MultipartFile.fromBytes(
|
||||||
bytes!,
|
bytes!,
|
||||||
filename: filename,
|
filename: name,
|
||||||
contentType: mimeType,
|
contentType: mimeType,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
multiPartFile = await MultipartFile.fromFile(
|
multiPartFile = await MultipartFile.fromFile(
|
||||||
path!,
|
path!,
|
||||||
filename: filename,
|
filename: name,
|
||||||
contentType: mimeType,
|
contentType: mimeType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ class Filter extends Equatable {
|
|||||||
this.key,
|
this.key,
|
||||||
}) : operator = operator.rawValue;
|
}) : operator = operator.rawValue;
|
||||||
|
|
||||||
|
/// An empty filter
|
||||||
|
const Filter.empty()
|
||||||
|
: value = const <String, Object?>{},
|
||||||
|
operator = null,
|
||||||
|
key = null;
|
||||||
|
|
||||||
/// Combines the provided filters and matches the values
|
/// Combines the provided filters and matches the values
|
||||||
/// matched by all filters.
|
/// matched by all filters.
|
||||||
factory Filter.and(List<Filter> filters) =>
|
factory Filter.and(List<Filter> filters) =>
|
||||||
@@ -172,9 +178,6 @@ class Filter extends Equatable {
|
|||||||
String? key,
|
String? key,
|
||||||
}) = Filter.__;
|
}) = Filter.__;
|
||||||
|
|
||||||
/// An empty filter
|
|
||||||
factory Filter.empty() => const Filter.raw(value: {});
|
|
||||||
|
|
||||||
/// Creates a custom [Filter] from a raw map value
|
/// Creates a custom [Filter] from a raw map value
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:stream_chat/src/core/models/action.dart';
|
import 'package:stream_chat/src/core/models/action.dart';
|
||||||
import 'package:stream_chat/src/core/models/attachment.dart';
|
import 'package:stream_chat/src/core/models/attachment.dart';
|
||||||
|
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../../utils.dart';
|
import '../../utils.dart';
|
||||||
@@ -41,5 +42,59 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('fileName, mimeType property and extraData manipulation', () {
|
||||||
|
final file = AttachmentFile(size: 3, path: 'myfolder/myfile.txt');
|
||||||
|
final attachment = Attachment(file: file);
|
||||||
|
|
||||||
|
expect(attachment.fileSize, 3);
|
||||||
|
expect(attachment.mimeType, 'text/plain');
|
||||||
|
expect(attachment.toJson(), {
|
||||||
|
'title': 'myfile.txt',
|
||||||
|
'actions': [],
|
||||||
|
'file_size': 3,
|
||||||
|
'mime_type': 'text/plain'
|
||||||
|
});
|
||||||
|
expect(Attachment.fromJson(attachment.toJson()).toJson(), {
|
||||||
|
'title': 'myfile.txt',
|
||||||
|
'actions': [],
|
||||||
|
'file_size': 3,
|
||||||
|
'mime_type': 'text/plain'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setting the size and mimeType using extraData should work fine
|
||||||
|
var newAttachment = Attachment(
|
||||||
|
extraData: const {
|
||||||
|
'file_size': 6,
|
||||||
|
'mime_type': 'application/pdf',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(newAttachment.extraData['file_size'], 6);
|
||||||
|
expect(newAttachment.extraData['mime_type'], 'application/pdf');
|
||||||
|
expect(newAttachment.fileSize, 6);
|
||||||
|
expect(newAttachment.mimeType, 'application/pdf');
|
||||||
|
|
||||||
|
// switching a new file should update size and mimeType
|
||||||
|
final fileTwo = AttachmentFile(size: 12, path: 'myfolder/fileTwo.pdf');
|
||||||
|
newAttachment = attachment.copyWith(file: fileTwo);
|
||||||
|
|
||||||
|
expect(newAttachment.extraData['file_size'], 12);
|
||||||
|
expect(newAttachment.extraData['mime_type'], 'application/pdf');
|
||||||
|
expect(newAttachment.fileSize, 12);
|
||||||
|
expect(newAttachment.mimeType, 'application/pdf');
|
||||||
|
|
||||||
|
// if file is available, should override size and mimeType.
|
||||||
|
final fileThree = AttachmentFile(size: 9, path: 'myfolder/fileThree.png');
|
||||||
|
newAttachment = attachment.copyWith(file: fileThree, extraData: {
|
||||||
|
'file_size': 88,
|
||||||
|
'mime_type': 'application/pdf',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(newAttachment.extraData['file_size'], 9);
|
||||||
|
expect(newAttachment.extraData['mime_type'], 'image/png');
|
||||||
|
expect(newAttachment.fileSize, 9);
|
||||||
|
expect(newAttachment.mimeType, 'image/png');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('empty', () {
|
test('empty', () {
|
||||||
final filter = Filter.empty();
|
const filter = Filter.empty();
|
||||||
expect(filter.value, {});
|
expect(filter.value, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,6 +226,12 @@ void main() {
|
|||||||
json.encode(value),
|
json.encode(value),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('empty', () {
|
||||||
|
const filter = Filter.empty();
|
||||||
|
final encoded = json.encode(filter);
|
||||||
|
expect(encoded, '{}');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('groupedFilter', () {
|
test('groupedFilter', () {
|
||||||
|
|||||||
@@ -1,19 +1,55 @@
|
|||||||
|
## Upcoming
|
||||||
|
|
||||||
|
🐞 Fixed
|
||||||
|
|
||||||
|
- [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case
|
||||||
|
there are no actions available to show.
|
||||||
|
- [[#349]](https://github.com/GetStream/stream-chat-flutter/issues/349): Fix `MessageInput` attachment render overflow error.
|
||||||
|
|
||||||
|
🔄 Changed
|
||||||
|
|
||||||
|
- Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls.
|
||||||
|
|
||||||
## 2.2.1
|
## 2.2.1
|
||||||
|
|
||||||
🛑️ Breaking Changes from `2.2.1`
|
⚠️ Deprecated
|
||||||
|
|
||||||
- `MessageSearchListView` paginationParams property is now non-nullable with a default value.
|
- `MessageSearchListView` `paginationParams` property is now deprecated in favor of `limit`.
|
||||||
```dart
|
```dart
|
||||||
|
// previous
|
||||||
paginationParams = const PaginationParams(limit: 30)
|
paginationParams = const PaginationParams(limit: 30)
|
||||||
|
|
||||||
|
// new
|
||||||
|
limit = 30
|
||||||
```
|
```
|
||||||
- `UserListView` pagination property is now non-nullable with a default value.
|
- `UserListView` `pagination` property is now deprecated in favor of `limit`.
|
||||||
```dart
|
```dart
|
||||||
|
// previous
|
||||||
pagination = const PaginationParams(limit: 30)
|
pagination = const PaginationParams(limit: 30)
|
||||||
|
|
||||||
|
// new
|
||||||
|
limit = 30
|
||||||
|
```
|
||||||
|
- `ChannelListView` `pagination` property is now deprecated in favor of `limit`.
|
||||||
|
```dart
|
||||||
|
// previous
|
||||||
|
pagination = const PaginationParams(limit: 30)
|
||||||
|
|
||||||
|
// new
|
||||||
|
limit = 30
|
||||||
|
```
|
||||||
|
|
||||||
|
🔄 Changed
|
||||||
|
|
||||||
|
- `UserListViewCore` filter property now has a default value.
|
||||||
|
```dart
|
||||||
|
filter = const Filter.empty()
|
||||||
```
|
```
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
- Fixed `MessageSearchListView` pagination.
|
- Fixed `MessageSearchListView` pagination.
|
||||||
|
- Fixed `MessageWidget` attachment tap callbacks.
|
||||||
|
|
||||||
## 2.2.1
|
## 2.2.1
|
||||||
|
|
||||||
|
|||||||
@@ -19,42 +19,36 @@ class AttachmentTitle extends StatelessWidget {
|
|||||||
final Attachment attachment;
|
final Attachment attachment;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => GestureDetector(
|
Widget build(BuildContext context) {
|
||||||
onTap: () {
|
final normalizedTitleLink = attachment.titleLink?.replaceFirst(
|
||||||
if (attachment.titleLink != null) {
|
RegExp(r'https?://(www\.)?'),
|
||||||
launchURL(context, attachment.titleLink);
|
'',
|
||||||
}
|
);
|
||||||
},
|
return GestureDetector(
|
||||||
child: Padding(
|
onTap: () {
|
||||||
padding: const EdgeInsets.all(8),
|
final titleLink = attachment.titleLink;
|
||||||
child: Column(
|
if (titleLink != null) launchURL(context, titleLink);
|
||||||
mainAxisSize: MainAxisSize.min,
|
},
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
child: Padding(
|
||||||
children: <Widget>[
|
padding: const EdgeInsets.all(8),
|
||||||
if (attachment.title != null)
|
child: Column(
|
||||||
Text(
|
mainAxisSize: MainAxisSize.min,
|
||||||
attachment.title!,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
overflow: TextOverflow.ellipsis,
|
children: <Widget>[
|
||||||
style: messageTheme.messageTextStyle?.copyWith(
|
if (attachment.title != null)
|
||||||
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
Text(
|
||||||
fontWeight: FontWeight.bold,
|
attachment.title!,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: messageTheme.messageTextStyle?.copyWith(
|
||||||
|
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
if (attachment.titleLink != null ||
|
),
|
||||||
attachment.ogScrapeUrl != null)
|
if (normalizedTitleLink != null)
|
||||||
Text(
|
Text(normalizedTitleLink, style: messageTheme.messageTextStyle),
|
||||||
Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl!)
|
],
|
||||||
.authority
|
|
||||||
.split('.')
|
|
||||||
.reversed
|
|
||||||
.take(2)
|
|
||||||
.toList()
|
|
||||||
.reversed
|
|
||||||
.join('.'),
|
|
||||||
style: messageTheme.messageTextStyle,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,7 +258,8 @@ class FileAttachment extends AttachmentWidget {
|
|||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
splashRadius: 16,
|
splashRadius: 16,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
launchURL(context, attachment.assetUrl);
|
final assetUrl = attachment.assetUrl;
|
||||||
|
if (assetUrl != null) launchURL(context, assetUrl);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,13 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(2),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => onAttachmentTap ?? _onImageTap(context),
|
onTap: () {
|
||||||
|
if (onAttachmentTap != null) {
|
||||||
|
onAttachmentTap?.call();
|
||||||
|
} else {
|
||||||
|
_onImageTap(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
height: size?.height,
|
height: size?.height,
|
||||||
width: size?.width,
|
width: size?.width,
|
||||||
@@ -253,21 +259,12 @@ class GiphyAttachment extends AttachmentWidget {
|
|||||||
Widget _buildSentAttachment(BuildContext context, String imageUrl) =>
|
Widget _buildSentAttachment(BuildContext context, String imageUrl) =>
|
||||||
SizedBox(
|
SizedBox(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () async {
|
onTap: () {
|
||||||
final res =
|
if (onAttachmentTap != null) {
|
||||||
await Navigator.push(context, MaterialPageRoute(builder: (_) {
|
onAttachmentTap?.call();
|
||||||
final channel = StreamChannel.of(context).channel;
|
} else {
|
||||||
return StreamChannel(
|
_onImageTap(context);
|
||||||
channel: channel,
|
}
|
||||||
child: FullScreenMedia(
|
|
||||||
mediaAttachments: [attachment],
|
|
||||||
userName: message.user?.name,
|
|
||||||
message: message,
|
|
||||||
onShowMessage: onShowMessage,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
if (res != null) onReturnAction!(res);
|
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ typedef ViewInfoCallback = void Function(Channel);
|
|||||||
/// Modify it to change the widget appearance.
|
/// Modify it to change the widget appearance.
|
||||||
class ChannelListView extends StatefulWidget {
|
class ChannelListView extends StatefulWidget {
|
||||||
/// Instantiate a new ChannelListView
|
/// Instantiate a new ChannelListView
|
||||||
const ChannelListView({
|
ChannelListView({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.filter,
|
this.filter,
|
||||||
this.sort,
|
this.sort,
|
||||||
@@ -68,9 +68,12 @@ class ChannelListView extends StatefulWidget {
|
|||||||
this.presence = false,
|
this.presence = false,
|
||||||
this.memberLimit,
|
this.memberLimit,
|
||||||
this.messageLimit,
|
this.messageLimit,
|
||||||
this.pagination = const PaginationParams(
|
@Deprecated(
|
||||||
limit: 25,
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
),
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
this.pagination,
|
||||||
|
int? limit,
|
||||||
this.onChannelTap,
|
this.onChannelTap,
|
||||||
this.onChannelLongPress,
|
this.onChannelLongPress,
|
||||||
this.channelWidget,
|
this.channelWidget,
|
||||||
@@ -92,7 +95,8 @@ class ChannelListView extends StatefulWidget {
|
|||||||
this.onDeletePressed,
|
this.onDeletePressed,
|
||||||
this.swipeActions,
|
this.swipeActions,
|
||||||
this.channelListController,
|
this.channelListController,
|
||||||
}) : super(key: key);
|
}) : limit = limit ?? pagination?.limit ?? 25,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
/// If true a default swipe to action behaviour will be added to this widget
|
/// If true a default swipe to action behaviour will be added to this widget
|
||||||
final bool swipeToAction;
|
final bool swipeToAction;
|
||||||
@@ -129,7 +133,14 @@ class ChannelListView extends StatefulWidget {
|
|||||||
/// limit: the number of channels to return (max is 30)
|
/// limit: the number of channels to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams pagination;
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
final PaginationParams? pagination;
|
||||||
|
|
||||||
|
/// The amount of channels requested per API call.
|
||||||
|
final int limit;
|
||||||
|
|
||||||
/// Function called when tapping on a channel
|
/// Function called when tapping on a channel
|
||||||
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
|
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
|
||||||
@@ -218,7 +229,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
presence: widget.presence,
|
presence: widget.presence,
|
||||||
memberLimit: widget.memberLimit,
|
memberLimit: widget.memberLimit,
|
||||||
messageLimit: widget.messageLimit,
|
messageLimit: widget.messageLimit,
|
||||||
pagination: widget.pagination,
|
limit: widget.limit,
|
||||||
channelListController: _channelListController,
|
channelListController: _channelListController,
|
||||||
listBuilder: widget.listBuilder ?? _buildListView,
|
listBuilder: widget.listBuilder ?? _buildListView,
|
||||||
emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget,
|
emptyBuilder: widget.emptyBuilder ?? _buildEmptyWidget,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class ImageGroup extends StatelessWidget {
|
|||||||
required this.size,
|
required this.size,
|
||||||
this.onReturnAction,
|
this.onReturnAction,
|
||||||
this.onShowMessage,
|
this.onShowMessage,
|
||||||
|
this.onAttachmentTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// List of attachments to show
|
/// List of attachments to show
|
||||||
@@ -23,6 +24,9 @@ class ImageGroup extends StatelessWidget {
|
|||||||
/// Callback when attachment is returned to from other screens
|
/// Callback when attachment is returned to from other screens
|
||||||
final ValueChanged<ReturnActionType>? onReturnAction;
|
final ValueChanged<ReturnActionType>? onReturnAction;
|
||||||
|
|
||||||
|
/// Callback when attachment is tapped
|
||||||
|
final void Function(Message message, Attachment attachment)? onAttachmentTap;
|
||||||
|
|
||||||
/// Message which images are attached to
|
/// Message which images are attached to
|
||||||
final Message message;
|
final Message message;
|
||||||
|
|
||||||
@@ -117,6 +121,10 @@ class ImageGroup extends StatelessWidget {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
int index,
|
int index,
|
||||||
) async {
|
) async {
|
||||||
|
if (onAttachmentTap != null) {
|
||||||
|
return onAttachmentTap!(message, images[index]);
|
||||||
|
}
|
||||||
|
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
final res = await Navigator.push(
|
final res = await Navigator.push(
|
||||||
|
|||||||
@@ -608,6 +608,8 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
crossFadeState: _actionsShrunk
|
crossFadeState: _actionsShrunk
|
||||||
? CrossFadeState.showFirst
|
? CrossFadeState.showFirst
|
||||||
: CrossFadeState.showSecond,
|
: CrossFadeState.showSecond,
|
||||||
|
firstCurve: Curves.easeOut,
|
||||||
|
secondCurve: Curves.easeIn,
|
||||||
firstChild: IconButton(
|
firstChild: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_actionsShrunk) {
|
if (_actionsShrunk) {
|
||||||
@@ -634,20 +636,17 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
!widget.showCommandsButton &&
|
!widget.showCommandsButton &&
|
||||||
widget.actions?.isNotEmpty != true
|
widget.actions?.isNotEmpty != true
|
||||||
? const Offstage()
|
? const Offstage()
|
||||||
: FittedBox(
|
: Wrap(
|
||||||
child: Row(
|
children: <Widget>[
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
if (!widget.disableAttachments)
|
||||||
children: <Widget>[
|
_buildAttachmentButton(context),
|
||||||
if (!widget.disableAttachments)
|
if (widget.showCommandsButton &&
|
||||||
_buildAttachmentButton(context),
|
widget.editMessage == null &&
|
||||||
if (widget.showCommandsButton &&
|
channel.state != null &&
|
||||||
widget.editMessage == null &&
|
channel.config?.commands.isNotEmpty == true)
|
||||||
channel.state != null &&
|
_buildCommandButton(context),
|
||||||
channel.config?.commands.isNotEmpty == true)
|
...widget.actions ?? [],
|
||||||
_buildCommandButton(context),
|
].insertBetween(const SizedBox(width: 8)),
|
||||||
...widget.actions ?? [],
|
|
||||||
].insertBetween(const SizedBox(width: 8)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -1001,108 +1000,120 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
|
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeOut,
|
||||||
height: _openFilePickerSection ? _kMinMediaPickerSize : 0,
|
height: _openFilePickerSection ? _kMinMediaPickerSize : 0,
|
||||||
child: Material(
|
child: SingleChildScrollView(
|
||||||
color: _streamChatTheme.colorTheme.inputBg,
|
child: SizedBox(
|
||||||
child: Column(
|
height: _kMinMediaPickerSize,
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Material(
|
||||||
children: [
|
color: _streamChatTheme.colorTheme.inputBg,
|
||||||
Row(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Row(
|
||||||
icon: StreamSvgIcon.pictures(
|
children: [
|
||||||
color: _getIconColor(0),
|
IconButton(
|
||||||
|
icon: StreamSvgIcon.pictures(
|
||||||
|
color: _getIconColor(0),
|
||||||
|
),
|
||||||
|
onPressed:
|
||||||
|
_attachmentContainsFile && _attachments.isNotEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
setState(() {
|
||||||
|
_filePickerIndex = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 32,
|
||||||
|
icon: StreamSvgIcon.files(
|
||||||
|
color: _getIconColor(1),
|
||||||
|
),
|
||||||
|
onPressed:
|
||||||
|
!_attachmentContainsFile && _attachments.isNotEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
pickFile(DefaultAttachmentTypes.file);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: StreamSvgIcon.camera(
|
||||||
|
color: _getIconColor(2),
|
||||||
|
),
|
||||||
|
onPressed: attachmentLimitCrossed ||
|
||||||
|
(_attachmentContainsFile &&
|
||||||
|
_attachments.isNotEmpty)
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
pickFile(DefaultAttachmentTypes.image,
|
||||||
|
camera: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
icon: StreamSvgIcon.record(
|
||||||
|
color: _getIconColor(3),
|
||||||
|
),
|
||||||
|
onPressed: attachmentLimitCrossed ||
|
||||||
|
(_attachmentContainsFile &&
|
||||||
|
_attachments.isNotEmpty)
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
pickFile(DefaultAttachmentTypes.video,
|
||||||
|
camera: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _streamChatTheme.colorTheme.barsBg,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: _attachmentContainsFile && _attachments.isNotEmpty
|
child: Center(
|
||||||
? null
|
child: Padding(
|
||||||
: () {
|
padding: const EdgeInsets.all(8),
|
||||||
setState(() {
|
child: Container(
|
||||||
_filePickerIndex = 0;
|
width: 40,
|
||||||
});
|
height: 4,
|
||||||
},
|
decoration: BoxDecoration(
|
||||||
),
|
color: _streamChatTheme.colorTheme.inputBg,
|
||||||
IconButton(
|
borderRadius: BorderRadius.circular(4),
|
||||||
iconSize: 32,
|
),
|
||||||
icon: StreamSvgIcon.files(
|
),
|
||||||
color: _getIconColor(1),
|
|
||||||
),
|
|
||||||
onPressed: !_attachmentContainsFile && _attachments.isNotEmpty
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
pickFile(DefaultAttachmentTypes.file);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: StreamSvgIcon.camera(
|
|
||||||
color: _getIconColor(2),
|
|
||||||
),
|
|
||||||
onPressed: attachmentLimitCrossed ||
|
|
||||||
(_attachmentContainsFile && _attachments.isNotEmpty)
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
pickFile(DefaultAttachmentTypes.image, camera: true);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
icon: StreamSvgIcon.record(
|
|
||||||
color: _getIconColor(3),
|
|
||||||
),
|
|
||||||
onPressed: attachmentLimitCrossed ||
|
|
||||||
(_attachmentContainsFile && _attachments.isNotEmpty)
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
pickFile(DefaultAttachmentTypes.video, camera: true);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _streamChatTheme.colorTheme.barsBg,
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(16),
|
|
||||||
topRight: Radius.circular(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Container(
|
|
||||||
width: 40,
|
|
||||||
height: 4,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _streamChatTheme.colorTheme.inputBg,
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (_openFilePickerSection)
|
||||||
|
Expanded(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _streamChatTheme.colorTheme.barsBg,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: _PickerWidget(
|
||||||
|
filePickerIndex: _filePickerIndex,
|
||||||
|
streamChatTheme: _streamChatTheme,
|
||||||
|
containsFile: _attachmentContainsFile,
|
||||||
|
selectedMedias: _attachments.keys.toList(),
|
||||||
|
onAddMoreFilesClick: pickFile,
|
||||||
|
onMediaSelected: (media) {
|
||||||
|
if (_attachments.containsKey(media.id)) {
|
||||||
|
setState(() => _attachments.remove(media.id));
|
||||||
|
} else {
|
||||||
|
_addAssetAttachment(media);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (_openFilePickerSection)
|
),
|
||||||
Expanded(
|
|
||||||
child: DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _streamChatTheme.colorTheme.barsBg,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: _PickerWidget(
|
|
||||||
filePickerIndex: _filePickerIndex,
|
|
||||||
streamChatTheme: _streamChatTheme,
|
|
||||||
containsFile: _attachmentContainsFile,
|
|
||||||
selectedMedias: _attachments.keys.toList(),
|
|
||||||
onAddMoreFilesClick: pickFile,
|
|
||||||
onMediaSelected: (media) {
|
|
||||||
if (_attachments.containsKey(media.id)) {
|
|
||||||
setState(() => _attachments.remove(media.id));
|
|
||||||
} else {
|
|
||||||
_addAssetAttachment(media);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1246,7 +1257,7 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Widget _buildReplyToMessage() {
|
Widget _buildReplyToMessage() {
|
||||||
if (!_hasQuotedMessage) return const Offstage();
|
if (!_hasQuotedMessage) return const Offstage();
|
||||||
final containsUrl = widget.quotedMessage!.attachments
|
final containsUrl = widget.quotedMessage!.attachments
|
||||||
.any((element) => element.ogScrapeUrl != null) ==
|
.any((element) => element.titleLink != null) ==
|
||||||
true;
|
true;
|
||||||
return QuotedMessageWidget(
|
return QuotedMessageWidget(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
@@ -1964,7 +1975,7 @@ class _PickerWidgetState extends State<_PickerWidget> {
|
|||||||
future: requestPermission,
|
future: requestPermission,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Offstage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.data!) {
|
if (snapshot.data!) {
|
||||||
|
|||||||
@@ -991,7 +991,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
|
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
|
||||||
|
|
||||||
final hasUrlAttachment =
|
final hasUrlAttachment =
|
||||||
message.attachments.any((it) => it.ogScrapeUrl != null) == true;
|
message.attachments.any((it) => it.titleLink != null) == true;
|
||||||
|
|
||||||
final borderSide =
|
final borderSide =
|
||||||
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
|
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
|
||||||
|
|||||||
@@ -53,12 +53,17 @@ typedef EmptyMessageSearchBuilder = Widget Function(
|
|||||||
/// Modify it to change the widget appearance.
|
/// Modify it to change the widget appearance.
|
||||||
class MessageSearchListView extends StatefulWidget {
|
class MessageSearchListView extends StatefulWidget {
|
||||||
/// Instantiate a new MessageSearchListView
|
/// Instantiate a new MessageSearchListView
|
||||||
const MessageSearchListView({
|
MessageSearchListView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.filters,
|
required this.filters,
|
||||||
this.messageQuery,
|
this.messageQuery,
|
||||||
this.sortOptions,
|
this.sortOptions,
|
||||||
this.paginationParams = const PaginationParams(limit: 30),
|
@Deprecated(
|
||||||
|
"'paginationParams' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
this.paginationParams,
|
||||||
|
int? limit,
|
||||||
this.messageFilters,
|
this.messageFilters,
|
||||||
this.separatorBuilder,
|
this.separatorBuilder,
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
@@ -71,7 +76,8 @@ class MessageSearchListView extends StatefulWidget {
|
|||||||
this.loadingBuilder,
|
this.loadingBuilder,
|
||||||
this.childBuilder,
|
this.childBuilder,
|
||||||
this.messageSearchListController,
|
this.messageSearchListController,
|
||||||
}) : super(key: key);
|
}) : limit = limit ?? paginationParams?.limit ?? 30,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
/// Message String to search on
|
/// Message String to search on
|
||||||
final String? messageQuery;
|
final String? messageQuery;
|
||||||
@@ -93,7 +99,14 @@ class MessageSearchListView extends StatefulWidget {
|
|||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams paginationParams;
|
@Deprecated(
|
||||||
|
"'paginationParams' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
final PaginationParams? paginationParams;
|
||||||
|
|
||||||
|
/// The amount of messages requested per API call.
|
||||||
|
final int limit;
|
||||||
|
|
||||||
/// The message query filters to use.
|
/// The message query filters to use.
|
||||||
/// You can query on any of the custom fields you've defined on the [Channel].
|
/// You can query on any of the custom fields you've defined on the [Channel].
|
||||||
@@ -152,7 +165,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
|||||||
filters: widget.filters,
|
filters: widget.filters,
|
||||||
sortOptions: widget.sortOptions,
|
sortOptions: widget.sortOptions,
|
||||||
messageQuery: widget.messageQuery,
|
messageQuery: widget.messageQuery,
|
||||||
paginationParams: widget.paginationParams,
|
limit: widget.limit,
|
||||||
messageFilters: widget.messageFilters,
|
messageFilters: widget.messageFilters,
|
||||||
messageSearchListController: _messageSearchListController,
|
messageSearchListController: _messageSearchListController,
|
||||||
emptyBuilder: widget.emptyBuilder ??
|
emptyBuilder: widget.emptyBuilder ??
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
this.bottomRowBuilder,
|
this.bottomRowBuilder,
|
||||||
this.deletedBottomRowBuilder,
|
this.deletedBottomRowBuilder,
|
||||||
this.onReturnAction,
|
this.onReturnAction,
|
||||||
Map<String, AttachmentBuilder>? customAttachmentBuilders,
|
this.customAttachmentBuilders,
|
||||||
this.readList,
|
this.readList,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.textPadding = const EdgeInsets.symmetric(
|
this.textPadding = const EdgeInsets.symmetric(
|
||||||
@@ -133,6 +133,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
messageTheme: messageTheme,
|
messageTheme: messageTheme,
|
||||||
onShowMessage: onShowMessage,
|
onShowMessage: onShowMessage,
|
||||||
onReturnAction: onReturnAction,
|
onReturnAction: onReturnAction,
|
||||||
|
onAttachmentTap: onAttachmentTap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
border,
|
border,
|
||||||
@@ -214,6 +215,11 @@ class MessageWidget extends StatefulWidget {
|
|||||||
),
|
),
|
||||||
onShowMessage: onShowMessage,
|
onShowMessage: onShowMessage,
|
||||||
onReturnAction: onReturnAction,
|
onReturnAction: onReturnAction,
|
||||||
|
onAttachmentTap: onAttachmentTap != null
|
||||||
|
? () {
|
||||||
|
onAttachmentTap(message, attachment);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
@@ -243,6 +249,11 @@ class MessageWidget extends StatefulWidget {
|
|||||||
mediaQueryData.size.width * 0.8,
|
mediaQueryData.size.width * 0.8,
|
||||||
mediaQueryData.size.height * 0.3,
|
mediaQueryData.size.height * 0.3,
|
||||||
),
|
),
|
||||||
|
onAttachmentTap: onAttachmentTap != null
|
||||||
|
? () {
|
||||||
|
onAttachmentTap(message, attachment);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
border,
|
border,
|
||||||
reverse,
|
reverse,
|
||||||
@@ -395,6 +406,9 @@ class MessageWidget extends StatefulWidget {
|
|||||||
/// Builder for respective attachment types
|
/// Builder for respective attachment types
|
||||||
final Map<String, AttachmentBuilder> attachmentBuilders;
|
final Map<String, AttachmentBuilder> attachmentBuilders;
|
||||||
|
|
||||||
|
/// Builder for respective attachment types (user facing builder)
|
||||||
|
final Map<String, AttachmentBuilder>? customAttachmentBuilders;
|
||||||
|
|
||||||
/// Center user avatar with bottom of the message
|
/// Center user avatar with bottom of the message
|
||||||
final bool translateUserAvatar;
|
final bool translateUserAvatar;
|
||||||
|
|
||||||
@@ -519,7 +533,7 @@ class MessageWidget extends StatefulWidget {
|
|||||||
showPinButton: showPinButton ?? this.showPinButton,
|
showPinButton: showPinButton ?? this.showPinButton,
|
||||||
showPinHighlight: showPinHighlight ?? this.showPinHighlight,
|
showPinHighlight: showPinHighlight ?? this.showPinHighlight,
|
||||||
customAttachmentBuilders:
|
customAttachmentBuilders:
|
||||||
customAttachmentBuilders ?? attachmentBuilders,
|
customAttachmentBuilders ?? this.customAttachmentBuilders,
|
||||||
translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar,
|
translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar,
|
||||||
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
|
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
|
||||||
onMessageTap: onMessageTap ?? this.onMessageTap,
|
onMessageTap: onMessageTap ?? this.onMessageTap,
|
||||||
@@ -566,12 +580,11 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
|
|
||||||
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
|
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
|
||||||
|
|
||||||
bool get hasNonUrlAttachments => widget.message.attachments
|
bool get hasNonUrlAttachments =>
|
||||||
.where((it) => it.ogScrapeUrl == null)
|
widget.message.attachments.where((it) => it.titleLink == null).isNotEmpty;
|
||||||
.isNotEmpty;
|
|
||||||
|
|
||||||
bool get hasUrlAttachments =>
|
bool get hasUrlAttachments =>
|
||||||
widget.message.attachments.any((it) => it.ogScrapeUrl != null) == true;
|
widget.message.attachments.any((it) => it.titleLink != null) == true;
|
||||||
|
|
||||||
bool get showBottomRow =>
|
bool get showBottomRow =>
|
||||||
showThreadReplyIndicator ||
|
showThreadReplyIndicator ||
|
||||||
@@ -975,9 +988,9 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
|
|
||||||
Widget _buildUrlAttachment() {
|
Widget _buildUrlAttachment() {
|
||||||
final urlAttachment = widget.message.attachments
|
final urlAttachment = widget.message.attachments
|
||||||
.firstWhere((element) => element.ogScrapeUrl != null);
|
.firstWhere((element) => element.titleLink != null);
|
||||||
|
|
||||||
final host = Uri.parse(urlAttachment.ogScrapeUrl!).host;
|
final host = Uri.parse(urlAttachment.titleLink!).host;
|
||||||
final splitList = host.split('.');
|
final splitList = host.split('.');
|
||||||
final hostName = splitList.length == 3 ? splitList[1] : splitList[0];
|
final hostName = splitList.length == 3 ? splitList[1] : splitList[0];
|
||||||
final hostDisplayName = urlAttachment.authorName?.capitalize() ??
|
final hostDisplayName = urlAttachment.authorName?.capitalize() ??
|
||||||
@@ -1143,7 +1156,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
final attachmentGroups = <String, List<Attachment>>{};
|
final attachmentGroups = <String, List<Attachment>>{};
|
||||||
|
|
||||||
widget.message.attachments
|
widget.message.attachments
|
||||||
.where((element) => element.ogScrapeUrl == null && element.type != null)
|
.where((element) => element.titleLink == null && element.type != null)
|
||||||
.forEach((e) {
|
.forEach((e) {
|
||||||
if (attachmentGroups[e.type] == null) {
|
if (attachmentGroups[e.type] == null) {
|
||||||
attachmentGroups[e.type!] = [];
|
attachmentGroups[e.type!] = [];
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
|
|
||||||
bool get _hasAttachments => message.attachments.isNotEmpty == true;
|
bool get _hasAttachments => message.attachments.isNotEmpty == true;
|
||||||
|
|
||||||
bool get _containsScrapeUrl =>
|
bool get _containsLinkAttachment =>
|
||||||
message.attachments.any((element) => element.ogScrapeUrl != null) == true;
|
message.attachments.any((element) => element.titleLink != null) == true;
|
||||||
|
|
||||||
bool get _containsText => message.text?.isNotEmpty == true;
|
bool get _containsText => message.text?.isNotEmpty == true;
|
||||||
|
|
||||||
@@ -198,9 +198,9 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
Widget _parseAttachments(BuildContext context) {
|
Widget _parseAttachments(BuildContext context) {
|
||||||
Widget child;
|
Widget child;
|
||||||
Attachment attachment;
|
Attachment attachment;
|
||||||
if (_containsScrapeUrl) {
|
if (_containsLinkAttachment) {
|
||||||
attachment = message.attachments.firstWhere(
|
attachment = message.attachments.firstWhere(
|
||||||
(element) => element.ogScrapeUrl != null,
|
(element) => element.titleLink != null,
|
||||||
);
|
);
|
||||||
child = _buildUrlAttachment(attachment);
|
child = _buildUrlAttachment(attachment);
|
||||||
} else {
|
} else {
|
||||||
@@ -280,7 +280,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Color? _getBackgroundColor(BuildContext context) {
|
Color? _getBackgroundColor(BuildContext context) {
|
||||||
if (_containsScrapeUrl) {
|
if (_containsLinkAttachment) {
|
||||||
return StreamChatTheme.of(context).colorTheme.linkBg;
|
return StreamChatTheme.of(context).colorTheme.linkBg;
|
||||||
}
|
}
|
||||||
return messageTheme.messageBackgroundColor;
|
return messageTheme.messageBackgroundColor;
|
||||||
|
|||||||
@@ -30,10 +30,8 @@ class UrlAttachment extends StatelessWidget {
|
|||||||
final chatThemeData = StreamChatTheme.of(context);
|
final chatThemeData = StreamChatTheme.of(context);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launchURL(
|
final titleLink = urlAttachment.titleLink;
|
||||||
context,
|
if (titleLink != null) launchURL(context, titleLink);
|
||||||
urlAttachment.ogScrapeUrl,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|||||||
@@ -46,12 +46,17 @@ typedef UserItemBuilder = Widget Function(BuildContext, User, bool);
|
|||||||
/// Modify it to change the widget appearance.
|
/// Modify it to change the widget appearance.
|
||||||
class UserListView extends StatefulWidget {
|
class UserListView extends StatefulWidget {
|
||||||
/// Instantiate a new UserListView
|
/// Instantiate a new UserListView
|
||||||
const UserListView({
|
UserListView({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.filter,
|
this.filter = const Filter.empty(),
|
||||||
this.sort,
|
this.sort,
|
||||||
this.presence,
|
this.presence,
|
||||||
this.pagination = const PaginationParams(limit: 30),
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
this.pagination,
|
||||||
|
int? limit,
|
||||||
this.onUserTap,
|
this.onUserTap,
|
||||||
this.onUserLongPress,
|
this.onUserLongPress,
|
||||||
this.userWidget,
|
this.userWidget,
|
||||||
@@ -71,11 +76,13 @@ class UserListView extends StatefulWidget {
|
|||||||
crossAxisCount == 1 || groupAlphabetically == false,
|
crossAxisCount == 1 || groupAlphabetically == false,
|
||||||
'Cannot group alphabetically when crossAxisCount > 1',
|
'Cannot group alphabetically when crossAxisCount > 1',
|
||||||
),
|
),
|
||||||
|
limit = limit ?? pagination?.limit ?? 30,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// The query filters to use.
|
/// The query filters to use.
|
||||||
/// You can query on any of the custom fields you've defined on the [Channel].
|
/// You can query on any of the custom fields you've defined on the [Channel].
|
||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
|
// TODO: Make it non-nullable in a future breaking release
|
||||||
final Filter? filter;
|
final Filter? filter;
|
||||||
|
|
||||||
/// The sorting used for the channels matching the filters.
|
/// The sorting used for the channels matching the filters.
|
||||||
@@ -93,7 +100,14 @@ class UserListView extends StatefulWidget {
|
|||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams pagination;
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
final PaginationParams? pagination;
|
||||||
|
|
||||||
|
/// The amount of users requested per API call.
|
||||||
|
final int limit;
|
||||||
|
|
||||||
/// Function called when tapping on a channel
|
/// Function called when tapping on a channel
|
||||||
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
|
/// By default it calls [Navigator.push] building a [MaterialPageRoute]
|
||||||
@@ -184,7 +198,7 @@ class _UserListViewState extends State<UserListView>
|
|||||||
),
|
),
|
||||||
listBuilder:
|
listBuilder:
|
||||||
widget.listBuilder ?? (context, list) => _buildListView(list),
|
widget.listBuilder ?? (context, list) => _buildListView(list),
|
||||||
pagination: widget.pagination,
|
limit: widget.limit,
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
presence: widget.presence,
|
presence: widget.presence,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import 'package:stream_chat_flutter/src/extension.dart';
|
import 'package:stream_chat_flutter/src/extension.dart';
|
||||||
|
|
||||||
/// Launch URL
|
/// Launch URL
|
||||||
Future<void> launchURL(BuildContext context, String? url) async {
|
Future<void> launchURL(BuildContext context, String url) async {
|
||||||
if (url != null && await canLaunch(url)) {
|
if (await canLaunch(url)) {
|
||||||
await launch(url);
|
await launch(url);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ void main() {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: StreamChannel(
|
body: StreamChannel(
|
||||||
channel: MockChannel(),
|
channel: MockChannel(),
|
||||||
child: const ChannelListView(),
|
child: ChannelListView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ void main() {
|
|||||||
home: Builder(
|
home: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
return const Scaffold(
|
return Scaffold(
|
||||||
body: UsersBloc(
|
body: UsersBloc(
|
||||||
child: UserListView(),
|
child: UserListView(),
|
||||||
),
|
),
|
||||||
@@ -85,7 +85,7 @@ void main() {
|
|||||||
home: Builder(
|
home: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
return const Scaffold(
|
return Scaffold(
|
||||||
body: UsersBloc(
|
body: UsersBloc(
|
||||||
child: UserListView(),
|
child: UserListView(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,19 +1,44 @@
|
|||||||
## Upcoming
|
## Upcoming
|
||||||
|
|
||||||
🛑️ Breaking Changes from `2.2.1`
|
⚠️ Deprecated
|
||||||
|
|
||||||
- `MessageSearchListViewCore` paginationParams property is now non-nullable with a default value.
|
- `MessageSearchListViewCore` `paginationParams` property is now deprecated in favor of `limit`.
|
||||||
```dart
|
```dart
|
||||||
|
// previous
|
||||||
paginationParams = const PaginationParams(limit: 30)
|
paginationParams = const PaginationParams(limit: 30)
|
||||||
|
|
||||||
|
// new
|
||||||
|
limit = 30
|
||||||
```
|
```
|
||||||
- `UserListViewCore` pagination property is now non-nullable with a default value.
|
- `UserListViewCore` `pagination` property is now deprecated in favor of `limit`.
|
||||||
```dart
|
```dart
|
||||||
|
// previous
|
||||||
pagination = const PaginationParams(limit: 30)
|
pagination = const PaginationParams(limit: 30)
|
||||||
|
|
||||||
|
// new
|
||||||
|
limit = 30
|
||||||
|
```
|
||||||
|
- `ChannelListViewCore` `pagination` property is now deprecated in favor of `limit`.
|
||||||
|
```dart
|
||||||
|
// previous
|
||||||
|
pagination = const PaginationParams(limit: 30)
|
||||||
|
|
||||||
|
// new
|
||||||
|
limit = 30
|
||||||
|
```
|
||||||
|
|
||||||
|
🔄 Changed
|
||||||
|
|
||||||
|
- `UserListViewCore` filter property now has a default value.
|
||||||
|
```dart
|
||||||
|
filter = const Filter.empty()
|
||||||
```
|
```
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
- Fixed `MessageSearchBloc` pagination.
|
- Fixed `MessageSearchBloc` pagination.
|
||||||
|
- [[#673]](https://github.com/GetStream/stream-chat-flutter/issues/673): Fix `Core Widgets` not getting rebuild with new
|
||||||
|
data on configuration change.
|
||||||
|
|
||||||
## 2.2.1
|
## 2.2.1
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import 'package:stream_chat_flutter_core/src/typedef.dart';
|
|||||||
/// information about the channels.
|
/// information about the channels.
|
||||||
class ChannelListCore extends StatefulWidget {
|
class ChannelListCore extends StatefulWidget {
|
||||||
/// Instantiate a new ChannelListView
|
/// Instantiate a new ChannelListView
|
||||||
const ChannelListCore({
|
ChannelListCore({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.errorBuilder,
|
required this.errorBuilder,
|
||||||
required this.emptyBuilder,
|
required this.emptyBuilder,
|
||||||
@@ -69,11 +69,15 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
this.memberLimit,
|
this.memberLimit,
|
||||||
this.messageLimit,
|
this.messageLimit,
|
||||||
this.sort,
|
this.sort,
|
||||||
this.pagination = const PaginationParams(
|
@Deprecated(
|
||||||
limit: 25,
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
),
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
this.pagination,
|
||||||
this.channelListController,
|
this.channelListController,
|
||||||
}) : super(key: key);
|
int? limit,
|
||||||
|
}) : limit = limit ?? pagination?.limit ?? 25,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
/// A [ChannelListController] allows reloading and pagination.
|
/// A [ChannelListController] allows reloading and pagination.
|
||||||
/// Use [ChannelListController.loadData] and
|
/// Use [ChannelListController.loadData] and
|
||||||
@@ -124,7 +128,14 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
/// limit: the number of channels to return (max is 30)
|
/// limit: the number of channels to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams pagination;
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
final PaginationParams? pagination;
|
||||||
|
|
||||||
|
/// The amount of channels requested per API call.
|
||||||
|
final int limit;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ChannelListCoreState createState() => ChannelListCoreState();
|
ChannelListCoreState createState() => ChannelListCoreState();
|
||||||
@@ -162,7 +173,7 @@ class ChannelListCoreState extends State<ChannelListCore> {
|
|||||||
presence: widget.presence,
|
presence: widget.presence,
|
||||||
memberLimit: widget.memberLimit,
|
memberLimit: widget.memberLimit,
|
||||||
messageLimit: widget.messageLimit,
|
messageLimit: widget.messageLimit,
|
||||||
paginationParams: widget.pagination,
|
paginationParams: PaginationParams(limit: widget.limit),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Fetches more channels with updated pagination and updates the widget
|
/// Fetches more channels with updated pagination and updates the widget
|
||||||
@@ -174,7 +185,8 @@ class ChannelListCoreState extends State<ChannelListCore> {
|
|||||||
presence: widget.presence,
|
presence: widget.presence,
|
||||||
memberLimit: widget.memberLimit,
|
memberLimit: widget.memberLimit,
|
||||||
messageLimit: widget.messageLimit,
|
messageLimit: widget.messageLimit,
|
||||||
paginationParams: widget.pagination.copyWith(
|
paginationParams: PaginationParams(
|
||||||
|
limit: widget.limit,
|
||||||
offset: _channelsBloc.channels?.length ?? 0,
|
offset: _channelsBloc.channels?.length ?? 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -214,15 +226,14 @@ class ChannelListCoreState extends State<ChannelListCore> {
|
|||||||
void didUpdateWidget(ChannelListCore oldWidget) {
|
void didUpdateWidget(ChannelListCore oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
if (jsonEncode(widget.filter) != jsonEncode(oldWidget.filter) ||
|
||||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
||||||
widget.state != oldWidget.state ||
|
widget.state != oldWidget.state ||
|
||||||
widget.watch != oldWidget.watch ||
|
widget.watch != oldWidget.watch ||
|
||||||
widget.presence != oldWidget.presence ||
|
widget.presence != oldWidget.presence ||
|
||||||
widget.messageLimit != oldWidget.messageLimit ||
|
widget.messageLimit != oldWidget.messageLimit ||
|
||||||
widget.memberLimit != oldWidget.memberLimit ||
|
widget.memberLimit != oldWidget.memberLimit ||
|
||||||
widget.pagination.toJson().toString() !=
|
widget.limit != oldWidget.limit) {
|
||||||
oldWidget.pagination.toJson().toString()) {
|
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class MessageSearchListCore extends StatefulWidget {
|
|||||||
/// * [errorBuilder]
|
/// * [errorBuilder]
|
||||||
/// * [loadingBuilder]
|
/// * [loadingBuilder]
|
||||||
/// * [childBuilder]
|
/// * [childBuilder]
|
||||||
const MessageSearchListCore({
|
MessageSearchListCore({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.emptyBuilder,
|
required this.emptyBuilder,
|
||||||
required this.errorBuilder,
|
required this.errorBuilder,
|
||||||
@@ -47,9 +47,14 @@ class MessageSearchListCore extends StatefulWidget {
|
|||||||
required this.filters,
|
required this.filters,
|
||||||
this.messageQuery,
|
this.messageQuery,
|
||||||
this.sortOptions,
|
this.sortOptions,
|
||||||
this.paginationParams = const PaginationParams(limit: 30),
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
this.paginationParams,
|
||||||
this.messageFilters,
|
this.messageFilters,
|
||||||
this.messageSearchListController,
|
this.messageSearchListController,
|
||||||
|
int? limit,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
messageQuery != null || messageFilters != null,
|
messageQuery != null || messageFilters != null,
|
||||||
'Provide at least `query` or `messageFilters`',
|
'Provide at least `query` or `messageFilters`',
|
||||||
@@ -58,6 +63,13 @@ class MessageSearchListCore extends StatefulWidget {
|
|||||||
messageQuery == null || messageFilters == null,
|
messageQuery == null || messageFilters == null,
|
||||||
"Can't provide both `query` and `messageFilters` at the same time",
|
"Can't provide both `query` and `messageFilters` at the same time",
|
||||||
),
|
),
|
||||||
|
assert(
|
||||||
|
paginationParams?.offset == null ||
|
||||||
|
paginationParams?.offset == 0 ||
|
||||||
|
sortOptions == null,
|
||||||
|
'Cannot specify `offset` with `sortOptions` parameter',
|
||||||
|
),
|
||||||
|
limit = limit ?? paginationParams?.limit ?? 30,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// A [MessageSearchListController] allows reloading and pagination.
|
/// A [MessageSearchListController] allows reloading and pagination.
|
||||||
@@ -84,7 +96,14 @@ class MessageSearchListCore extends StatefulWidget {
|
|||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of messages to return (max is 30)
|
/// limit: the number of messages to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
final PaginationParams paginationParams;
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
final PaginationParams? paginationParams;
|
||||||
|
|
||||||
|
/// The amount of messages requested per API call.
|
||||||
|
final int limit;
|
||||||
|
|
||||||
/// The message query filters to use.
|
/// The message query filters to use.
|
||||||
/// You can query on any of the custom fields you've defined on the [Channel].
|
/// You can query on any of the custom fields you've defined on the [Channel].
|
||||||
@@ -157,19 +176,19 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
|
|||||||
filter: widget.filters,
|
filter: widget.filters,
|
||||||
sort: widget.sortOptions,
|
sort: widget.sortOptions,
|
||||||
query: widget.messageQuery,
|
query: widget.messageQuery,
|
||||||
pagination: widget.paginationParams,
|
|
||||||
messageFilter: widget.messageFilters,
|
messageFilter: widget.messageFilters,
|
||||||
|
pagination: PaginationParams(limit: widget.limit),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Fetches more messages with updated pagination and updates the widget
|
/// Fetches more messages with updated pagination and updates the widget
|
||||||
Future<void> paginateData() {
|
Future<void> paginateData() {
|
||||||
PaginationParams pagination;
|
var pagination = PaginationParams(limit: widget.limit);
|
||||||
if (widget.sortOptions != null) {
|
if (widget.sortOptions != null) {
|
||||||
pagination = widget.paginationParams.copyWith(
|
pagination = pagination.copyWith(
|
||||||
next: _messageSearchBloc?.nextId,
|
next: _messageSearchBloc?.nextId,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
pagination = widget.paginationParams.copyWith(
|
pagination = pagination.copyWith(
|
||||||
offset: _messageSearchBloc?.messageResponses?.length,
|
offset: _messageSearchBloc?.messageResponses?.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -185,13 +204,12 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(MessageSearchListCore oldWidget) {
|
void didUpdateWidget(MessageSearchListCore oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.filters.toString() != oldWidget.filters.toString() ||
|
if (jsonEncode(widget.filters) != jsonEncode(oldWidget.filters) ||
|
||||||
jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) ||
|
jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) ||
|
||||||
widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() ||
|
widget.messageQuery != oldWidget.messageQuery ||
|
||||||
widget.messageFilters?.toString() !=
|
jsonEncode(widget.messageFilters) !=
|
||||||
oldWidget.messageFilters?.toString() ||
|
jsonEncode(oldWidget.messageFilters) ||
|
||||||
widget.paginationParams.toJson().toString() !=
|
widget.limit != oldWidget.limit) {
|
||||||
oldWidget.paginationParams.toJson().toString()) {
|
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,19 +57,25 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|||||||
/// [errorBuilder] must all be supplied and not null.
|
/// [errorBuilder] must all be supplied and not null.
|
||||||
class UserListCore extends StatefulWidget {
|
class UserListCore extends StatefulWidget {
|
||||||
/// Instantiate a new [UserListCore]
|
/// Instantiate a new [UserListCore]
|
||||||
const UserListCore({
|
UserListCore({
|
||||||
required this.errorBuilder,
|
required this.errorBuilder,
|
||||||
required this.emptyBuilder,
|
required this.emptyBuilder,
|
||||||
required this.loadingBuilder,
|
required this.loadingBuilder,
|
||||||
required this.listBuilder,
|
required this.listBuilder,
|
||||||
Key? key,
|
Key? key,
|
||||||
this.filter,
|
this.filter = const Filter.empty(),
|
||||||
this.sort,
|
this.sort,
|
||||||
this.presence,
|
this.presence,
|
||||||
this.pagination = const PaginationParams(limit: 30),
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
this.pagination,
|
||||||
this.groupAlphabetically = false,
|
this.groupAlphabetically = false,
|
||||||
this.userListController,
|
this.userListController,
|
||||||
}) : super(key: key);
|
int? limit,
|
||||||
|
}) : limit = limit ?? pagination?.limit ?? 30,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
/// A [UserListController] allows reloading and pagination.
|
/// A [UserListController] allows reloading and pagination.
|
||||||
/// Use [UserListController.loadData] and [UserListController.paginateData]
|
/// Use [UserListController.loadData] and [UserListController.paginateData]
|
||||||
@@ -91,6 +97,7 @@ class UserListCore extends StatefulWidget {
|
|||||||
/// The query filters to use.
|
/// The query filters to use.
|
||||||
/// You can query on any of the custom fields you've defined on the [Channel].
|
/// You can query on any of the custom fields you've defined on the [Channel].
|
||||||
/// You can also filter other built-in channel fields.
|
/// You can also filter other built-in channel fields.
|
||||||
|
// TODO: Make it non-nullable in a future breaking release
|
||||||
final Filter? filter;
|
final Filter? filter;
|
||||||
|
|
||||||
/// The sorting used for the channels matching the filters.
|
/// The sorting used for the channels matching the filters.
|
||||||
@@ -106,7 +113,14 @@ class UserListCore extends StatefulWidget {
|
|||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams pagination;
|
@Deprecated(
|
||||||
|
"'pagination' is deprecated and shouldn't be used. "
|
||||||
|
"This property is no longer used, Please use 'limit' instead",
|
||||||
|
)
|
||||||
|
final PaginationParams? pagination;
|
||||||
|
|
||||||
|
/// The amount of users requested per API call.
|
||||||
|
final int limit;
|
||||||
|
|
||||||
/// Set it to true to group users by their first character
|
/// Set it to true to group users by their first character
|
||||||
///
|
///
|
||||||
@@ -193,7 +207,7 @@ class UserListCoreState extends State<UserListCore>
|
|||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
presence: widget.presence,
|
presence: widget.presence,
|
||||||
pagination: widget.pagination,
|
pagination: PaginationParams(limit: widget.limit),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Fetches more users with updated pagination and updates the widget
|
/// Fetches more users with updated pagination and updates the widget
|
||||||
@@ -201,7 +215,8 @@ class UserListCoreState extends State<UserListCore>
|
|||||||
filter: widget.filter,
|
filter: widget.filter,
|
||||||
sort: widget.sort,
|
sort: widget.sort,
|
||||||
presence: widget.presence,
|
presence: widget.presence,
|
||||||
pagination: widget.pagination.copyWith(
|
pagination: PaginationParams(
|
||||||
|
limit: widget.limit,
|
||||||
offset: _usersBloc!.users?.length ?? 0,
|
offset: _usersBloc!.users?.length ?? 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -209,11 +224,10 @@ class UserListCoreState extends State<UserListCore>
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(UserListCore oldWidget) {
|
void didUpdateWidget(UserListCore oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
|
if (jsonEncode(widget.filter) != jsonEncode(oldWidget.filter) ||
|
||||||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
|
||||||
widget.presence != oldWidget.presence ||
|
widget.presence != oldWidget.presence ||
|
||||||
widget.pagination.toJson().toString() !=
|
widget.limit != oldWidget.limit) {
|
||||||
oldWidget.pagination.toJson().toString()) {
|
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -508,4 +508,17 @@ void main() {
|
|||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('`widget.limit` should match `widget.pagination.limit`', () {
|
||||||
|
const pagination = PaginationParams(limit: 30);
|
||||||
|
final channelListCore = ChannelListCore(
|
||||||
|
listBuilder: (_, __) => const Offstage(),
|
||||||
|
loadingBuilder: (BuildContext context) => const Offstage(),
|
||||||
|
emptyBuilder: (BuildContext context) => const Offstage(),
|
||||||
|
errorBuilder: (BuildContext context, Object error) => const Offstage(),
|
||||||
|
pagination: pagination,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(channelListCore.limit, pagination.limit);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|||||||
import 'matchers/get_message_response_matcher.dart';
|
import 'matchers/get_message_response_matcher.dart';
|
||||||
import 'mocks.dart';
|
import 'mocks.dart';
|
||||||
|
|
||||||
const testFilter = Filter.custom(operator: '\$test', value: 'testValue');
|
const testFilter = Filter.custom(key: 'test', value: 'testValue');
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
List<GetMessageResponse> _generateMessages({
|
List<GetMessageResponse> _generateMessages({
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
|||||||
|
|
||||||
import 'mocks.dart';
|
import 'mocks.dart';
|
||||||
|
|
||||||
const testFilter = Filter.custom(operator: '\$test', value: 'testValue');
|
const testFilter = Filter.custom(key: 'test', value: 'testValue');
|
||||||
const testMessageFilter = Filter.custom(operator: '\$test', value: 'testValue');
|
const testMessageFilter = Filter.custom(key: 'test', value: 'testValue');
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
List<GetMessageResponse> _generateMessages({
|
List<GetMessageResponse> _generateMessages({
|
||||||
@@ -551,4 +551,19 @@ void main() {
|
|||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('`widget.limit` should match `widget.pagination.limit`', () {
|
||||||
|
const pagination = PaginationParams(limit: 30);
|
||||||
|
final messageSearchListCore = MessageSearchListCore(
|
||||||
|
childBuilder: (List<GetMessageResponse> messages) => const Offstage(),
|
||||||
|
loadingBuilder: (BuildContext context) => const Offstage(),
|
||||||
|
emptyBuilder: (BuildContext context) => const Offstage(),
|
||||||
|
errorBuilder: (BuildContext context, Object? error) => const Offstage(),
|
||||||
|
filters: testFilter,
|
||||||
|
messageFilters: testMessageFilter,
|
||||||
|
paginationParams: pagination,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(messageSearchListCore.limit, pagination.limit);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,4 +520,17 @@ void main() {
|
|||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('`widget.limit` should match `widget.pagination.limit`', () {
|
||||||
|
const pagination = PaginationParams(limit: 30);
|
||||||
|
final userListCore = UserListCore(
|
||||||
|
listBuilder: (_, __) => const Offstage(),
|
||||||
|
loadingBuilder: (BuildContext context) => const Offstage(),
|
||||||
|
emptyBuilder: (BuildContext context) => const Offstage(),
|
||||||
|
errorBuilder: (BuildContext context, Object error) => const Offstage(),
|
||||||
|
pagination: pagination,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(userListCore.limit, pagination.limit);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user