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/assets/themes/juniper/JuniperDark.png b/assets/themes/juniper/JuniperDark.png new file mode 100644 index 00000000..3948e081 Binary files /dev/null and b/assets/themes/juniper/JuniperDark.png differ 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 diff --git a/lib/models/message.dart b/lib/models/message.dart index decdf51b..1557e16d 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -34,7 +34,7 @@ abstract class Message { Widget getWidget(BuildContext context, Key key, int index); - Widget getPreviewWidget(BuildContext context); + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}); } Message compileOverlay(MessageInfo messageInfo) { diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index d8ebc6cf..bb293e9e 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -49,24 +49,27 @@ class FileMessage extends Message { } @override - Widget getPreviewWidget(BuildContext context) { + Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { return ChangeNotifierProvider.value( value: this.metadata, 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, - height: 100, + padding: EdgeInsets.all(1.0), + decoration: BoxDecoration(), + clipBehavior: Clip.antiAliasWithSaveLayer, + constraints: BoxConstraints(minHeight: 50, maxHeight: 50, minWidth: 50, maxWidth: 300), + alignment: Alignment.centerLeft, child: FileBubble( nameSuggestion, rootHash, 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..95b1db5d 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.loose(MediaQuery.sizeOf(context)), 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..bb9d0a7c 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.loose(MediaQuery.sizeOf(context)), 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/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/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/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/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/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 c78e090a..c50ece85 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,17 +79,18 @@ 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, + mainAxisSize: MainAxisSize.min, children: [ Container( - height: 24, clipBehavior: Clip.hardEdge, + height: Provider.of(context).fontScaling * 14.0 + 5.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, @@ -120,47 +119,65 @@ class _ContactRowState extends State { child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context), )), Container( - padding: EdgeInsets.all(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: 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( diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index ac151618..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 @@ -146,130 +147,130 @@ 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, 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( + 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), + 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 { @@ -319,41 +320,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 @@ -361,46 +348,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, + ), ); } @@ -419,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/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..0a6c1c27 100644 --- a/lib/widgets/malformedbubble.dart +++ b/lib/widgets/malformedbubble.dart @@ -13,42 +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)], - )) - ]))))); - }); + 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, + )) + ]))))); } } 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..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,34 +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, - 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/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..300a05d0 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: Platform.isAndroid ? 10 : 100, fit: FlexFit.loose, child: widget.child); _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft; _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft; diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index 4e53a773..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: 24, + 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, @@ -61,6 +64,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, ))) ], diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index afcc07bf..045a380d 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'; @@ -45,12 +42,11 @@ 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.loose(size); + Widget wdgMessage = compileMessageContentWidget(context, constraints, fromMe, widget.body, _focus, formatMessages, showClickableLinks); var wdgQuote = FutureBuilder( future: widget.quotedMessage, builder: (context, snapshot) { @@ -82,33 +78,35 @@ 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.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), + 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: IntrinsicWidth(child: qMessage.getPreviewWidget(context))), + ])) + ])), + ), + ); } catch (e) { return MalformedBubble(); } @@ -122,44 +120,40 @@ 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 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), - 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), - ), + // 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: 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])))))); - }); + ), + 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]))))); } } 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( 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"