Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
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
147 changes: 141 additions & 6 deletions packages/url_launcher/url_launcher/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide Link;
import 'package:flutter/rendering.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
Expand All @@ -21,20 +22,25 @@ class MyApp extends StatelessWidget {
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'URL Launcher'),
initialRoute: '/links',
routes: {
'/links': (BuildContext context) => LinksPage(title: 'Links'),
'/url_launcher': (BuildContext context) =>
UrlLauncherPage(title: 'URL Launcher'),
},
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
class UrlLauncherPage extends StatefulWidget {
UrlLauncherPage({Key key, this.title}) : super(key: key);
final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
_UrlLauncherPageState createState() => _UrlLauncherPageState();
}

class _MyHomePageState extends State<MyHomePage> {
class _UrlLauncherPageState extends State<UrlLauncherPage> {
Future<void> _launched;
String _phone = '';

Expand Down Expand Up @@ -204,3 +210,132 @@ class _MyHomePageState extends State<MyHomePage> {
);
}
}

class LinksPage extends StatefulWidget {
LinksPage({Key key, this.title}) : super(key: key);
final String title;

@override
_LinksPageState createState() => _LinksPageState();
}

class _LinksPageState extends State<LinksPage> {
Future<void> _launched;
String _phone = '';

Widget _launchStatus(BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return const Text('');
}
}

@override
Widget build(BuildContext context) {
final Uri fullUri = Uri.parse('https://www.cylog.org/headers/');
final Uri routeName = Uri.parse('/url_launcher');

return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
onChanged: (String text) => setState(() {
_phone = text;
}),
decoration: const InputDecoration(
hintText: 'Input the phone number to launch')),
),
Link(
uri: Uri.parse('tel:$_phone'),
builder: (BuildContext context, FollowLink followLink) =>
RaisedButton(
onPressed: followLink,
child: const Text('Make phone call'),
),
),
Padding(
padding: EdgeInsets.only(top: 32.0, bottom: 16.0),
child: Text('fullUri: $fullUri'),
),
Link(
uri: fullUri,
target: LinkTarget.blank,
builder: (BuildContext context, FollowLink followLink) {
return RaisedButton(
onPressed: () => setState(() {
_launched = followLink();
}),
child: const Text('Launch in blank'),
);
},
),
const Padding(padding: EdgeInsets.all(16.0)),
Link(
uri: fullUri,
target: LinkTarget.self,
builder: (BuildContext context, FollowLink followLink) {
return RaisedButton(
onPressed: () => setState(() {
_launched = followLink();
}),
child: const Text('Launch in self'),
);
},
),
Padding(
padding: EdgeInsets.only(top: 32.0, bottom: 16.0),
child: Text('routeName: $routeName'),
),
Link(
uri: routeName,
target: LinkTarget.blank,
builder: (BuildContext context, FollowLink followLink) {
return RaisedButton(
onPressed: () => setState(() {
_launched = followLink();
}),
child: Text('Push in blank'),
);
},
),
const Padding(padding: EdgeInsets.all(16.0)),
Link(
uri: routeName,
target: LinkTarget.self,
builder: (BuildContext context, FollowLink followLink) {
return RaisedButton(
onPressed: () => setState(() {
_launched = followLink();
}),
child: Text('Push in self'),
);
},
),
const Padding(padding: EdgeInsets.all(16.0)),
Link(
uri: null,
builder: (BuildContext context, FollowLink followLink) {
return RaisedButton(
onPressed: followLink,
child: Text('Disabled'),
);
},
),
const Padding(padding: EdgeInsets.all(16.0)),
FutureBuilder<void>(future: _launched, builder: _launchStatus),
],
),
],
),
);
}
}
3 changes: 3 additions & 0 deletions packages/url_launcher/url_launcher/lib/url_launcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

export 'package:url_launcher_platform_interface/link.dart'
show Link, LinkTarget, LinkWidgetBuilder, FollowLink;

/// Parses the specified URL string and delegates handling of it to the
/// underlying platform.
///
Expand Down
133 changes: 133 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,133 @@
// 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 'package:flutter/foundation.dart';
import 'package:flutter/material.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(Link linkWidget);

/// 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.
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');
}

/// Used to override the delegate that builds the link.
set linkDelegate(LinkDelegate delegate) {
Link._linkDelegate = delegate;
}

/// 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) => RaisedButton(
/// 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) => RaisedButton(
/// onPressed: followLink,
/// // ... other properties here ...
/// )},
/// );
/// ```
class Link extends StatelessWidget {
/// Called at build time to construct the widget tree under the link.
final LinkWidgetBuilder builder;

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

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

/// Whether the link is disabled or not.
bool get isDisabled => uri == null;

static LinkDelegate _linkDelegate = (Link link) => _DefaultLinkDelegate(link);

/// Creates a widget that renders a real link on the web, and uses WebViews in
/// native platforms to open links.
Link({
Key key,
@required this.uri,
LinkTarget target,
@required this.builder,
}) : target = target ?? LinkTarget.defaultTarget,
super(key: key);

@override
Widget build(BuildContext context) {
return _linkDelegate(this);
}
}

class _DefaultLinkDelegate extends StatelessWidget {
const _DefaultLinkDelegate(this.link);

final Link link;

Future<void> _followLink() {
// The default link delegate uses url launcher to open URIs.
// TODO(mdebbar): use url_launcher.
return Future<void>.value(null);
}

@override
Widget build(BuildContext context) {
return link.builder(context, link.isDisabled ? null : _followLink);
}
}
Loading