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 e57ebb3..dab3a77 100644 --- a/packages/flutter_chat_view/lib/src/components/chat_bottom.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_bottom.dart @@ -2,8 +2,11 @@ // // SPDX-License-Identifier: BSD-3-Clause +import "package:emoji_picker_flutter/emoji_picker_flutter.dart"; +import "package:flutter/foundation.dart" as foundation; import "package:flutter/material.dart"; import "package:flutter_chat_view/flutter_chat_view.dart"; +import "package:google_fonts/google_fonts.dart"; class ChatBottom extends StatefulWidget { const ChatBottom({ @@ -41,14 +44,32 @@ class ChatBottom extends StatefulWidget { } class _ChatBottomState extends State { - final TextEditingController _textEditingController = TextEditingController(); bool _isTyping = false; bool _isSending = false; + bool _emojiPickerShowing = false; + late final EmojiTextEditingController _emojiTextEditingController; + late final ScrollController _scrollController; + late final FocusNode _focusNode; + late final TextStyle _emojiTextStyle; + + final bool isApple = [TargetPlatform.iOS, TargetPlatform.macOS] + .contains(foundation.defaultTargetPlatform); @override - Widget build(BuildContext context) { - _textEditingController.addListener(() { - if (_textEditingController.text.isEmpty) { + void initState() { + var fontSize = 24 * (isApple ? 1.2 : 1.0); + // Define Custom Emoji Font & Text Style + _emojiTextStyle = DefaultEmojiTextStyle.copyWith( + fontFamily: GoogleFonts.notoColorEmoji().fontFamily, + fontSize: fontSize, + ); + + _emojiTextEditingController = EmojiTextEditingController(emojiTextStyle: _emojiTextStyle); + _scrollController = ScrollController(); + _focusNode = FocusNode(); + + _emojiTextEditingController.addListener(() { + if (_emojiTextEditingController.text.isEmpty) { setState(() { _isTyping = false; }); @@ -58,56 +79,123 @@ class _ChatBottomState extends State { }); } }); - 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_outlined, - color: widget.iconColor, - ), - ), - IconButton( - disabledColor: widget.iconDisabledColor, - color: widget.iconColor, - onPressed: _isTyping && !_isSending - ? () async { - setState(() { - _isSending = true; - }); - - var value = _textEditingController.text; - - if (value.isNotEmpty) { - await widget.onMessageSubmit(value); - _textEditingController.clear(); - } - - setState(() { - _isSending = false; - }); - } - : null, - icon: const Icon( - Icons.send, - ), - ), - ], - ), - widget.translations, - context, - ), - ), - ); + super.initState(); } + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), + child: Column( + children: [ + SizedBox( + height: 45, + child: widget.messageInputBuilder( + _emojiTextEditingController, + _focusNode, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + setState(() { + _emojiPickerShowing = !_emojiPickerShowing; + if (!_emojiPickerShowing) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + }); + } else { + _focusNode.unfocus(); + } + }); + }, + icon: Icon( + _emojiPickerShowing + ? Icons.keyboard + : Icons.emoji_emotions_outlined, + ), + ), + IconButton( + onPressed: widget.onPressSelectImage, + icon: Icon( + Icons.image_outlined, + color: widget.iconColor, + ), + ), + IconButton( + disabledColor: widget.iconDisabledColor, + color: widget.iconColor, + onPressed: _isTyping && !_isSending + ? () async { + setState(() { + _isSending = true; + }); + + var value = _emojiTextEditingController.text; + + if (value.isNotEmpty) { + await widget.onMessageSubmit(value); + _emojiTextEditingController.clear(); + } + + setState(() { + _isSending = false; + }); + } + : null, + icon: const Icon( + Icons.send, + ), + ), + ], + ), + widget.translations, + context, + ), + ), + Offstage( + offstage: !_emojiPickerShowing, + child: EmojiPicker( + textEditingController: _emojiTextEditingController, + scrollController: _scrollController, + config: Config( + height: 256, + checkPlatformCompatibility: true, + emojiTextStyle: _emojiTextStyle, + emojiViewConfig: const EmojiViewConfig( + backgroundColor: Colors.white, + ), + swapCategoryAndBottomBar: true, + skinToneConfig: const SkinToneConfig(), + categoryViewConfig: const CategoryViewConfig( + backgroundColor: Colors.white, + dividerColor: Colors.white, + indicatorColor: Colors.blue, + iconColorSelected: Colors.black, + iconColor: Color(0xFF8B98A0), + categoryIcons: CategoryIcons( + recentIcon: Icons.access_time_outlined, + smileyIcon: Icons.emoji_emotions_outlined, + animalIcon: Icons.cruelty_free_outlined, + foodIcon: Icons.coffee_outlined, + activityIcon: Icons.sports_soccer_outlined, + travelIcon: Icons.directions_car_filled_outlined, + objectIcon: Icons.lightbulb_outline, + symbolIcon: Icons.emoji_symbols_outlined, + flagIcon: Icons.flag_outlined, + ), + ), + bottomActionBarConfig: const BottomActionBarConfig( + backgroundColor: Colors.white, + buttonColor: Colors.white, + buttonIconColor: Color(0xFF8B98A0), + ), + ), + ), + ), + ], + ), + ); } 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 6355973..3793d28 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 @@ -3,10 +3,13 @@ // SPDX-License-Identifier: BSD-3-Clause import "package:cached_network_image/cached_network_image.dart"; +import "package:emoji_picker_flutter/emoji_picker_flutter.dart"; +import "package:flutter/foundation.dart" as foundation; 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_chat_view/src/services/date_formatter.dart"; +import "package:google_fonts/google_fonts.dart"; class ChatDetailRow extends StatefulWidget { const ChatDetailRow({ @@ -43,10 +46,19 @@ class ChatDetailRow extends StatefulWidget { class _ChatDetailRowState extends State { final DateFormatter _dateFormatter = DateFormatter(); + final _emojiUtils = EmojiPickerUtils(); @override Widget build(BuildContext context) { var theme = Theme.of(context); + var isApple = [TargetPlatform.iOS, TargetPlatform.macOS] + .contains(foundation.defaultTargetPlatform); + var fontSize = 24 * (isApple ? 1.2 : 1.0); + + var emojiTextStyle = DefaultEmojiTextStyle.copyWith( + fontFamily: GoogleFonts.notoColorEmoji().fontFamily, + fontSize: fontSize, + ); var isNewDate = widget.previousMessage != null && widget.message.timestamp.day != widget.previousMessage?.timestamp.day; @@ -135,11 +147,16 @@ class _ChatDetailRowState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( - child: Text( - (widget.message as ChatTextMessageModel).text, - style: TextStyle( - fontSize: 16, - color: theme.textTheme.labelMedium?.color, + child: RichText( + text: TextSpan( + children: _emojiUtils.setEmojiTextStyle( + (widget.message as ChatTextMessageModel) + .text, + emojiStyle: emojiTextStyle,), + style: TextStyle( + fontSize: 16, + color: theme.textTheme.labelMedium?.color, + ), ), ), ), diff --git a/packages/flutter_chat_view/lib/src/components/chat_row.dart b/packages/flutter_chat_view/lib/src/components/chat_row.dart index 547f5b8..cf0f35d 100644 --- a/packages/flutter_chat_view/lib/src/components/chat_row.dart +++ b/packages/flutter_chat_view/lib/src/components/chat_row.dart @@ -91,7 +91,7 @@ class ChatRow extends StatelessWidget { width: 20, height: 20, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, + color: theme.colorScheme.primary, shape: BoxShape.circle, ), child: Center( 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 d0e51a1..0d5849e 100644 --- a/packages/flutter_chat_view/lib/src/config/chat_options.dart +++ b/packages/flutter_chat_view/lib/src/config/chat_options.dart @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause +import "package:emoji_picker_flutter/emoji_picker_flutter.dart"; import "package:flutter/material.dart"; import "package:flutter_chat_view/flutter_chat_view.dart"; import "package:flutter_chat_view/src/components/chat_image.dart"; @@ -85,12 +86,14 @@ Widget _createNewChatButton( Widget _createMessageInput( TextEditingController textEditingController, + FocusNode focusNode, Widget suffixIcon, ChatTranslations translations, BuildContext context, ) { var theme = Theme.of(context); return TextField( + focusNode: focusNode, textCapitalization: TextCapitalization.sentences, controller: textEditingController, decoration: InputDecoration( @@ -235,7 +238,8 @@ typedef ButtonBuilder = Widget Function( ); typedef TextInputBuilder = Widget Function( - TextEditingController textEditingController, + EmojiTextEditingController textEditingController, + FocusNode focusNode, Widget suffixIcon, ChatTranslations translations, BuildContext context, diff --git a/packages/flutter_chat_view/pubspec.yaml b/packages/flutter_chat_view/pubspec.yaml index b156bbe..69ff9b4 100644 --- a/packages/flutter_chat_view/pubspec.yaml +++ b/packages/flutter_chat_view/pubspec.yaml @@ -16,6 +16,8 @@ dependencies: flutter: sdk: flutter intl: any + emoji_picker_flutter: ^2.2.0 + google_fonts: ^6.2.1 flutter_chat_interface: git: url: https://github.com/Iconica-Development/flutter_chat