From d703a9636f1aa20b64e5217a0eb6171f2507633a Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 13 Jun 2022 09:30:42 -0700 Subject: [PATCH 1/4] Fix Contact Message Date not displaying date for day old messages --- lib/widgets/contactrow.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index a1769f19..1f5b6e74 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -3,11 +3,8 @@ import 'dart:io'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/profile.dart'; -import 'package:cwtch/models/profileservers.dart'; import 'package:cwtch/views/contactsview.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/painting.dart'; -import 'package:cwtch/views/messageview.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -156,7 +153,7 @@ class _ContactRowState extends State { return AppLocalizations.of(context)!.conversationNotificationPolicyNever; } // If the last message was over a day ago, just state the date - if (DateTime.now().difference(date).inDays > 1) { + if (DateTime.now().difference(date).inDays > 0) { return DateFormat.yMd(Platform.localeName).format(date.toLocal()); } // Otherwise just state the time. From 3961692817a713dbb0b95da4984931da790e9b96 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 13 Jun 2022 10:06:06 -0700 Subject: [PATCH 2/4] Nicer Quoted Messages --- lib/models/messages/filemessage.dart | 3 ++ lib/models/messages/quotedmessage.dart | 11 +++++-- lib/models/messages/textmessage.dart | 5 +++- lib/widgets/filebubble.dart | 41 ++++++++++++++++---------- lib/widgets/quotedmessage.dart | 15 ++++++++-- 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index 3ba18fad..7c139822 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -64,6 +64,8 @@ class FileMessage extends Message { } return Container( alignment: Alignment.center, + width: 50, + height: 50, child: FileBubble( nameSuggestion, rootHash, @@ -71,6 +73,7 @@ class FileMessage extends Message { fileSize, isAuto: metadata.isAuto, interactive: false, + isPreview: true, )); }); } diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 3fefee0b..084953eb 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:cwtch/models/message.dart'; -import 'package:cwtch/models/messages/malformedmessage.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messagerow.dart'; import 'package:cwtch/widgets/quotedmessage.dart'; @@ -30,8 +29,14 @@ class QuotedMessage extends Message { value: this.metadata, builder: (bcontext, child) { try { - dynamic message = jsonDecode(this.content); - return Text(message["body"]); + dynamic message = jsonDecode( + this.content, + ); + var content = message["body"]; + return Text( + content, + overflow: TextOverflow.ellipsis, + ); } catch (e) { return MalformedBubble(); } diff --git a/lib/models/messages/textmessage.dart b/lib/models/messages/textmessage.dart index 01a51048..b2496f19 100644 --- a/lib/models/messages/textmessage.dart +++ b/lib/models/messages/textmessage.dart @@ -21,7 +21,10 @@ class TextMessage extends Message { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { - return Text(this.content.substring(0, min(this.content.length, 50))); + return Text( + this.content, + overflow: TextOverflow.ellipsis, + ); }); } diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index d3e1e7b2..e533eee8 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -25,8 +25,9 @@ class FileBubble extends StatefulWidget { final int fileSize; final bool interactive; final bool isAuto; + final bool isPreview; - FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.isAuto = false, this.interactive = true}); + FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.isAuto = false, this.interactive = true, this.isPreview = false}); @override FileBubbleState createState() => FileBubbleState(); @@ -44,6 +45,22 @@ class FileBubbleState extends State { super.initState(); } + Widget getPreview(context) { + return Image.file( + myFile!, + cacheWidth: (MediaQuery.of(context).size.width * 0.6).floor(), + // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... + filterQuality: FilterQuality.medium, + fit: BoxFit.scaleDown, + alignment: Alignment.center, + height: MediaQuery.of(context).size.height * 0.30, + isAntiAlias: false, + errorBuilder: (context, error, stackTrace) { + return MalformedBubble(); + }, + ); + } + @override Widget build(BuildContext context) { var fromMe = Provider.of(context).senderHandle == Provider.of(context).onion; @@ -109,6 +126,12 @@ class FileBubbleState extends State { senderDisplayStr = Provider.of(context).senderHandle; } } + + // we don't preview a non downloaded file... + if (widget.isPreview && myFile != null) { + return getPreview(context); + } + return LayoutBuilder(builder: (bcontext, constraints) { var wdgSender = Visibility( visible: widget.interactive, @@ -133,21 +156,7 @@ class FileBubbleState extends State { child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( - child: Padding( - padding: EdgeInsets.all(1.0), - child: Image.file( - myFile!, - cacheWidth: (MediaQuery.of(bcontext).size.width * 0.6).floor(), - // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... - filterQuality: FilterQuality.medium, - fit: BoxFit.scaleDown, - alignment: Alignment.center, - height: MediaQuery.of(bcontext).size.height * 0.30, - isAntiAlias: false, - errorBuilder: (context, error, stackTrace) { - return MalformedBubble(); - }, - )), + child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)), onTap: () { pop(bcontext, myFile!, wdgMessage); }, diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index 0195d3cb..220a915d 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -7,6 +7,7 @@ import 'package:cwtch/third_party/linkify/flutter_linkify.dart'; import 'package:cwtch/views/contactsview.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; @@ -94,10 +95,18 @@ class QuotedMessageBubbleState extends State { child: Container( margin: EdgeInsets.all(5), padding: EdgeInsets.all(5), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(), + height: 75, color: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor, - child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [ - Center(widthFactor: 1, child: Padding(padding: EdgeInsets.all(10.0), child: Icon(Icons.reply, size: 32, color: qTextColor))), - Center(widthFactor: 1.0, child: DefaultTextStyle(child: qMessage.getPreviewWidget(context), style: TextStyle(color: qTextColor))) + child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ + Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), + DefaultTextStyle( + textWidthBasis: TextWidthBasis.parent, + child: qMessage.getPreviewWidget(context), + style: TextStyle(color: qTextColor), + overflow: TextOverflow.fade, + ) ])))); } catch (e) { print(e); From 04c335e7a4bdc4113d5b57c0e93a4f39c3f2ec5b Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 14 Jun 2022 18:25:13 -0700 Subject: [PATCH 3/4] formatting toolbar --- lib/views/messageview.dart | 260 +++++++++++++++++++++++++++++-------- 1 file changed, 208 insertions(+), 52 deletions(-) diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index b69066f2..6030c79c 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:ui'; import 'package:crypto/crypto.dart'; import 'package:cwtch/cwtch/cwtch.dart'; import 'package:cwtch/cwtch_icons_icons.dart'; @@ -10,6 +11,7 @@ import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/messagecache.dart'; import 'package:cwtch/models/messages/quotedmessage.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/third_party/linkify/flutter_linkify.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/profileimage.dart'; @@ -44,6 +46,7 @@ class _MessageViewState extends State { ItemPositionsListener scrollListener = ItemPositionsListener.create(); File? imagePreview; bool showDown = false; + bool showPreview = false; @override void initState() { @@ -91,6 +94,7 @@ class _MessageViewState extends State { return Card(child: Center(child: Text(AppLocalizations.of(context)!.addContactFirst))); } + var showMessageFormattingPreview = Provider.of(context).isExperimentEnabled(FormattingExperiment); var showFileSharing = Provider.of(context).isExperimentEnabled(FileSharingExperiment); var appBarButtons = []; if (Provider.of(context).isOnline()) { @@ -176,11 +180,11 @@ class _MessageViewState extends State { actions: appBarButtons, ), body: Padding( - padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), + padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 182.0), child: MessageList( scrollListener, )), - bottomSheet: _buildComposeBox(), + bottomSheet: showPreview && showMessageFormattingPreview ? _buildPreviewBox() : _buildComposeBox(), )); } @@ -313,64 +317,216 @@ class _MessageViewState extends State { Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, LastMessageSeenTimeKey, DateTime.now().toIso8601String()); } - Widget _buildComposeBox() { - bool isOffline = Provider.of(context).isOnline() == false; - bool isGroup = Provider.of(context).isGroup; + Widget _buildPreviewBox() { + var showClickableLinks = Provider.of(context).isExperimentEnabled(ClickableLinksExperiment); - var charLength = ctrlrCompose.value.text.characters.length; - var expectedLength = ctrlrCompose.value.text.length; - var numberOfBytesMoreThanChar = (expectedLength - charLength); + var wdgMessage = Padding( + padding: EdgeInsets.all(8), + child: SelectableLinkify( + text: ctrlrCompose.text + '\n', + // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? + options: LinkifyOptions(messageFormatting: true, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), + linkifiers: [UrlLinkifier()], + onOpen: showClickableLinks ? null : null, + //key: Key(myKey), + style: TextStyle( + color: Provider.of(context).theme.messageFromMeTextColor, + fontSize: 16, + ), + linkStyle: TextStyle( + color: Provider.of(context).theme.messageFromMeTextColor, + fontSize: 16, + ), + codeStyle: TextStyle( + // note: these colors are flipped + fontSize: 16, + color: Provider.of(context).theme.messageFromOtherTextColor, + backgroundColor: Provider.of(context).theme.messageFromOtherBackgroundColor), + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + )); + + var showMessageFormattingPreview = Provider.of(context).isExperimentEnabled(FormattingExperiment); + var preview = showMessageFormattingPreview + ? IconButton( + icon: Icon(Icons.text_fields), + onPressed: () { + setState(() { + showPreview = false; + }); + }) + : Container(); var composeBox = Container( color: Provider.of(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), - height: 100, - child: Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of(context).theme.defaultButtonActiveColor))), - child: RawKeyboardListener( - focusNode: FocusNode(), - onKey: handleKeyPress, - child: Padding( - padding: EdgeInsets.all(8), - child: TextFormField( - key: Key('txtCompose'), - controller: ctrlrCompose, - focusNode: focusNode, - autofocus: !Platform.isAndroid, - textInputAction: TextInputAction.newline, - keyboardType: TextInputType.multiline, - enableIMEPersonalizedLearning: false, - minLines: 1, - maxLength: (isGroup ? GroupMessageLengthMax : P2PMessageLengthMax) - numberOfBytesMoreThanChar, - maxLengthEnforcement: MaxLengthEnforcement.enforced, - maxLines: null, - onFieldSubmitted: _sendMessage, - enabled: true, // always allow editing... - onChanged: (String x) { - setState(() { - // we need to force a rerender here to update the max length count - }); - }, - decoration: InputDecoration( - hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage, - hintStyle: TextStyle(color: Provider.of(context).theme.sendHintTextColor), - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - enabled: true, - suffixIcon: ElevatedButton( - key: Key("btnSend"), - style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))), - child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of(context).theme.defaultButtonTextColor), - onPressed: isOffline ? null : _sendMessage, - ))), - )))), + height: 164 + ((ctrlrCompose.text.split("\n").length - 1) * 16), + child: Column( + children: [ + Row(mainAxisAlignment: MainAxisAlignment.start, children: [preview]), + Container( + decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of(context).theme.defaultButtonActiveColor))), + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [wdgMessage])), ], ), ); + return Container( + color: Provider.of(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [composeBox])); + } + + Widget _buildComposeBox() { + bool isOffline = Provider.of(context).isOnline() == false; + bool isGroup = Provider.of(context).isGroup; + var showToolbar = Provider.of(context).isExperimentEnabled(FormattingExperiment); + var charLength = ctrlrCompose.value.text.characters.length; + var expectedLength = ctrlrCompose.value.text.length; + var numberOfBytesMoreThanChar = (expectedLength - charLength); + + var bold = IconButton( + icon: Icon(Icons.format_bold), + onPressed: () { + setState(() { + var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); + var selection = ctrlrCompose.selection; + var start = ctrlrCompose.selection.start; + var end = ctrlrCompose.selection.end; + ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "**" + selected + "**"); + ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2); + }); + }); + + var italic = IconButton( + icon: Icon(Icons.format_italic), + onPressed: () { + setState(() { + var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); + var selection = ctrlrCompose.selection; + var start = ctrlrCompose.selection.start; + var end = ctrlrCompose.selection.end; + ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "*" + selected + "*"); + ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + }); + }); + + var code = IconButton( + icon: Icon(Icons.code), + onPressed: () { + setState(() { + var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); + var selection = ctrlrCompose.selection; + var start = ctrlrCompose.selection.start; + var end = ctrlrCompose.selection.end; + ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "`" + selected + "`"); + ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + }); + }); + + var superscript = IconButton( + icon: Icon(Icons.superscript), + onPressed: () { + setState(() { + var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); + var selection = ctrlrCompose.selection; + var start = ctrlrCompose.selection.start; + var end = ctrlrCompose.selection.end; + ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "^" + selected + "^"); + ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + }); + }); + + var subscript = IconButton( + icon: Icon(Icons.subscript), + onPressed: () { + setState(() { + var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); + var selection = ctrlrCompose.selection; + var start = ctrlrCompose.selection.start; + var end = ctrlrCompose.selection.end; + ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "_" + selected + "_"); + ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + }); + }); + + var strikethrough = IconButton( + icon: Icon(Icons.format_strikethrough), + onPressed: () { + setState(() { + var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); + var selection = ctrlrCompose.selection; + var start = ctrlrCompose.selection.start; + var end = ctrlrCompose.selection.end; + ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "~~" + selected + "~~"); + ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2); + }); + }); + + var preview = IconButton( + icon: Icon(Icons.text_format), + onPressed: () { + setState(() { + showPreview = true; + }); + }); + + var vline = Padding( + padding: EdgeInsets.symmetric(vertical: 1, horizontal: 2), + child: Container(height: 16, width: 1, decoration: BoxDecoration(color: Provider.of(context).theme.messageFromMeTextColor))); + + var formattingToolbar = Container( + decoration: BoxDecoration(color: Provider.of(context).theme.defaultButtonActiveColor), + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [bold, italic, code, superscript, subscript, strikethrough, vline, preview])); + + var textField = Container( + decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of(context).theme.defaultButtonActiveColor))), + child: RawKeyboardListener( + focusNode: FocusNode(), + onKey: handleKeyPress, + child: Padding( + padding: EdgeInsets.all(8), + child: TextFormField( + key: Key('txtCompose'), + controller: ctrlrCompose, + focusNode: focusNode, + autofocus: !Platform.isAndroid, + textInputAction: TextInputAction.newline, + keyboardType: TextInputType.multiline, + enableIMEPersonalizedLearning: false, + minLines: 1, + maxLength: (isGroup ? GroupMessageLengthMax : P2PMessageLengthMax) - numberOfBytesMoreThanChar, + maxLengthEnforcement: MaxLengthEnforcement.enforced, + maxLines: 3, + onFieldSubmitted: _sendMessage, + enabled: true, // always allow editing... + + onChanged: (String x) { + setState(() { + // we need to force a rerender here to update the max length count + }); + }, + decoration: InputDecoration( + hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage, + hintStyle: TextStyle(color: Provider.of(context).theme.sendHintTextColor), + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + enabled: true, + suffixIcon: ElevatedButton( + key: Key("btnSend"), + style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))), + child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of(context).theme.defaultButtonTextColor), + onPressed: isOffline ? null : _sendMessage, + ))), + ))); + + var textEditChildren; + if (showToolbar) { + textEditChildren = [formattingToolbar, textField]; + } else { + textEditChildren = [textField]; + } + + var composeBox = + Container(color: Provider.of(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren)); var children; if (Provider.of(context).selectedConversation != null && Provider.of(context).selectedIndex != null) { @@ -419,7 +575,7 @@ class _MessageViewState extends State { children = [composeBox]; } - return Container(color: Provider.of(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, children: children)); + return Container(color: Provider.of(context).theme.backgroundMainColor, child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: children)); } // Send the message if enter is pressed without the shift key... From 7bae6485f790e8c1a8a086492e6be2e43abe8839 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 14 Jun 2022 18:44:24 -0700 Subject: [PATCH 4/4] Fixup Formatting PR (Dans Comments) --- lib/views/messageview.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 6030c79c..81b2da6e 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -324,11 +324,9 @@ class _MessageViewState extends State { padding: EdgeInsets.all(8), child: SelectableLinkify( text: ctrlrCompose.text + '\n', - // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? options: LinkifyOptions(messageFormatting: true, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), linkifiers: [UrlLinkifier()], onOpen: showClickableLinks ? null : null, - //key: Key(myKey), style: TextStyle( color: Provider.of(context).theme.messageFromMeTextColor, fontSize: 16, @@ -361,10 +359,12 @@ class _MessageViewState extends State { color: Provider.of(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), + + // 164 minimum height + 16px for every line of text so the entire message is displayed when previewed. height: 164 + ((ctrlrCompose.text.split("\n").length - 1) * 16), child: Column( children: [ - Row(mainAxisAlignment: MainAxisAlignment.start, children: [preview]), + Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [preview]), Container( decoration: BoxDecoration(border: Border(top: BorderSide(color: Provider.of(context).theme.defaultButtonActiveColor))), child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [wdgMessage])),