diff --git a/CHANGELOG.md b/CHANGELOG.md index 8585979..d6f6212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.3.1 + +- Added more options for styling the UI. +- Changed the way profile images are shown. +- Added an ontapUser in the chat. +- Changed the way the time is shown in the chat after a message. +- Added option to customize chat title and username chat message widget. + ## 1.2.1 - Fixed bug in the LocalChatService diff --git a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart index 97f7b7a..7a54863 100644 --- a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstory.dart @@ -31,6 +31,7 @@ Widget _chatScreenRoute( BuildContext context, ) => ChatScreen( + unreadMessageTextStyle: configuration.unreadMessageTextStyle, service: configuration.chatService, options: configuration.chatOptionsBuilder(context), onNoChats: () async => Navigator.of(context).push( @@ -84,11 +85,31 @@ Widget _chatDetailScreenRoute( String chatId, ) => ChatDetailScreen( + chatTitleBuilder: configuration.chatTitleBuilder, + usernameBuilder: configuration.usernameBuilder, + loadingWidgetBuilder: configuration.loadingWidgetBuilder, + iconDisabledColor: configuration.iconDisabledColor, pageSize: configuration.messagePageSize, options: configuration.chatOptionsBuilder(context), translations: configuration.translations, service: configuration.chatService, chatId: chatId, + textfieldBottomPadding: configuration.textfieldBottomPadding ?? 0, + onPressUserProfile: (userId) async { + if (configuration.onPressUserProfile != null) { + return configuration.onPressUserProfile?.call(); + } + return Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _chatProfileScreenRoute( + configuration, + context, + chatId, + userId, + ), + ), + ); + }, onMessageSubmit: (message) async { if (configuration.onMessageSubmit != null) { await configuration.onMessageSubmit?.call(message); diff --git a/packages/flutter_chat/lib/src/flutter_chat_userstory.dart b/packages/flutter_chat/lib/src/flutter_chat_userstory.dart index ffcf1f4..a5f6a46 100644 --- a/packages/flutter_chat/lib/src/flutter_chat_userstory.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_userstory.dart @@ -15,6 +15,7 @@ List getChatStoryRoutes( path: ChatUserStoryRoutes.chatScreen, pageBuilder: (context, state) { var chatScreen = ChatScreen( + unreadMessageTextStyle: configuration.unreadMessageTextStyle, service: configuration.chatService, options: configuration.chatOptionsBuilder(context), onNoChats: () async => @@ -53,11 +54,24 @@ List getChatStoryRoutes( pageBuilder: (context, state) { var chatId = state.pathParameters['id']; var chatDetailScreen = ChatDetailScreen( + chatTitleBuilder: configuration.chatTitleBuilder, + usernameBuilder: configuration.usernameBuilder, + loadingWidgetBuilder: configuration.loadingWidgetBuilder, + iconDisabledColor: configuration.iconDisabledColor, pageSize: configuration.messagePageSize, options: configuration.chatOptionsBuilder(context), translations: configuration.translations, service: configuration.chatService, chatId: chatId!, + textfieldBottomPadding: configuration.textfieldBottomPadding ?? 0, + onPressUserProfile: (userId) async { + if (configuration.onPressUserProfile != null) { + return configuration.onPressUserProfile?.call(); + } + return context.push( + ChatUserStoryRoutes.chatProfileScreenPath(chatId, userId), + ); + }, onMessageSubmit: (message) async { if (configuration.onMessageSubmit != null) { await configuration.onMessageSubmit?.call(message); diff --git a/packages/flutter_chat/lib/src/models/chat_configuration.dart b/packages/flutter_chat/lib/src/models/chat_configuration.dart index f2449df..fc2d95f 100644 --- a/packages/flutter_chat/lib/src/models/chat_configuration.dart +++ b/packages/flutter_chat/lib/src/models/chat_configuration.dart @@ -31,6 +31,12 @@ class ChatUserStoryConfiguration { this.afterMessageSent, this.messagePageSize = 20, this.onPressUserProfile, + this.textfieldBottomPadding = 0, + this.iconDisabledColor = Colors.grey, + this.unreadMessageTextStyle, + this.loadingWidgetBuilder, + this.usernameBuilder, + this.chatTitleBuilder, }); /// The service responsible for handling chat-related functionalities. @@ -91,4 +97,10 @@ class ChatUserStoryConfiguration { /// Callback function triggered when user profile is pressed. final Function()? onPressUserProfile; + final double? textfieldBottomPadding; + final Color? iconDisabledColor; + final TextStyle? unreadMessageTextStyle; + final Widget? Function(BuildContext context)? loadingWidgetBuilder; + final Widget Function(String userFullName)? usernameBuilder; + final Widget Function(String chatTitle)? chatTitleBuilder; } diff --git a/packages/flutter_chat/pubspec.yaml b/packages/flutter_chat/pubspec.yaml index baef44a..88055b4 100644 --- a/packages/flutter_chat/pubspec.yaml +++ b/packages/flutter_chat/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_chat description: A new Flutter package project. -version: 1.2.1 +version: 1.3.1 publish_to: none @@ -20,17 +20,17 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_chat path: packages/flutter_chat_view - ref: 1.2.1 + ref: 1.3.1 flutter_chat_interface: git: url: https://github.com/Iconica-Development/flutter_chat path: packages/flutter_chat_interface - ref: 1.2.1 + ref: 1.3.1 flutter_chat_local: git: url: https://github.com/Iconica-Development/flutter_chat path: packages/flutter_chat_local - ref: 1.2.1 + ref: 1.3.1 dev_dependencies: flutter_iconica_analysis: diff --git a/packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart b/packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart index 287d092..24915ae 100644 --- a/packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart +++ b/packages/flutter_chat_firebase/lib/service/firebase_chat_detail_service.dart @@ -238,6 +238,9 @@ class FirebaseChatDetailService onCancel: () async { await _subscription?.cancel(); _subscription = null; + _cumulativeMessages = []; + lastChat = chatId; + lastMessage = null; debugPrint('Canceling messages stream'); }, ); @@ -259,14 +262,16 @@ class FirebaseChatDetailService /// [pageSize]: The number of messages to fetch. /// [chatId]: The ID of the chat. @override - Future fetchMoreMessage(int pageSize, String chatId) async { - if (lastChat == null) { - lastChat = chatId; - } else if (lastChat != chatId) { + Future fetchMoreMessage( + int pageSize, + String chatId, + ) async { + if (lastChat != chatId) { _cumulativeMessages = []; lastChat = chatId; lastMessage = null; } + // get the x amount of last messages from the oldest message that is in // cumulative messages and add that to the list var messages = []; diff --git a/packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart b/packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart index a5a15b4..5f0abca 100644 --- a/packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart +++ b/packages/flutter_chat_firebase/lib/service/firebase_chat_overview_service.dart @@ -90,11 +90,13 @@ class FirebaseChatOverviewService implements ChatOverviewService { for (var element in event.docChanges) { var chat = element.doc.data(); if (chat == null) return; + var otherUser = await _userService.getUser( chat.users.firstWhere( (element) => element != currentUser?.id, ), ); + var unread = await _addUnreadChatSubscription(chat.id!, currentUser!.id!); diff --git a/packages/flutter_chat_firebase/pubspec.yaml b/packages/flutter_chat_firebase/pubspec.yaml index a4b259d..aca55af 100644 --- a/packages/flutter_chat_firebase/pubspec.yaml +++ b/packages/flutter_chat_firebase/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_chat_firebase description: A new Flutter package project. -version: 1.2.1 +version: 1.3.1 publish_to: none environment: @@ -23,7 +23,7 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_chat path: packages/flutter_chat_interface - ref: 1.2.1 + ref: 1.3.1 dev_dependencies: flutter_iconica_analysis: diff --git a/packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart b/packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart index 2fb39e3..6522b97 100644 --- a/packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart +++ b/packages/flutter_chat_interface/lib/src/service/chat_detail_service.dart @@ -21,8 +21,10 @@ abstract class ChatDetailService with ChangeNotifier { String chatId, ); - /// Fetches more messages for the specified chat with a given page size. - Future fetchMoreMessage(int pageSize, String chatId); + Future fetchMoreMessage( + int pageSize, + String chatId, + ); /// Retrieves the list of messages for the chat. List getMessages(); diff --git a/packages/flutter_chat_interface/pubspec.yaml b/packages/flutter_chat_interface/pubspec.yaml index b317766..8e12ada 100644 --- a/packages/flutter_chat_interface/pubspec.yaml +++ b/packages/flutter_chat_interface/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_chat_interface description: A new Flutter package project. -version: 1.2.1 +version: 1.3.1 publish_to: none environment: diff --git a/packages/flutter_chat_local/lib/service/local_chat_detail_service.dart b/packages/flutter_chat_local/lib/service/local_chat_detail_service.dart index c9c5cb7..4a9aab5 100644 --- a/packages/flutter_chat_local/lib/service/local_chat_detail_service.dart +++ b/packages/flutter_chat_local/lib/service/local_chat_detail_service.dart @@ -25,7 +25,10 @@ class LocalChatDetailService with ChangeNotifier implements ChatDetailService { late StreamSubscription? _subscription; @override - Future fetchMoreMessage(int pageSize, String chatId) async { + Future fetchMoreMessage( + int pageSize, + String chatId, + ) async { await chatOverviewService.getChatById(chatId).then((value) { _cumulativeMessages.clear(); _cumulativeMessages.addAll(value.messages!); @@ -39,7 +42,9 @@ class LocalChatDetailService with ChangeNotifier implements ChatDetailService { List getMessages() => _cumulativeMessages; @override - Stream> getMessagesStream(String chatId) { + Stream> getMessagesStream( + String chatId, + ) { _controller.onListen = () async { _subscription = chatOverviewService.getChatById(chatId).asStream().listen((event) { diff --git a/packages/flutter_chat_local/lib/service/local_chat_user_service.dart b/packages/flutter_chat_local/lib/service/local_chat_user_service.dart index 0836b48..5a0f5a7 100644 --- a/packages/flutter_chat_local/lib/service/local_chat_user_service.dart +++ b/packages/flutter_chat_local/lib/service/local_chat_user_service.dart @@ -16,10 +16,17 @@ class LocalChatUserService implements ChatUserService { lastName: 'Doe', imageUrl: 'https://picsum.photos/200/300', ), + ChatUserModel( + id: '3', + firstName: 'ico', + lastName: 'nica', + imageUrl: 'https://picsum.photos/100/200', + ), ]; @override - Future> getAllUsers() => Future.value(users); + Future> getAllUsers() => + Future.value(users.where((element) => element.id != '3').toList()); @override Future getCurrentUser() => Future.value(ChatUserModel()); diff --git a/packages/flutter_chat_local/pubspec.yaml b/packages/flutter_chat_local/pubspec.yaml index b33cb07..ec1382b 100644 --- a/packages/flutter_chat_local/pubspec.yaml +++ b/packages/flutter_chat_local/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_chat_local description: "A new Flutter package project." -version: 1.2.1 +version: 1.3.1 publish_to: none homepage: @@ -15,7 +15,7 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_chat path: packages/flutter_chat_interface - ref: 1.2.1 + ref: 1.3.1 dev_dependencies: flutter_test: diff --git a/packages/flutter_chat_view/lib/src/components/chat_bottom.dart b/packages/flutter_chat_view/lib/src/components/chat_bottom.dart index 31e74d1..1571c98 100644 --- a/packages/flutter_chat_view/lib/src/components/chat_bottom.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_bottom.dart @@ -13,6 +13,7 @@ class ChatBottom extends StatefulWidget { required this.translations, this.onPressSelectImage, this.iconColor, + this.iconDisabledColor, super.key, }); @@ -33,6 +34,7 @@ class ChatBottom extends StatefulWidget { /// The color of the icons. final Color? iconColor; + final Color? iconDisabledColor; @override State createState() => _ChatBottomState(); @@ -40,48 +42,61 @@ class ChatBottom extends StatefulWidget { class _ChatBottomState extends State { final TextEditingController _textEditingController = TextEditingController(); - + bool _isTyping = false; @override - Widget build(BuildContext context) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 17, - ), - child: SizedBox( - height: 45, - child: widget.messageInputBuilder( - _textEditingController, - Padding( - padding: const EdgeInsets.only(right: 15.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: widget.onPressSelectImage, - icon: Icon( - Icons.image, - color: widget.iconColor, - ), - ), - IconButton( - onPressed: () async { - var value = _textEditingController.text; - - if (value.isNotEmpty) { - await widget.onMessageSubmit(value); - _textEditingController.clear(); - } - }, - icon: Icon( - Icons.send, - color: widget.iconColor, - ), - ), - ], + Widget build(BuildContext context) { + _textEditingController.addListener(() { + if (_textEditingController.text.isEmpty) { + setState(() { + _isTyping = false; + }); + } else { + setState(() { + _isTyping = true; + }); + } + }); + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), + child: SizedBox( + height: 45, + child: widget.messageInputBuilder( + _textEditingController, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: widget.onPressSelectImage, + icon: Icon( + Icons.image, + color: widget.iconColor, + ), ), - ), - widget.translations, + IconButton( + disabledColor: widget.iconDisabledColor, + color: widget.iconColor, + onPressed: _isTyping + ? () async { + var value = _textEditingController.text; + + if (value.isNotEmpty) { + await widget.onMessageSubmit(value); + _textEditingController.clear(); + } + } + : null, + icon: const Icon( + Icons.send, + ), + ), + ], ), + widget.translations, ), - ); + ), + ); + } } diff --git a/packages/flutter_chat_view/lib/src/components/chat_detail_row.dart b/packages/flutter_chat_view/lib/src/components/chat_detail_row.dart index 5838096..3140518 100644 --- a/packages/flutter_chat_view/lib/src/components/chat_detail_row.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_detail_row.dart @@ -13,6 +13,8 @@ class ChatDetailRow extends StatefulWidget { required this.translations, required this.message, required this.userAvatarBuilder, + required this.onPressUserProfile, + this.usernameBuilder, this.previousMessage, this.showTime = false, super.key, @@ -29,6 +31,8 @@ class ChatDetailRow extends StatefulWidget { /// The previous chat message model. final ChatMessageModel? previousMessage; + final Function(String? userId) onPressUserProfile; + final Widget Function(String userFullName)? usernameBuilder; /// Flag indicating whether to show the time. final bool showTime; @@ -46,6 +50,10 @@ class _ChatDetailRowState extends State { widget.message.timestamp.day != widget.previousMessage?.timestamp.day; var isSameSender = widget.previousMessage == null || widget.previousMessage?.sender.id != widget.message.sender.id; + var isSameMinute = widget.previousMessage != null && + widget.message.timestamp.minute == + widget.previousMessage?.timestamp.minute; + var hasHeader = isNewDate || isSameSender; return Padding( padding: EdgeInsets.only( @@ -55,17 +63,22 @@ class _ChatDetailRowState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (isNewDate || isSameSender) ...[ - Padding( - padding: const EdgeInsets.only(left: 10.0), - child: widget.message.sender.imageUrl != null && - widget.message.sender.imageUrl!.isNotEmpty - ? ChatImage( - image: widget.message.sender.imageUrl!, - ) - : widget.userAvatarBuilder( - widget.message.sender, - 30, - ), + GestureDetector( + onTap: () => widget.onPressUserProfile( + widget.message.sender.id, + ), + child: Padding( + padding: const EdgeInsets.only(left: 10.0), + child: widget.message.sender.imageUrl != null && + widget.message.sender.imageUrl!.isNotEmpty + ? ChatImage( + image: widget.message.sender.imageUrl!, + ) + : widget.userAvatarBuilder( + widget.message.sender, + 40, + ), + ), ), ] else ...[ const SizedBox( @@ -83,16 +96,23 @@ class _ChatDetailRowState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - widget.message.sender.fullName?.toUpperCase() ?? - widget.translations.anonymousUser, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: - Theme.of(context).textTheme.labelMedium?.color, + if (widget.usernameBuilder != null) + widget.usernameBuilder!( + widget.message.sender.fullName ?? '', + ) + else + Text( + widget.message.sender.fullName?.toUpperCase() ?? + widget.translations.anonymousUser, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .textTheme + .labelMedium + ?.color, + ), ), - ), Padding( padding: const EdgeInsets.only(top: 5.0), child: Text( @@ -111,35 +131,41 @@ class _ChatDetailRowState extends State { Padding( padding: const EdgeInsets.only(top: 3.0), child: widget.message is ChatTextMessageModel - ? RichText( - text: TextSpan( - text: + ? Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( (widget.message as ChatTextMessageModel).text, - style: TextStyle( - fontSize: 16, - color: Theme.of(context) - .textTheme - .labelMedium - ?.color, + style: TextStyle( + fontSize: 16, + color: Theme.of(context) + .textTheme + .labelMedium + ?.color, + ), + ), ), - children: [ - if (widget.showTime) - TextSpan( - text: " ${_dateFormatter.format( - date: widget.message.timestamp, - showFullDate: true, - ).split(' ').last}", - style: const TextStyle( - fontSize: 12, - color: Color(0xFFBBBBBB), - ), - ) - else - const TextSpan(), - ], - ), - overflow: TextOverflow.ellipsis, - maxLines: 999, + if (widget.showTime && + !isSameMinute && + !isNewDate && + !hasHeader) + Text( + _dateFormatter + .format( + date: widget.message.timestamp, + showFullDate: true, + ) + .split(' ') + .last, + style: const TextStyle( + fontSize: 12, + color: Color(0xFFBBBBBB), + ), + textAlign: TextAlign.end, + ), + ], ) : CachedNetworkImage( imageUrl: (widget.message as ChatImageMessageModel) diff --git a/packages/flutter_chat_view/lib/src/components/chat_image.dart b/packages/flutter_chat_view/lib/src/components/chat_image.dart index d89eee4..b4f5915 100644 --- a/packages/flutter_chat_view/lib/src/components/chat_image.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_image.dart @@ -35,6 +35,7 @@ class ChatImage extends StatelessWidget { height: size, child: image.isNotEmpty ? CachedNetworkImage( + fadeInDuration: Duration.zero, imageUrl: image, fit: BoxFit.cover, ) diff --git a/packages/flutter_chat_view/lib/src/config/chat_options.dart b/packages/flutter_chat_view/lib/src/config/chat_options.dart index d8c9e43..a26656d 100644 --- a/packages/flutter_chat_view/lib/src/config/chat_options.dart +++ b/packages/flutter_chat_view/lib/src/config/chat_options.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_chat_view/flutter_chat_view.dart'; import 'package:flutter_chat_view/src/components/chat_image.dart'; import 'package:flutter_image_picker/flutter_image_picker.dart'; +import 'package:flutter_profile/flutter_profile.dart'; class ChatOptions { const ChatOptions({ @@ -69,6 +70,7 @@ Widget _createMessageInput( controller: textEditingController, decoration: InputDecoration( hintText: translations.messagePlaceholder, + suffixIcon: suffixIcon, ), ); @@ -114,8 +116,12 @@ Widget _createUserAvatar( ChatUserModel user, double size, ) => - ChatImage( - image: user.imageUrl ?? '', + Avatar( + user: User( + firstName: user.firstName, + lastName: user.lastName, + imageUrl: user.imageUrl, + ), size: size, ); Widget _createGroupAvatar( diff --git a/packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart b/packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart index dcbaa6a..34b71e8 100644 --- a/packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart +++ b/packages/flutter_chat_view/lib/src/screens/chat_detail_screen.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_chat_view/flutter_chat_view.dart'; import 'package:flutter_chat_view/src/components/chat_bottom.dart'; import 'package:flutter_chat_view/src/components/chat_detail_row.dart'; @@ -21,9 +20,15 @@ class ChatDetailScreen extends StatefulWidget { required this.service, required this.pageSize, required this.chatId, + required this.textfieldBottomPadding, + required this.onPressChatTitle, + required this.onPressUserProfile, + this.chatTitleBuilder, + this.usernameBuilder, + this.loadingWidgetBuilder, this.translations = const ChatTranslations(), - this.onPressChatTitle, this.iconColor, + this.iconDisabledColor, this.showTime = false, super.key, }); @@ -39,13 +44,20 @@ class ChatDetailScreen extends StatefulWidget { // called at the start of the screen to set the chat to read // or when a new message is received final Future Function(ChatModel chat) onReadChat; - final Function(BuildContext context, ChatModel chat)? onPressChatTitle; + final Function(BuildContext context, ChatModel chat) onPressChatTitle; /// The color of the icon buttons in the chat bottom. final Color? iconColor; final bool showTime; final ChatService service; final int pageSize; + final double textfieldBottomPadding; + final Color? iconDisabledColor; + final Function(String? userId) onPressUserProfile; + // ignore: avoid_positional_boolean_parameters + final Widget? Function(BuildContext context)? loadingWidgetBuilder; + final Widget Function(String userFullName)? usernameBuilder; + final Widget Function(String chatTitle)? chatTitleBuilder; @override State createState() => _ChatDetailScreenState(); @@ -71,7 +83,7 @@ class _ChatDetailScreenState extends State { chat = await widget.service.chatOverviewService.getChatById(widget.chatId); - if (detailRows.isEmpty) { + if (detailRows.isEmpty && context.mounted) { await widget.service.chatDetailService.fetchMoreMessage( widget.pageSize, chat!.id!, @@ -99,6 +111,8 @@ class _ChatDetailScreenState extends State { translations: widget.translations, userAvatarBuilder: widget.options.userAvatarBuilder, previousMessage: previousMessage, + onPressUserProfile: widget.onPressUserProfile, + usernameBuilder: widget.usernameBuilder, ), ); previousMessage = message; @@ -132,14 +146,13 @@ class _ChatDetailScreenState extends State { ), ).then( (image) async { + if (image == null) return; var messenger = ScaffoldMessenger.of(context) ..showSnackBar( getImageLoadingSnackbar(widget.translations), ) ..activate(); - if (image != null) { - await widget.onUploadImage(image); - } + await widget.onUploadImage(image); Future.delayed(const Duration(seconds: 1), () { messenger.hideCurrentSnackBar(); }); @@ -155,7 +168,7 @@ class _ChatDetailScreenState extends State { appBar: AppBar( centerTitle: true, title: GestureDetector( - onTap: () => widget.onPressChatTitle?.call(context, chatModel!), + onTap: () => widget.onPressChatTitle.call(context, chatModel!), child: Row( mainAxisSize: MainAxisSize.min, children: chat == null @@ -177,77 +190,100 @@ class _ChatDetailScreenState extends State { Expanded( child: Padding( padding: const EdgeInsets.only(left: 15.5), - child: Text( - (chatModel is GroupChatModel) - ? chatModel.title - : (chatModel is PersonalChatModel) - ? chatModel.user.fullName ?? - widget.translations.anonymousUser - : '', - style: const TextStyle(fontSize: 18), - ), + child: widget.chatTitleBuilder != null + ? widget.chatTitleBuilder!.call( + (chatModel is GroupChatModel) + ? chatModel.title + : (chatModel is PersonalChatModel) + ? chatModel.user.fullName ?? + widget + .translations.anonymousUser + : '', + ) + : Text( + (chatModel is GroupChatModel) + ? chatModel.title + : (chatModel is PersonalChatModel) + ? chatModel.user.fullName ?? + widget + .translations.anonymousUser + : '', + style: const TextStyle(fontSize: 18), + ), ), ), ], ), ), ), - body: Column( + body: Stack( children: [ - Expanded( - child: Listener( - onPointerMove: (event) async { - var isTop = controller.position.pixels == - controller.position.maxScrollExtent; - - if (!showIndicator && - !isTop && - controller.position.userScrollDirection == - ScrollDirection.reverse) { - setState(() { - showIndicator = true; - }); - await widget.service.chatDetailService - .fetchMoreMessage(widget.pageSize, widget.chatId); - Future.delayed(const Duration(seconds: 2), () { - if (mounted) { + Column( + children: [ + Expanded( + child: Listener( + onPointerMove: (event) async { + if (!showIndicator && + controller.offset >= + controller.position.maxScrollExtent && + !controller.position.outOfRange) { setState(() { - showIndicator = false; + showIndicator = true; + }); + await widget.service.chatDetailService + .fetchMoreMessage( + widget.pageSize, + widget.chatId, + ); + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + showIndicator = false; + }); + } }); } - }); - } - }, - child: ListView( - shrinkWrap: true, - physics: const AlwaysScrollableScrollPhysics(), - controller: controller, - reverse: true, - padding: const EdgeInsets.only(top: 24.0), - children: [ - ...detailRows, - if (showIndicator) ...[ - const SizedBox( + }, + child: ListView( + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + controller: controller, + reverse: true, + padding: const EdgeInsets.only(top: 24.0), + children: [ + ...detailRows, + ], + ), + ), + ), + if (chatModel != null) + ChatBottom( + chat: chatModel, + messageInputBuilder: widget.options.messageInputBuilder, + onPressSelectImage: onPressSelectImage, + onMessageSubmit: widget.onMessageSubmit, + translations: widget.translations, + iconColor: widget.iconColor, + iconDisabledColor: widget.iconDisabledColor, + ), + SizedBox( + height: widget.textfieldBottomPadding, + ), + ], + ), + if (showIndicator) + widget.loadingWidgetBuilder?.call(context) ?? + const Column( + children: [ + SizedBox( height: 10, ), - const Center(child: CircularProgressIndicator()), - const SizedBox( + Center(child: CircularProgressIndicator()), + SizedBox( height: 10, ), ], - ], - ), - ), - ), - if (chatModel != null) - ChatBottom( - chat: chatModel, - messageInputBuilder: widget.options.messageInputBuilder, - onPressSelectImage: onPressSelectImage, - onMessageSubmit: widget.onMessageSubmit, - translations: widget.translations, - iconColor: widget.iconColor, - ), + ), ], ), ); diff --git a/packages/flutter_chat_view/lib/src/screens/chat_screen.dart b/packages/flutter_chat_view/lib/src/screens/chat_screen.dart index f96f44d..77f8c5f 100644 --- a/packages/flutter_chat_view/lib/src/screens/chat_screen.dart +++ b/packages/flutter_chat_view/lib/src/screens/chat_screen.dart @@ -17,6 +17,7 @@ class ChatScreen extends StatefulWidget { required this.onPressChat, required this.onDeleteChat, required this.service, + this.unreadMessageTextStyle, this.onNoChats, this.deleteChatDialog, this.translations = const ChatTranslations(), @@ -50,6 +51,7 @@ class ChatScreen extends StatefulWidget { /// Disables the swipe to dismiss feature for chats that are not deletable. final bool disableDismissForPermanentChats; + final TextStyle? unreadMessageTextStyle; @override State createState() => _ChatScreenState(); @@ -86,10 +88,11 @@ class _ChatScreenState extends State { padding: const EdgeInsets.only(right: 22.0), child: Text( '${snapshot.data ?? 0} ${translations.chatsUnread}', - style: const TextStyle( - color: Color(0xFFBBBBBB), - fontSize: 14, - ), + style: widget.unreadMessageTextStyle ?? + const TextStyle( + color: Color(0xFFBBBBBB), + fontSize: 14, + ), ), ), ), diff --git a/packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart b/packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart index 07b6d79..2f6fb17 100644 --- a/packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart +++ b/packages/flutter_chat_view/lib/src/screens/new_chat_screen.dart @@ -82,6 +82,7 @@ class _NewChatScreenState extends State { onPressed: () { setState(() { _isSearching = !_isSearching; + query = ''; }); if (_isSearching) { @@ -114,12 +115,15 @@ class _NewChatScreenState extends State { var user = filteredUsers[index]; return GestureDetector( child: widget.options.chatRowContainerBuilder( - ChatRow( - avatar: widget.options.userAvatarBuilder( - user, - 40.0, + Container( + color: Colors.transparent, + child: ChatRow( + avatar: widget.options.userAvatarBuilder( + user, + 40.0, + ), + title: user.fullName ?? widget.translations.anonymousUser, ), - title: user.fullName ?? widget.translations.anonymousUser, ), ), onTap: () async { diff --git a/packages/flutter_chat_view/pubspec.yaml b/packages/flutter_chat_view/pubspec.yaml index 64b0d7c..df69cf0 100644 --- a/packages/flutter_chat_view/pubspec.yaml +++ b/packages/flutter_chat_view/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_chat_view description: A standard flutter package. -version: 1.2.1 +version: 1.3.1 publish_to: none @@ -20,7 +20,7 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_chat path: packages/flutter_chat_interface - ref: 1.2.1 + ref: 1.3.1 cached_network_image: ^3.2.2 flutter_image_picker: git: