Merge pull request 'Resize/Scaling Fixes' (#818) from fixchat into trunk
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #818 Reviewed-by: Dan Ballard <dan@openprivacy.ca>
This commit is contained in:
commit
8bc0605503
|
@ -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 |
|
@ -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
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
;
|
;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cwtch/constants.dart';
|
import 'package:cwtch/constants.dart';
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)],
|
])))));
|
||||||
))
|
|
||||||
])))));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]))));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))))
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
)))
|
)))
|
||||||
],
|
],
|
||||||
|
|
|
@ -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]))))));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue