blob: 67aa24430ca79e971835718c16071136fb750fa9 [file] [log] [blame]
// Copyright 2013 The Flutter 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 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
/// The function used to push routes to the Flutter framework.
@visibleForTesting
Future<ByteData> Function(Object?, String) pushRouteToFrameworkFunction =
pushRouteNameToFramework;
/// A widget that renders a real link on the web, and uses WebViews in native
/// platforms to open links.
///
/// Example link to an external URL:
///
/// ```dart
/// Link(
/// uri: Uri.parse('https://flutter.dev'),
/// builder: (BuildContext context, FollowLink followLink) => ElevatedButton(
/// onPressed: followLink,
/// // ... other properties here ...
/// )},
/// );
/// ```
///
/// Example link to a route name within the app:
///
/// ```dart
/// Link(
/// uri: Uri.parse('/home'),
/// builder: (BuildContext context, FollowLink followLink) => ElevatedButton(
/// onPressed: followLink,
/// // ... other properties here ...
/// )},
/// );
/// ```
class Link extends StatelessWidget implements LinkInfo {
/// Creates a widget that renders a real link on the web, and uses WebViews in
/// native platforms to open links.
const Link({
Key? key,
required this.uri,
this.target = LinkTarget.defaultTarget,
required this.builder,
}) : super(key: key);
/// Called at build time to construct the widget tree under the link.
@override
final LinkWidgetBuilder builder;
/// The destination that this link leads to.
@override
final Uri? uri;
/// The target indicating where to open the link.
@override
final LinkTarget target;
/// Whether the link is disabled or not.
@override
bool get isDisabled => uri == null;
LinkDelegate get _effectiveDelegate {
return UrlLauncherPlatform.instance.linkDelegate ??
DefaultLinkDelegate.create;
}
@override
Widget build(BuildContext context) {
return _effectiveDelegate(this);
}
}
/// The default delegate used on non-web platforms.
///
/// For external URIs, it uses url_launche APIs. For app route names, it uses
/// event channel messages to instruct the framework to push the route name.
class DefaultLinkDelegate extends StatelessWidget {
/// Creates a delegate for the given [link].
const DefaultLinkDelegate(this.link);
/// Given a [link], creates an instance of [DefaultLinkDelegate].
///
/// This is a static method so it can be used as a tear-off.
static DefaultLinkDelegate create(LinkInfo link) {
return DefaultLinkDelegate(link);
}
/// Information about the link built by the app.
final LinkInfo link;
bool get _useWebView {
if (link.target == LinkTarget.self) {
return true;
}
if (link.target == LinkTarget.blank) {
return false;
}
return false;
}
Future<void> _followLink(BuildContext context) async {
if (!link.uri!.hasScheme) {
// A uri that doesn't have a scheme is an internal route name. In this
// case, we push it via Flutter's navigation system instead of letting the
// browser handle it.
final String routeName = link.uri.toString();
await pushRouteToFrameworkFunction(context, routeName);
return;
}
// At this point, we know that the link is external. So we use the `launch`
// API to open the link.
final String urlString = link.uri.toString();
if (await canLaunch(urlString)) {
await launch(
urlString,
forceSafariVC: _useWebView,
forceWebView: _useWebView,
);
} else {
FlutterError.reportError(FlutterErrorDetails(
exception: 'Could not launch link $urlString',
stack: StackTrace.current,
library: 'url_launcher',
context: ErrorDescription('during launching a link'),
));
}
}
@override
Widget build(BuildContext context) {
return link.builder(
context,
link.isDisabled ? null : () => _followLink(context),
);
}
}