From 81f67353dc94cba44a47bb58796b1a184b9b025c Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 9 Jun 2025 12:54:18 +0700 Subject: [PATCH] TF-3773 Set limit maximum line is 3 and eclipse the middle in case of too long text Signed-off-by: dab246 --- .../base/widget/clean_messages_banner.dart | 131 ++++++++++++++---- 1 file changed, 105 insertions(+), 26 deletions(-) diff --git a/lib/features/base/widget/clean_messages_banner.dart b/lib/features/base/widget/clean_messages_banner.dart index ed80372be..42b108589 100644 --- a/lib/features/base/widget/clean_messages_banner.dart +++ b/lib/features/base/widget/clean_messages_banner.dart @@ -1,4 +1,3 @@ - import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/utils/theme_utils.dart'; import 'package:flutter/gestures.dart'; @@ -20,32 +19,112 @@ class CleanMessagesBanner extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: AppColor.m3LayerDarkOutline.withOpacity(0.08), - borderRadius: const BorderRadius.all(Radius.circular(8)), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12), - margin: margin, - alignment: Alignment.center, - child: Text.rich( - TextSpan( - style: Theme.of(context).textTheme.bodySmall?.copyWith( + return LayoutBuilder(builder: (_, constraints) { + final textStyle = Theme.of(context).textTheme.bodySmall!.copyWith( color: AppColor.steelGray400, - ), - children: [ - TextSpan(text: '$message '), - TextSpan( - text: positiveAction, - style: ThemeUtils.textStyleInter700( - color: AppColor.blue700, - fontSize: 14, - ), - recognizer: TapGestureRecognizer()..onTap = onPositiveAction, - ), - ], + ); + final actionStyle = ThemeUtils.textStyleInter700( + color: AppColor.blue700, + fontSize: 14, + ); + + final actionText = ' $positiveAction'; + + const horizontalPadding = 16; + final maxWidth = constraints.maxWidth - horizontalPadding; + const maxLines = 3; + + final actionTp = TextPainter( + text: TextSpan(text: actionText, style: actionStyle), + textDirection: TextDirection.ltr, + maxLines: 1, + )..layout(maxWidth: maxWidth); + + final maxMessageWidth = maxWidth - actionTp.width; + + String finalMessage = message; + + TextPainter messageTp = _buildMessageTp( + finalMessage, + textStyle, + maxLines, + maxMessageWidth, + ); + + if (messageTp.didExceedMaxLines) { + finalMessage = _truncateMiddle( + finalMessage, + textStyle, + maxMessageWidth, + maxLines, + ); + } + + return Container( + decoration: BoxDecoration( + color: AppColor.m3LayerDarkOutline.withOpacity(0.08), + borderRadius: const BorderRadius.all(Radius.circular(8)), ), - ), - ); + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12), + margin: margin, + alignment: Alignment.center, + child: Text.rich( + TextSpan( + style: textStyle, + children: [ + TextSpan(text: '$finalMessage '), + TextSpan( + text: actionText, + style: actionStyle, + recognizer: TapGestureRecognizer()..onTap = onPositiveAction, + ), + ], + ), + maxLines: maxLines, + overflow: TextOverflow.clip, + ), + ); + }); + } + + TextPainter _buildMessageTp( + String text, + TextStyle style, + int maxLines, + double maxWidth, + ) { + return TextPainter( + text: TextSpan(text: text, style: style), + textDirection: TextDirection.ltr, + maxLines: maxLines, + )..layout(maxWidth: maxWidth); + } + + String _truncateMiddle( + String text, + TextStyle style, + double maxWidth, + int maxLines, + ) { + const ellipsis = '...'; + int left = 0; + int right = text.length; + + while (left < right) { + final mid = ((right - left) ~/ 2) + left; + final leftStr = text.substring(0, mid ~/ 2); + final rightStr = text.substring(text.length - mid ~/ 2); + final display = '$leftStr$ellipsis$rightStr'; + final tp = _buildMessageTp(display, style, maxLines, maxWidth); + if (tp.didExceedMaxLines) { + right = mid; + } else { + left = mid + 1; + } + } + + final leftStr = text.substring(0, left ~/ 2); + final rightStr = text.substring(text.length - left ~/ 2); + return '$leftStr$ellipsis$rightStr'; } }