Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.9

* Laid the groundwork for introducing a Link widget.

## 1.0.8

* Added webOnlyWindowName parameter
Expand Down
113 changes: 113 additions & 0 deletions packages/url_launcher/url_launcher_platform_interface/lib/link.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// Signature for a function provided by the [Link] widget that instructs it to
/// follow the link.
typedef FollowLink = Future<void> Function();

/// Signature for a builder function passed to the [Link] widget to construct
/// the widget tree under it.
typedef LinkWidgetBuilder = Widget Function(
BuildContext context,
FollowLink followLink,
);

/// Signature for a delegate function to build the [Link] widget.
typedef LinkDelegate = Widget Function(LinkInfo linkWidget);

final MethodCodec _codec = const JSONMethodCodec();

/// Defines where a Link URL should be open.
///
/// This is a class instead of an enum to allow future customizability e.g.
/// opening a link in a specific iframe.
Comment on lines +29 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change, I had a comment about target being any String in the previous PR

class LinkTarget {
/// Const private constructor with a [debugLabel] to allow the creation of
/// multiple distinct const instances.
const LinkTarget._({this.debugLabel});

/// Used to distinguish multiple const instances of [LinkTarget].
final String debugLabel;

/// Use the default target for each platform.
///
/// On Android, the default is [blank]. On the web, the default is [self].
///
/// iOS, on the other hand, defaults to [self] for web URLs, and [blank] for
/// non-web URLs.
static const defaultTarget = LinkTarget._(debugLabel: 'defaultTarget');

/// On the web, this opens the link in the same tab where the flutter app is
/// running.
///
/// On Android and iOS, this opens the link in a webview within the app.
static const self = LinkTarget._(debugLabel: 'self');

/// On the web, this opens the link in a new tab or window (depending on the
/// browser and user configuration).
///
/// On Android and iOS, this opens the link in the browser or the relevant
/// app.
static const blank = LinkTarget._(debugLabel: 'blank');
}

/// Encapsulates all the information necessary to build a Link widget.
abstract class LinkInfo {
/// Called at build time to construct the widget tree under the link.
LinkWidgetBuilder get builder;

/// The destination that this link leads to.
Uri get uri;

/// The target indicating where to open the link.
LinkTarget get target;

/// Whether the link is disabled or not.
bool get isDisabled;
}

/// Pushes the [routeName] into Flutter's navigation system via a platform
/// message.
Future<ByteData> pushRouteNameToFramework(
BuildContext context,
String routeName, {
@visibleForTesting bool debugForceRouter = false,
}) {
final Completer<ByteData> completer = Completer<ByteData>();
if (debugForceRouter || _hasRouter(context)) {
SystemNavigator.routeInformationUpdated(location: routeName);
window.onPlatformMessage(
'flutter/navigation',
_codec.encodeMethodCall(
MethodCall('pushRouteInformation', <dynamic, dynamic>{
'location': routeName,
'state': null,
}),
),
completer.complete,
);
} else {
window.onPlatformMessage(
'flutter/navigation',
_codec.encodeMethodCall(MethodCall('pushRoute', routeName)),
completer.complete,
);
}
return completer.future;
}

bool _hasRouter(BuildContext context) {
try {
return Router.of(context) != null;
} on AssertionError {
// When a `Router` can't be found, an assertion error is thrown.
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import 'dart:async';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart' show required;

import 'link.dart';
import 'url_launcher_platform_interface.dart';

const MethodChannel _channel = MethodChannel('plugins.flutter.io/url_launcher');

/// An implementation of [UrlLauncherPlatform] that uses method channels.
class MethodChannelUrlLauncher extends UrlLauncherPlatform {
@override
final LinkDelegate linkDelegate = null;

@override
Future<bool> canLaunch(String url) {
return _channel.invokeMethod<bool>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:async';

import 'package:meta/meta.dart' show required;
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:url_launcher_platform_interface/link.dart';

import 'method_channel_url_launcher.dart';

Expand Down Expand Up @@ -38,6 +39,9 @@ abstract class UrlLauncherPlatform extends PlatformInterface {
_instance = instance;
}

/// The delegate used by the Link widget to build itself.
LinkDelegate get linkDelegate;
Comment on lines +42 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is defined as:

  @override
  LinkDelegate linkDelegate = (LinkInfo linkInfo) => WebLinkDelegate(linkInfo);

In the web implementation, how can you override a getter with the function above? Should the signature here be more explicit?


/// Returns `true` if this platform is able to launch [url].
Future<bool> canLaunch(String url) {
throw UnimplementedError('canLaunch() has not been implemented.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: A common platform interface for the url_launcher plugin.
homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
version: 1.0.8
version: 1.0.9

dependencies:
flutter:
Expand All @@ -19,4 +19,4 @@ dev_dependencies:

environment:
sdk: ">=2.1.0 <3.0.0"
flutter: ">=1.9.1+hotfix.4 <2.0.0"
flutter: ">=1.22.0 <2.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui';

import 'package:mockito/mockito.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:url_launcher_platform_interface/link.dart';

final MethodCodec _codec = const JSONMethodCodec();

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

PlatformMessageCallback oldHandler;
MethodCall lastCall;

setUp(() {
oldHandler = window.onPlatformMessage;
window.onPlatformMessage = (
String name,
ByteData data,
PlatformMessageResponseCallback callback,
) {
lastCall = _codec.decodeMethodCall(data);
callback(_codec.encodeSuccessEnvelope(true));
};
});

tearDown(() {
window.onPlatformMessage = oldHandler;
});

test('pushRouteNameToFramework() calls pushRoute when no Router', () async {
await pushRouteNameToFramework(CustomBuildContext(), '/foo/bar');
expect(
lastCall,
isMethodCall(
'pushRoute',
arguments: '/foo/bar',
),
);
});

test(
'pushRouteNameToFramework() calls pushRouteInformation when Router exists',
() async {
await pushRouteNameToFramework(
CustomBuildContext(),
'/foo/bar',
debugForceRouter: true,
);
expect(
lastCall,
isMethodCall(
'pushRouteInformation',
arguments: <dynamic, dynamic>{
'location': '/foo/bar',
'state': null,
},
),
);
},
);
}

class CustomBuildContext<T> extends Mock implements BuildContext {}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/method_channel_url_launcher.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

Expand Down Expand Up @@ -286,4 +287,7 @@ class UrlLauncherPlatformMock extends Mock
class ImplementsUrlLauncherPlatform extends Mock
implements UrlLauncherPlatform {}

class ExtendsUrlLauncherPlatform extends UrlLauncherPlatform {}
class ExtendsUrlLauncherPlatform extends UrlLauncherPlatform {
@override
final LinkDelegate linkDelegate = null;
}