Merge pull request #772 from GetStream/fix/focus

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