Skip to content

Touchable links in messages #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 6, 2023
Merged
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ PODS:
- sqlite3/perf-threadsafe
- sqlite3/rtree
- SwiftyGif (5.4.4)
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`)
Expand All @@ -77,6 +79,7 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
trunk:
Expand Down Expand Up @@ -105,6 +108,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
app_settings: d103828c9f5d515c4df9ee754dabd443f7cedcf3
Expand All @@ -121,6 +126,7 @@ SPEC CHECKSUMS:
sqlite3: fd89671d969f3e73efe503ce203e28b016b58f68
sqlite3_flutter_libs: 04ba0d14a04335a2fbf9a331e8664f401fbccdd5
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4

PODFILE CHECKSUM: 985e5b058f26709dc81f9ae74ea2b2775bdbcefe

Expand Down
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ void main() {
return true;
}());
LicenseRegistry.addLicense(additionalLicenses);
LiveDataBinding.ensureInitialized();
LiveZulipBinding.ensureInitialized();
runApp(const ZulipApp());
}
51 changes: 36 additions & 15 deletions lib/model/binding.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:url_launcher/url_launcher.dart' as url_launcher;
export 'package:url_launcher/url_launcher.dart' show LaunchMode;

import '../widgets/store.dart';
import 'store.dart';

/// A singleton service providing the app's data.
/// A singleton service providing the app's data and use of Flutter plugins.
///
/// Only one instance will be constructed in the lifetime of the app,
/// by calling the `ensureInitialized` static method on a subclass.
Expand All @@ -18,27 +20,27 @@ import 'store.dart';
/// [TestWidgetsFlutterBinding].
/// This version is simplified because we don't (yet?) have enough complexity
/// to put into these bindings to need to use mixins to split them up.
abstract class DataBinding {
DataBinding() {
abstract class ZulipBinding {
ZulipBinding() {
assert(_instance == null);
initInstance();
}

/// The single instance of [DataBinding].
static DataBinding get instance => checkInstance(_instance);
static DataBinding? _instance;
/// The single instance of [ZulipBinding].
static ZulipBinding get instance => checkInstance(_instance);
static ZulipBinding? _instance;

static T checkInstance<T extends DataBinding>(T? instance) {
static T checkInstance<T extends ZulipBinding>(T? instance) {
assert(() {
if (instance == null) {
throw FlutterError.fromParts([
ErrorSummary('Zulip binding has not yet been initialized.'),
ErrorHint(
'In the app, this is done by the `LiveDataBinding.ensureInitialized()` call '
'In the app, this is done by the `LiveZulipBinding.ensureInitialized()` call '
'in the `void main()` method.',
),
ErrorHint(
'In a test, one can call `TestDataBinding.ensureInitialized()` as the '
'In a test, one can call `TestZulipBinding.ensureInitialized()` as the '
'first line in the test\'s `main()` method to initialize the binding.',
),
]);
Expand All @@ -62,24 +64,43 @@ abstract class DataBinding {
/// In application code, use [GlobalStoreWidget.of] to get access
/// to a [GlobalStore].
Future<GlobalStore> loadGlobalStore();

/// Pass [url] to the underlying platform, via package:url_launcher.
///
/// This wraps [url_launcher.launchUrl].
Future<bool> launchUrl(
Uri url, {
url_launcher.LaunchMode mode = url_launcher.LaunchMode.platformDefault,
});
}

/// A concrete binding for use in the live application.
///
/// The global store returned by [loadGlobalStore], and consequently by
/// [GlobalStoreWidget.of] in application code, will be a [LiveGlobalStore].
/// It therefore uses a live server and live, persistent local database.
class LiveDataBinding extends DataBinding {
/// Initialize the binding if necessary, and ensure it is a [LiveDataBinding].
static LiveDataBinding ensureInitialized() {
if (DataBinding._instance == null) {
LiveDataBinding();
///
/// Methods wrapping a plugin, like [launchUrl], invoke the actual
/// underlying plugin method.
class LiveZulipBinding extends ZulipBinding {
/// Initialize the binding if necessary, and ensure it is a [LiveZulipBinding].
static LiveZulipBinding ensureInitialized() {
if (ZulipBinding._instance == null) {
LiveZulipBinding();
}
return DataBinding.instance as LiveDataBinding;
return ZulipBinding.instance as LiveZulipBinding;
}

@override
Future<GlobalStore> loadGlobalStore() {
return LiveGlobalStore.load();
}

@override
Future<bool> launchUrl(
Uri url, {
url_launcher.LaunchMode mode = url_launcher.LaunchMode.platformDefault,
}) {
return url_launcher.launchUrl(url, mode: mode);
}
}
4 changes: 3 additions & 1 deletion lib/model/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,6 @@ class InlineCodeNode extends InlineContainerNode {
class LinkNode extends InlineContainerNode {
const LinkNode({super.debugHtmlNode, required super.nodes, required this.url});

// TODO(#71): Use [LinkNode.url] to open links
final String url; // Left as a string, to defer parsing until link actually followed.

// Unlike other [ContentNode]s, the identity is useful to show in debugging
Expand Down Expand Up @@ -559,6 +558,9 @@ class _ZulipContentParser {
|| classes.contains('user-group-mention'))
&& (classes.length == 1
|| (classes.length == 2 && classes.contains('silent')))) {
// TODO assert UserMentionNode can't contain LinkNode;
// either a debug-mode check, or perhaps we can make expectations much
// tighter on a UserMentionNode's contents overall.
return UserMentionNode(nodes: nodes(), debugHtmlNode: debugHtmlNode);
}

Expand Down
Loading