From 65ff084952ba8c2313972c85f3ad0ee61bc05759 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 10 Jun 2022 14:21:40 -0700 Subject: [PATCH] make links in replies clickable --- lib/controllers/open_link_modal.dart | 61 +++++++++++++++++++++++++++ lib/widgets/messagebubble.dart | 62 +--------------------------- lib/widgets/quotedmessage.dart | 23 ++++++++++- 3 files changed, 84 insertions(+), 62 deletions(-) create mode 100644 lib/controllers/open_link_modal.dart diff --git a/lib/controllers/open_link_modal.dart b/lib/controllers/open_link_modal.dart new file mode 100644 index 00000000..7b021f6d --- /dev/null +++ b/lib/controllers/open_link_modal.dart @@ -0,0 +1,61 @@ +import 'package:cwtch/third_party/linkify/linkify.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +void modalOpenLink(BuildContext ctx, LinkableElement link) { + showModalBottomSheet( + context: ctx, + builder: (BuildContext bcontext) { + return Container( + height: 200, // bespoke value courtesy of the [TextField] docs + child: Center( + child: Padding( + padding: EdgeInsets.all(30.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(bcontext)!.clickableLinksWarning), + Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: [ + Container( + margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), + child: ElevatedButton( + child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy), + onPressed: () { + Clipboard.setData(new ClipboardData(text: link.url)); + + final snackBar = SnackBar( + content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification), + ); + + Navigator.pop(bcontext); + ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); + }, + ), + ), + Container( + margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), + child: ElevatedButton( + child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), + onPressed: () async { + if (await canLaunch(link.url)) { + await launch(link.url); + Navigator.pop(bcontext); + } else { + final snackBar = SnackBar( + content: Text(AppLocalizations.of(bcontext)!.clickableLinkError), + ); + ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); + } + }, + ), + ), + ]), + ], + )), + )); + }); +} diff --git a/lib/widgets/messagebubble.dart b/lib/widgets/messagebubble.dart index cd563fab..19e7a103 100644 --- a/lib/widgets/messagebubble.dart +++ b/lib/widgets/messagebubble.dart @@ -1,16 +1,13 @@ import 'dart:io'; +import 'package:cwtch/controllers/open_link_modal.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/third_party/linkify/flutter_linkify.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; -import 'package:intl/intl.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../settings.dart'; import 'messagebubbledecorations.dart'; @@ -55,7 +52,7 @@ class MessageBubbleState extends State { linkifiers: [UrlLinkifier()], onOpen: showClickableLinks ? (link) { - _modalOpenLink(context, link); + modalOpenLink(context, link); } : null, //key: Key(myKey), @@ -104,59 +101,4 @@ class MessageBubbleState extends State { children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]))))); }); } - - void _modalOpenLink(BuildContext ctx, LinkableElement link) { - showModalBottomSheet( - context: ctx, - builder: (BuildContext bcontext) { - return Container( - height: 200, // bespoke value courtesy of the [TextField] docs - child: Center( - child: Padding( - padding: EdgeInsets.all(30.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(bcontext)!.clickableLinksWarning), - Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), - child: ElevatedButton( - child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy), - onPressed: () { - Clipboard.setData(new ClipboardData(text: link.url)); - - final snackBar = SnackBar( - content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification), - ); - - Navigator.pop(bcontext); - ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); - }, - ), - ), - Container( - margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), - child: ElevatedButton( - child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), - onPressed: () async { - if (await canLaunch(link.url)) { - await launch(link.url); - Navigator.pop(bcontext); - } else { - final snackBar = SnackBar( - content: Text(AppLocalizations.of(bcontext)!.clickableLinkError), - ); - ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); - } - }, - ), - ), - ]), - ], - )), - )); - }); - } } diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index a29f9066..0195d3cb 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -1,7 +1,9 @@ +import 'package:cwtch/controllers/open_link_modal.dart'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/third_party/linkify/flutter_linkify.dart'; import 'package:cwtch/views/contactsview.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; @@ -45,12 +47,29 @@ class QuotedMessageBubbleState extends State { var wdgSender = SelectableText(senderDisplayStr, style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor)); - var wdgMessage = SelectableText( - widget.body + '\u202F', + var showClickableLinks = Provider.of(context).isExperimentEnabled(ClickableLinksExperiment); + var formatMessages = Provider.of(context).isExperimentEnabled(FormattingExperiment); + + var wdgMessage = SelectableLinkify( + text: widget.body + '\u202F', + // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? + options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), + linkifiers: [UrlLinkifier()], + onOpen: showClickableLinks + ? (link) { + modalOpenLink(context, link); + } + : null, + //key: Key(myKey), focusNode: _focus, style: TextStyle( color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor, ), + linkStyle: TextStyle(color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor), + codeStyle: TextStyle( + // note: these colors are flipped + color: fromMe ? Provider.of(context).theme.messageFromOtherTextColor : Provider.of(context).theme.messageFromMeTextColor, + backgroundColor: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor), textAlign: TextAlign.left, textWidthBasis: TextWidthBasis.longestLine, );