From 6e46b8e31a4aee3e122ebf45e9ef1a02073b9cea Mon Sep 17 00:00:00 2001 From: duong2417 Date: Sat, 16 Nov 2024 11:57:08 +0700 Subject: [PATCH] fix chat_bubble_widget: change to stf and mixin autokeppalive --- lib/_shared/widgets/chat_bubble_widget.dart | 180 +++++++++++--------- pubspec.yaml | 1 + 2 files changed, 103 insertions(+), 78 deletions(-) diff --git a/lib/_shared/widgets/chat_bubble_widget.dart b/lib/_shared/widgets/chat_bubble_widget.dart index 1882ced..c63ea18 100644 --- a/lib/_shared/widgets/chat_bubble_widget.dart +++ b/lib/_shared/widgets/chat_bubble_widget.dart @@ -1,115 +1,139 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:image_network/image_network.dart'; -class ChatBubble extends StatelessWidget { +class ChatBubble extends StatefulWidget { final bool isMine; final String message; final String? photoUrl; final String? displayName; final Map translations; - final double _iconSize = 24.0; + const ChatBubble({ + required this.isMine, + required this.message, + required this.photoUrl, + required this.displayName, + this.translations = const {}, + super.key, + }); - const ChatBubble( - {required this.isMine, - required this.message, - required this.photoUrl, - required this.displayName, - this.translations = const {}, - super.key}); + @override + State createState() => _ChatBubbleState(); +} +class _ChatBubbleState extends State + with AutomaticKeepAliveClientMixin { + final double _iconSize = 24.0; + @override + bool get wantKeepAlive => true; @override Widget build(BuildContext context) { - final List widgets = []; + super.build(context); + final List widgets = [ + _buildAvatar(), + _buildMessageBubble(context), + ]; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + widget.isMine ? MainAxisAlignment.end : MainAxisAlignment.start, + children: widget.isMine ? widgets.reversed.toList() : widgets, + ), + ); + } - // user avatar - widgets.add(Padding( + Widget _buildAvatar() { + return Padding( padding: const EdgeInsets.all(8.0), child: ClipRRect( borderRadius: BorderRadius.circular(_iconSize), - child: photoUrl == null + child: widget.photoUrl == null ? const _DefaultPersonWidget() - : ImageNetwork( - image: photoUrl!, + : CachedNetworkImage( + imageUrl: widget.photoUrl!, width: _iconSize, height: _iconSize, - fitAndroidIos: BoxFit.fitWidth, - fitWeb: BoxFitWeb.contain, - onError: const _DefaultPersonWidget(), - onLoading: const _DefaultPersonWidget()), + fit: BoxFit.fitWidth, + placeholder: (context, url) => const _DefaultPersonWidget(), + errorWidget: (context, url, error) => + const _DefaultPersonWidget(), + ), ), - )); + ); + } - // message bubble - widgets.add(Container( + Widget _buildMessageBubble(BuildContext context) { + return Container( constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.8), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16.0), - color: isMine ? Colors.black26 : Colors.black87), + borderRadius: BorderRadius.circular(16.0), + color: widget.isMine ? Colors.black26 : Colors.black87, + ), padding: const EdgeInsets.all(8.0), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: - isMine ? CrossAxisAlignment.end : CrossAxisAlignment.start, + widget.isMine ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - // display name - Text( - displayName ?? 'Unknown', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: isMine ? Colors.black87 : Colors.grey, - fontWeight: FontWeight.bold), - ), - // original language - Text( - message, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Colors.white), - ), - // english version (if there is) - if (translations.isNotEmpty) - ...translations.entries - .where( - (element) => element.key != 'original', - ) - .map( - (e) => Text.rich( - TextSpan(children: [ - TextSpan( - text: '${e.key} ', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - fontWeight: FontWeight.bold, - color: - isMine ? Colors.black87 : Colors.grey)), - TextSpan( - text: e.value, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontStyle: FontStyle.italic, - color: isMine ? Colors.black87 : Colors.grey), - ) - ]), - textAlign: isMine ? TextAlign.right : TextAlign.left, - ), - ) + _buildDisplayName(context), + _buildOriginalMessage(context), + if (widget.translations.isNotEmpty) ..._buildTranslations(context), ], ), - )); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - isMine ? MainAxisAlignment.end : MainAxisAlignment.start, - children: isMine ? widgets.reversed.toList() : widgets, - ), ); } + + Widget _buildDisplayName(BuildContext context) { + return Text( + widget.displayName ?? 'Unknown', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: widget.isMine ? Colors.black87 : Colors.grey, + fontWeight: FontWeight.bold, + ), + ); + } + + Widget _buildOriginalMessage(BuildContext context) { + return Text( + widget.message, + style: + Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.white), + ); + } + + List _buildTranslations(BuildContext context) { + return widget.translations.entries + .where((element) => element.key != 'original') + .map( + (e) => Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${e.key} ', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + fontWeight: FontWeight.bold, + color: widget.isMine ? Colors.black87 : Colors.grey, + ), + ), + TextSpan( + text: e.value, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + fontStyle: FontStyle.italic, + color: widget.isMine ? Colors.black87 : Colors.grey, + ), + ), + ], + ), + textAlign: widget.isMine ? TextAlign.right : TextAlign.left, + ), + ) + .toList(); + } } class _DefaultPersonWidget extends StatelessWidget { diff --git a/pubspec.yaml b/pubspec.yaml index b74a4eb..bf67c92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: bloc_test: ^9.1.7 + cached_network_image: ^3.4.1 cloud_firestore: ^4.17.5 equatable: ^2.0.5 firebase_auth: ^4.16.0