Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🌟 use column instead of stack to remove arbitrary paddings #196

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 62 additions & 74 deletions lib/src/widgets/chat_groupedlist_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -117,29 +118,10 @@ class _ChatGroupedListWidgetState extends State<ChatGroupedListWidget>

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;
jonasbadstuebner marked this conversation as resolved.
Show resolved Hide resolved
});
});
}

void _initializeAnimation() {
Expand Down Expand Up @@ -176,65 +158,71 @@ class _ChatGroupedListWidgetState extends State<ChatGroupedListWidget>
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,
jonasbadstuebner marked this conversation as resolved.
Show resolved Hide resolved
),
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,
jonasbadstuebner marked this conversation as resolved.
Show resolved Hide resolved
),
],
),
),
);
}
Expand Down
1 change: 1 addition & 0 deletions lib/src/widgets/chat_list_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class _ChatListWidgetState extends State<ChatListWidget>
valueListenable: showPopUp,
builder: (_, showPopupValue, child) {
return Stack(
clipBehavior: Clip.none,
jonasbadstuebner marked this conversation as resolved.
Show resolved Hide resolved
children: [
ChatGroupedListWidget(
showPopUp: showPopupValue,
Expand Down
166 changes: 90 additions & 76 deletions lib/src/widgets/chat_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,83 +211,97 @@ class _ChatViewState extends State<ChatView>
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<ReplyMessage>(
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, this should be as big as the textfield is and not some large value like screen height.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot know how large the Textfield is, because the Textfield is built after the chat list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a different solution, let me know what you think after my next push.

child: ValueListenableBuilder<ReplyMessage>(
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(),
jonasbadstuebner marked this conversation as resolved.
Show resolved Hide resolved
][0],
jonasbadstuebner marked this conversation as resolved.
Show resolved Hide resolved
),
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,
),
],
),
);
Expand Down
7 changes: 6 additions & 1 deletion lib/src/widgets/chatview_state_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -31,7 +35,8 @@ class ChatViewStateWidget extends StatelessWidget {
children: [
Text(
(chatViewStateWidgetConfig?.title
.getChatViewStateTitle(chatViewState))!,
.getChatViewStateTitle(chatViewState)) ??
fallbackTitle,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will compulsorily show the title even when the end user may not want it. Please revert this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a good argument against the current usage:

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following _TypeError was thrown building ChatViewStateWidget(dirty):                                                                                       
Null check operator used on a null value                                                                                                                       
                                                                                                                                                               
The relevant error-causing widget was:                                                                                                                         
  ChatViewStateWidget                                                                                                                                          
  ChatViewStateWidget:file:///home/jbadstuebner/Dokumente/git/github.com/jonasbadstuebner/flutter_chatview/remove-arbitrary-padding/lib/src/widgets/chat_view.dart:215:46
                                                                                                                                                               
When the exception was thrown, this was the stack:                                                                                                             
#0      ChatViewStateWidget.build (package:chatview/src/widgets/chatview_state_widget.dart:38:59)
#1      StatelessElement.build (package:flutter/src/widgets/framework.dart:5557:49)         
#2      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5487:15)                                                                   
#3      Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)                                                                                    
#4      StatelessElement.update (package:flutter/src/widgets/framework.dart:5563:5)
#5      Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)            
#6      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)                                                                   
#7      Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)                                                                                    
#8      ProxyElement.update (package:flutter/src/widgets/framework.dart:5816:5)    
#9      Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)            
#10     Element.updateChildren (package:flutter/src/widgets/framework.dart:3976:32)         
#11     MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6929:17)                                                              
#12     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)   
#13     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6776:14)
[…]

So the Text widget is always rendered, even if the value is null. So setting it to null will crash your app currently, instead of not showing an "error" or "loading" state.

I suggest we fall back to a default title and just hide the Text widget if the title is "".

style: chatViewStateWidgetConfig?.titleTextStyle ??
const TextStyle(
fontSize: 22,
Expand Down
Loading