Compare commits

...

3 Commits

17 changed files with 98 additions and 55 deletions

View File

@ -123,6 +123,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
key: Key('app'), key: Key('app'),
navigatorKey: navKey, navigatorKey: navKey,
locale: settings.locale, locale: settings.locale,
showPerformanceOverlay: false,
localizationsDelegates: <LocalizationsDelegate<dynamic>>[ localizationsDelegates: <LocalizationsDelegate<dynamic>>[
AppLocalizations.delegate, AppLocalizations.delegate,
MaterialLocalizationDelegate(), MaterialLocalizationDelegate(),

View File

@ -59,7 +59,7 @@ class FileMessage extends Message {
if (shareObj == null) { if (shareObj == null) {
return MessageRow(MalformedBubble(), 0); return MessageRow(MalformedBubble(), 0);
} }
String nameSuggestion = shareObj['n'] 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;
@ -68,8 +68,7 @@ class FileMessage extends Message {
} }
return Container( return Container(
alignment: Alignment.center, alignment: Alignment.center,
width: 50, height: 100,
height: 50,
child: FileBubble( child: FileBubble(
nameSuggestion, nameSuggestion,
rootHash, rootHash,

View File

@ -28,12 +28,12 @@ class TextMessage extends Message {
return SelectableLinkify( return SelectableLinkify(
text: content + '\u202F', text: content + '\u202F',
options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: false, looseUrl: true, defaultToHttps: true), options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: false, looseUrl: true, defaultToHttps: true),
linkifiers: [UrlLinkifier()], linkifiers: [],
onOpen: null, onOpen: null,
textAlign: TextAlign.left, textAlign: TextAlign.left,
style: TextStyle(overflow: TextOverflow.ellipsis), style: TextStyle(overflow: TextOverflow.fade),
codeStyle: TextStyle(overflow: TextOverflow.ellipsis), codeStyle: TextStyle(overflow: TextOverflow.ellipsis),
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.parent,
); );
}); });
} }

View File

@ -122,12 +122,15 @@ class Linkify extends StatelessWidget {
linkifiers: linkifiers, linkifiers: linkifiers,
); );
return Text.rich( return SelectionArea(
buildTextSpan( child: RichText(
selectionRegistrar: SelectionContainer.maybeOf(context),
text: buildTextSpan(
elements, elements,
style: Theme.of(context).textTheme.bodyText2?.merge(style), style: Theme.of(context).textTheme.bodyText2?.merge(style),
onOpen: onOpen, onOpen: onOpen,
useMouseRegion: true, useMouseRegion: true,
context: context,
linkStyle: Theme.of(context) linkStyle: Theme.of(context)
.textTheme .textTheme
.bodyText2 .bodyText2
@ -148,7 +151,7 @@ class Linkify extends StatelessWidget {
locale: locale, locale: locale,
textWidthBasis: textWidthBasis, textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior, textHeightBehavior: textHeightBehavior,
); ));
} }
} }
@ -297,6 +300,7 @@ class SelectableLinkify extends StatelessWidget {
style: Theme.of(context).textTheme.bodyText2?.merge(style), style: Theme.of(context).textTheme.bodyText2?.merge(style),
codeStyle: Theme.of(context).textTheme.bodyText2?.merge(codeStyle), codeStyle: Theme.of(context).textTheme.bodyText2?.merge(codeStyle),
onOpen: onOpen, onOpen: onOpen,
context: context,
linkStyle: Theme.of(context) linkStyle: Theme.of(context)
.textTheme .textTheme
.bodyText2 .bodyText2
@ -329,6 +333,7 @@ class SelectableLinkify extends StatelessWidget {
cursorHeight: cursorHeight, cursorHeight: cursorHeight,
selectionControls: selectionControls, selectionControls: selectionControls,
onSelectionChanged: onSelectionChanged, onSelectionChanged: onSelectionChanged,
style: style,
); );
} }
} }
@ -337,11 +342,13 @@ class LinkableSpan extends WidgetSpan {
LinkableSpan({ LinkableSpan({
required MouseCursor mouseCursor, required MouseCursor mouseCursor,
required InlineSpan inlineSpan, required InlineSpan inlineSpan,
required BuildContext context,
}) : super( }) : super(
child: MouseRegion( child: MouseRegion(
cursor: mouseCursor, cursor: mouseCursor,
child: Text.rich( child: RichText(
inlineSpan, text: inlineSpan,
selectionRegistrar: SelectionContainer.maybeOf(context),
), ),
), ),
); );
@ -354,6 +361,7 @@ TextSpan buildTextSpan(
TextStyle? linkStyle, TextStyle? linkStyle,
TextStyle? codeStyle, TextStyle? codeStyle,
LinkCallback? onOpen, LinkCallback? onOpen,
required BuildContext context,
bool useMouseRegion = false, bool useMouseRegion = false,
}) { }) {
return TextSpan( return TextSpan(
@ -361,20 +369,18 @@ TextSpan buildTextSpan(
(element) { (element) {
if (element is LinkableElement) { if (element is LinkableElement) {
if (useMouseRegion) { if (useMouseRegion) {
return TooltipSpan( return TextSpan(
message: element.url, text: element.text,
inlineSpan: LinkableSpan( style: linkStyle,
mouseCursor: SystemMouseCursors.click, mouseCursor: SystemMouseCursors.click,
inlineSpan: TextSpan(text: element.text, style: linkStyle, recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null, semanticsLabel: element.text), recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
)); semanticsLabel: element.text);
} else { } else {
return TooltipSpan( return TextSpan(
message: element.url, text: element.text,
inlineSpan: TextSpan( style: linkStyle,
text: element.text, recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
style: linkStyle, );
recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
));
} }
} else if (element is BoldElement) { } else if (element is BoldElement) {
return TextSpan(text: element.text.replaceAll("*", ""), style: style?.copyWith(fontWeight: FontWeight.bold), semanticsLabel: element.text); return TextSpan(text: element.text.replaceAll("*", ""), style: style?.copyWith(fontWeight: FontWeight.bold), semanticsLabel: element.text);
@ -428,11 +434,13 @@ class TooltipSpan extends WidgetSpan {
TooltipSpan({ TooltipSpan({
required String message, required String message,
required InlineSpan inlineSpan, required InlineSpan inlineSpan,
required BuildContext context,
}) : super( }) : super(
child: Tooltip( child: Tooltip(
message: message, message: message,
child: Text.rich( child: RichText(
inlineSpan, text: inlineSpan,
selectionRegistrar: SelectionContainer.maybeOf(context),
), ),
), ),
); );

View File

@ -114,9 +114,12 @@ class _AddContactViewState extends State<AddContactView> {
/// The Add Peer Tab allows a peer to add a specific non-group peer to their contact lists /// The Add Peer Tab allows a peer to add a specific non-group peer to their contact lists
/// We also provide a convenient way to copy their onion. /// We also provide a convenient way to copy their onion.
Widget addPeerTab(bcontext) { Widget addPeerTab(bcontext) {
ScrollController controller = ScrollController();
return Scrollbar( return Scrollbar(
controller: controller,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: controller,
child: Container( child: Container(
margin: EdgeInsets.all(30), margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
@ -187,10 +190,12 @@ class _AddContactViewState extends State<AddContactView> {
if (Provider.of<ProfileInfoState>(bcontext).serverList.servers.isEmpty) { if (Provider.of<ProfileInfoState>(bcontext).serverList.servers.isEmpty) {
return Text(AppLocalizations.of(bcontext)!.addServerFirst); return Text(AppLocalizations.of(bcontext)!.addServerFirst);
} }
ScrollController controller = ScrollController();
return Scrollbar( return Scrollbar(
controller: controller,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: controller,
child: Container( child: Container(
margin: EdgeInsets.all(30), margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),

View File

@ -38,6 +38,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
final ctrlrPass = TextEditingController(text: ""); final ctrlrPass = TextEditingController(text: "");
final ctrlrPass2 = TextEditingController(text: ""); final ctrlrPass2 = TextEditingController(text: "");
final ctrlrOnion = TextEditingController(text: ""); final ctrlrOnion = TextEditingController(text: "");
ScrollController controller = ScrollController();
late bool usePassword; late bool usePassword;
late bool deleted; late bool deleted;
@ -75,8 +76,10 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
return Consumer<Settings>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: controller,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: controller,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(

View File

@ -67,11 +67,14 @@ class _AddEditServerViewState extends State<AddEditServerView> {
} }
Widget _buildSettingsList() { Widget _buildSettingsList() {
ScrollController controller = ScrollController();
return Consumer2<ServerInfoState, Settings>(builder: (context, serverInfoState, settings, child) { return Consumer2<ServerInfoState, Settings>(builder: (context, serverInfoState, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: controller,
child: SingleChildScrollView( child: SingleChildScrollView(
controller: controller,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(

View File

@ -36,6 +36,8 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
static const androidSettingsChangeChannel = const MethodChannel('androidSettingsChanged'); static const androidSettingsChangeChannel = const MethodChannel('androidSettingsChanged');
bool powerExempt = false; bool powerExempt = false;
ScrollController settingsListScrollController = ScrollController();
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
@ -95,9 +97,11 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor); var appIcon = Icon(Icons.info, color: settings.current().mainTextColor);
return Scrollbar( return Scrollbar(
key: Key("SettingsView"), key: Key("SettingsView"),
isAlwaysShown: true, trackVisibility: true,
controller: settingsListScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: settingsListScrollController,
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(

View File

@ -27,6 +27,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
final ctrlrNick = TextEditingController(text: ""); final ctrlrNick = TextEditingController(text: "");
final ctrlrGroupAddr = TextEditingController(text: ""); final ctrlrGroupAddr = TextEditingController(text: "");
ScrollController groupSettingsScrollController = ScrollController();
@override @override
void initState() { void initState() {
@ -55,9 +56,11 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
return Consumer<Settings>(builder: (context, settings, child) { return Consumer<Settings>(builder: (context, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: groupSettingsScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: groupSettingsScrollController,
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,

View File

@ -149,6 +149,8 @@ class _MessageViewState extends State<MessageView> {
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor, backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
floatingActionButton: showDown floatingActionButton: showDown
? FloatingActionButton( ? FloatingActionButton(
// heroTags need to be unique per screen (important when we pop up and down)...
heroTag: "popDown" + Provider.of<ContactInfoState>(context, listen: false).onion,
child: Icon(Icons.arrow_downward, color: Provider.of<Settings>(context).current().defaultButtonTextColor), child: Icon(Icons.arrow_downward, color: Provider.of<Settings>(context).current().defaultButtonTextColor),
onPressed: () { onPressed: () {
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0; Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;

View File

@ -28,6 +28,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
} }
final ctrlrNick = TextEditingController(text: ""); final ctrlrNick = TextEditingController(text: "");
ScrollController peerSettingsScrollController = ScrollController();
@override @override
void initState() { void initState() {
@ -83,9 +84,11 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
} }
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: peerSettingsScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: peerSettingsScrollController,
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,

View File

@ -76,10 +76,13 @@ class _ProfileServersView extends State<ProfileServersView> {
))); )));
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
ScrollController controller = ScrollController();
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: controller,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: controller,
child: Container( child: Container(
margin: EdgeInsets.fromLTRB(5, 0, 5, 10), margin: EdgeInsets.fromLTRB(5, 0, 5, 10),
padding: EdgeInsets.fromLTRB(5, 0, 5, 10), padding: EdgeInsets.fromLTRB(5, 0, 5, 10),

View File

@ -21,6 +21,7 @@ class _TorStatusView extends State<TorStatusView> {
TextEditingController torSocksPortController = TextEditingController(); TextEditingController torSocksPortController = TextEditingController();
TextEditingController torControlPortController = TextEditingController(); TextEditingController torControlPortController = TextEditingController();
TextEditingController torConfigController = TextEditingController(); TextEditingController torConfigController = TextEditingController();
ScrollController torScrollContoller = ScrollController();
@override @override
void dispose() { void dispose() {
@ -52,7 +53,8 @@ class _TorStatusView extends State<TorStatusView> {
return Consumer<TorStatus>(builder: (context, torStatus, child) { return Consumer<TorStatus>(builder: (context, torStatus, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: torScrollContoller,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: ConstrainedBox( child: ConstrainedBox(

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:cwtch/config.dart'; import 'package:cwtch/config.dart';
import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/filedownloadprogress.dart'; import 'package:cwtch/models/filedownloadprogress.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
@ -131,6 +132,13 @@ class FileBubbleState extends State<FileBubble> {
// we don't preview a non downloaded file... // we don't preview a non downloaded file...
if (widget.isPreview && myFile != null) { if (widget.isPreview && myFile != null) {
return getPreview(context); return getPreview(context);
} else if (widget.isPreview && myFile == null) {
return Row(
children: [
Icon(CwtchIcons.attached_file_2, size: 32, color: Provider.of<Settings>(context).theme.messageFromMeTextColor),
Flexible(child: Text(widget.nameSuggestion, style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)))
],
);
} }
return LayoutBuilder(builder: (bcontext, constraints) { return LayoutBuilder(builder: (bcontext, constraints) {
@ -329,7 +337,7 @@ class FileBubbleState extends State<FileBubble> {
maxLines: 4, maxLines: 4,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
), ),
leading: Icon(Icons.attach_file, size: 32, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)); leading: Icon(CwtchIcons.attached_file_2, size: 32, color: Provider.of<Settings>(context).theme.messageFromMeTextColor));
} }
// Construct an file chrome // Construct an file chrome
@ -377,7 +385,7 @@ class FileBubbleState extends State<FileBubble> {
maxLines: 4, maxLines: 4,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
), ),
leading: Icon(Icons.attach_file, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor), leading: Icon(CwtchIcons.attached_file_2, size: 32, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor),
trailing: Visibility( trailing: Visibility(
visible: speed != "0 B/s", visible: speed != "0 B/s",
child: SelectableText( child: SelectableText(

View File

@ -215,14 +215,16 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
var mr = MouseRegion( var mr = MouseRegion(
// For desktop... // For desktop...
onHover: (event) { onHover: (event) {
setState(() { if (Provider.of<AppState>(context, listen: false).hoveredIndex != Provider.of<MessageMetadata>(context, listen: false).messageID) {
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageID; setState(() {
}); Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
});
}
}, },
onExit: (event) { onExit: (event) {
setState(() { // setState(() {
Provider.of<AppState>(context, listen: false).hoveredIndex = -1; // Provider.of<AppState>(context, listen: false).hoveredIndex = -1;
}); //});
}, },
child: GestureDetector( child: GestureDetector(
onPanUpdate: (details) { onPanUpdate: (details) {
@ -371,6 +373,7 @@ void modalShowReplies(
context: ctx, context: ctx,
builder: (BuildContext bcontext) { builder: (BuildContext bcontext) {
List<Message> replies = getReplies(cache, messageID); List<Message> replies = getReplies(cache, messageID);
ScrollController controller = ScrollController();
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: profile, value: profile,
@ -432,7 +435,8 @@ void modalShowReplies(
} }
return Scrollbar( return Scrollbar(
isAlwaysShown: true, trackVisibility: true,
controller: controller,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: ConstrainedBox( child: ConstrainedBox(

View File

@ -125,16 +125,11 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
height: 75, height: 75,
child: Column(children: [ child: Column(children: [
Align(alignment: Alignment.centerLeft, child: wdgReplyingTo), Align(alignment: Alignment.centerLeft, child: wdgReplyingTo),
Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Flexible(
child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)),
Flexible( Flexible(child: qMessage.getPreviewWidget(context)),
child: DefaultTextStyle( ]))
textWidthBasis: TextWidthBasis.parent,
child: qMessage.getPreviewWidget(context),
style: TextStyle(color: qTextColor),
overflow: TextOverflow.fade,
))
])
])))); ]))));
} catch (e) { } catch (e) {
return MalformedBubble(); return MalformedBubble();

View File

@ -40,9 +40,9 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
Visibility( Visibility(
visible: !running, visible: !running,
child: Icon( child: Icon(
CwtchIcons.negative_heart_24px, CwtchIcons.negative_heart_24px,
color: Provider.of<Settings>(context).theme.portraitOfflineBorderColor, color: Provider.of<Settings>(context).theme.portraitOfflineBorderColor,
)), )),
])), ])),
Expanded( Expanded(
child: Column( child: Column(