Right aligning message timestamp with trailing right-hand-side of flutter message text field

779

Example: Message box with Timestamp

I think you can try this way:

  1. Calculate/Simulate text and check the message is overlapping the timestamp.
  2. If it overlaps, add \n to change the line. Otherwise, return the original one.

You need the following things:

  1. LayoutBuilder: BoxConstraint above the text widget
  2. TextPainter: 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,
    ...
  )
}

enter image description here

Share:
779
vipes
Author by

vipes

Updated on December 12, 2022

Comments

  • vipes
    vipes over 1 year

    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:

    1

    And more:

    2

    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.

    enter image description here enter image description here

    • altShiftDev
      altShiftDev about 3 years
      From the Xs and checkmarks it seems you don't like it when the text bubble becomes double width due to the date being forced on it's own line, is that correct?
    • vipes
      vipes about 3 years
      Not sure about double width, but yes it's when the additional spaces create a gap on the RHS of the last line of text so that the time is no longer right aligned under the text. It's most evident on the second bad example of the second image.
  • vipes
    vipes about 3 years
    Wow, that looks awesome thanks - I really appreciate your efforts. I just need to finish the piece I'm working on currently, then I will try it out - shouldn't be too long.
  • Jens Becker
    Jens Becker about 3 years
    Thanks for the answer. Could you please share the complete code to make this work? So the complete code for the chat bubble? That would be awesome!