From bdccc767da5881aa8281fa5178a1d9d49d88542d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Schmaljohann?= <> Date: Fri, 22 Apr 2022 16:26:30 +0200 Subject: [PATCH 1/8] added linklauncher --- .../lib/src/link_laucher.dart | 37 +++++++++++++++++++ .../lib/zweidenker_heinzelmen.dart | 1 + packages/zweidenker_heinzelmen/pubspec.yaml | 2 + 3 files changed, 40 insertions(+) create mode 100644 packages/zweidenker_heinzelmen/lib/src/link_laucher.dart diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart new file mode 100644 index 0000000..5b2aaeb --- /dev/null +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// Wrapper class for opening url's inside apps +class LinkLauncher { + /// Creates a LinkLauncher + const LinkLauncher(); + + /// Opens an url in a modal style on a mobile device or in a new tap on a web client + void openWebPage({ + required String url, + CustomTabsOptions? tabsOptions, + SafariViewControllerOptions? safariViewControllerOptions, + }) { + if (Platform.isAndroid || Platform.isIOS) { + FlutterWebBrowser.openWebPage( + url: url, + customTabsOptions: tabsOptions ?? + const CustomTabsOptions( + instantAppsEnabled: true, + urlBarHidingEnabled: true, + ), + safariVCOptions: safariViewControllerOptions ?? + const SafariViewControllerOptions( + barCollapsingEnabled: true, + modalPresentationCapturesStatusBarAppearance: true, + dismissButtonStyle: SafariViewControllerDismissButtonStyle.done, + modalPresentationStyle: UIModalPresentationStyle.fullScreen, + ), + ); + } else { + launch(url); + } + } +} diff --git a/packages/zweidenker_heinzelmen/lib/zweidenker_heinzelmen.dart b/packages/zweidenker_heinzelmen/lib/zweidenker_heinzelmen.dart index 637bcd4..4bbee3d 100644 --- a/packages/zweidenker_heinzelmen/lib/zweidenker_heinzelmen.dart +++ b/packages/zweidenker_heinzelmen/lib/zweidenker_heinzelmen.dart @@ -3,3 +3,4 @@ library zweidenker_heinzelmen; export 'package:zweidenker_heinzelmen/src/app_version.dart'; export 'package:zweidenker_heinzelmen/src/feature_toggle.dart'; export 'package:zweidenker_heinzelmen/src/simple_loader_mixin.dart'; +export 'package:zweidenker_heinzelmen/src/link_laucher.dart'; diff --git a/packages/zweidenker_heinzelmen/pubspec.yaml b/packages/zweidenker_heinzelmen/pubspec.yaml index 7e2cc9d..46dff90 100644 --- a/packages/zweidenker_heinzelmen/pubspec.yaml +++ b/packages/zweidenker_heinzelmen/pubspec.yaml @@ -10,8 +10,10 @@ environment: dependencies: flutter: sdk: flutter + flutter_web_browser: ^0.17.1 package_info_plus: ^1.4.2 universal_platform: ^1.0.0+1 + url_launcher: ^6.0.20 dev_dependencies: flutter_test: From 294a85e416f66fa61fd898c3b70e35578f033f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Schmaljohann?= <> Date: Fri, 22 Apr 2022 16:27:28 +0200 Subject: [PATCH 2/8] updated changelog and version --- packages/zweidenker_heinzelmen/CHANGELOG.md | 4 ++++ packages/zweidenker_heinzelmen/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/zweidenker_heinzelmen/CHANGELOG.md b/packages/zweidenker_heinzelmen/CHANGELOG.md index 60d725a..9234000 100644 --- a/packages/zweidenker_heinzelmen/CHANGELOG.md +++ b/packages/zweidenker_heinzelmen/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.2 - Added LinkLauncher +* Added LinkLauncher helper + + ## 0.0.1 - Initial Release * Added the following helpers * AppVersion diff --git a/packages/zweidenker_heinzelmen/pubspec.yaml b/packages/zweidenker_heinzelmen/pubspec.yaml index 46dff90..969eafd 100644 --- a/packages/zweidenker_heinzelmen/pubspec.yaml +++ b/packages/zweidenker_heinzelmen/pubspec.yaml @@ -1,6 +1,6 @@ name: zweidenker_heinzelmen description: A Flutter Package with useful helpers that are used across Flutter Apps by ZWEIDENKER -version: 0.0.1 +version: 0.0.2 repository: https://github.com/zweidenker/flutter-heinzelmen/packages/zweidenker_heinzelmen environment: From 1c79418b79bef9b70c863372db1e2ab8954e75c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Schmaljohann?= <> Date: Fri, 22 Apr 2022 16:39:28 +0200 Subject: [PATCH 3/8] added more docu --- packages/zweidenker_heinzelmen/lib/src/link_laucher.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart index 5b2aaeb..bb1b20f 100644 --- a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -9,12 +9,15 @@ class LinkLauncher { const LinkLauncher(); /// Opens an url in a modal style on a mobile device or in a new tap on a web client + /// Use [tabsOptions] to customize the Chrome taps on Android + /// Use [safariViewControllerOptions] to customize the SafariController on iOS void openWebPage({ required String url, CustomTabsOptions? tabsOptions, SafariViewControllerOptions? safariViewControllerOptions, }) { if (Platform.isAndroid || Platform.isIOS) { + /// Opens an url in a modal style on a mobile device FlutterWebBrowser.openWebPage( url: url, customTabsOptions: tabsOptions ?? @@ -31,6 +34,7 @@ class LinkLauncher { ), ); } else { + /// Opens the url in a new tab in the browser launch(url); } } From 2ac6d4c68ffb01dfbca04c2498414c334339d566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Schmaljohann?= <56348804+soeren-schmaljohann-2denker@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:20:27 +0200 Subject: [PATCH 4/8] fixed typo Co-authored-by: Anton Borries --- packages/zweidenker_heinzelmen/lib/src/link_laucher.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart index bb1b20f..ed04a02 100644 --- a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -9,7 +9,7 @@ class LinkLauncher { const LinkLauncher(); /// Opens an url in a modal style on a mobile device or in a new tap on a web client - /// Use [tabsOptions] to customize the Chrome taps on Android + /// Use [tabsOptions] to customize the Chrome tabs on Android /// Use [safariViewControllerOptions] to customize the SafariController on iOS void openWebPage({ required String url, From 0a5e93ede51ab6ac3c6a69e92157c0d9ac898aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Schmaljohann?= <> Date: Tue, 26 Apr 2022 11:25:53 +0200 Subject: [PATCH 5/8] use universalplatform --- .../zweidenker_heinzelmen/lib/src/link_laucher.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart index ed04a02..724a551 100644 --- a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -1,6 +1,5 @@ -import 'dart:io'; - import 'package:flutter_web_browser/flutter_web_browser.dart'; +import 'package:universal_platform/universal_platform.dart'; import 'package:url_launcher/url_launcher.dart'; /// Wrapper class for opening url's inside apps @@ -16,7 +15,10 @@ class LinkLauncher { CustomTabsOptions? tabsOptions, SafariViewControllerOptions? safariViewControllerOptions, }) { - if (Platform.isAndroid || Platform.isIOS) { + if (UniversalPlatform.isDesktopOrWeb) { + /// Opens the url in a new tab in the browser + launch(url); + } else { /// Opens an url in a modal style on a mobile device FlutterWebBrowser.openWebPage( url: url, @@ -33,9 +35,6 @@ class LinkLauncher { modalPresentationStyle: UIModalPresentationStyle.fullScreen, ), ); - } else { - /// Opens the url in a new tab in the browser - launch(url); } } } From 9cc9fdb6576f745800656e4e507cc17fd7e5f702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Schmaljohann?= <> Date: Tue, 26 Apr 2022 11:26:29 +0200 Subject: [PATCH 6/8] expose used package --- packages/zweidenker_heinzelmen/lib/src/link_laucher.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart index 724a551..4dfdd0c 100644 --- a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -2,6 +2,9 @@ import 'package:flutter_web_browser/flutter_web_browser.dart'; import 'package:universal_platform/universal_platform.dart'; import 'package:url_launcher/url_launcher.dart'; +export 'package:flutter_web_browser/flutter_web_browser.dart' + show CustomTabsOptions, SafariViewControllerOptions; + /// Wrapper class for opening url's inside apps class LinkLauncher { /// Creates a LinkLauncher From e29c4d49191292d891bc5e4df83f433a3ef41ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88ren=20Schmaljohann?= <> Date: Thu, 28 Apr 2022 10:35:38 +0200 Subject: [PATCH 7/8] added browser test --- .../lib/src/link_laucher.dart | 8 +-- packages/zweidenker_heinzelmen/pubspec.yaml | 1 + .../test/src/link_launcher_test.dart | 64 +++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart index 4dfdd0c..d39f34f 100644 --- a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -13,17 +13,17 @@ class LinkLauncher { /// Opens an url in a modal style on a mobile device or in a new tap on a web client /// Use [tabsOptions] to customize the Chrome tabs on Android /// Use [safariViewControllerOptions] to customize the SafariController on iOS - void openWebPage({ + Future openWebPage({ required String url, CustomTabsOptions? tabsOptions, SafariViewControllerOptions? safariViewControllerOptions, - }) { + }) async { if (UniversalPlatform.isDesktopOrWeb) { /// Opens the url in a new tab in the browser - launch(url); + await launch(url); } else { /// Opens an url in a modal style on a mobile device - FlutterWebBrowser.openWebPage( + await FlutterWebBrowser.openWebPage( url: url, customTabsOptions: tabsOptions ?? const CustomTabsOptions( diff --git a/packages/zweidenker_heinzelmen/pubspec.yaml b/packages/zweidenker_heinzelmen/pubspec.yaml index 969eafd..778d077 100644 --- a/packages/zweidenker_heinzelmen/pubspec.yaml +++ b/packages/zweidenker_heinzelmen/pubspec.yaml @@ -19,6 +19,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 + url_launcher_platform_interface: ^2.0.5 flutter: diff --git a/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart b/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart new file mode 100644 index 0000000..52d8204 --- /dev/null +++ b/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart @@ -0,0 +1,64 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zweidenker_heinzelmen/zweidenker_heinzelmen.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + const LinkLauncher _linkLauncher = LinkLauncher(); + const _testUri = 'https://en.apptivegrid.de/'; + final _mock = _MockUrlLauncherPlatform(); + + setUpAll(() { + UrlLauncherPlatform.instance = _mock; + }); + + test('Browser is called', () async { + _mock.setLaunchExpectations( + url: _testUri, + useWebView: false, + useSafariVC: false, + ); + await _linkLauncher.openWebPage(url: _testUri); + expect(_mock.launchCalled, true); + }); +} + +class _MockUrlLauncherPlatform extends Fake + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform { + String? url; + bool? useSafariVC; + bool? useWebView; + + bool launchCalled = false; + + void setLaunchExpectations({ + required String url, + required bool? useSafariVC, + required bool useWebView, + }) { + this.url = url; + this.useSafariVC = useSafariVC; + this.useWebView = useWebView; + } + + @override + Future launch( + String url, { + required bool useSafariVC, + required bool useWebView, + required bool enableJavaScript, + required bool enableDomStorage, + required bool universalLinksOnly, + required Map headers, + String? webOnlyWindowName, + }) async { + expect(url, this.url); + expect(useSafariVC, this.useSafariVC); + expect(useWebView, this.useWebView); + launchCalled = true; + return true; + } +} From 9e1330482b1177f270c99e40eec87ca2894cec1f Mon Sep 17 00:00:00 2001 From: Anton Borries Date: Tue, 3 May 2022 15:57:00 +0200 Subject: [PATCH 8/8] Clean up Tests using Mockito Adopted the Test to use Mockito for Mocking Also added option to open Link Externally that always uses url_launcher fixed deprecation and imports --- .../lib/src/link_laucher.dart | 16 ++- packages/zweidenker_heinzelmen/pubspec.yaml | 4 +- .../test/infra/mocks.dart | 6 + .../test/src/link_launcher_test.dart | 113 ++++++++++-------- 4 files changed, 83 insertions(+), 56 deletions(-) diff --git a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart index d39f34f..16b3885 100644 --- a/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart +++ b/packages/zweidenker_heinzelmen/lib/src/link_laucher.dart @@ -11,20 +11,27 @@ class LinkLauncher { const LinkLauncher(); /// Opens an url in a modal style on a mobile device or in a new tap on a web client + /// if [openExternally] is `true` this will also be launched externally /// Use [tabsOptions] to customize the Chrome tabs on Android /// Use [safariViewControllerOptions] to customize the SafariController on iOS Future openWebPage({ - required String url, + required Uri url, + bool openExternally = false, CustomTabsOptions? tabsOptions, SafariViewControllerOptions? safariViewControllerOptions, }) async { - if (UniversalPlatform.isDesktopOrWeb) { + if (openExternally || UniversalPlatform.isDesktopOrWeb) { /// Opens the url in a new tab in the browser - await launch(url); + await launchUrl(url, mode: LaunchMode.externalApplication); } else { + // coverage:ignore-start + // Ignore for Coverage as UniversalPlatform is not testable with a mock + // Check https://github.com/gskinnerTeam/flutter-universal-platform/issues/15 for reference + // This means that this branch of the if/else can not be properly testable + /// Opens an url in a modal style on a mobile device await FlutterWebBrowser.openWebPage( - url: url, + url: url.toString(), customTabsOptions: tabsOptions ?? const CustomTabsOptions( instantAppsEnabled: true, @@ -38,6 +45,7 @@ class LinkLauncher { modalPresentationStyle: UIModalPresentationStyle.fullScreen, ), ); + // coverage:ignore-end } } } diff --git a/packages/zweidenker_heinzelmen/pubspec.yaml b/packages/zweidenker_heinzelmen/pubspec.yaml index 778d077..905cbcd 100644 --- a/packages/zweidenker_heinzelmen/pubspec.yaml +++ b/packages/zweidenker_heinzelmen/pubspec.yaml @@ -13,12 +13,14 @@ dependencies: flutter_web_browser: ^0.17.1 package_info_plus: ^1.4.2 universal_platform: ^1.0.0+1 - url_launcher: ^6.0.20 + url_launcher: ^6.1.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 + mocktail: ^0.3.0 + plugin_platform_interface: ^2.1.2 url_launcher_platform_interface: ^2.0.5 flutter: diff --git a/packages/zweidenker_heinzelmen/test/infra/mocks.dart b/packages/zweidenker_heinzelmen/test/infra/mocks.dart index 8b13789..e1aadaa 100644 --- a/packages/zweidenker_heinzelmen/test/infra/mocks.dart +++ b/packages/zweidenker_heinzelmen/test/infra/mocks.dart @@ -1 +1,7 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +class MockUrlLauncherPlatform extends Mock + with MockPlatformInterfaceMixin + implements UrlLauncherPlatform {} diff --git a/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart b/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart index 52d8204..ae07af0 100644 --- a/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart +++ b/packages/zweidenker_heinzelmen/test/src/link_launcher_test.dart @@ -1,64 +1,75 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:zweidenker_heinzelmen/zweidenker_heinzelmen.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +import 'package:zweidenker_heinzelmen/zweidenker_heinzelmen.dart'; + +import '../infra/mocks.dart'; void main() { - const LinkLauncher _linkLauncher = LinkLauncher(); - const _testUri = 'https://en.apptivegrid.de/'; - final _mock = _MockUrlLauncherPlatform(); + const LinkLauncher linkLauncher = LinkLauncher(); + final testUri = Uri.parse('https://en.apptivegrid.de/'); + final mockUrlLauncher = MockUrlLauncherPlatform(); setUpAll(() { - UrlLauncherPlatform.instance = _mock; + UrlLauncherPlatform.instance = mockUrlLauncher; }); - test('Browser is called', () async { - _mock.setLaunchExpectations( - url: _testUri, - useWebView: false, - useSafariVC: false, - ); - await _linkLauncher.openWebPage(url: _testUri); - expect(_mock.launchCalled, true); - }); -} + group('Url Launcher', () { + test('Calls Url Launcher on Desktop', () async { + // TODO: Set Universal Explicitly to desktop (https://github.com/gskinnerTeam/flutter-universal-platform/issues/15) + when( + () => mockUrlLauncher.launch( + testUri.toString(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((invocation) async => true); + await linkLauncher.openWebPage(url: testUri); -class _MockUrlLauncherPlatform extends Fake - with MockPlatformInterfaceMixin - implements UrlLauncherPlatform { - String? url; - bool? useSafariVC; - bool? useWebView; + verify( + () => mockUrlLauncher.launch( + testUri.toString(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).called(1); + }); - bool launchCalled = false; + test('Open Externally. Calls Url Launcher', () async { + when( + () => mockUrlLauncher.launch( + testUri.toString(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).thenAnswer((invocation) async => true); + await linkLauncher.openWebPage(url: testUri, openExternally: true); - void setLaunchExpectations({ - required String url, - required bool? useSafariVC, - required bool useWebView, - }) { - this.url = url; - this.useSafariVC = useSafariVC; - this.useWebView = useWebView; - } + verify( + () => mockUrlLauncher.launch( + testUri.toString(), + useSafariVC: any(named: 'useSafariVC'), + useWebView: any(named: 'useWebView'), + enableJavaScript: any(named: 'enableJavaScript'), + enableDomStorage: any(named: 'enableDomStorage'), + universalLinksOnly: any(named: 'universalLinksOnly'), + headers: any(named: 'headers'), + ), + ).called(1); + }); + }); - @override - Future launch( - String url, { - required bool useSafariVC, - required bool useWebView, - required bool enableJavaScript, - required bool enableDomStorage, - required bool universalLinksOnly, - required Map headers, - String? webOnlyWindowName, - }) async { - expect(url, this.url); - expect(useSafariVC, this.useSafariVC); - expect(useWebView, this.useWebView); - launchCalled = true; - return true; - } + // TODO: Add Test for non Desktop/Web (Waiting for https://github.com/gskinnerTeam/flutter-universal-platform/issues/15) }