5

My goal is to reproduce the complex layout Telegram (and a few other chat apps) use for their chat message bubbles. The bubble isn't complicated but having the text in the bubble align well with the date is proving to be ridiculously complicated:

screenshots of telegram chat bubbles wrapping perfectly

A similar post has been made and answered here, but critically it does not handle wrapping text properly (cases #1 & #2 detailed below) as the padding-right remains constant wasting a lot of screen real estate and it looks bad. (See here if that wasn't clear: 2).

I've compiled what I believe are the 3 "use cases" to reproduce this layout in the image above:

Case #1: is by far the most complicated, it appears to be a small container for the date widget taking up the minimal amount of space, perhaps a row with mainAxisSize set to min. The complicated part is in how the text is allowed to flow above and beside while wrapping naturally to avoid any overlap.

Case #2: if #1 above were to overlap, due to a text line with an almost perfect amount of characters (so as to not wrap naturally), it would transition to the layout seen in case #2 below which appears to be a column with two nested rows.

Case #3: this is by far the easiest to achieve in isolation and can be done in many ways, the simplest being a single row with two text widgets. I'm fairly certain if a solution accomplishes #1 & #2 above, this one should be free.

What I've tried: Stacking the two, with the date being wrapped in a Positioned and something like bottom: 0, right: 15. This basically only achieves Case #1 if you add padding-right of at least 25.

I've also tried RichText which has been the most promising so far, it sort of handles all cases but is far less elegant than what telegram is doing. The major downside to this is I'll still have to use a some sort of Stack to place the "sent and seen" icon check-marks since Icons can't be included in RickText spans ...so I'll still need some padding right + bottom so they don't overlap. Here's the code for that and an image below:

RichText(
  text: TextSpan(
    text: _message.textContent,
    style: DefaultTextStyle.of(context).style.merge(TextStyle(fontSize: 16)),
    children: <TextSpan>[
      TextSpan(text: ' ' + DateFormat('H:mm a').format(_message.createdDate.toLocal()).toString(),
        style: Theme.of(context).textTheme.caption.merge(
          TextStyle(
            fontSize: 10,
          )
        ),
      ),
    ],
  ),
),

enter image description here

Ideally the date would be a separate widget and the two would be flex aligned with spaceBetween so the date remains always in the bottom right corner like in the telegram images above. Oh and I'm using the bubble: ^1.1.9+1 widget for the actual chat bubbles.

altShiftDev
  • 1,564
  • 1
  • 9
  • 20
  • 1
    if you want to get the bounds of every line you need to use `TextPainter` or `Paragraph` API – pskink Nov 19 '19 at 07:36

1 Answers1

3

Found the solution and felt really dumb about how simple it is, but can't complain as it works perfectly:

Text(
  _message.textContent + '                ',
  style: DefaultTextStyle.of(context).style.merge(TextStyle(fontSize: 16)),
),

The "magic" here is by adding 30 or so spaces to the end of your message you give the perfect amount of padding to only the bottom line of a wrapping text widget. No complex or expensive repaints or layout logic needed.

Make sure to put a non-printing character at the end of your string of spaces or Flutter will perform some sort of trimRight() on your Text Widget by default and it won't work. I used U+202F, if you copy the code from here (and StackOverflow doesn't auto-remove these characters it should work).

Put the above Text widget into a Stack with your date/icon positioned bottom-right like so:

Positioned(
  width: 60,
  height: 9,
  right: 0,
  bottom: 0,
  child: Row(
    mainAxisSize: MainAxisSize.min,
    mainAxisAlignment: MainAxisAlignment.end,
    children: <Widget>[
      Text(
        DateFormat('H:mm a').format(_message.createdDate.toLocal()).toString(),
        style: Theme.of(context).textTheme.caption.merge(TextStyle(fontSize: 10)),
        textAlign: TextAlign.right,
      ),
      Padding(
        padding: const EdgeInsets.only(left: 4.0),
        child: Icon(Icons.done_all, size: 13),
      )
    ],
  ),
),
altShiftDev
  • 1,564
  • 1
  • 9
  • 20
  • Holy crap this works well! So much less complicated than other solutions posted on SO. – vipes Jan 27 '21 at 23:21
  • This does have a subtle issue though if you verge on the perfectionist like me. The additional spaces count towards the justifying of the text meaning that if you combine this with a Flexible widget, you will get some ugly right hand side padding rather than the date being right aligned under the longest last word in a multi-line text field. – vipes Feb 14 '21 at 22:32
  • @vipes got a screenshot? I can't picture it. – altShiftDev Feb 16 '21 at 14:19
  • Sure, take a look at this question I raised: https://stackoverflow.com/questions/66201124/right-aligning-message-timestamp-with-trailing-right-hand-side-of-flutter-messag – vipes Feb 17 '21 at 10:41