From 52d0a6cf3f7d145b7686b9f43484bd04bc146327 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 8 Feb 2024 13:51:49 -0800 Subject: [PATCH 01/15] Fix Chat Resize Layout --- lib/models/message.dart | 4 +- lib/models/messages/filemessage.dart | 2 +- lib/models/messages/invitemessage.dart | 2 +- lib/models/messages/malformedmessage.dart | 2 +- lib/models/messages/quotedmessage.dart | 4 +- lib/models/messages/textmessage.dart | 4 +- lib/third_party/linkify/flutter_linkify.dart | 182 ++++--------------- lib/views/messageview.dart | 22 +-- lib/widgets/filebubble.dart | 5 +- lib/widgets/invitationbubble.dart | 79 ++++---- lib/widgets/malformedbubble.dart | 7 +- lib/widgets/messageBubbleWidgetHelpers.dart | 6 +- lib/widgets/messagebubble.dart | 76 ++++---- lib/widgets/messagebubbledecorations.dart | 1 + lib/widgets/messagelist.dart | 10 +- lib/widgets/messagerow.dart | 4 +- lib/widgets/quotedmessage.dart | 76 ++++---- lib/widgets/staticmessagebubble.dart | 9 +- 18 files changed, 176 insertions(+), 319 deletions(-) diff --git a/lib/models/message.dart b/lib/models/message.dart index decdf51b..391286c8 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -34,7 +34,9 @@ abstract class Message { Widget getWidget(BuildContext context, Key key, int index); - Widget getPreviewWidget(BuildContext context); + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}); + + //double requestedWidth(BuildContext context, BoxConstraints constraints); } Message compileOverlay(MessageInfo messageInfo) { diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index d8ebc6cf..26cf60a4 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -49,7 +49,7 @@ class FileMessage extends Message { } @override - Widget getPreviewWidget(BuildContext context) { + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { diff --git a/lib/models/messages/invitemessage.dart b/lib/models/messages/invitemessage.dart index 63c9236f..e2f339f6 100644 --- a/lib/models/messages/invitemessage.dart +++ b/lib/models/messages/invitemessage.dart @@ -48,7 +48,7 @@ class InviteMessage extends Message { } @override - Widget getPreviewWidget(BuildContext context) { + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { diff --git a/lib/models/messages/malformedmessage.dart b/lib/models/messages/malformedmessage.dart index 033663f2..ece8338c 100644 --- a/lib/models/messages/malformedmessage.dart +++ b/lib/models/messages/malformedmessage.dart @@ -18,7 +18,7 @@ class MalformedMessage extends Message { } @override - Widget getPreviewWidget(BuildContext context) { + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 81e4ef0c..0564dcad 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -30,7 +30,7 @@ class QuotedMessage extends Message { QuotedMessage(this.metadata, this.content); @override - Widget getPreviewWidget(BuildContext context) { + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { @@ -40,7 +40,7 @@ class QuotedMessage extends Message { ); var content = message["body"]; var formatMessages = Provider.of(bcontext).isExperimentEnabled(FormattingExperiment); - return compileMessageContentWidget(context, false, content, FocusNode(), formatMessages, false); + return compileMessageContentWidget(context, constraints ?? BoxConstraints.expand(), false, content, FocusNode(), formatMessages, false); } catch (e) { return MalformedBubble(); } diff --git a/lib/models/messages/textmessage.dart b/lib/models/messages/textmessage.dart index bbbb7f23..c2914a0b 100644 --- a/lib/models/messages/textmessage.dart +++ b/lib/models/messages/textmessage.dart @@ -21,12 +21,12 @@ class TextMessage extends Message { TextMessage(this.metadata, this.content); @override - Widget getPreviewWidget(BuildContext context) { + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { return ChangeNotifierProvider.value( value: this.metadata, builder: (bcontext, child) { var formatMessages = Provider.of(bcontext).isExperimentEnabled(FormattingExperiment); - return compileMessageContentWidget(context, false, content, FocusNode(), formatMessages, false); + return compileMessageContentWidget(context, constraints ?? BoxConstraints.expand(), false, content, FocusNode(), formatMessages, false); ; }); } diff --git a/lib/third_party/linkify/flutter_linkify.dart b/lib/third_party/linkify/flutter_linkify.dart index 9b61eca9..e6595dbc 100644 --- a/lib/third_party/linkify/flutter_linkify.dart +++ b/lib/third_party/linkify/flutter_linkify.dart @@ -37,124 +37,6 @@ export 'linkify.dart' show LinkifyElement, LinkifyOptions, LinkableElement, Text /// Callback clicked link typedef LinkCallback = void Function(LinkableElement link); -/// Turns URLs into links -class Linkify extends StatelessWidget { - /// Text to be linkified - final String text; - - /// Linkifiers to be used for linkify - final List linkifiers; - - /// Callback for tapping a link - final LinkCallback? onOpen; - - /// linkify's options. - final LinkifyOptions options; - - // TextSpan - - /// Style for non-link text - final TextStyle? style; - - /// Style of link text - final TextStyle? linkStyle; - - // Text.rich - - /// How the text should be aligned horizontally. - final TextAlign textAlign; - - /// Text direction of the text - final TextDirection? textDirection; - - /// The maximum number of lines for the text to span, wrapping if necessary - final int? maxLines; - - /// How visual overflow should be handled. - final TextOverflow overflow; - - /// The number of font pixels for each logical pixel - final double textScaleFactor; - - /// Whether the text should break at soft line breaks. - final bool softWrap; - - /// The strut style used for the vertical layout - final StrutStyle? strutStyle; - - /// Used to select a font when the same Unicode character can - /// be rendered differently, depending on the locale - final Locale? locale; - - /// Defines how to measure the width of the rendered text. - final TextWidthBasis textWidthBasis; - - /// Defines how the paragraph will apply TextStyle.height to the ascent of the first line and descent of the last line. - final TextHeightBehavior? textHeightBehavior; - - const Linkify({ - Key? key, - required this.text, - this.linkifiers = defaultLinkifiers, - this.onOpen, - this.options = const LinkifyOptions(), - // TextSpan - this.style, - this.linkStyle, - // RichText - this.textAlign = TextAlign.start, - this.textDirection, - this.maxLines, - this.overflow = TextOverflow.clip, - this.textScaleFactor = 1.0, - this.softWrap = true, - this.strutStyle, - this.locale, - this.textWidthBasis = TextWidthBasis.parent, - this.textHeightBehavior, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final elements = linkify( - text, - options: options, - linkifiers: linkifiers, - ); - - return SelectionArea( - child: RichText( - selectionRegistrar: SelectionContainer.maybeOf(context), - text: buildTextSpan( - elements, - style: Theme.of(context).textTheme.bodyText2?.merge(style), - onOpen: onOpen, - useMouseRegion: true, - context: context, - linkStyle: Theme.of(context) - .textTheme - .bodyText2 - ?.merge(style) - .copyWith( - color: Colors.blueAccent, - decoration: TextDecoration.underline, - ) - .merge(linkStyle), - ), - textAlign: textAlign, - textDirection: textDirection, - maxLines: maxLines, - overflow: overflow, - textScaleFactor: textScaleFactor, - softWrap: softWrap, - strutStyle: strutStyle, - locale: locale, - textWidthBasis: textWidthBasis, - textHeightBehavior: textHeightBehavior, - )); - } -} - /// Turns URLs into links class SelectableLinkify extends StatelessWidget { /// Text to be linkified @@ -175,13 +57,13 @@ class SelectableLinkify extends StatelessWidget { // TextSpan /// Style for code text - final TextStyle? codeStyle; + final TextStyle codeStyle; /// Style for non-link text - final TextStyle? style; + final TextStyle style; /// Style of link text - final TextStyle? linkStyle; + final TextStyle linkStyle; // Text.rich @@ -214,9 +96,6 @@ class SelectableLinkify extends StatelessWidget { /// Whether this text field should focus itself if nothing else is already focused. final bool autofocus; - /// Configuration of toolbar options - final ToolbarOptions? toolbarOptions; - /// How thick the cursor will be final double cursorWidth; @@ -250,6 +129,8 @@ class SelectableLinkify extends StatelessWidget { /// Called when the user changes the selection of text (including the cursor location). final SelectionChangedCallback? onSelectionChanged; + final BoxConstraints constraints; + const SelectableLinkify({ Key? key, required this.text, @@ -257,11 +138,11 @@ class SelectableLinkify extends StatelessWidget { this.onOpen, this.options = const LinkifyOptions(), // TextSpan - this.style, - this.linkStyle, + required this.style, + required this.linkStyle, // RichText this.textAlign, - this.codeStyle, + required this.codeStyle, this.textDirection, this.minLines, this.maxLines, @@ -271,7 +152,6 @@ class SelectableLinkify extends StatelessWidget { this.strutStyle, this.showCursor = false, this.autofocus = false, - this.toolbarOptions, this.cursorWidth = 2.0, this.cursorRadius, this.cursorColor, @@ -284,6 +164,7 @@ class SelectableLinkify extends StatelessWidget { this.cursorHeight, this.selectionControls, this.onSelectionChanged, + required this.constraints, }) : super(key: key); @override @@ -295,25 +176,17 @@ class SelectableLinkify extends StatelessWidget { ); return Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(), + constraints: constraints, child: SelectableText.rich( - buildTextSpan( - elements, - style: Theme.of(context).textTheme.bodyText2?.merge(style), - codeStyle: Theme.of(context).textTheme.bodyText2?.merge(codeStyle), - onOpen: onOpen, - context: context, - linkStyle: Theme.of(context) - .textTheme - .bodyText2 - ?.merge(style) - .copyWith( - color: Colors.blueAccent, - decoration: TextDecoration.underline, - ) - .merge(linkStyle), - ), + buildTextSpan(elements, + style: style, + codeStyle: codeStyle, + onOpen: onOpen, + context: context, + linkStyle: linkStyle.copyWith( + decoration: TextDecoration.underline, + ), + constraints: constraints), textAlign: textAlign, textDirection: textDirection, minLines: minLines, @@ -323,7 +196,6 @@ class SelectableLinkify extends StatelessWidget { showCursor: showCursor, textScaleFactor: textScaleFactor, autofocus: autofocus, - toolbarOptions: toolbarOptions, cursorWidth: cursorWidth, cursorRadius: cursorRadius, cursorColor: cursorColor, @@ -366,8 +238,24 @@ TextSpan buildTextSpan( LinkCallback? onOpen, required BuildContext context, bool useMouseRegion = false, + required BoxConstraints constraints, }) { + // Ok, so the problem here is that Flutter really wants to optimize this function + // out of the rebuild process. This is fine when the screen gets smaller because + // Flutter forces TextSpan to rebuild with the new constraints automatically. + // HOWEVER, when the screen gets larger, Flutter seems to think that it doesn't + // need to bother rebuilding this TextSpan because it already fits in the provided constraints. + // To force a rebuild here we append a constraint-determined space character to the end of the + // text element. + // (I tried a few other things, including the docs-sanctioned MediaQuery.sizeOf(context) - which promises a rebuild + // but Flutter is pretty good at optimizing "useless" checks out) + String inlineText = "\u0020"; + if (constraints.maxWidth % 2 == 0) { + inlineText = "\u00A0"; + } + elements.add(TextElement(inlineText)); return TextSpan( + style: style, children: elements.map( (element) { if (element is LinkableElement) { diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 5136ce8f..4de4508d 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -10,21 +10,16 @@ import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/chatmessage.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; -import 'package:cwtch/models/message_draft.dart'; import 'package:cwtch/models/messagecache.dart'; -import 'package:cwtch/models/messages/invitemessage.dart'; import 'package:cwtch/models/messages/quotedmessage.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/third_party/linkify/flutter_linkify.dart'; -import 'package:cwtch/widgets/invitationbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:cwtch/controllers/filesharing.dart' as filesharing; import 'package:cwtch/widgets/staticmessagebubble.dart'; - -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/views/peersettingsview.dart'; import 'package:cwtch/widgets/DropdownContacts.dart'; @@ -493,6 +488,7 @@ class _MessageViewState extends State { backgroundColor: Provider.of(context).theme.messageFromOtherBackgroundColor), textAlign: TextAlign.left, textWidthBasis: TextWidthBasis.longestLine, + constraints: BoxConstraints.expand(), )); var showMessageFormattingPreview = Provider.of(context).isExperimentEnabled(FormattingExperiment); @@ -601,22 +597,6 @@ class _MessageViewState extends State { }); }); - // var subscript = IconButton( - // icon: Icon(Icons.subscript), - // tooltip: AppLocalizations.of(context)!.tooltipSubscript, - // onPressed: () { - // setState(() { - // var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; - // 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); - // Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; - // }); - // }); - var strikethrough = IconButton( icon: Icon(Icons.format_strikethrough), tooltip: AppLocalizations.of(context)!.tooltipStrikethrough, diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index ac151618..e0369688 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -150,7 +150,10 @@ class FileBubbleState extends State { var wdgSender = Visibility( visible: widget.interactive, child: Container( - height: 14 * Provider.of(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, fromMe, senderDisplayStr))); + height: 14 * Provider.of(context).fontScaling, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration(), + child: compileSenderWidget(context, constraints, fromMe, senderDisplayStr))); var isPreview = false; var wdgMessage = !showFileSharing ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of(context).scaleFonts(defaultTextStyle)) diff --git a/lib/widgets/invitationbubble.dart b/lib/widgets/invitationbubble.dart index 97489f86..5af90875 100644 --- a/lib/widgets/invitationbubble.dart +++ b/lib/widgets/invitationbubble.dart @@ -56,47 +56,46 @@ class InvitationBubbleState extends State { senderDisplayStr = Provider.of(context).senderHandle; } } - - var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); - // If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from - // some kind of malfeasance. - var selfInvite = widget.inviteNick == Provider.of(context).onion; - if (selfInvite) { - return MalformedBubble(); - } - - var wdgMessage = isGroup && !showGroupInvite - ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of(context).scaleFonts(defaultTextStyle)) - : fromMe - ? senderInviteChrome( - AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget) - : (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, widget.inviteNick, widget.inviteTarget)); - - Widget wdgDecorations; - if (isGroup && !showGroupInvite) { - wdgDecorations = Text('\u202F'); - } else if (fromMe) { - wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); - } else if (isAccepted) { - wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); - } else if (this.rejected) { - wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); - } else { - wdgDecorations = Center( - widthFactor: 1, - child: Wrap(children: [ - Padding( - padding: EdgeInsets.all(5), - child: ElevatedButton( - child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)), - Padding( - padding: EdgeInsets.all(5), - child: ElevatedButton( - child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), - ])); - } - return LayoutBuilder(builder: (context, constraints) { + var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr); + // If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from + // some kind of malfeasance. + var selfInvite = widget.inviteNick == Provider.of(context).onion; + if (selfInvite) { + return MalformedBubble(); + } + + var wdgMessage = isGroup && !showGroupInvite + ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of(context).scaleFonts(defaultTextStyle)) + : fromMe + ? senderInviteChrome( + AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget) + : (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, widget.inviteNick, widget.inviteTarget)); + + Widget wdgDecorations; + if (isGroup && !showGroupInvite) { + wdgDecorations = Text('\u202F'); + } else if (fromMe) { + wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); + } else if (isAccepted) { + wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); + } else if (this.rejected) { + wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); + } else { + wdgDecorations = Center( + widthFactor: 1, + child: Wrap(children: [ + Padding( + padding: EdgeInsets.all(5), + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)), + Padding( + padding: EdgeInsets.all(5), + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), + ])); + } + //print(constraints.toString()+", "+constraints.maxWidth.toString()); return Center( widthFactor: 1.0, diff --git a/lib/widgets/malformedbubble.dart b/lib/widgets/malformedbubble.dart index c56c48c7..7a9ad6e3 100644 --- a/lib/widgets/malformedbubble.dart +++ b/lib/widgets/malformedbubble.dart @@ -46,7 +46,12 @@ class MalformedBubbleState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: [Text(AppLocalizations.of(context)!.malformedMessage)], + children: [ + Text( + AppLocalizations.of(context)!.malformedMessage, + overflow: TextOverflow.ellipsis, + ) + ], )) ]))))); }); diff --git a/lib/widgets/messageBubbleWidgetHelpers.dart b/lib/widgets/messageBubbleWidgetHelpers.dart index 118c22b4..8771ffcd 100644 --- a/lib/widgets/messageBubbleWidgetHelpers.dart +++ b/lib/widgets/messageBubbleWidgetHelpers.dart @@ -5,7 +5,7 @@ import '../settings.dart'; import '../themes/opaque.dart'; import '../third_party/linkify/flutter_linkify.dart'; -Widget compileSenderWidget(BuildContext context, bool fromMe, String senderDisplayStr) { +Widget compileSenderWidget(BuildContext context, BoxConstraints? constraints, bool fromMe, String senderDisplayStr) { return Container( height: 14 * Provider.of(context).fontScaling, clipBehavior: Clip.hardEdge, @@ -21,7 +21,7 @@ Widget compileSenderWidget(BuildContext context, bool fromMe, String senderDispl ))); } -Widget compileMessageContentWidget(BuildContext context, bool fromMe, String content, FocusNode focus, bool formatMessages, bool showClickableLinks) { +Widget compileMessageContentWidget(BuildContext context, BoxConstraints constraints, fromMe, String content, FocusNode focus, bool formatMessages, bool showClickableLinks) { return SelectableLinkify( text: content + '\u202F', // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? @@ -32,7 +32,6 @@ Widget compileMessageContentWidget(BuildContext context, bool fromMe, String con modalOpenLink(context, link); } : null, - //key: Key(myKey), focusNode: focus, style: Provider.of(context) .scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor)), @@ -43,6 +42,7 @@ Widget compileMessageContentWidget(BuildContext context, bool fromMe, String con color: fromMe ? Provider.of(context).theme.messageFromOtherTextColor : Provider.of(context).theme.messageFromMeTextColor, backgroundColor: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor)), textAlign: TextAlign.left, + constraints: constraints, textWidthBasis: TextWidthBasis.longestLine, ); } diff --git a/lib/widgets/messagebubble.dart b/lib/widgets/messagebubble.dart index 5f36f6c6..b8747371 100644 --- a/lib/widgets/messagebubble.dart +++ b/lib/widgets/messagebubble.dart @@ -45,49 +45,45 @@ class MessageBubbleState extends State { senderDisplayStr = Provider.of(context).senderHandle; } } - var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); - var wdgMessage = compileMessageContentWidget(context, fromMe, widget.content, _focus, formatMessages, showClickableLinks); + + Size size = MediaQuery.sizeOf(context); + + var wdgSender = compileSenderWidget(context, null, fromMe, senderDisplayStr); + var wdgMessage = compileMessageContentWidget(context, BoxConstraints.loose(size), fromMe, widget.content, _focus, formatMessages, showClickableLinks); var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); var error = Provider.of(context).error; - return LayoutBuilder(builder: (context, constraints) { - //print(constraints.toString()+", "+constraints.maxWidth.toString()); - return RepaintBoundary( - child: Container( - child: Container( - decoration: BoxDecoration( - color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), - border: Border.all( - color: error - ? malformedColor - : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), - width: 1), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(borderRadiousEh), - topRight: Radius.circular(borderRadiousEh), - bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, - bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), - ), - ), - child: Padding( - padding: EdgeInsets.all(9.0), - child: Theme( - data: Theme.of(context).copyWith( - textSelectionTheme: TextSelectionThemeData( - cursorColor: Provider.of(context).theme.messageSelectionColor, - selectionColor: Provider.of(context).theme.messageSelectionColor, - selectionHandleColor: Provider.of(context).theme.messageSelectionColor), + return Container( + decoration: BoxDecoration( + color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), + border: Border.all( + color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), + width: 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(borderRadiousEh), + topRight: Radius.circular(borderRadiousEh), + bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, + bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), + ), + ), + child: Padding( + padding: EdgeInsets.all(9.0), + child: Theme( + data: Theme.of(context).copyWith( + textSelectionTheme: TextSelectionThemeData( + cursorColor: Provider.of(context).theme.messageSelectionColor, + selectionColor: Provider.of(context).theme.messageSelectionColor, + selectionHandleColor: Provider.of(context).theme.messageSelectionColor), - // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ - textButtonTheme: TextButtonThemeData( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), - ), - ), - child: Column( - crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))))); - }); + // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ + textButtonTheme: TextButtonThemeData( + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), + ), + ), + child: Column( + crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, + mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))); } } diff --git a/lib/widgets/messagebubbledecorations.dart b/lib/widgets/messagebubbledecorations.dart index 80b04651..55bbc3eb 100644 --- a/lib/widgets/messagebubbledecorations.dart +++ b/lib/widgets/messagebubbledecorations.dart @@ -29,6 +29,7 @@ class _MessageBubbleDecoration extends State { mainAxisSize: MainAxisSize.min, children: [ Text(prettyDate, + overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 9.0 * Provider.of(context).fontScaling, fontWeight: FontWeight.w200, diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index 2774bf0d..1c2a987d 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -1,10 +1,8 @@ -import 'package:cwtch/config.dart'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/messagecache.dart'; import 'package:cwtch/models/profile.dart'; -import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -35,10 +33,6 @@ class _MessageListState extends State { bool isP2P = !Provider.of(context).isGroup; bool isGroupAndSyncing = Provider.of(context).isGroup == true && Provider.of(context).status == "Authenticated"; - // Older checks, no longer used, kept for reference. - //bool isGroupAndSynced = Provider.of(context).isGroup && Provider.of(context).status == "Synced"; - //bool isGroupAndNotAuthenticated = Provider.of(context).isGroup && Provider.of(context).status != "Authenticated"; - bool preserveHistoryByDefault = Provider.of(context, listen: false).preserveHistoryByDefault; bool showEphemeralWarning = (isP2P && (!preserveHistoryByDefault && Provider.of(context).savePeerHistory != "SaveHistory")); bool showOfflineWarning = Provider.of(context).isOnline() == false; @@ -143,14 +137,14 @@ class _MessageListState extends State { return FutureBuilder( future: messageHandler(itemBuilderContext, profileOnion, contactHandle, ByIndex(messageIndex)), - builder: (context, snapshot) { + builder: (fbcontext, snapshot) { if (snapshot.hasData) { var message = snapshot.data as Message; // here we create an index key for the contact and assign it to the row. Indexes are unique so we can // reliably use this without running into duplicate keys...it isn't ideal as it means keys need to be re-built // when new messages are added...however it is better than the alternative of not having widget keys at all. var key = Provider.of(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex); - return message.getWidget(context, key, messageIndex); + return message.getWidget(fbcontext, key, messageIndex); } else { return MessageLoadingBubble(); } diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 3de2d4cb..0230a986 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch_icons_icons.dart'; -import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profile.dart'; @@ -16,7 +15,6 @@ import 'package:cwtch/widgets/profileimage.dart'; import 'package:flutter/physics.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import '../main.dart'; import '../models/messagecache.dart'; @@ -65,7 +63,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi var isContact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle) != null; var isGroup = Provider.of(context).contactList.getContact(Provider.of(context, listen: false).conversationIdentifier)!.isGroup; var isBlocked = isContact ? Provider.of(context).contactList.findContact(Provider.of(context).senderHandle)!.isBlocked : false; - var actualMessage = Flexible(flex: Platform.isAndroid ? 10 : 3, fit: FlexFit.loose, child: widget.child); + var actualMessage = Flexible(flex: 1, fit: FlexFit.loose, child: widget.child); _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft; _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft; diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index afcc07bf..781a8ede 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -45,12 +45,10 @@ class QuotedMessageBubbleState extends State { } } - var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); - var showClickableLinks = Provider.of(context).isExperimentEnabled(ClickableLinksExperiment); var formatMessages = Provider.of(context).isExperimentEnabled(FormattingExperiment); - var wdgMessage = compileMessageContentWidget(context, fromMe, widget.body, _focus, formatMessages, showClickableLinks); - + Size size = MediaQuery.of(context).size; + BoxConstraints constraints = BoxConstraints(maxWidth: size.width, minWidth: size.width); var wdgQuote = FutureBuilder( future: widget.quotedMessage, builder: (context, snapshot) { @@ -123,43 +121,39 @@ class QuotedMessageBubbleState extends State { var error = Provider.of(context).error; - return LayoutBuilder(builder: (context, constraints) { - return RepaintBoundary( - child: Container( - child: Container( - decoration: BoxDecoration( - color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), - border: Border.all( - color: error - ? malformedColor - : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), - width: 1), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(borderRadiousEh), - topRight: Radius.circular(borderRadiousEh), - bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, - bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), - ), - ), - child: Padding( - padding: EdgeInsets.all(9.0), - child: Theme( - data: Theme.of(context).copyWith( - textSelectionTheme: TextSelectionThemeData( - cursorColor: Provider.of(context).theme.messageSelectionColor, - selectionColor: Provider.of(context).theme.messageSelectionColor, - selectionHandleColor: Provider.of(context).theme.messageSelectionColor), + var wdgMessage = compileMessageContentWidget(context, constraints, fromMe, widget.body, _focus, formatMessages, showClickableLinks); + var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr); + return Container( + decoration: BoxDecoration( + color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), + border: Border.all( + color: error ? malformedColor : (fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor), + width: 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(borderRadiousEh), + topRight: Radius.circular(borderRadiousEh), + bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, + bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), + ), + ), + child: Padding( + padding: EdgeInsets.all(9.0), + child: Theme( + data: Theme.of(context).copyWith( + textSelectionTheme: TextSelectionThemeData( + cursorColor: Provider.of(context).theme.messageSelectionColor, + selectionColor: Provider.of(context).theme.messageSelectionColor, + selectionHandleColor: Provider.of(context).theme.messageSelectionColor), - // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ - textButtonTheme: TextButtonThemeData( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), - ), - ), - child: Column( - crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))))); - }); + // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ + textButtonTheme: TextButtonThemeData( + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), + ), + ), + child: Column( + crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, + mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))); } } diff --git a/lib/widgets/staticmessagebubble.dart b/lib/widgets/staticmessagebubble.dart index f146c98c..5bb22b92 100644 --- a/lib/widgets/staticmessagebubble.dart +++ b/lib/widgets/staticmessagebubble.dart @@ -42,13 +42,10 @@ class StaticMessageBubbleState extends State { senderDisplayStr = widget.profile.nickname; } - var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); - - var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate); - - var error = widget.metadata.error; - return LayoutBuilder(builder: (context, constraints) { + var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr); + var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate); + var error = widget.metadata.error; //print(constraints.toString()+", "+constraints.maxWidth.toString()); return RepaintBoundary( child: Container( From 30dd0982dba1a7d69110083df661308ef6c4e164 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 8 Feb 2024 13:54:21 -0800 Subject: [PATCH 02/15] Fix Juniper Theme --- assets/themes/juniper/JuniperDark.png | Bin 0 -> 19550 bytes assets/themes/juniper/theme.yml | 7 ++++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 assets/themes/juniper/JuniperDark.png diff --git a/assets/themes/juniper/JuniperDark.png b/assets/themes/juniper/JuniperDark.png new file mode 100644 index 0000000000000000000000000000000000000000..3948e0813c696931f395d322a41169898e94761b GIT binary patch literal 19550 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 XVNZ`Vz2G`700000NkvXXu0mjfaIlyQ literal 0 HcmV?d00001 diff --git a/assets/themes/juniper/theme.yml b/assets/themes/juniper/theme.yml index 9295f965..c91db450 100644 --- a/assets/themes/juniper/theme.yml +++ b/assets/themes/juniper/theme.yml @@ -45,4 +45,9 @@ themes: portraitProfileBadgeColor: accent portraitProfileBadgeTextColor: mainTextColor dropShadowColor: accentAlt - chatReactionIconColor: accentAlt \ No newline at end of file + chatReactionIconColor: accentAlt + chatImage: JuniperDark.png + chatImageColor: userBubble + messageSelectionColor: accent + textfieldSelectionColor: accent + menuBackgroundColor: accent \ No newline at end of file From abd32293eb9c8a67196c5019100c2c141c4eb597 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 8 Feb 2024 14:00:17 -0800 Subject: [PATCH 03/15] Fixup Quote Sizing --- lib/models/messages/textmessage.dart | 2 +- lib/widgets/quotedmessage.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/messages/textmessage.dart b/lib/models/messages/textmessage.dart index c2914a0b..bb9d0a7c 100644 --- a/lib/models/messages/textmessage.dart +++ b/lib/models/messages/textmessage.dart @@ -26,7 +26,7 @@ class TextMessage extends Message { value: this.metadata, builder: (bcontext, child) { var formatMessages = Provider.of(bcontext).isExperimentEnabled(FormattingExperiment); - return compileMessageContentWidget(context, constraints ?? BoxConstraints.expand(), false, content, FocusNode(), formatMessages, false); + return compileMessageContentWidget(context, constraints ?? BoxConstraints.loose(MediaQuery.sizeOf(context)), false, content, FocusNode(), formatMessages, false); ; }); } diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index 781a8ede..8bc3638e 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -48,7 +48,7 @@ class QuotedMessageBubbleState extends State { var showClickableLinks = Provider.of(context).isExperimentEnabled(ClickableLinksExperiment); var formatMessages = Provider.of(context).isExperimentEnabled(FormattingExperiment); Size size = MediaQuery.of(context).size; - BoxConstraints constraints = BoxConstraints(maxWidth: size.width, minWidth: size.width); + BoxConstraints constraints = BoxConstraints.loose(size); var wdgQuote = FutureBuilder( future: widget.quotedMessage, builder: (context, snapshot) { From 659c7fe75e047ea8a2c09f6c809028359ea58096 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 8 Feb 2024 14:10:43 -0800 Subject: [PATCH 04/15] Fixup Malformed Sizes + Preview Contraints --- lib/models/messages/filemessage.dart | 4 +- lib/models/messages/quotedmessage.dart | 2 +- lib/widgets/malformedbubble.dart | 76 ++++++++++++-------------- 3 files changed, 37 insertions(+), 45 deletions(-) diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index 26cf60a4..2c7b4726 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -55,14 +55,14 @@ class FileMessage extends Message { builder: (bcontext, child) { dynamic shareObj = jsonDecode(this.content); if (shareObj == null) { - return MessageRow(MalformedBubble(), 0); + return MalformedBubble(); } String nameSuggestion = shareObj['f'] as String; String rootHash = shareObj['h'] as String; String nonce = shareObj['n'] as String; int fileSize = shareObj['s'] as int; if (!validHash(rootHash, nonce)) { - return MessageRow(MalformedBubble(), 0); + return MalformedBubble(); } return Container( alignment: Alignment.center, diff --git a/lib/models/messages/quotedmessage.dart b/lib/models/messages/quotedmessage.dart index 0564dcad..95b1db5d 100644 --- a/lib/models/messages/quotedmessage.dart +++ b/lib/models/messages/quotedmessage.dart @@ -40,7 +40,7 @@ class QuotedMessage extends Message { ); var content = message["body"]; var formatMessages = Provider.of(bcontext).isExperimentEnabled(FormattingExperiment); - return compileMessageContentWidget(context, constraints ?? BoxConstraints.expand(), false, content, FocusNode(), formatMessages, false); + return compileMessageContentWidget(context, constraints ?? BoxConstraints.loose(MediaQuery.sizeOf(context)), false, content, FocusNode(), formatMessages, false); } catch (e) { return MalformedBubble(); } diff --git a/lib/widgets/malformedbubble.dart b/lib/widgets/malformedbubble.dart index 7a9ad6e3..0a6c1c27 100644 --- a/lib/widgets/malformedbubble.dart +++ b/lib/widgets/malformedbubble.dart @@ -13,47 +13,39 @@ class MalformedBubble extends StatefulWidget { class MalformedBubbleState extends State { @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - return Center( - widthFactor: 1.0, - child: Container( - decoration: BoxDecoration( - color: malformedColor, - border: Border.all(color: malformedColor, width: 1), - borderRadius: BorderRadius.only( - topLeft: Radius.zero, - topRight: Radius.zero, - bottomLeft: Radius.zero, - bottomRight: Radius.zero, - ), - ), - child: Center( - widthFactor: 1.0, - child: Padding( - padding: EdgeInsets.all(9.0), - child: Row(mainAxisSize: MainAxisSize.min, children: [ - Center( - widthFactor: 1, - child: Padding( - padding: EdgeInsets.all(4), - child: Icon( - CwtchIcons.favorite_black_24dp_broken, - size: 24, - ))), - Center( - widthFactor: 1.0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.malformedMessage, - overflow: TextOverflow.ellipsis, - ) - ], - )) - ]))))); - }); + return Container( + decoration: BoxDecoration( + color: malformedColor, + border: Border.all(color: malformedColor, width: 1), + borderRadius: BorderRadius.only( + topLeft: Radius.zero, + topRight: Radius.zero, + bottomLeft: Radius.zero, + bottomRight: Radius.zero, + ), + ), + child: FittedBox( + fit: BoxFit.contain, + child: Center( + widthFactor: 1.0, + child: Padding( + padding: EdgeInsets.all(9.0), + child: Row(mainAxisSize: MainAxisSize.min, children: [ + Center( + widthFactor: 1, + child: Padding( + padding: EdgeInsets.all(4), + child: Icon( + CwtchIcons.favorite_black_24dp_broken, + size: 24, + ))), + Center( + widthFactor: 1.0, + child: Text( + AppLocalizations.of(context)!.malformedMessage, + overflow: TextOverflow.ellipsis, + textWidthBasis: TextWidthBasis.longestLine, + )) + ]))))); } } From cb079c2fd34caf1ea6a170c10122e94a02bf45c2 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 8 Feb 2024 15:43:10 -0800 Subject: [PATCH 05/15] Fix Up File Sharing Overlay --- lib/views/filesharingview.dart | 3 - lib/widgets/filebubble.dart | 331 ++++++++++------------ lib/widgets/messagebubbledecorations.dart | 55 ++-- lib/widgets/messagerow.dart | 2 +- 4 files changed, 178 insertions(+), 213 deletions(-) diff --git a/lib/views/filesharingview.dart b/lib/views/filesharingview.dart index d6399ccf..f368d3bc 100644 --- a/lib/views/filesharingview.dart +++ b/lib/views/filesharingview.dart @@ -1,9 +1,6 @@ import 'dart:convert'; -import 'package:cwtch/config.dart'; -import 'package:cwtch/cwtch/cwtch.dart'; import 'package:cwtch/main.dart'; -import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/settings.dart'; diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index e0369688..fb7a102c 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -146,133 +146,129 @@ class FileBubbleState extends State { ); } - return LayoutBuilder(builder: (bcontext, constraints) { - var wdgSender = Visibility( - visible: widget.interactive, - child: Container( - height: 14 * Provider.of(context).fontScaling, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(), - child: compileSenderWidget(context, constraints, fromMe, senderDisplayStr))); - var isPreview = false; - var wdgMessage = !showFileSharing - ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of(context).scaleFonts(defaultTextStyle)) - : fromMe - ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize) - : (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, - Provider.of(context).downloadSpeed(widget.fileKey()))); - Widget wdgDecorations; + var wdgSender = Visibility( + visible: widget.interactive, + child: Container( + height: 14 * Provider.of(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, null, fromMe, senderDisplayStr))); + var isPreview = false; + var wdgMessage = !showFileSharing + ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of(context).scaleFonts(defaultTextStyle)) + : fromMe + ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash) + : (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, + Provider.of(context).downloadSpeed(widget.fileKey()))); + Widget wdgDecorations; - if (!showFileSharing) { - wdgDecorations = Text('\u202F'); - } else if (downloadComplete && path != null) { - // in this case, whatever marked download.complete would have also set the path - if (myFile != null && Provider.of(context).shouldPreview(path)) { - isPreview = true; - wdgDecorations = Center( - widthFactor: 1.0, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)), - onTap: () { - pop(bcontext, myFile!, widget.nameSuggestion); - }, - ))); - } else { - wdgDecorations = Visibility( - visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle))); - } - } else if (downloadActive) { - if (!downloadGotManifest) { - wdgDecorations = Visibility( - visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle))); - } else { - wdgDecorations = Visibility( - visible: widget.interactive, - child: LinearProgressIndicator( - value: Provider.of(context).downloadProgress(widget.fileKey()), - color: Provider.of(context).theme.defaultButtonActiveColor, - )); - } - } else if (flagStarted) { - // in this case, the download was done in a previous application launch, - // so we probably have to request an info lookup - if (!downloadInterrupted) { - wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); - // We should have already requested this... - } else { - var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; - wdgDecorations = Visibility( - visible: widget.interactive, - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)), - ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of(context).scaleFonts(defaultTextButtonStyle))) - ])); - } - } else if (!senderIsContact) { - wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept, style: Provider.of(context).scaleFonts(defaultTextStyle)); - } else if (!widget.isAuto || Provider.of(context).attributes["file-missing"] == "false") { - //Note: we need this second case to account for scenarios where a user deletes the downloaded file, we won't automatically - // fetch it again, so we need to offer the user the ability to restart.. + if (!showFileSharing) { + wdgDecorations = Text('\u202F'); + } else if (fromMe) { + wdgDecorations = Text('\u202F'); + } else if (downloadComplete && path != null) { + // in this case, whatever marked download.complete would have also set the path + if (myFile != null && Provider.of(context).shouldPreview(path)) { + isPreview = true; + wdgDecorations = Center( + widthFactor: 1.0, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)), + onTap: () { + pop(context, myFile!, widget.nameSuggestion); + }, + ))); + } else { + wdgDecorations = Visibility( + visible: widget.interactive, child: SelectableText(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle))); + } + } else if (downloadActive) { + if (!downloadGotManifest) { + wdgDecorations = Visibility( + visible: widget.interactive, child: SelectableText(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle))); + } else { wdgDecorations = Visibility( visible: widget.interactive, - child: Center( - widthFactor: 1, - child: Wrap(children: [ - Padding( - padding: EdgeInsets.all(5), - child: ElevatedButton( - child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), - ]))); - } else { - wdgDecorations = Container(); + child: LinearProgressIndicator( + value: Provider.of(context).downloadProgress(widget.fileKey()), + color: Provider.of(context).theme.defaultButtonActiveColor, + )); } + } else if (flagStarted) { + // in this case, the download was done in a previous application launch, + // so we probably have to request an info lookup + if (!downloadInterrupted) { + wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); + // We should have already requested this... + } else { + var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; + wdgDecorations = Visibility( + visible: widget.interactive, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)), + ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of(context).scaleFonts(defaultTextButtonStyle))) + ])); + } + } else if (!senderIsContact) { + wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept, style: Provider.of(context).scaleFonts(defaultTextStyle)); + } else if (!widget.isAuto || Provider.of(context).attributes["file-missing"] == "false") { + //Note: we need this second case to account for scenarios where a user deletes the downloaded file, we won't automatically + // fetch it again, so we need to offer the user the ability to restart.. + wdgDecorations = Visibility( + visible: widget.interactive, + child: Center( + widthFactor: 1, + child: Wrap(children: [ + Padding( + padding: EdgeInsets.all(5), + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), + ]))); + } else { + wdgDecorations = Container(); + } - return Container( - constraints: constraints, - decoration: BoxDecoration( - color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, - border: Border.all(color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, width: 1), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(borderRadius), - topRight: Radius.circular(borderRadius), - bottomLeft: fromMe ? Radius.circular(borderRadius) : Radius.zero, - bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadius), - ), + return Container( + decoration: BoxDecoration( + color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, + border: Border.all(color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, width: 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(borderRadius), + topRight: Radius.circular(borderRadius), + bottomLeft: fromMe ? Radius.circular(borderRadius) : Radius.zero, + bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadius), ), - child: Theme( - data: Theme.of(context).copyWith( - textSelectionTheme: TextSelectionThemeData( - cursorColor: Provider.of(context).theme.messageSelectionColor, - selectionColor: Provider.of(context).theme.messageSelectionColor, - selectionHandleColor: Provider.of(context).theme.messageSelectionColor), + ), + child: Theme( + data: Theme.of(context).copyWith( + textSelectionTheme: TextSelectionThemeData( + cursorColor: Provider.of(context).theme.messageSelectionColor, + selectionColor: Provider.of(context).theme.messageSelectionColor, + selectionHandleColor: Provider.of(context).theme.messageSelectionColor), - // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ - textButtonTheme: TextButtonThemeData( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), - ), + // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ + textButtonTheme: TextButtonThemeData( + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), ), - child: Padding( - padding: EdgeInsets.all(9.0), - child: Column( - crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - wdgSender, - isPreview - ? Container( - width: 0, - padding: EdgeInsets.zero, - margin: EdgeInsets.zero, - ) - : wdgMessage, - wdgDecorations, - messageStatusWidget - ]), - ))); - }); + ), + child: Padding( + padding: EdgeInsets.all(9.0), + child: Column( + crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, + mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + wdgSender, + isPreview + ? Container( + width: 0, + padding: EdgeInsets.zero, + margin: EdgeInsets.zero, + ) + : wdgMessage, + wdgDecorations, + messageStatusWidget + ]), + ))); } void _btnAccept() async { @@ -322,41 +318,27 @@ class FileBubbleState extends State { } // Construct an file chrome for the sender - Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) { + Widget senderFileChrome(String chrome, String fileName, String rootHash) { var settings = Provider.of(context); return ListTile( visualDensity: VisualDensity.compact, - title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ - SelectableText( - chrome + '\u202F', - style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromMeTextColor)), - textAlign: TextAlign.left, - maxLines: 2, - textWidthBasis: TextWidthBasis.longestLine, - ), - SelectableText( - fileName + '\u202F', - style: - settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of(context).theme.messageFromMeTextColor)), - textAlign: TextAlign.left, - textWidthBasis: TextWidthBasis.parent, - maxLines: 2, - ), - SelectableText( - prettyBytes(fileSize) + '\u202F' + '\n', - style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromMeTextColor)), - textAlign: TextAlign.left, - maxLines: 2, - ) - ]), + contentPadding: EdgeInsets.all(1.0), + title: SelectableText( + fileName + '\u202F', + style: + settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of(context).theme.messageFromMeTextColor)), + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + maxLines: 2, + ), subtitle: SelectableText( - 'sha512: ' + rootHash + '\u202F', + prettyBytes(widget.fileSize) + '\u202F' + '\n' + 'sha512: ' + rootHash + '\u202F', style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, maxLines: 4, - textWidthBasis: TextWidthBasis.parent, + textWidthBasis: TextWidthBasis.longestLine, ), - leading: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of(context).theme.messageFromMeTextColor)); + leading: FittedBox(child: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of(context).theme.messageFromOtherTextColor))); } // Construct an file chrome @@ -364,46 +346,33 @@ class FileBubbleState extends State { var settings = Provider.of(context); return ListTile( visualDensity: VisualDensity.compact, - title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ - SelectableText( - chrome + '\u202F', - style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), - textAlign: TextAlign.left, - maxLines: 2, - textWidthBasis: TextWidthBasis.longestLine, - ), - SelectableText( - fileName + '\u202F', - style: settings - .scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of(context).theme.messageFromOtherTextColor)), - textAlign: TextAlign.left, - textWidthBasis: TextWidthBasis.parent, - maxLines: 2, - ), - SelectableText( - AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F' + '\n', - style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), - textAlign: TextAlign.left, - maxLines: 2, - ) - ]), + contentPadding: EdgeInsets.all(1.0), + title: SelectableText( + fileName + '\u202F', + style: + settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of(context).theme.messageFromOtherTextColor)), + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + maxLines: 2, + ), subtitle: SelectableText( - 'sha512: ' + rootHash + '\u202F', + prettyBytes(widget.fileSize) + '\u202F' + '\n' + 'sha512: ' + rootHash + '\u202F', style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, maxLines: 4, - textWidthBasis: TextWidthBasis.parent, + textWidthBasis: TextWidthBasis.longestLine, ), - leading: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of(context).theme.messageFromOtherTextColor), - trailing: Visibility( - visible: speed != "0 B/s", - child: SelectableText( - speed + '\u202F', - style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), - textAlign: TextAlign.left, - maxLines: 1, - textWidthBasis: TextWidthBasis.longestLine, - )), + leading: FittedBox(child: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of(context).theme.messageFromOtherTextColor)), + // Note: not using Visible here because we want to shrink this to nothing when not in use... + trailing: speed == "0 B/s" + ? null + : SelectableText( + speed + '\u202F', + style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), + textAlign: TextAlign.left, + maxLines: 1, + textWidthBasis: TextWidthBasis.longestLine, + ), ); } diff --git a/lib/widgets/messagebubbledecorations.dart b/lib/widgets/messagebubbledecorations.dart index 55bbc3eb..104a41b0 100644 --- a/lib/widgets/messagebubbledecorations.dart +++ b/lib/widgets/messagebubbledecorations.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -23,35 +24,33 @@ class _MessageBubbleDecoration extends State { Widget build(BuildContext context) { var prettyDate = prettyDateString(context, widget.messageDate.toLocal()); - return Center( - widthFactor: 1.0, + return FittedBox( child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(prettyDate, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 9.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.w200, - fontFamily: "Inter", - color: widget.fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor), - textAlign: widget.fromMe ? TextAlign.right : TextAlign.left), - !widget.fromMe - ? SizedBox(width: 1, height: 1) - : Padding( - padding: EdgeInsets.all(1.0), - child: widget.ackd == true + mainAxisSize: MainAxisSize.min, + children: [ + Text(prettyDate, + overflow: TextOverflow.ellipsis, + textWidthBasis: TextWidthBasis.longestLine, + style: TextStyle( + fontSize: 9.0 * Provider.of(context).fontScaling, + fontWeight: FontWeight.w200, + fontFamily: "Inter", + color: widget.fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor), + textAlign: widget.fromMe ? TextAlign.right : TextAlign.left), + !widget.fromMe + ? SizedBox(width: 1, height: 1) + : Padding( + padding: EdgeInsets.all(1.0), + child: widget.ackd == true + ? Tooltip( + message: AppLocalizations.of(context)!.acknowledgedLabel, child: Icon(Icons.check_circle_outline, color: Provider.of(context).theme.messageFromMeTextColor, size: 16)) + : (widget.errored == true ? Tooltip( - message: AppLocalizations.of(context)!.acknowledgedLabel, - child: Icon(Icons.check_circle_outline, color: Provider.of(context).theme.messageFromMeTextColor, size: 16)) - : (widget.errored == true - ? Tooltip( - message: AppLocalizations.of(context)!.couldNotSendMsgError, - child: Icon(Icons.error_outline, color: Provider.of(context).theme.messageFromMeTextColor, size: 16)) - : Tooltip( - message: AppLocalizations.of(context)!.pendingLabel, - child: Icon(Icons.hourglass_bottom_outlined, color: Provider.of(context).theme.messageFromMeTextColor, size: 16)))) - ], - )); + message: AppLocalizations.of(context)!.couldNotSendMsgError, child: Icon(Icons.error_outline, color: Provider.of(context).theme.messageFromMeTextColor, size: 16)) + : Tooltip( + message: AppLocalizations.of(context)!.pendingLabel, + child: Icon(Icons.hourglass_bottom_outlined, color: Provider.of(context).theme.messageFromMeTextColor, size: 16)))) + ], + )); } } diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 0230a986..0385bade 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -63,7 +63,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi var isContact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle) != null; var isGroup = Provider.of(context).contactList.getContact(Provider.of(context, listen: false).conversationIdentifier)!.isGroup; var isBlocked = isContact ? Provider.of(context).contactList.findContact(Provider.of(context).senderHandle)!.isBlocked : false; - var actualMessage = Flexible(flex: 1, fit: FlexFit.loose, child: widget.child); + var actualMessage = Flexible(flex: Platform.isAndroid ? 10 : 7, fit: FlexFit.loose, child: widget.child); _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft; _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft; From 907cc262bbb1224378f4b4273dbe6d447ff2a994 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 09:40:23 -0800 Subject: [PATCH 06/15] Remove Comments Out Interface --- lib/models/message.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/models/message.dart b/lib/models/message.dart index 391286c8..1557e16d 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -35,8 +35,6 @@ abstract class Message { Widget getWidget(BuildContext context, Key key, int index); Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}); - - //double requestedWidth(BuildContext context, BoxConstraints constraints); } Message compileOverlay(MessageInfo messageInfo) { From cd476f39c0c244c3ed77b31ee550a8c14eca16a3 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 09:56:03 -0800 Subject: [PATCH 07/15] Make Contact Row Layout More Consistent --- lib/widgets/contactrow.dart | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index c78e090a..dbeb0ee7 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -82,14 +82,15 @@ class _ContactRowState extends State { padding: EdgeInsets.all(10.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, children: [ Container( - height: 24, clipBehavior: Clip.hardEdge, + height: Provider.of(context).fontScaling * 14.0 + 10.0, decoration: BoxDecoration(), child: Text( - contact.augmentedNickname(context) + (contact.messageDraft.isEmpty() ? "" : "*"), - + contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"), style: TextStyle( fontSize: Provider.of(context).fontScaling * 14.0, fontFamily: "Inter", @@ -101,13 +102,11 @@ class _ContactRowState extends State { overflow: TextOverflow.clip, maxLines: 1, )), - syncStatus ?? Container(), - Visibility( - visible: !Provider.of(context).streamerMode, - child: Text(contact.onion, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor : Provider.of(context).theme.mainTextColor)), - ), + syncStatus ?? + SizedBox( + width: 0, + height: 0, + ), // we need to ignore the child widget in this context, otherwise gesture events will flow down... IgnorePointer( ignoring: true, @@ -121,6 +120,7 @@ class _ContactRowState extends State { )), Container( padding: EdgeInsets.all(0), + height: Provider.of(context).fontScaling * 14.0 + 10.0, child: contact.isInvitation == true ? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: [ Padding( @@ -161,6 +161,15 @@ class _ContactRowState extends State { ) : Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())))), ), + Visibility( + visible: !Provider.of(context).streamerMode, + child: Text( + contact.onion, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: ((contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor : Provider.of(context).theme.mainTextColor) as Color) + .withOpacity(0.8)), + )), ], ))), Visibility( From 497a12e8b672188d40c711c8b235d5a3eb26eb05 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 10:06:36 -0800 Subject: [PATCH 08/15] Use monospace for cwtch identifiers so they are fixed width --- lib/views/profilemgrview.dart | 1 - lib/widgets/contactrow.dart | 6 ++++-- lib/widgets/profilerow.dart | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index dd00ea59..7adecc85 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:cwtch/constants.dart'; diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index dbeb0ee7..a6276ae8 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -166,9 +166,11 @@ class _ContactRowState extends State { child: Text( contact.onion, overflow: TextOverflow.ellipsis, - style: TextStyle( + style: Provider.of(context).scaleFonts(TextStyle( + fontSize: 13.0, + fontFamily: "RobotoMono", color: ((contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor : Provider.of(context).theme.mainTextColor) as Color) - .withOpacity(0.8)), + .withOpacity(0.8))), )), ], ))), diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index 4e53a773..bb5b5366 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -45,7 +45,7 @@ class _ProfileRowState extends State { child: Column( children: [ Container( - height: 24, + height: 18.0 * Provider.of(context).fontScaling + 10.0, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: Text( @@ -61,6 +61,10 @@ class _ProfileRowState extends State { child: Text( profile.onion, softWrap: true, + style: TextStyle( + fontFamily: "RobotoMono", + fontSize: 14.0 * Provider.of(context).fontScaling, + color: ((Provider.of(context).theme.mainTextColor) as Color).withOpacity(0.8)), overflow: TextOverflow.ellipsis, ))) ], From a7041770a018ae84377e1702a48da429e9bf501d Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 10:18:11 -0800 Subject: [PATCH 09/15] A few more profile screen scaling tweaks --- lib/widgets/contactrow.dart | 8 ++++---- lib/widgets/profilerow.dart | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index a6276ae8..ef8027c3 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -65,7 +65,7 @@ class _ContactRowState extends State { splashFactory: InkSplash.splashFactory, child: Ink( color: selected ? Provider.of(context).theme.backgroundHilightElementColor : Colors.transparent, - child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(6.0), //border size child: ProfileImage( @@ -79,7 +79,7 @@ class _ContactRowState extends State { )), Expanded( child: Padding( - padding: EdgeInsets.all(10.0), + padding: EdgeInsets.all(6.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -87,7 +87,7 @@ class _ContactRowState extends State { children: [ Container( clipBehavior: Clip.hardEdge, - height: Provider.of(context).fontScaling * 14.0 + 10.0, + height: Provider.of(context).fontScaling * 14.0 + 5.0, decoration: BoxDecoration(), child: Text( contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"), @@ -120,7 +120,7 @@ class _ContactRowState extends State { )), Container( padding: EdgeInsets.all(0), - height: Provider.of(context).fontScaling * 14.0 + 10.0, + height: Provider.of(context).fontScaling * 14.0 + 5.0, child: contact.isInvitation == true ? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: [ Padding( diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index bb5b5366..65fb34a1 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -43,11 +43,14 @@ class _ProfileRowState extends State { border: profile.isOnline ? Provider.of(context).theme.portraitOnlineBorderColor : Provider.of(context).theme.portraitOfflineBorderColor)), Expanded( child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, children: [ Container( height: 18.0 * Provider.of(context).fontScaling + 10.0, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), + padding: EdgeInsets.all(5.0), child: Text( profile.nickname, semanticsLabel: profile.nickname, From 4578cc51ec7267f1d74fc4d3cb19c042fe55f8d0 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 13:34:10 -0800 Subject: [PATCH 10/15] Upgrade Cwtch, Fix Android File Sharing, Fixup UI Scaling --- LIBCWTCH-GO.version | 2 +- lib/views/groupsettingsview.dart | 2 +- lib/views/peersettingsview.dart | 6 +- lib/widgets/contactrow.dart | 108 ++++++++++++++++--------------- 4 files changed, 64 insertions(+), 54 deletions(-) diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 7e597873..3062ce16 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2024-01-15-10-14-v0.0.10-9-g425c3e6 \ No newline at end of file +2024-02-09-13-23-v0.0.11 \ No newline at end of file diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index e6659289..1334ee41 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -48,7 +48,7 @@ class _GroupSettingsViewState extends State { return Scaffold( appBar: AppBar( title: Container( - height: 24, + height: Provider.of(context).fontScaling * 24.0, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: Text(Provider.of(context).nickname + " " + AppLocalizations.of(context)!.conversationSettings)), diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index ff307427..5920240c 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -49,7 +49,11 @@ class _PeerSettingsViewState extends State { } return Scaffold( appBar: AppBar( - title: Container(height: 24, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)), + title: Container( + height: Provider.of(context).fontScaling * 24.0, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration(), + child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)), ), body: _buildSettingsList(), ); diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index ef8027c3..c50ece85 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -119,59 +119,65 @@ class _ContactRowState extends State { child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context), )), Container( - padding: EdgeInsets.all(0), - height: Provider.of(context).fontScaling * 14.0 + 5.0, - child: contact.isInvitation == true - ? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: [ - Padding( - padding: EdgeInsets.all(2), - child: TextButton.icon( - label: Text( - AppLocalizations.of(context)!.tooltipAcceptContactRequest, - style: Provider.of(context).scaleFonts(defaultTextButtonStyle), - ), - icon: Icon( - Icons.favorite, - size: 16, - color: Provider.of(context).theme.mainTextColor, - ), - onPressed: _btnApprove, - )), - Padding( - padding: EdgeInsets.all(2), - child: TextButton.icon( - label: Text( - AppLocalizations.of(context)!.tooltipRejectContactRequest, - style: Provider.of(context).scaleFonts(defaultTextButtonStyle), - ), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), - foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), - icon: Icon(Icons.delete, size: 16, color: Provider.of(context).theme.mainTextColor), - onPressed: _btnReject, - )) - ]) - : (contact.isBlocked - ? IconButton( - padding: EdgeInsets.zero, - splashRadius: Material.defaultSplashRadius / 2, - iconSize: 16, - icon: Icon(Icons.block, color: Provider.of(context).theme.mainTextColor), - onPressed: () {}, - ) - : Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())))), - ), + padding: EdgeInsets.all(0), + height: Provider.of(context).fontScaling * 14.0 + 5.0, + child: contact.isInvitation == true + ? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: [ + Padding( + padding: EdgeInsets.all(2), + child: TextButton.icon( + label: Text( + AppLocalizations.of(context)!.tooltipAcceptContactRequest, + style: Provider.of(context).scaleFonts(defaultTextButtonStyle), + ), + icon: Icon( + Icons.favorite, + size: 16, + color: Provider.of(context).theme.mainTextColor, + ), + onPressed: _btnApprove, + )), + Padding( + padding: EdgeInsets.all(2), + child: TextButton.icon( + label: Text( + AppLocalizations.of(context)!.tooltipRejectContactRequest, + style: Provider.of(context).scaleFonts(defaultTextButtonStyle), + ), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), + foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), + icon: Icon(Icons.delete, size: 16, color: Provider.of(context).theme.mainTextColor), + onPressed: _btnReject, + )) + ]) + : (contact.isBlocked + ? IconButton( + padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0), + splashRadius: Material.defaultSplashRadius / 2, + iconSize: 16, + icon: Icon(Icons.block, color: Provider.of(context).theme.mainTextColor), + onPressed: null, + ) + : Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())), + style: Provider.of(context).scaleFonts(TextStyle( + fontSize: 12.0, + fontFamily: "Inter", + ))))), Visibility( visible: !Provider.of(context).streamerMode, - child: Text( - contact.onion, - overflow: TextOverflow.ellipsis, - style: Provider.of(context).scaleFonts(TextStyle( - fontSize: 13.0, - fontFamily: "RobotoMono", - color: ((contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor : Provider.of(context).theme.mainTextColor) as Color) - .withOpacity(0.8))), - )), + child: Container( + padding: EdgeInsets.all(0), + height: Provider.of(context).fontScaling * 13.0 + 5.0, + child: Text( + contact.onion, + overflow: TextOverflow.ellipsis, + style: Provider.of(context).scaleFonts(TextStyle( + fontSize: 13.0, + fontFamily: "RobotoMono", + color: ((contact.isBlocked ? Provider.of(context).theme.portraitBlockedTextColor : Provider.of(context).theme.mainTextColor) as Color) + .withOpacity(0.8))), + ))), ], ))), Visibility( From 5001255a8adaed10f8b5950a76e34276a2638a58 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 13:37:13 -0800 Subject: [PATCH 11/15] Prep 1.14 RC --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index fc20f95d..20c3cf65 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.13.2+39 +version: 1.14.0+40 environment: sdk: ">=2.17.0 <4.0.0" From 9efc3e3c4a8002726eca4b77fe699e3c174760e7 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 13:49:00 -0800 Subject: [PATCH 12/15] Fix Width Calc --- lib/widgets/filebubble.dart | 1 + lib/widgets/messagerow.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index fb7a102c..00f1e67f 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -228,6 +228,7 @@ class FileBubbleState extends State { } return Container( + constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.3), decoration: BoxDecoration( color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, border: Border.all(color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, width: 1), diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 0385bade..300a05d0 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -63,7 +63,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi var isContact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle) != null; var isGroup = Provider.of(context).contactList.getContact(Provider.of(context, listen: false).conversationIdentifier)!.isGroup; var isBlocked = isContact ? Provider.of(context).contactList.findContact(Provider.of(context).senderHandle)!.isBlocked : false; - var actualMessage = Flexible(flex: Platform.isAndroid ? 10 : 7, fit: FlexFit.loose, child: widget.child); + var actualMessage = Flexible(flex: Platform.isAndroid ? 10 : 100, fit: FlexFit.loose, child: widget.child); _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft; _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft; From ad9c974dbdc9c26bd0f32c0112b94c9098da9e49 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 15:25:39 -0800 Subject: [PATCH 13/15] Fix Quoted Message Width --- lib/models/messages/filemessage.dart | 7 ++- lib/widgets/quotedmessage.dart | 80 +++++++++++++++------------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index 2c7b4726..c5c2eece 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -65,8 +65,11 @@ class FileMessage extends Message { return MalformedBubble(); } return Container( - alignment: Alignment.center, - height: 100, + padding: EdgeInsets.all(1.0), + decoration: BoxDecoration(), + clipBehavior: Clip.antiAliasWithSaveLayer, + width: 100, + height: 50, child: FileBubble( nameSuggestion, rootHash, diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index 8bc3638e..fe90ab52 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -1,11 +1,8 @@ -import 'package:cwtch/controllers/open_link_modal.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.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:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -49,6 +46,7 @@ class QuotedMessageBubbleState extends State { var formatMessages = Provider.of(context).isExperimentEnabled(FormattingExperiment); Size size = MediaQuery.of(context).size; BoxConstraints constraints = BoxConstraints.loose(size); + Widget wdgMessage = compileMessageContentWidget(context, constraints, fromMe, widget.body, _focus, formatMessages, showClickableLinks); var wdgQuote = FutureBuilder( future: widget.quotedMessage, builder: (context, snapshot) { @@ -80,33 +78,34 @@ class QuotedMessageBubbleState extends State { ); // Swap the background color for quoted tweets.. return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - var messageInfo = Provider.of(context, listen: false).messageCache.getByContentHash(qMessage.getMetadata().contenthash); - if (messageInfo != null) { - var index = Provider.of(context, listen: false).messageCache.findIndex(messageInfo.metadata.messageID); - if (index != null) { - Provider.of(context, listen: false).messageScrollController.scrollTo(index: index, duration: Duration(milliseconds: 100)); - } - } - }, - child: Container( - margin: EdgeInsets.all(5), - padding: EdgeInsets.all(5), - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - color: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor, - ), - height: 75, - child: Column(children: [ - Align(alignment: Alignment.centerLeft, child: wdgReplyingTo), - Flexible( - child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), - Flexible(child: qMessage.getPreviewWidget(context)), - ])) - ])))); + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + var messageInfo = Provider.of(context, listen: false).messageCache.getByContentHash(qMessage.getMetadata().contenthash); + if (messageInfo != null) { + var index = Provider.of(context, listen: false).messageCache.findIndex(messageInfo.metadata.messageID); + if (index != null) { + Provider.of(context, listen: false).messageScrollController.scrollTo(index: index, duration: Duration(milliseconds: 100)); + } + } + }, + child: Container( + margin: EdgeInsets.all(5), + padding: EdgeInsets.all(5), + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor, + ), + height: 75, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + Align(alignment: Alignment.centerLeft, child: wdgReplyingTo), + Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ + Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), + IntrinsicWidth(child: qMessage.getPreviewWidget(context)), + ]) + ])), + ), + ); } catch (e) { return MalformedBubble(); } @@ -120,8 +119,13 @@ class QuotedMessageBubbleState extends State { var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); var error = Provider.of(context).error; - - var wdgMessage = compileMessageContentWidget(context, constraints, fromMe, widget.body, _focus, formatMessages, showClickableLinks); + var spacer = Container( + height: 75, + decoration: BoxDecoration( + color: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor, + ), + child: SizedBox(), + ); var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr); return Container( decoration: BoxDecoration( @@ -150,10 +154,12 @@ class QuotedMessageBubbleState extends State { style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.menuBackgroundColor)), ), ), - child: Column( - crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, - mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))); + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, + mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + verticalDirection: VerticalDirection.up, + children: fromMe ? [wdgDecorations, wdgMessage, wdgQuote] : [wdgDecorations, wdgMessage, wdgQuote, wdgSender]))))); } } From 8acefb8b0b222f8cb784a6ef60086c285051b516 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 15:26:21 -0800 Subject: [PATCH 14/15] Remove old code --- lib/widgets/quotedmessage.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index fe90ab52..235b9c95 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -119,13 +119,6 @@ class QuotedMessageBubbleState extends State { var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); var error = Provider.of(context).error; - var spacer = Container( - height: 75, - decoration: BoxDecoration( - color: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor, - ), - child: SizedBox(), - ); var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr); return Container( decoration: BoxDecoration( From e4b2e7936f7855ece1accd818696c6ce1bfbce58 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 9 Feb 2024 15:54:19 -0800 Subject: [PATCH 15/15] Fixup Debug Mode Overlaps --- lib/models/messages/filemessage.dart | 4 ++-- lib/widgets/filebubble.dart | 31 ++++++++++++++-------------- lib/widgets/quotedmessage.dart | 9 ++++---- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index c5c2eece..bb293e9e 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -68,8 +68,8 @@ class FileMessage extends Message { padding: EdgeInsets.all(1.0), decoration: BoxDecoration(), clipBehavior: Clip.antiAliasWithSaveLayer, - width: 100, - height: 50, + constraints: BoxConstraints(minHeight: 50, maxHeight: 50, minWidth: 50, maxWidth: 300), + alignment: Alignment.centerLeft, child: FileBubble( nameSuggestion, rootHash, diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 00f1e67f..2e790cab 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -52,20 +52,21 @@ class FileBubbleState extends State { } Widget getPreview(context) { - return Image.file( - myFile!, - // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... - cacheWidth: 1024, - cacheHeight: 1024, - filterQuality: FilterQuality.medium, - fit: BoxFit.scaleDown, - alignment: Alignment.center, - height: min(MediaQuery.of(context).size.height * 0.30, 150), - isAntiAlias: false, - errorBuilder: (context, error, stackTrace) { - return MalformedBubble(); - }, - ); + return Container( + constraints: BoxConstraints(maxHeight: min(MediaQuery.of(context).size.height, 150)), + child: Image.file( + myFile!, + // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... + cacheWidth: 1024, + cacheHeight: 1024, + filterQuality: FilterQuality.medium, + fit: BoxFit.scaleDown, + alignment: Alignment.center, + isAntiAlias: false, + errorBuilder: (context, error, stackTrace) { + return MalformedBubble(); + }, + )); } @override @@ -392,7 +393,7 @@ class FileBubbleState extends State { title: Text(meta), trailing: IconButton( icon: Icon(Icons.close), - color: Provider.of(bcontext, listen: false).theme.toolbarIconColor, + color: Provider.of(bcontext, listen: false).theme.mainTextColor, iconSize: 32, onPressed: () { Navigator.pop(bcontext, true); diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index 235b9c95..045a380d 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -92,17 +92,18 @@ class QuotedMessageBubbleState extends State { child: Container( margin: EdgeInsets.all(5), padding: EdgeInsets.all(5), - clipBehavior: Clip.antiAlias, + clipBehavior: Clip.antiAliasWithSaveLayer, decoration: BoxDecoration( color: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor, ), height: 75, child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Align(alignment: Alignment.centerLeft, child: wdgReplyingTo), - Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ + Flexible( + child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), - IntrinsicWidth(child: qMessage.getPreviewWidget(context)), - ]) + Flexible(child: IntrinsicWidth(child: qMessage.getPreviewWidget(context))), + ])) ])), ), );