Merge branch 'develop' into fix/indicator
This commit is contained in:
@@ -4,25 +4,27 @@
|
||||
|
||||
- `MessageListView` now allows more better control over spacing after messages using `spacingWidgetBuilder`.
|
||||
- `StreamChannel` can now fetch messages around a message ID with the `queryAroundMessage` call.
|
||||
- Added `MessageListView.keyboardDismissBehavior` property.
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [[#766]]`AttachmentActionsModal` now has customisation options for actions.
|
||||
- Fixed `MessageWidget` null errors associated with `channel.memberCount`.
|
||||
- Fixed adding attachments on web.
|
||||
- [[#767]](https://github.com/GetStream/stream-chat-flutter/issues/767): Fix `MessageInput` focus behaviour when sending messages.
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Updated Dart SDK constraints to `>=2.14.0 <3.0.0`
|
||||
- Updated Dart SDK constraints to `>=2.14.0 <3.0.0`.
|
||||
- Updated `stream_chat_flutter_core` dependency to [`3.2.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Fixed message highlight animation alignment in `MessageListView`
|
||||
- Fixed message highlight animation alignment in `MessageListView`.
|
||||
- [[#491]](https://github.com/GetStream/stream-chat-flutter/issues/491): Fix `MediaListView` showing media in wrong order.
|
||||
- Fixed `MessageListView` initialIndex not working in some cases.
|
||||
- Improved `MessageListView` rendering in case of reordering.
|
||||
- Fix image thumbnail generation when using Stream CDN
|
||||
- Fix image thumbnail generation when using Stream CDN.
|
||||
|
||||
✅ Added
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class PositionedList extends StatefulWidget {
|
||||
this.addSemanticIndexes = true,
|
||||
this.addRepaintBoundaries = true,
|
||||
this.addAutomaticKeepAlives = true,
|
||||
this.keyboardDismissBehavior,
|
||||
}) : assert((positionedIndex == 0) || (positionedIndex < itemCount),
|
||||
'positionedIndex cannot be 0 and must be smaller than itemCount'),
|
||||
super(key: key);
|
||||
@@ -134,6 +135,10 @@ class PositionedList extends StatefulWidget {
|
||||
/// See [SliverChildBuilderDelegate.addAutomaticKeepAlives].
|
||||
final bool addAutomaticKeepAlives;
|
||||
|
||||
/// [ScrollViewKeyboardDismissBehavior] the defines how this [PositionedList] will
|
||||
/// dismiss the keyboard automatically.
|
||||
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _PositionedListState();
|
||||
}
|
||||
@@ -173,6 +178,7 @@ class _PositionedListState extends State<PositionedList> {
|
||||
anchor: widget.alignment,
|
||||
center: _centerKey,
|
||||
controller: scrollController,
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
reverse: widget.reverse,
|
||||
cacheExtent: widget.cacheExtent,
|
||||
|
||||
@@ -28,9 +28,12 @@ class UnboundedCustomScrollView extends CustomScrollView {
|
||||
List<Widget> slivers = const <Widget>[],
|
||||
int? semanticChildCount,
|
||||
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
|
||||
ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior,
|
||||
}) : _anchor = anchor,
|
||||
super(
|
||||
key: key,
|
||||
keyboardDismissBehavior: keyboardDismissBehavior ??
|
||||
ScrollViewKeyboardDismissBehavior.manual,
|
||||
scrollDirection: scrollDirection,
|
||||
reverse: reverse,
|
||||
controller: controller,
|
||||
|
||||
+9
@@ -52,6 +52,7 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
this.addRepaintBoundaries = true,
|
||||
this.minCacheExtent,
|
||||
this.findChildIndexCallback,
|
||||
this.keyboardDismissBehavior,
|
||||
}) : itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?,
|
||||
separatorBuilder = null,
|
||||
super(key: key);
|
||||
@@ -77,6 +78,7 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
this.addRepaintBoundaries = true,
|
||||
this.minCacheExtent,
|
||||
this.findChildIndexCallback,
|
||||
this.keyboardDismissBehavior,
|
||||
}) : assert(separatorBuilder != null, 'seperatorBuilder cannot be null'),
|
||||
itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?,
|
||||
super(key: key);
|
||||
@@ -92,6 +94,10 @@ class ScrollablePositionedList extends StatefulWidget {
|
||||
/// index of the child element with that associated key, or null if not found.
|
||||
final ChildIndexGetter? findChildIndexCallback;
|
||||
|
||||
/// [ScrollViewKeyboardDismissBehavior] the defines how this [PositionedList] will
|
||||
/// dismiss the keyboard automatically.
|
||||
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
|
||||
|
||||
/// Number of items the [itemBuilder] can produce.
|
||||
final int itemCount;
|
||||
|
||||
@@ -344,6 +350,7 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (_) => _isTransitioning,
|
||||
child: PositionedList(
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
separatorBuilder: widget.separatorBuilder,
|
||||
itemCount: widget.itemCount,
|
||||
@@ -374,6 +381,8 @@ class _ScrollablePositionedListState extends State<ScrollablePositionedList>
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (_) => false,
|
||||
child: PositionedList(
|
||||
keyboardDismissBehavior:
|
||||
widget.keyboardDismissBehavior,
|
||||
itemBuilder: widget.itemBuilder,
|
||||
separatorBuilder: widget.separatorBuilder,
|
||||
itemCount: widget.itemCount,
|
||||
|
||||
@@ -204,6 +204,7 @@ class MessageInput extends StatefulWidget {
|
||||
this.commandButtonBuilder,
|
||||
this.customOverlays = const [],
|
||||
this.mentionAllAppUsers = false,
|
||||
this.shouldKeepFocusAfterMessage,
|
||||
}) : assert(
|
||||
initialMessage == null || editMessage == null,
|
||||
"Can't provide both `initialMessage` and `editMessage`",
|
||||
@@ -322,6 +323,8 @@ class MessageInput extends StatefulWidget {
|
||||
/// Defaults to false.
|
||||
final bool mentionAllAppUsers;
|
||||
|
||||
final bool? shouldKeepFocusAfterMessage;
|
||||
|
||||
@override
|
||||
MessageInputState createState() => MessageInputState();
|
||||
|
||||
@@ -1759,7 +1762,9 @@ class MessageInputState extends State<MessageInput> {
|
||||
return;
|
||||
}
|
||||
|
||||
final shouldUnfocus = _commandEnabled;
|
||||
var shouldKeepFocus = widget.shouldKeepFocusAfterMessage;
|
||||
|
||||
shouldKeepFocus ??= !_commandEnabled;
|
||||
|
||||
if (_commandEnabled) {
|
||||
text = '${'/${_chosenCommand!.name} '}$text';
|
||||
@@ -1822,8 +1827,10 @@ class MessageInputState extends State<MessageInput> {
|
||||
sendingFuture = channel.updateMessage(message);
|
||||
}
|
||||
|
||||
if (!shouldUnfocus) {
|
||||
if (shouldKeepFocus) {
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
} else {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
final resp = await sendingFuture;
|
||||
|
||||
@@ -211,9 +211,14 @@ class MessageListView extends StatefulWidget {
|
||||
this.reverse = true,
|
||||
this.paginationLimit = 20,
|
||||
this.paginationLoadingIndicatorBuilder,
|
||||
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
this.spacingWidgetBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
/// [ScrollViewKeyboardDismissBehavior] the defines how this [PositionedList] will
|
||||
/// dismiss the keyboard automatically.
|
||||
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
|
||||
|
||||
/// Function used to build a custom message widget
|
||||
final MessageBuilder? messageBuilder;
|
||||
|
||||
@@ -490,9 +495,6 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
childAnchor: Alignment.topCenter,
|
||||
message: statusString,
|
||||
child: LazyLoadScrollView(
|
||||
onPageScrollStart: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onStartOfPage: () async {
|
||||
_inBetweenList = false;
|
||||
if (!_upToDate) {
|
||||
@@ -518,6 +520,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
key: (initialIndex != 0 && initialAlignment != 0)
|
||||
? ValueKey('$initialIndex-$initialAlignment')
|
||||
: null,
|
||||
keyboardDismissBehavior: widget.keyboardDismissBehavior,
|
||||
itemPositionsListener: _itemPositionListener,
|
||||
initialScrollIndex: initialIndex,
|
||||
initialAlignment: initialAlignment,
|
||||
|
||||
@@ -476,7 +476,7 @@ void main() {
|
||||
_stateSetter?.call(() => limit = 6);
|
||||
|
||||
final updatedChannels = _generateChannels(mockClient, count: limit);
|
||||
final updatedPagination = pagination.copyWith(limit: limit);
|
||||
final updatedPagination = PaginationParams(limit: limit);
|
||||
when(() => mockClient.queryChannels(
|
||||
filter: any(named: 'filter'),
|
||||
sort: any(named: 'sort'),
|
||||
|
||||
@@ -518,7 +518,7 @@ void main() {
|
||||
_stateSetter?.call(() => limit = 6);
|
||||
|
||||
final updatedMessageResponseList = _generateMessages(count: limit);
|
||||
final updatedPagination = pagination.copyWith(limit: limit);
|
||||
final updatedPagination = PaginationParams(limit: limit);
|
||||
when(() => mockClient.search(
|
||||
testFilter,
|
||||
query: any(named: 'query'),
|
||||
|
||||
@@ -189,9 +189,7 @@ void main() {
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
)).called(
|
||||
2, // Fetching After messages + Fetching Before messages,
|
||||
);
|
||||
)).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -214,14 +212,10 @@ void main() {
|
||||
child: const Offstage(key: childKey),
|
||||
);
|
||||
|
||||
final beforePagination = PaginationParams(
|
||||
lessThan: initialMessageId,
|
||||
limit: 20,
|
||||
);
|
||||
|
||||
final afterPagination = PaginationParams(
|
||||
greaterThanOrEqual: initialMessageId,
|
||||
limit: 20,
|
||||
final paginationParams = PaginationParams(
|
||||
idAround: initialMessageId,
|
||||
after: 20,
|
||||
before: 20,
|
||||
);
|
||||
|
||||
when(() => mockChannel.initialized).thenAnswer((_) async => true);
|
||||
@@ -232,17 +226,7 @@ void main() {
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: beforePagination,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
)).thenAnswer((_) async => ChannelState(messages: messages));
|
||||
|
||||
when(() => mockChannel.query(
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: afterPagination,
|
||||
messagesPagination: paginationParams,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
@@ -267,17 +251,7 @@ void main() {
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: beforePagination,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
)).called(1);
|
||||
|
||||
verify(() => mockChannel.query(
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: afterPagination,
|
||||
messagesPagination: paginationParams,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
@@ -285,29 +259,15 @@ void main() {
|
||||
|
||||
_stateSetter?.call(() => initialMessageId = 'testInitialMessageId2');
|
||||
|
||||
final updatedBeforePagination = beforePagination.copyWith(
|
||||
lessThan: initialMessageId,
|
||||
);
|
||||
|
||||
final updatedAfterPagination = afterPagination.copyWith(
|
||||
greaterThanOrEqual: initialMessageId,
|
||||
final updatedPaginationParams = paginationParams.copyWith(
|
||||
idAround: initialMessageId,
|
||||
);
|
||||
|
||||
when(() => mockChannel.query(
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: updatedBeforePagination,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
)).thenAnswer((_) async => ChannelState(messages: messages));
|
||||
|
||||
when(() => mockChannel.query(
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: updatedAfterPagination,
|
||||
messagesPagination: updatedPaginationParams,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
@@ -319,17 +279,7 @@ void main() {
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: updatedBeforePagination,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
)).called(1);
|
||||
|
||||
verify(() => mockChannel.query(
|
||||
state: any(named: 'state'),
|
||||
watch: any(named: 'watch'),
|
||||
presence: any(named: 'presence'),
|
||||
messagesPagination: updatedAfterPagination,
|
||||
messagesPagination: updatedPaginationParams,
|
||||
membersPagination: any(named: 'membersPagination'),
|
||||
watchersPagination: any(named: 'watchersPagination'),
|
||||
preferOffline: any(named: 'preferOffline'),
|
||||
|
||||
@@ -496,7 +496,7 @@ void main() {
|
||||
_stateSetter?.call(() => limit = 6);
|
||||
|
||||
final updatedUsers = _generateUsers(count: limit);
|
||||
final updatedPagination = pagination.copyWith(limit: limit);
|
||||
final updatedPagination = PaginationParams(limit: limit);
|
||||
when(() => mockClient.queryUsers(
|
||||
filter: any(named: 'filter'),
|
||||
sort: any(named: 'sort'),
|
||||
|
||||
Reference in New Issue
Block a user