As I alluded to in my previous post on the subject (Multi-line flutter text field occupies all of Flexible space with ugly right padding) I'm a bit of a perfectionist. Unfortunately my flutter layout-fu isn't as strong as my ambition. I'm creating a messaging app, and I'm working on adding a timestamp to the message box. My code so far (thanks also to this answer: Complex alignment of a sub widget based on wrapping text like in the Telegram chat messenger) is this:
Code to create a row:
Widget getAppUserMessageRow() {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(width: AppState.i.chatItemUserLeftInset),
Flexible(
fit: FlexFit.loose,
child: message.cm.messageType.getMessageWidget(message),
),
],
);
}
Code to create the message box itself:
Widget build(BuildContext context) {
bool isFromAppUser = message.cm.isFromAppUser(AppState.i.activeUserId);
return Container(
padding: EdgeInsets.symmetric(
vertical: AppState.i.chatItemMessageVerticalInset),
child:
Container(
decoration: BoxDecoration(
color: isFromAppUser ? AppState.i.chatItemUserMessageBackgroundColour : Colors.white,
//.withOpacity(!message.isFromAppUser ? 0.1 : 0.8),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(isFromAppUser ? AppState.i.chatItemMessageBorderRadius : 0),
topRight: Radius.circular(isFromAppUser ? 0 : AppState.i.chatItemMessageBorderRadius),
bottomRight: Radius.circular(isFromAppUser ? AppState.i.chatItemMessageCurvedBorderRadius : AppState.i.chatItemMessageBorderRadius),
bottomLeft: Radius.circular(isFromAppUser ? AppState.i.chatItemMessageBorderRadius : AppState.i.chatItemMessageCurvedBorderRadius),
),
boxShadow: [
BoxShadow(
color: AppState.i.chatItemMessageBoxShadowColour,
spreadRadius: AppState.i.chatItemMessageBoxShadowSpreadRadius,
blurRadius: AppState.i.chatItemMessageBoxShadowBlurRadius,
offset: AppState.i.chatItemMessageBoxShadowOffset, // changes position of shadow
),
],
),
padding: EdgeInsets.symmetric(
vertical: AppState.i.chatItemMessageVerInset,
horizontal: AppState.i.chatItemMessageHorInset),
child: Stack(
children: [
Text(
// WTF? This calculates enough space for the message receipt time to wrap
message.cm.messageText + (isFromAppUser ? ' \u202F' : ' \u202F'),
style: TextStyle(
fontSize: AppState.i.chatItemMessageTextFontSize,
color:
isFromAppUser ? AppState.i.chatItemMessageUserTextFontColour : AppState.i.chatItemMessageOtherUserTextFontColour,
),
textWidthBasis: TextWidthBasis.longestLine,
),
// This Positioned item creates the message timestamp in the area gained by adding the spaces above, assuming the spaces forced
// The text field into adding an extra line.
Positioned(
width: 60,
height: 15,
right: 0,
bottom: -2,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
DateFormat.Hm().format(message.cm.messageDisplayTime(AppState.i.activeUserId)),
style: TextStyle(
fontSize: AppState.i.chatItemMessageTimeFontSize,
color: isFromAppUser ? AppState.i.chatItemMessageUserTimeFontColour : AppState.i.chatItemMessageOtherUserTimeFontColour,
),
textAlign: TextAlign.right,
),
!isFromAppUser ? Container() :
Padding(
padding: EdgeInsets.only(left: AppState.i.chatItemMessageReceiptLeftInset),
child: message.cm.getReadReceiptIcon()
)
],
),
),
],
),
)
);
}
Here are some examples of good and bad - look at the right-hand-side padding on the bad examples. The timestamp should be more right-aligned with the text above:
And more:
Adding spaces + a non-printable character to make room for the date timestamp + read receipt combo is a genius trick, but with a downside. It pushes out the right alignment when the text is close to the space that will be occupied by the timestamp. So in some cases as per the above, the box consumes unnecessary space.
So, can this be fixed, or can a different layout be used to achieve the effect I'm looking for? I can't use anything too computational, because right now the performance is pretty decent when scrolling long lists.
To make this slightly more concrete, some 'fixed' examples.
Example: Message box with Timestamp
I think you can try this way:
\n
to change the line. Otherwise, return the original one.You need the following things:
LayoutBuilder
: BoxConstraint above the text widgetTextPainter
: Simulate the layout (also need the TextStyle
, textWidthBasis
or there may affect the layout result)Here is the code:
// assume 'message' is a String need to be displayed
...
LayoutBuilder(builder: (context, constraints) {
final reserve = ' \u202F';
final originalPainter = TextPainter(
text: TextSpan(text: text, style: TextStyle(fontSize: 20)),
textDirection: TextDirection.ltr,
textWidthBasis: TextWidthBasis.longestLine,
)..layout(maxWidth: constraints.maxWidth);
final reservePainter = TextPainter(
text: TextSpan(text: text + reserve, style: TextStyle(fontSize: 20)),
textDirection: TextDirection.ltr,
textWidthBasis: TextWidthBasis.longestLine,
)..layout(maxWidth: constraints.maxWidth);
final changeLine = reservePainter.width > originalPainter.width + 0.001 || reservePainter.height > originalPainter.height + 0.001;
return Text(
changeLine ? text+'\n' : text,
...
)
}