forked from cwtch.im/cwtch-ui
WIP: add experimental clickable links with dialog
(copy / open). Bug remaining for selectable text
This commit is contained in:
parent
4971879b0e
commit
ec1dd05ba1
|
@ -30,6 +30,11 @@
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||||
const FileSharingExperiment = "filesharing";
|
const FileSharingExperiment = "filesharing";
|
||||||
|
const ClickableLinksExperiment = "clickable-links";
|
||||||
|
|
||||||
enum DualpaneMode {
|
enum DualpaneMode {
|
||||||
Single,
|
Single,
|
||||||
|
|
|
@ -204,6 +204,22 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()),
|
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text("Enable Clickable Links", style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
|
subtitle: Text("The clickable links experiment allows you to click on URLs shared in messages."),
|
||||||
|
value: settings.isExperimentEnabled(ClickableLinksExperiment),
|
||||||
|
onChanged: (bool value) {
|
||||||
|
if (value) {
|
||||||
|
settings.enableExperiment(ClickableLinksExperiment);
|
||||||
|
} else {
|
||||||
|
settings.disableExperiment(ClickableLinksExperiment);
|
||||||
|
}
|
||||||
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||||
|
secondary: Icon(Icons.link, color: settings.current().mainTextColor()),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
AboutListTile(
|
AboutListTile(
|
||||||
|
|
|
@ -3,9 +3,13 @@ import 'dart:io';
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.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:provider/provider.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'messagebubbledecorations.dart';
|
import 'messagebubbledecorations.dart';
|
||||||
|
@ -28,6 +32,7 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
var prettyDate = "";
|
var prettyDate = "";
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
// var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
// var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||||
|
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
|
||||||
|
|
||||||
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||||
prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(messageDate.toLocal());
|
prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(messageDate.toLocal());
|
||||||
|
@ -45,7 +50,10 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
var wdgSender = SelectableText(senderDisplayStr,
|
var wdgSender = SelectableText(senderDisplayStr,
|
||||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
||||||
|
|
||||||
var wdgMessage = SelectableText(
|
var wdgMessage;
|
||||||
|
|
||||||
|
if (!showClickableLinks) {
|
||||||
|
wdgMessage = SelectableText(
|
||||||
widget.content + '\u202F',
|
widget.content + '\u202F',
|
||||||
//key: Key(myKey),
|
//key: Key(myKey),
|
||||||
focusNode: _focus,
|
focusNode: _focus,
|
||||||
|
@ -55,6 +63,24 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
wdgMessage = SelectableLinkify(
|
||||||
|
text: widget.content + '\u202F',
|
||||||
|
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
|
||||||
|
options: LinkifyOptions(humanize: false),
|
||||||
|
linkifiers: [UrlLinkifier()], // TODO: double-check on this (only web links to avoid Android messiness)
|
||||||
|
onOpen: (link) {
|
||||||
|
_modalOpenLink(context, link);
|
||||||
|
},
|
||||||
|
//key: Key(myKey),
|
||||||
|
focusNode: _focus,
|
||||||
|
style: TextStyle(
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
|
|
||||||
|
@ -90,4 +116,58 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))));
|
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])))));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _modalOpenLink(BuildContext ctx, LinkableElement link) {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: ctx,
|
||||||
|
builder: (BuildContext bcontext) {
|
||||||
|
return Container(
|
||||||
|
// TODO: Ask re: hard-coded height
|
||||||
|
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: <Widget>[
|
||||||
|
Text(
|
||||||
|
"Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?"
|
||||||
|
),
|
||||||
|
// TODO: Ask about styling preferences (should this be a reusable "inline-button"?)
|
||||||
|
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: Text("Copy link", semanticsLabel: "Copy link"),
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(new ClipboardData(text: link.url));
|
||||||
|
|
||||||
|
// TODO: Ask about desired SnackBar + modal behaviour
|
||||||
|
final snackBar = SnackBar(
|
||||||
|
content: Text(AppLocalizations.of(context)!.copiedClipboardNotification),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: Text("Open link", semanticsLabel: "Open link"),
|
||||||
|
onPressed: () async {
|
||||||
|
if (await canLaunch(link.url)) {
|
||||||
|
await launch(link.url);
|
||||||
|
} else {
|
||||||
|
throw 'Could not launch $link';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@ import Foundation
|
||||||
|
|
||||||
import package_info_plus_macos
|
import package_info_plus_macos
|
||||||
import path_provider_macos
|
import path_provider_macos
|
||||||
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,14 @@ PODS:
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- path_provider_macos (0.0.1):
|
- path_provider_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- url_launcher_macos (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
|
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
|
||||||
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
|
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
|
||||||
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
|
@ -17,12 +20,15 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
|
||||||
path_provider_macos:
|
path_provider_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
|
||||||
|
url_launcher_macos:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||||
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
|
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
|
||||||
path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b
|
path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b
|
||||||
|
url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4
|
||||||
|
|
||||||
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
|
||||||
|
|
||||||
COCOAPODS: 1.9.3
|
COCOAPODS: 1.11.2
|
||||||
|
|
62
pubspec.lock
62
pubspec.lock
|
@ -35,7 +35,7 @@ packages:
|
||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.2.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -125,6 +125,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_linkify:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_linkify
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.2"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -189,6 +196,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.3"
|
version: "0.6.3"
|
||||||
|
linkify:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: linkify
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -411,6 +425,48 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.12"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -447,5 +503,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.13.0 <3.0.0"
|
dart: ">=2.14.0 <3.0.0"
|
||||||
flutter: ">=2.0.0"
|
flutter: ">=2.5.0"
|
||||||
|
|
|
@ -43,6 +43,8 @@ dependencies:
|
||||||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||||
file_picker: ^4.0.1
|
file_picker: ^4.0.1
|
||||||
file_picker_desktop: ^1.1.0
|
file_picker_desktop: ^1.1.0
|
||||||
|
flutter_linkify: ^5.0.2
|
||||||
|
url_launcher: ^6.0.12
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
msix: ^2.1.3
|
msix: ^2.1.3
|
||||||
|
|
Loading…
Reference in New Issue