Merge pull request 'Resize/Scaling Fixes' (#818) from fixchat into trunk
continuous-integration/drone/push Build is passing Details

Reviewed-on: #818
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
This commit is contained in:
Sarah Jamie Lewis 2024-02-10 00:11:14 +00:00
commit 8bc0605503
28 changed files with 520 additions and 671 deletions

View File

@ -1 +1 @@
2024-01-15-10-14-v0.0.10-9-g425c3e6 2024-02-09-13-23-v0.0.11

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -45,4 +45,9 @@ themes:
portraitProfileBadgeColor: accent portraitProfileBadgeColor: accent
portraitProfileBadgeTextColor: mainTextColor portraitProfileBadgeTextColor: mainTextColor
dropShadowColor: accentAlt dropShadowColor: accentAlt
chatReactionIconColor: accentAlt chatReactionIconColor: accentAlt
chatImage: JuniperDark.png
chatImageColor: userBubble
messageSelectionColor: accent
textfieldSelectionColor: accent
menuBackgroundColor: accent

View File

@ -34,7 +34,7 @@ abstract class Message {
Widget getWidget(BuildContext context, Key key, int index); Widget getWidget(BuildContext context, Key key, int index);
Widget getPreviewWidget(BuildContext context); Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints});
} }
Message compileOverlay(MessageInfo messageInfo) { Message compileOverlay(MessageInfo messageInfo) {

View File

@ -49,24 +49,27 @@ class FileMessage extends Message {
} }
@override @override
Widget getPreviewWidget(BuildContext context) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: this.metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
dynamic shareObj = jsonDecode(this.content); dynamic shareObj = jsonDecode(this.content);
if (shareObj == null) { if (shareObj == null) {
return MessageRow(MalformedBubble(), 0); return MalformedBubble();
} }
String nameSuggestion = shareObj['f'] as String; String nameSuggestion = shareObj['f'] as String;
String rootHash = shareObj['h'] as String; String rootHash = shareObj['h'] as String;
String nonce = shareObj['n'] as String; String nonce = shareObj['n'] as String;
int fileSize = shareObj['s'] as int; int fileSize = shareObj['s'] as int;
if (!validHash(rootHash, nonce)) { if (!validHash(rootHash, nonce)) {
return MessageRow(MalformedBubble(), 0); return MalformedBubble();
} }
return Container( return Container(
alignment: Alignment.center, padding: EdgeInsets.all(1.0),
height: 100, decoration: BoxDecoration(),
clipBehavior: Clip.antiAliasWithSaveLayer,
constraints: BoxConstraints(minHeight: 50, maxHeight: 50, minWidth: 50, maxWidth: 300),
alignment: Alignment.centerLeft,
child: FileBubble( child: FileBubble(
nameSuggestion, nameSuggestion,
rootHash, rootHash,

View File

@ -48,7 +48,7 @@ class InviteMessage extends Message {
} }
@override @override
Widget getPreviewWidget(BuildContext context) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: this.metadata,
builder: (bcontext, child) { builder: (bcontext, child) {

View File

@ -18,7 +18,7 @@ class MalformedMessage extends Message {
} }
@override @override
Widget getPreviewWidget(BuildContext context) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: this.metadata,
builder: (bcontext, child) { builder: (bcontext, child) {

View File

@ -30,7 +30,7 @@ class QuotedMessage extends Message {
QuotedMessage(this.metadata, this.content); QuotedMessage(this.metadata, this.content);
@override @override
Widget getPreviewWidget(BuildContext context) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: this.metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
@ -40,7 +40,7 @@ class QuotedMessage extends Message {
); );
var content = message["body"]; var content = message["body"];
var formatMessages = Provider.of<Settings>(bcontext).isExperimentEnabled(FormattingExperiment); var formatMessages = Provider.of<Settings>(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) { } catch (e) {
return MalformedBubble(); return MalformedBubble();
} }

View File

@ -21,12 +21,12 @@ class TextMessage extends Message {
TextMessage(this.metadata, this.content); TextMessage(this.metadata, this.content);
@override @override
Widget getPreviewWidget(BuildContext context) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: this.metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
var formatMessages = Provider.of<Settings>(bcontext).isExperimentEnabled(FormattingExperiment); var formatMessages = Provider.of<Settings>(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);
; ;
}); });
} }

View File

@ -37,124 +37,6 @@ export 'linkify.dart' show LinkifyElement, LinkifyOptions, LinkableElement, Text
/// Callback clicked link /// Callback clicked link
typedef LinkCallback = void Function(LinkableElement 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<Linkifier> 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 /// Turns URLs into links
class SelectableLinkify extends StatelessWidget { class SelectableLinkify extends StatelessWidget {
/// Text to be linkified /// Text to be linkified
@ -175,13 +57,13 @@ class SelectableLinkify extends StatelessWidget {
// TextSpan // TextSpan
/// Style for code text /// Style for code text
final TextStyle? codeStyle; final TextStyle codeStyle;
/// Style for non-link text /// Style for non-link text
final TextStyle? style; final TextStyle style;
/// Style of link text /// Style of link text
final TextStyle? linkStyle; final TextStyle linkStyle;
// Text.rich // Text.rich
@ -214,9 +96,6 @@ class SelectableLinkify extends StatelessWidget {
/// Whether this text field should focus itself if nothing else is already focused. /// Whether this text field should focus itself if nothing else is already focused.
final bool autofocus; final bool autofocus;
/// Configuration of toolbar options
final ToolbarOptions? toolbarOptions;
/// How thick the cursor will be /// How thick the cursor will be
final double cursorWidth; final double cursorWidth;
@ -250,6 +129,8 @@ class SelectableLinkify extends StatelessWidget {
/// Called when the user changes the selection of text (including the cursor location). /// Called when the user changes the selection of text (including the cursor location).
final SelectionChangedCallback? onSelectionChanged; final SelectionChangedCallback? onSelectionChanged;
final BoxConstraints constraints;
const SelectableLinkify({ const SelectableLinkify({
Key? key, Key? key,
required this.text, required this.text,
@ -257,11 +138,11 @@ class SelectableLinkify extends StatelessWidget {
this.onOpen, this.onOpen,
this.options = const LinkifyOptions(), this.options = const LinkifyOptions(),
// TextSpan // TextSpan
this.style, required this.style,
this.linkStyle, required this.linkStyle,
// RichText // RichText
this.textAlign, this.textAlign,
this.codeStyle, required this.codeStyle,
this.textDirection, this.textDirection,
this.minLines, this.minLines,
this.maxLines, this.maxLines,
@ -271,7 +152,6 @@ class SelectableLinkify extends StatelessWidget {
this.strutStyle, this.strutStyle,
this.showCursor = false, this.showCursor = false,
this.autofocus = false, this.autofocus = false,
this.toolbarOptions,
this.cursorWidth = 2.0, this.cursorWidth = 2.0,
this.cursorRadius, this.cursorRadius,
this.cursorColor, this.cursorColor,
@ -284,6 +164,7 @@ class SelectableLinkify extends StatelessWidget {
this.cursorHeight, this.cursorHeight,
this.selectionControls, this.selectionControls,
this.onSelectionChanged, this.onSelectionChanged,
required this.constraints,
}) : super(key: key); }) : super(key: key);
@override @override
@ -295,25 +176,17 @@ class SelectableLinkify extends StatelessWidget {
); );
return Container( return Container(
clipBehavior: Clip.hardEdge, constraints: constraints,
decoration: BoxDecoration(),
child: SelectableText.rich( child: SelectableText.rich(
buildTextSpan( buildTextSpan(elements,
elements, style: style,
style: Theme.of(context).textTheme.bodyText2?.merge(style), codeStyle: codeStyle,
codeStyle: Theme.of(context).textTheme.bodyText2?.merge(codeStyle), onOpen: onOpen,
onOpen: onOpen, context: context,
context: context, linkStyle: linkStyle.copyWith(
linkStyle: Theme.of(context) decoration: TextDecoration.underline,
.textTheme ),
.bodyText2 constraints: constraints),
?.merge(style)
.copyWith(
color: Colors.blueAccent,
decoration: TextDecoration.underline,
)
.merge(linkStyle),
),
textAlign: textAlign, textAlign: textAlign,
textDirection: textDirection, textDirection: textDirection,
minLines: minLines, minLines: minLines,
@ -323,7 +196,6 @@ class SelectableLinkify extends StatelessWidget {
showCursor: showCursor, showCursor: showCursor,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
autofocus: autofocus, autofocus: autofocus,
toolbarOptions: toolbarOptions,
cursorWidth: cursorWidth, cursorWidth: cursorWidth,
cursorRadius: cursorRadius, cursorRadius: cursorRadius,
cursorColor: cursorColor, cursorColor: cursorColor,
@ -366,8 +238,24 @@ TextSpan buildTextSpan(
LinkCallback? onOpen, LinkCallback? onOpen,
required BuildContext context, required BuildContext context,
bool useMouseRegion = false, 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( return TextSpan(
style: style,
children: elements.map<InlineSpan>( children: elements.map<InlineSpan>(
(element) { (element) {
if (element is LinkableElement) { if (element is LinkableElement) {

View File

@ -1,9 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cwtch/config.dart';
import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/main.dart'; import 'package:cwtch/main.dart';
import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.dart';

View File

@ -48,7 +48,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Container( title: Container(
height: 24, height: Provider.of<Settings>(context).fontScaling * 24.0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: BoxDecoration(),
child: Text(Provider.of<ContactInfoState>(context).nickname + " " + AppLocalizations.of(context)!.conversationSettings)), child: Text(Provider.of<ContactInfoState>(context).nickname + " " + AppLocalizations.of(context)!.conversationSettings)),

View File

@ -10,21 +10,16 @@ import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/chatmessage.dart'; import 'package:cwtch/models/chatmessage.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/message_draft.dart';
import 'package:cwtch/models/messagecache.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/messages/quotedmessage.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/third_party/linkify/flutter_linkify.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/malformedbubble.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:cwtch/widgets/profileimage.dart'; import 'package:cwtch/widgets/profileimage.dart';
import 'package:cwtch/controllers/filesharing.dart' as filesharing; import 'package:cwtch/controllers/filesharing.dart' as filesharing;
import 'package:cwtch/widgets/staticmessagebubble.dart'; import 'package:cwtch/widgets/staticmessagebubble.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/views/peersettingsview.dart'; import 'package:cwtch/views/peersettingsview.dart';
import 'package:cwtch/widgets/DropdownContacts.dart'; import 'package:cwtch/widgets/DropdownContacts.dart';
@ -493,6 +488,7 @@ class _MessageViewState extends State<MessageView> {
backgroundColor: Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor), backgroundColor: Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
textAlign: TextAlign.left, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
constraints: BoxConstraints.expand(),
)); ));
var showMessageFormattingPreview = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment); var showMessageFormattingPreview = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
@ -601,22 +597,6 @@ class _MessageViewState extends State<MessageView> {
}); });
}); });
// var subscript = IconButton(
// icon: Icon(Icons.subscript),
// tooltip: AppLocalizations.of(context)!.tooltipSubscript,
// onPressed: () {
// setState(() {
// var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
// });
// });
var strikethrough = IconButton( var strikethrough = IconButton(
icon: Icon(Icons.format_strikethrough), icon: Icon(Icons.format_strikethrough),
tooltip: AppLocalizations.of(context)!.tooltipStrikethrough, tooltip: AppLocalizations.of(context)!.tooltipStrikethrough,

View File

@ -49,7 +49,11 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
} }
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Container(height: 24, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)), title: Container(
height: Provider.of<Settings>(context).fontScaling * 24.0,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)),
), ),
body: _buildSettingsList(), body: _buildSettingsList(),
); );

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:cwtch/constants.dart'; import 'package:cwtch/constants.dart';

View File

@ -65,7 +65,7 @@ class _ContactRowState extends State<ContactRow> {
splashFactory: InkSplash.splashFactory, splashFactory: InkSplash.splashFactory,
child: Ink( child: Ink(
color: selected ? Provider.of<Settings>(context).theme.backgroundHilightElementColor : Colors.transparent, color: selected ? Provider.of<Settings>(context).theme.backgroundHilightElementColor : Colors.transparent,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [
Padding( Padding(
padding: const EdgeInsets.all(6.0), //border size padding: const EdgeInsets.all(6.0), //border size
child: ProfileImage( child: ProfileImage(
@ -79,17 +79,18 @@ class _ContactRowState extends State<ContactRow> {
)), )),
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(6.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
height: 24,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
decoration: BoxDecoration(), decoration: BoxDecoration(),
child: Text( child: Text(
contact.augmentedNickname(context) + (contact.messageDraft.isEmpty() ? "" : "*"), contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"),
style: TextStyle( style: TextStyle(
fontSize: Provider.of<Settings>(context).fontScaling * 14.0, fontSize: Provider.of<Settings>(context).fontScaling * 14.0,
fontFamily: "Inter", fontFamily: "Inter",
@ -101,13 +102,11 @@ class _ContactRowState extends State<ContactRow> {
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
maxLines: 1, maxLines: 1,
)), )),
syncStatus ?? Container(), syncStatus ??
Visibility( SizedBox(
visible: !Provider.of<Settings>(context).streamerMode, width: 0,
child: Text(contact.onion, height: 0,
overflow: TextOverflow.ellipsis, ),
style: TextStyle(color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor)),
),
// we need to ignore the child widget in this context, otherwise gesture events will flow down... // we need to ignore the child widget in this context, otherwise gesture events will flow down...
IgnorePointer( IgnorePointer(
ignoring: true, ignoring: true,
@ -120,47 +119,65 @@ class _ContactRowState extends State<ContactRow> {
child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context), child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context),
)), )),
Container( Container(
padding: EdgeInsets.all(0), padding: EdgeInsets.all(0),
child: contact.isInvitation == true height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: <Widget>[ child: contact.isInvitation == true
Padding( ? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: <Widget>[
padding: EdgeInsets.all(2), Padding(
child: TextButton.icon( padding: EdgeInsets.all(2),
label: Text( child: TextButton.icon(
AppLocalizations.of(context)!.tooltipAcceptContactRequest, label: Text(
style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle), AppLocalizations.of(context)!.tooltipAcceptContactRequest,
), style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle),
icon: Icon( ),
Icons.favorite, icon: Icon(
size: 16, Icons.favorite,
color: Provider.of<Settings>(context).theme.mainTextColor, size: 16,
), color: Provider.of<Settings>(context).theme.mainTextColor,
onPressed: _btnApprove, ),
)), onPressed: _btnApprove,
Padding( )),
padding: EdgeInsets.all(2), Padding(
child: TextButton.icon( padding: EdgeInsets.all(2),
label: Text( child: TextButton.icon(
AppLocalizations.of(context)!.tooltipRejectContactRequest, label: Text(
style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle), AppLocalizations.of(context)!.tooltipRejectContactRequest,
), style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle),
style: ButtonStyle( ),
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor), style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)), backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
icon: Icon(Icons.delete, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor), foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
onPressed: _btnReject, icon: Icon(Icons.delete, size: 16, color: Provider.of<Settings>(context).theme.mainTextColor),
)) onPressed: _btnReject,
]) ))
: (contact.isBlocked ])
? IconButton( : (contact.isBlocked
padding: EdgeInsets.zero, ? IconButton(
splashRadius: Material.defaultSplashRadius / 2, padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0),
iconSize: 16, splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor), iconSize: 16,
onPressed: () {}, icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor),
) onPressed: null,
: Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())))), )
), : Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())),
style: Provider.of<Settings>(context).scaleFonts(TextStyle(
fontSize: 12.0,
fontFamily: "Inter",
))))),
Visibility(
visible: !Provider.of<Settings>(context).streamerMode,
child: Container(
padding: EdgeInsets.all(0),
height: Provider.of<Settings>(context).fontScaling * 13.0 + 5.0,
child: Text(
contact.onion,
overflow: TextOverflow.ellipsis,
style: Provider.of<Settings>(context).scaleFonts(TextStyle(
fontSize: 13.0,
fontFamily: "RobotoMono",
color: ((contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor) as Color)
.withOpacity(0.8))),
))),
], ],
))), ))),
Visibility( Visibility(

View File

@ -52,20 +52,21 @@ class FileBubbleState extends State<FileBubble> {
} }
Widget getPreview(context) { Widget getPreview(context) {
return Image.file( return Container(
myFile!, constraints: BoxConstraints(maxHeight: min(MediaQuery.of(context).size.height, 150)),
// limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... child: Image.file(
cacheWidth: 1024, myFile!,
cacheHeight: 1024, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
filterQuality: FilterQuality.medium, cacheWidth: 1024,
fit: BoxFit.scaleDown, cacheHeight: 1024,
alignment: Alignment.center, filterQuality: FilterQuality.medium,
height: min(MediaQuery.of(context).size.height * 0.30, 150), fit: BoxFit.scaleDown,
isAntiAlias: false, alignment: Alignment.center,
errorBuilder: (context, error, stackTrace) { isAntiAlias: false,
return MalformedBubble(); errorBuilder: (context, error, stackTrace) {
}, return MalformedBubble();
); },
));
} }
@override @override
@ -146,130 +147,130 @@ class FileBubbleState extends State<FileBubble> {
); );
} }
return LayoutBuilder(builder: (bcontext, constraints) { var wdgSender = Visibility(
var wdgSender = Visibility( visible: widget.interactive,
visible: widget.interactive, child: Container(
child: Container( height: 14 * Provider.of<Settings>(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, null, fromMe, senderDisplayStr)));
height: 14 * Provider.of<Settings>(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, fromMe, senderDisplayStr))); var isPreview = false;
var isPreview = false; var wdgMessage = !showFileSharing
var wdgMessage = !showFileSharing ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)) : fromMe
: fromMe ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash)
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize) : (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize,
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey())));
Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey()))); Widget wdgDecorations;
Widget wdgDecorations;
if (!showFileSharing) { if (!showFileSharing) {
wdgDecorations = Text('\u202F'); wdgDecorations = Text('\u202F');
} else if (downloadComplete && path != null) { } else if (fromMe) {
// in this case, whatever marked download.complete would have also set the path wdgDecorations = Text('\u202F');
if (myFile != null && Provider.of<Settings>(context).shouldPreview(path)) { } else if (downloadComplete && path != null) {
isPreview = true; // in this case, whatever marked download.complete would have also set the path
wdgDecorations = Center( if (myFile != null && Provider.of<Settings>(context).shouldPreview(path)) {
widthFactor: 1.0, isPreview = true;
child: MouseRegion( wdgDecorations = Center(
cursor: SystemMouseCursors.click, widthFactor: 1.0,
child: GestureDetector( child: MouseRegion(
child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)), cursor: SystemMouseCursors.click,
onTap: () { child: GestureDetector(
pop(bcontext, myFile!, widget.nameSuggestion); child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)),
}, onTap: () {
))); pop(context, myFile!, widget.nameSuggestion);
} else { },
wdgDecorations = Visibility( )));
visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))); } else {
} wdgDecorations = Visibility(
} else if (downloadActive) { visible: widget.interactive, child: SelectableText(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)));
if (!downloadGotManifest) { }
wdgDecorations = Visibility( } else if (downloadActive) {
visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))); if (!downloadGotManifest) {
} else { wdgDecorations = Visibility(
wdgDecorations = Visibility( visible: widget.interactive, child: SelectableText(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)));
visible: widget.interactive, } else {
child: LinearProgressIndicator(
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
color: Provider.of<Settings>(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<Settings>(context).scaleFonts(defaultTextStyle));
// We should have already requested this...
} else {
var path = Provider.of<ProfileInfoState>(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<Settings>(context).scaleFonts(defaultTextStyle)),
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))
]));
}
} else if (!senderIsContact) {
wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (!widget.isAuto || Provider.of<MessageMetadata>(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( wdgDecorations = Visibility(
visible: widget.interactive, visible: widget.interactive,
child: Center( child: LinearProgressIndicator(
widthFactor: 1, value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
child: Wrap(children: [ color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
Padding( ));
padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
])));
} else {
wdgDecorations = Container();
} }
} 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<Settings>(context).scaleFonts(defaultTextStyle));
// We should have already requested this...
} else {
var path = Provider.of<ProfileInfoState>(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<Settings>(context).scaleFonts(defaultTextStyle)),
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))
]));
}
} else if (!senderIsContact) {
wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (!widget.isAuto || Provider.of<MessageMetadata>(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<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
])));
} else {
wdgDecorations = Container();
}
return Container( return Container(
constraints: constraints, constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor,
border: Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, width: 1), border: Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, width: 1),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(borderRadius), topLeft: Radius.circular(borderRadius),
topRight: Radius.circular(borderRadius), topRight: Radius.circular(borderRadius),
bottomLeft: fromMe ? Radius.circular(borderRadius) : Radius.zero, bottomLeft: fromMe ? Radius.circular(borderRadius) : Radius.zero,
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadius), bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadius),
),
), ),
child: Theme( ),
data: Theme.of(context).copyWith( child: Theme(
textSelectionTheme: TextSelectionThemeData( data: Theme.of(context).copyWith(
cursorColor: Provider.of<Settings>(context).theme.messageSelectionColor, textSelectionTheme: TextSelectionThemeData(
selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor, cursorColor: Provider.of<Settings>(context).theme.messageSelectionColor,
selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor), selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor,
selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor),
// Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯
textButtonTheme: TextButtonThemeData( textButtonTheme: TextButtonThemeData(
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.menuBackgroundColor)), style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.menuBackgroundColor)),
),
), ),
child: Padding( ),
padding: EdgeInsets.all(9.0), child: Padding(
child: Column( padding: EdgeInsets.all(9.0),
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, child: Column(
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [ mainAxisSize: MainAxisSize.min,
wdgSender, children: [
isPreview wdgSender,
? Container( isPreview
width: 0, ? Container(
padding: EdgeInsets.zero, width: 0,
margin: EdgeInsets.zero, padding: EdgeInsets.zero,
) margin: EdgeInsets.zero,
: wdgMessage, )
wdgDecorations, : wdgMessage,
messageStatusWidget wdgDecorations,
]), messageStatusWidget
))); ]),
}); )));
} }
void _btnAccept() async { void _btnAccept() async {
@ -319,41 +320,27 @@ class FileBubbleState extends State<FileBubble> {
} }
// Construct an file chrome for the sender // 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<Settings>(context); var settings = Provider.of<Settings>(context);
return ListTile( return ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ contentPadding: EdgeInsets.all(1.0),
SelectableText( title: SelectableText(
chrome + '\u202F', fileName + '\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style:
textAlign: TextAlign.left, settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
maxLines: 2, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
), maxLines: 2,
SelectableText( ),
fileName + '\u202F',
style:
settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.parent,
maxLines: 2,
),
SelectableText(
prettyBytes(fileSize) + '\u202F' + '\n',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
)
]),
subtitle: SelectableText( subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F', prettyBytes(widget.fileSize) + '\u202F' + '\n' + 'sha512: ' + rootHash + '\u202F',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 4, maxLines: 4,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.longestLine,
), ),
leading: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)); leading: FittedBox(child: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)));
} }
// Construct an file chrome // Construct an file chrome
@ -361,46 +348,33 @@ class FileBubbleState extends State<FileBubble> {
var settings = Provider.of<Settings>(context); var settings = Provider.of<Settings>(context);
return ListTile( return ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ contentPadding: EdgeInsets.all(1.0),
SelectableText( title: SelectableText(
chrome + '\u202F', fileName + '\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)), style:
textAlign: TextAlign.left, settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
maxLines: 2, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
), maxLines: 2,
SelectableText( ),
fileName + '\u202F',
style: settings
.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(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<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
)
]),
subtitle: SelectableText( subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F', prettyBytes(widget.fileSize) + '\u202F' + '\n' + 'sha512: ' + rootHash + '\u202F',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)), style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 4, maxLines: 4,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.longestLine,
), ),
leading: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor), leading: FittedBox(child: Icon(CwtchIcons.attached_file_3, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
trailing: Visibility( // Note: not using Visible here because we want to shrink this to nothing when not in use...
visible: speed != "0 B/s", trailing: speed == "0 B/s"
child: SelectableText( ? null
speed + '\u202F', : SelectableText(
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)), speed + '\u202F',
textAlign: TextAlign.left, style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
maxLines: 1, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine, maxLines: 1,
)), textWidthBasis: TextWidthBasis.longestLine,
),
); );
} }
@ -419,7 +393,7 @@ class FileBubbleState extends State<FileBubble> {
title: Text(meta), title: Text(meta),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.close), icon: Icon(Icons.close),
color: Provider.of<Settings>(bcontext, listen: false).theme.toolbarIconColor, color: Provider.of<Settings>(bcontext, listen: false).theme.mainTextColor,
iconSize: 32, iconSize: 32,
onPressed: () { onPressed: () {
Navigator.pop(bcontext, true); Navigator.pop(bcontext, true);

View File

@ -56,47 +56,46 @@ class InvitationBubbleState extends State<InvitationBubble> {
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle; senderDisplayStr = Provider.of<MessageMetadata>(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<ProfileInfoState>(context).onion;
if (selfInvite) {
return MalformedBubble();
}
var wdgMessage = isGroup && !showGroupInvite
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe
? senderInviteChrome(
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(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<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
} else if (isAccepted) {
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (this.rejected) {
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of<Settings>(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<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)),
Padding(
padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
]));
}
return LayoutBuilder(builder: (context, constraints) { 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<ProfileInfoState>(context).onion;
if (selfInvite) {
return MalformedBubble();
}
var wdgMessage = isGroup && !showGroupInvite
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe
? senderInviteChrome(
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(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<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
} else if (isAccepted) {
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (this.rejected) {
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of<Settings>(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<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)),
Padding(
padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
]));
}
//print(constraints.toString()+", "+constraints.maxWidth.toString()); //print(constraints.toString()+", "+constraints.maxWidth.toString());
return Center( return Center(
widthFactor: 1.0, widthFactor: 1.0,

View File

@ -13,42 +13,39 @@ class MalformedBubble extends StatefulWidget {
class MalformedBubbleState extends State<MalformedBubble> { class MalformedBubbleState extends State<MalformedBubble> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) { return Container(
return Center( decoration: BoxDecoration(
widthFactor: 1.0, color: malformedColor,
child: Container( border: Border.all(color: malformedColor, width: 1),
decoration: BoxDecoration( borderRadius: BorderRadius.only(
color: malformedColor, topLeft: Radius.zero,
border: Border.all(color: malformedColor, width: 1), topRight: Radius.zero,
borderRadius: BorderRadius.only( bottomLeft: Radius.zero,
topLeft: Radius.zero, bottomRight: Radius.zero,
topRight: Radius.zero, ),
bottomLeft: Radius.zero, ),
bottomRight: Radius.zero, child: FittedBox(
), fit: BoxFit.contain,
), child: Center(
child: Center( widthFactor: 1.0,
widthFactor: 1.0, child: Padding(
child: Padding( padding: EdgeInsets.all(9.0),
padding: EdgeInsets.all(9.0), child: Row(mainAxisSize: MainAxisSize.min, children: [
child: Row(mainAxisSize: MainAxisSize.min, children: [ Center(
Center( widthFactor: 1,
widthFactor: 1, child: Padding(
child: Padding( padding: EdgeInsets.all(4),
padding: EdgeInsets.all(4), child: Icon(
child: Icon( CwtchIcons.favorite_black_24dp_broken,
CwtchIcons.favorite_black_24dp_broken, size: 24,
size: 24, ))),
))), Center(
Center( widthFactor: 1.0,
widthFactor: 1.0, child: Text(
child: Column( AppLocalizations.of(context)!.malformedMessage,
crossAxisAlignment: CrossAxisAlignment.start, overflow: TextOverflow.ellipsis,
mainAxisAlignment: MainAxisAlignment.start, textWidthBasis: TextWidthBasis.longestLine,
mainAxisSize: MainAxisSize.min, ))
children: [Text(AppLocalizations.of(context)!.malformedMessage)], ])))));
))
])))));
});
} }
} }

View File

@ -5,7 +5,7 @@ import '../settings.dart';
import '../themes/opaque.dart'; import '../themes/opaque.dart';
import '../third_party/linkify/flutter_linkify.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( return Container(
height: 14 * Provider.of<Settings>(context).fontScaling, height: 14 * Provider.of<Settings>(context).fontScaling,
clipBehavior: Clip.hardEdge, 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( return SelectableLinkify(
text: content + '\u202F', text: content + '\u202F',
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? // 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); modalOpenLink(context, link);
} }
: null, : null,
//key: Key(myKey),
focusNode: focus, focusNode: focus,
style: Provider.of<Settings>(context) style: Provider.of<Settings>(context)
.scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)), .scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
@ -43,6 +42,7 @@ Widget compileMessageContentWidget(BuildContext context, bool fromMe, String con
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor, color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor,
backgroundColor: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor)), backgroundColor: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
constraints: constraints,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
); );
} }

View File

@ -45,49 +45,45 @@ class MessageBubbleState extends State<MessageBubble> {
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle; senderDisplayStr = Provider.of<MessageMetadata>(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<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate); var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
var error = Provider.of<MessageMetadata>(context).error; var error = Provider.of<MessageMetadata>(context).error;
return LayoutBuilder(builder: (context, constraints) { return Container(
//print(constraints.toString()+", "+constraints.maxWidth.toString()); decoration: BoxDecoration(
return RepaintBoundary( color: error ? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
child: Container( border: Border.all(
child: Container( color: error ? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
decoration: BoxDecoration( width: 1),
color: error ? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor), borderRadius: BorderRadius.only(
border: Border.all( topLeft: Radius.circular(borderRadiousEh),
color: error topRight: Radius.circular(borderRadiousEh),
? malformedColor bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor), bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
width: 1), ),
borderRadius: BorderRadius.only( ),
topLeft: Radius.circular(borderRadiousEh), child: Padding(
topRight: Radius.circular(borderRadiousEh), padding: EdgeInsets.all(9.0),
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, child: Theme(
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), data: Theme.of(context).copyWith(
), textSelectionTheme: TextSelectionThemeData(
), cursorColor: Provider.of<Settings>(context).theme.messageSelectionColor,
child: Padding( selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor,
padding: EdgeInsets.all(9.0), selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor),
child: Theme(
data: Theme.of(context).copyWith(
textSelectionTheme: TextSelectionThemeData(
cursorColor: Provider.of<Settings>(context).theme.messageSelectionColor,
selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor,
selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor),
// Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯
textButtonTheme: TextButtonThemeData( textButtonTheme: TextButtonThemeData(
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.menuBackgroundColor)), style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.menuBackgroundColor)),
), ),
), ),
child: Column( child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))))); children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]))));
});
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -23,34 +24,33 @@ class _MessageBubbleDecoration extends State<MessageBubbleDecoration> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var prettyDate = prettyDateString(context, widget.messageDate.toLocal()); var prettyDate = prettyDateString(context, widget.messageDate.toLocal());
return Center( return FittedBox(
widthFactor: 1.0,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(prettyDate, Text(prettyDate,
style: TextStyle( overflow: TextOverflow.ellipsis,
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling, textWidthBasis: TextWidthBasis.longestLine,
fontWeight: FontWeight.w200, style: TextStyle(
fontFamily: "Inter", fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
color: widget.fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor), fontWeight: FontWeight.w200,
textAlign: widget.fromMe ? TextAlign.right : TextAlign.left), fontFamily: "Inter",
!widget.fromMe color: widget.fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor),
? SizedBox(width: 1, height: 1) textAlign: widget.fromMe ? TextAlign.right : TextAlign.left),
: Padding( !widget.fromMe
padding: EdgeInsets.all(1.0), ? SizedBox(width: 1, height: 1)
child: widget.ackd == true : 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<Settings>(context).theme.messageFromMeTextColor, size: 16))
: (widget.errored == true
? Tooltip( ? Tooltip(
message: AppLocalizations.of(context)!.acknowledgedLabel, message: AppLocalizations.of(context)!.couldNotSendMsgError, child: Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16))
child: Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16)) : Tooltip(
: (widget.errored == true message: AppLocalizations.of(context)!.pendingLabel,
? Tooltip( child: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16))))
message: AppLocalizations.of(context)!.couldNotSendMsgError, ],
child: Icon(Icons.error_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16)) ));
: Tooltip(
message: AppLocalizations.of(context)!.pendingLabel,
child: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16))))
],
));
} }
} }

View File

@ -1,10 +1,8 @@
import 'package:cwtch/config.dart';
import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/messagecache.dart'; import 'package:cwtch/models/messagecache.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
@ -35,10 +33,6 @@ class _MessageListState extends State<MessageList> {
bool isP2P = !Provider.of<ContactInfoState>(context).isGroup; bool isP2P = !Provider.of<ContactInfoState>(context).isGroup;
bool isGroupAndSyncing = Provider.of<ContactInfoState>(context).isGroup == true && Provider.of<ContactInfoState>(context).status == "Authenticated"; bool isGroupAndSyncing = Provider.of<ContactInfoState>(context).isGroup == true && Provider.of<ContactInfoState>(context).status == "Authenticated";
// Older checks, no longer used, kept for reference.
//bool isGroupAndSynced = Provider.of<ContactInfoState>(context).isGroup && Provider.of<ContactInfoState>(context).status == "Synced";
//bool isGroupAndNotAuthenticated = Provider.of<ContactInfoState>(context).isGroup && Provider.of<ContactInfoState>(context).status != "Authenticated";
bool preserveHistoryByDefault = Provider.of<Settings>(context, listen: false).preserveHistoryByDefault; bool preserveHistoryByDefault = Provider.of<Settings>(context, listen: false).preserveHistoryByDefault;
bool showEphemeralWarning = (isP2P && (!preserveHistoryByDefault && Provider.of<ContactInfoState>(context).savePeerHistory != "SaveHistory")); bool showEphemeralWarning = (isP2P && (!preserveHistoryByDefault && Provider.of<ContactInfoState>(context).savePeerHistory != "SaveHistory"));
bool showOfflineWarning = Provider.of<ContactInfoState>(context).isOnline() == false; bool showOfflineWarning = Provider.of<ContactInfoState>(context).isOnline() == false;
@ -143,14 +137,14 @@ class _MessageListState extends State<MessageList> {
return FutureBuilder( return FutureBuilder(
future: messageHandler(itemBuilderContext, profileOnion, contactHandle, ByIndex(messageIndex)), future: messageHandler(itemBuilderContext, profileOnion, contactHandle, ByIndex(messageIndex)),
builder: (context, snapshot) { builder: (fbcontext, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
var message = snapshot.data as Message; 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 // 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 // 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. // when new messages are added...however it is better than the alternative of not having widget keys at all.
var key = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex); var key = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex);
return message.getWidget(context, key, messageIndex); return message.getWidget(fbcontext, key, messageIndex);
} else { } else {
return MessageLoadingBubble(); return MessageLoadingBubble();
} }

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:cwtch/config.dart'; import 'package:cwtch/config.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
@ -16,7 +15,6 @@ import 'package:cwtch/widgets/profileimage.dart';
import 'package:flutter/physics.dart'; import 'package:flutter/physics.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../main.dart'; import '../main.dart';
import '../models/messagecache.dart'; import '../models/messagecache.dart';
@ -65,7 +63,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
var isContact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle) != null; var isContact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle) != null;
var isGroup = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier)!.isGroup; var isGroup = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier)!.isGroup;
var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle)!.isBlocked : false; var isBlocked = isContact ? Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(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; _dragAffinity = fromMe ? Alignment.centerRight : Alignment.centerLeft;
_dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft; _dragAlignment = fromMe ? Alignment.centerRight : Alignment.centerLeft;

View File

@ -43,11 +43,14 @@ class _ProfileRowState extends State<ProfileRow> {
border: profile.isOnline ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor)), border: profile.isOnline ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor)),
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
height: 24, height: 18.0 * Provider.of<Settings>(context).fontScaling + 10.0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: BoxDecoration(),
padding: EdgeInsets.all(5.0),
child: Text( child: Text(
profile.nickname, profile.nickname,
semanticsLabel: profile.nickname, semanticsLabel: profile.nickname,
@ -61,6 +64,10 @@ class _ProfileRowState extends State<ProfileRow> {
child: Text( child: Text(
profile.onion, profile.onion,
softWrap: true, softWrap: true,
style: TextStyle(
fontFamily: "RobotoMono",
fontSize: 14.0 * Provider.of<Settings>(context).fontScaling,
color: ((Provider.of<Settings>(context).theme.mainTextColor) as Color).withOpacity(0.8)),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
))) )))
], ],

View File

@ -1,11 +1,8 @@
import 'package:cwtch/controllers/open_link_modal.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.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/malformedbubble.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -45,12 +42,11 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
} }
} }
var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment); var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
var formatMessages = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment); var formatMessages = Provider.of<Settings>(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( var wdgQuote = FutureBuilder(
future: widget.quotedMessage, future: widget.quotedMessage,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -82,33 +78,35 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
); );
// Swap the background color for quoted tweets.. // Swap the background color for quoted tweets..
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
var messageInfo = Provider.of<ContactInfoState>(context, listen: false).messageCache.getByContentHash(qMessage.getMetadata().contenthash); var messageInfo = Provider.of<ContactInfoState>(context, listen: false).messageCache.getByContentHash(qMessage.getMetadata().contenthash);
if (messageInfo != null) { if (messageInfo != null) {
var index = Provider.of<ContactInfoState>(context, listen: false).messageCache.findIndex(messageInfo.metadata.messageID); var index = Provider.of<ContactInfoState>(context, listen: false).messageCache.findIndex(messageInfo.metadata.messageID);
if (index != null) { if (index != null) {
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: index, duration: Duration(milliseconds: 100)); Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: index, duration: Duration(milliseconds: 100));
} }
} }
}, },
child: Container( child: Container(
margin: EdgeInsets.all(5), margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5), padding: EdgeInsets.all(5),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: BoxDecoration( decoration: BoxDecoration(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
), ),
height: 75, height: 75,
child: Column(children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [
Align(alignment: Alignment.centerLeft, child: wdgReplyingTo), Align(alignment: Alignment.centerLeft, child: wdgReplyingTo),
Flexible( Flexible(
child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ 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)), Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)),
Flexible(child: qMessage.getPreviewWidget(context)), Flexible(child: IntrinsicWidth(child: qMessage.getPreviewWidget(context))),
])) ]))
])))); ])),
),
);
} catch (e) { } catch (e) {
return MalformedBubble(); return MalformedBubble();
} }
@ -122,44 +120,40 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate); var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
var error = Provider.of<MessageMetadata>(context).error; var error = Provider.of<MessageMetadata>(context).error;
var wdgSender = compileSenderWidget(context, constraints, fromMe, senderDisplayStr);
return Container(
decoration: BoxDecoration(
color: error ? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
border: Border.all(
color: error ? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(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<Settings>(context).theme.messageSelectionColor,
selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor,
selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor),
return LayoutBuilder(builder: (context, constraints) { // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯
return RepaintBoundary( textButtonTheme: TextButtonThemeData(
child: Container( style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.menuBackgroundColor)),
child: Container(
decoration: BoxDecoration(
color: error ? malformedColor : (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
border: Border.all(
color: error
? malformedColor
: (fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(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: IntrinsicWidth(
child: Theme( child: Column(
data: Theme.of(context).copyWith( crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
textSelectionTheme: TextSelectionThemeData( mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
cursorColor: Provider.of<Settings>(context).theme.messageSelectionColor, mainAxisSize: MainAxisSize.min,
selectionColor: Provider.of<Settings>(context).theme.messageSelectionColor, verticalDirection: VerticalDirection.up,
selectionHandleColor: Provider.of<Settings>(context).theme.messageSelectionColor), children: fromMe ? [wdgDecorations, wdgMessage, wdgQuote] : [wdgDecorations, wdgMessage, wdgQuote, wdgSender])))));
// 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<Settings>(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]))))));
});
} }
} }

View File

@ -42,13 +42,10 @@ class StaticMessageBubbleState extends State<StaticMessageBubble> {
senderDisplayStr = widget.profile.nickname; 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) { 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()); //print(constraints.toString()+", "+constraints.maxWidth.toString());
return RepaintBoundary( return RepaintBoundary(
child: Container( child: Container(

View File

@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.13.2+39 version: 1.14.0+40
environment: environment:
sdk: ">=2.17.0 <4.0.0" sdk: ">=2.17.0 <4.0.0"