From 70e2cb5137bbb2de1dd4c71802f4334140d64083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Badst=C3=BCbner?= Date: Mon, 17 Jun 2024 23:10:26 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8C=9F=20use=20column=20instead?= =?UTF-8?q?=20of=20stack=20to=20remove=20arbitrary=20paddings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/widgets/chat_groupedlist_widget.dart | 136 +++++++-------- lib/src/widgets/chat_list_widget.dart | 1 + lib/src/widgets/chat_view.dart | 166 ++++++++++--------- lib/src/widgets/chatview_state_widget.dart | 7 +- lib/src/widgets/custom_scroll_behavior.dart | 32 ++++ 5 files changed, 191 insertions(+), 151 deletions(-) create mode 100644 lib/src/widgets/custom_scroll_behavior.dart diff --git a/lib/src/widgets/chat_groupedlist_widget.dart b/lib/src/widgets/chat_groupedlist_widget.dart index bcbdd7e6..ce0c5cf2 100644 --- a/lib/src/widgets/chat_groupedlist_widget.dart +++ b/lib/src/widgets/chat_groupedlist_widget.dart @@ -22,6 +22,7 @@ import 'package:chatview/chatview.dart'; import 'package:chatview/src/extensions/extensions.dart'; import 'package:chatview/src/widgets/chat_view_inherited_widget.dart'; +import 'package:chatview/src/widgets/custom_scroll_behavior.dart'; import 'package:chatview/src/widgets/suggestions/suggestion_list.dart'; import 'package:chatview/src/widgets/type_indicator_widget.dart'; import 'package:flutter/material.dart'; @@ -117,29 +118,10 @@ class _ChatGroupedListWidgetState extends State bool get isEnableSwipeToSeeTime => widget.isEnableSwipeToSeeTime; - double chatTextFieldHeight = 0; - @override void initState() { super.initState(); _initializeAnimation(); - updateChatTextFieldHeight(); - } - - @override - void didUpdateWidget(covariant ChatGroupedListWidget oldWidget) { - super.didUpdateWidget(oldWidget); - updateChatTextFieldHeight(); - } - - void updateChatTextFieldHeight() { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; - setState(() { - chatTextFieldHeight = - provide?.chatTextFieldViewKey.currentContext?.size?.height ?? 10; - }); - }); } void _initializeAnimation() { @@ -176,65 +158,71 @@ class _ChatGroupedListWidgetState extends State Widget build(BuildContext context) { final suggestionsListConfig = suggestionsConfig?.listConfig ?? const SuggestionListConfig(); - return SingleChildScrollView( - reverse: true, - // When reaction popup is being appeared at that user should not scroll. - physics: showPopUp ? const NeverScrollableScrollPhysics() : null, - controller: widget.scrollController, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onHorizontalDragUpdate: (details) => isEnableSwipeToSeeTime - ? showPopUp - ? null - : _onHorizontalDrag(details) - : null, - onHorizontalDragEnd: (details) => isEnableSwipeToSeeTime - ? showPopUp - ? null - : _animationController?.reverse() - : null, - onTap: widget.onChatListTap, - child: _animationController != null - ? AnimatedBuilder( - animation: _animationController!, - builder: (context, child) { - return _chatStreamBuilder; - }, - ) - : _chatStreamBuilder, - ), - if (chatController != null) - ValueListenableBuilder( - valueListenable: chatController!.typingIndicatorNotifier, - builder: (context, value, child) => TypingIndicator( - typeIndicatorConfig: widget.typeIndicatorConfig, - chatBubbleConfig: chatBubbleConfig?.inComingChatBubbleConfig, - showIndicator: value, - ), + return ScrollConfiguration( + behavior: CustomScrollBehavior( + bottomPadding: MediaQuery.of(context).size.height, + ), + child: SingleChildScrollView( + reverse: true, + + // When reaction popup is being appeared at that user should not scroll. + physics: showPopUp ? const NeverScrollableScrollPhysics() : null, + controller: widget.scrollController, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onHorizontalDragUpdate: (details) => isEnableSwipeToSeeTime + ? showPopUp + ? null + : _onHorizontalDrag(details) + : null, + onHorizontalDragEnd: (details) => isEnableSwipeToSeeTime + ? showPopUp + ? null + : _animationController?.reverse() + : null, + onTap: widget.onChatListTap, + child: _animationController != null + ? AnimatedBuilder( + animation: _animationController!, + builder: (context, child) { + return _chatStreamBuilder; + }, + ) + : _chatStreamBuilder, ), - if (chatController != null) - Flexible( - child: Align( - alignment: suggestionsListConfig.axisAlignment.alignment, - child: ValueListenableBuilder( - valueListenable: chatController!.newSuggestions, - builder: (context, value, child) { - return SuggestionList( - suggestions: value, - ); - }, + if (chatController != null) + ValueListenableBuilder( + valueListenable: chatController!.typingIndicatorNotifier, + builder: (context, value, child) => TypingIndicator( + typeIndicatorConfig: widget.typeIndicatorConfig, + chatBubbleConfig: chatBubbleConfig?.inComingChatBubbleConfig, + showIndicator: value, + ), + ), + if (chatController != null) + Flexible( + child: Align( + alignment: suggestionsListConfig.axisAlignment.alignment, + child: ValueListenableBuilder( + valueListenable: chatController!.newSuggestions, + builder: (context, value, child) { + return SuggestionList( + suggestions: value, + ); + }, + ), ), ), - ), - // Adds bottom space to the message list, ensuring it is displayed - // above the message text field. - SizedBox( - height: chatTextFieldHeight, - ), - ], + // Adds bottom space to the message list, ensuring it is displayed + // above the message text field. + SizedBox( + height: MediaQuery.of(context).size.height, + ), + ], + ), ), ); } diff --git a/lib/src/widgets/chat_list_widget.dart b/lib/src/widgets/chat_list_widget.dart index e19c92d0..0fb7af69 100644 --- a/lib/src/widgets/chat_list_widget.dart +++ b/lib/src/widgets/chat_list_widget.dart @@ -184,6 +184,7 @@ class _ChatListWidgetState extends State valueListenable: showPopUp, builder: (_, showPopupValue, child) { return Stack( + clipBehavior: Clip.none, children: [ ChatGroupedListWidget( showPopUp: showPopupValue, diff --git a/lib/src/widgets/chat_view.dart b/lib/src/widgets/chat_view.dart index 22696ff7..9902db85 100644 --- a/lib/src/widgets/chat_view.dart +++ b/lib/src/widgets/chat_view.dart @@ -211,83 +211,97 @@ class _ChatViewState extends State children: [ if (widget.appBar != null) widget.appBar!, Expanded( - child: Stack( - children: [ - if (chatViewState.isLoading) - ChatViewStateWidget( - chatViewStateWidgetConfig: - chatViewStateConfig?.loadingWidgetConfig, - chatViewState: chatViewState, - ) - else if (chatViewState.noMessages) - ChatViewStateWidget( - chatViewStateWidgetConfig: - chatViewStateConfig?.noMessageWidgetConfig, - chatViewState: chatViewState, - onReloadButtonTap: - chatViewStateConfig?.onReloadButtonTap, - ) - else if (chatViewState.isError) - ChatViewStateWidget( - chatViewStateWidgetConfig: - chatViewStateConfig?.errorWidgetConfig, - chatViewState: chatViewState, - onReloadButtonTap: - chatViewStateConfig?.onReloadButtonTap, - ) - else if (chatViewState.hasMessages) - ValueListenableBuilder( - valueListenable: replyMessage, - builder: (_, state, child) { - return ChatListWidget( - replyMessage: state, - chatController: widget.chatController, - chatBackgroundConfig: widget.chatBackgroundConfig, - reactionPopupConfig: widget.reactionPopupConfig, - typeIndicatorConfig: widget.typeIndicatorConfig, - chatBubbleConfig: widget.chatBubbleConfig, - loadMoreData: widget.loadMoreData, - isLastPage: widget.isLastPage, - replyPopupConfig: widget.replyPopupConfig, - loadingWidget: widget.loadingWidget, - messageConfig: widget.messageConfig, - profileCircleConfig: widget.profileCircleConfig, - repliedMessageConfig: widget.repliedMessageConfig, - swipeToReplyConfig: widget.swipeToReplyConfig, - onChatListTap: widget.onChatListTap, - assignReplyMessage: (message) => _sendMessageKey - .currentState - ?.assignReplyMessage(message), - emojiPickerSheetConfig: - widget.emojiPickerSheetConfig, - ); - }, - ), - if (featureActiveConfig.enableTextField) - SendMessageWidget( - key: _sendMessageKey, - chatController: chatController, - sendMessageBuilder: widget.sendMessageBuilder, - sendMessageConfig: widget.sendMessageConfig, - backgroundColor: chatBackgroundConfig.backgroundColor, - onSendTap: (message, replyMessage, messageType) { - if (context.suggestionsConfig - ?.autoDismissOnSelection ?? - true) { - chatController.removeReplySuggestions(); - } - _onSendTap(message, replyMessage, messageType); - }, - onReplyCallback: (reply) => - replyMessage.value = reply, - onReplyCloseCallback: () => - replyMessage.value = const ReplyMessage(), - messageConfig: widget.messageConfig, - replyMessageBuilder: widget.replyMessageBuilder, - ), - ], - ), + child: [ + if (chatViewState.isLoading) + ChatViewStateWidget( + chatViewStateWidgetConfig: + chatViewStateConfig?.loadingWidgetConfig, + fallbackTitle: "Loading…", + chatViewState: chatViewState, + ) + else if (chatViewState.noMessages) + ChatViewStateWidget( + chatViewStateWidgetConfig: + chatViewStateConfig?.noMessageWidgetConfig, + fallbackTitle: "No messages yet.", + chatViewState: chatViewState, + onReloadButtonTap: + chatViewStateConfig?.onReloadButtonTap, + ) + else if (chatViewState.isError) + ChatViewStateWidget( + chatViewStateWidgetConfig: + chatViewStateConfig?.errorWidgetConfig, + fallbackTitle: "Error", + chatViewState: chatViewState, + onReloadButtonTap: + chatViewStateConfig?.onReloadButtonTap, + ) + else if (chatViewState.hasMessages) + Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + bottom: -MediaQuery.of(context).size.height, + child: ValueListenableBuilder( + valueListenable: replyMessage, + builder: (_, state, child) { + return ChatListWidget( + replyMessage: state, + chatController: widget.chatController, + chatBackgroundConfig: + widget.chatBackgroundConfig, + reactionPopupConfig: + widget.reactionPopupConfig, + typeIndicatorConfig: + widget.typeIndicatorConfig, + chatBubbleConfig: widget.chatBubbleConfig, + loadMoreData: widget.loadMoreData, + isLastPage: widget.isLastPage, + replyPopupConfig: widget.replyPopupConfig, + loadingWidget: widget.loadingWidget, + messageConfig: widget.messageConfig, + profileCircleConfig: + widget.profileCircleConfig, + repliedMessageConfig: + widget.repliedMessageConfig, + swipeToReplyConfig: widget.swipeToReplyConfig, + onChatListTap: widget.onChatListTap, + assignReplyMessage: (message) => + _sendMessageKey.currentState + ?.assignReplyMessage(message), + emojiPickerSheetConfig: + widget.emojiPickerSheetConfig, + ); + }, + ), + ) + ], + ) + else + Container(), + ][0], ), + if (featureActiveConfig.enableTextField) + SendMessageWidget( + key: _sendMessageKey, + chatController: chatController, + sendMessageBuilder: widget.sendMessageBuilder, + sendMessageConfig: widget.sendMessageConfig, + backgroundColor: Colors.transparent, + onSendTap: (message, replyMessage, messageType) { + if (context.suggestionsConfig?.autoDismissOnSelection ?? + true) { + chatController.removeReplySuggestions(); + } + _onSendTap(message, replyMessage, messageType); + }, + onReplyCallback: (reply) => replyMessage.value = reply, + onReplyCloseCallback: () => + replyMessage.value = const ReplyMessage(), + messageConfig: widget.messageConfig, + replyMessageBuilder: widget.replyMessageBuilder, + ), ], ), ); diff --git a/lib/src/widgets/chatview_state_widget.dart b/lib/src/widgets/chatview_state_widget.dart index 10de14d7..dea85345 100644 --- a/lib/src/widgets/chatview_state_widget.dart +++ b/lib/src/widgets/chatview_state_widget.dart @@ -6,6 +6,7 @@ class ChatViewStateWidget extends StatelessWidget { const ChatViewStateWidget({ Key? key, this.chatViewStateWidgetConfig, + required this.fallbackTitle, required this.chatViewState, this.onReloadButtonTap, }) : super(key: key); @@ -17,6 +18,9 @@ class ChatViewStateWidget extends StatelessWidget { /// Provides current state of chat view. final ChatViewState chatViewState; + /// Fallback if no title is given via config. + final String fallbackTitle; + /// Provides callback when user taps on reload button in error and no messages /// state. final VoidCallBack? onReloadButtonTap; @@ -31,7 +35,8 @@ class ChatViewStateWidget extends StatelessWidget { children: [ Text( (chatViewStateWidgetConfig?.title - .getChatViewStateTitle(chatViewState))!, + .getChatViewStateTitle(chatViewState)) ?? + fallbackTitle, style: chatViewStateWidgetConfig?.titleTextStyle ?? const TextStyle( fontSize: 22, diff --git a/lib/src/widgets/custom_scroll_behavior.dart b/lib/src/widgets/custom_scroll_behavior.dart new file mode 100644 index 00000000..d7c82d70 --- /dev/null +++ b/lib/src/widgets/custom_scroll_behavior.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class CustomScrollBehavior extends ScrollBehavior { + const CustomScrollBehavior({ + this.bottomPadding = 0, + }); + + /// The amount of space the scrollbar should have before reaching the bottom. + final double bottomPadding; + + @override + Widget buildScrollbar( + BuildContext context, Widget child, ScrollableDetails details) { + switch (getPlatform(context)) { + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + assert(details.controller != null); + return RawScrollbar( + padding: EdgeInsets.only( + bottom: bottomPadding, + ), + controller: details.controller, + child: child, + ); + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + return super.buildScrollbar(context, child, details); + } + } +}