Browse Source

Initial

master
aminborhani21 2 weeks ago
commit
0d7452aa32
57 changed files with 3449 additions and 0 deletions
  1. +66
    -0
      AUTHORS
  2. +90
    -0
      CHANGELOG.md
  3. +25
    -0
      LICENSE
  4. +15
    -0
      README.md
  5. +9
    -0
      example/README.md
  6. +26
    -0
      example/integration_test/url_launcher_test.dart
  7. +30
    -0
      example/ios/Flutter/AppFrameworkInfo.plist
  8. +2
    -0
      example/ios/Flutter/Debug.xcconfig
  9. +2
    -0
      example/ios/Flutter/Release.xcconfig
  10. +41
    -0
      example/ios/Podfile
  11. +771
    -0
      example/ios/Runner.xcodeproj/project.pbxproj
  12. +7
    -0
      example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  13. +126
    -0
      example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  14. +10
    -0
      example/ios/Runner.xcworkspace/contents.xcworkspacedata
  15. +8
    -0
      example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  16. +10
    -0
      example/ios/Runner/AppDelegate.h
  17. +17
    -0
      example/ios/Runner/AppDelegate.m
  18. +116
    -0
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  19. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  20. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  21. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  22. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  23. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  24. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  25. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  26. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  27. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  28. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  29. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  30. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  31. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  32. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  33. +27
    -0
      example/ios/Runner/Base.lproj/LaunchScreen.storyboard
  34. +26
    -0
      example/ios/Runner/Base.lproj/Main.storyboard
  35. +51
    -0
      example/ios/Runner/Info.plist
  36. +13
    -0
      example/ios/Runner/main.m
  37. +22
    -0
      example/ios/RunnerTests/Info.plist
  38. +151
    -0
      example/ios/RunnerTests/URLLauncherTests.swift
  39. +22
    -0
      example/ios/RunnerUITests/Info.plist
  40. +42
    -0
      example/ios/RunnerUITests/URLLauncherUITests.swift
  41. +242
    -0
      example/lib/main.dart
  42. +29
    -0
      example/pubspec.yaml
  43. +7
    -0
      example/test_driver/integration_test.dart
  44. +26
    -0
      ios/url_launcher_ios.podspec
  45. +28
    -0
      ios/url_launcher_ios/Package.swift
  46. +38
    -0
      ios/url_launcher_ios/Sources/url_launcher_ios/Launcher.swift
  47. +14
    -0
      ios/url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy
  48. +63
    -0
      ios/url_launcher_ios/Sources/url_launcher_ios/URLLaunchSession.swift
  49. +100
    -0
      ios/url_launcher_ios/Sources/url_launcher_ios/URLLauncherPlugin.swift
  50. +242
    -0
      ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift
  51. +208
    -0
      lib/src/messages.g.dart
  52. +172
    -0
      lib/url_launcher_ios.dart
  53. +3
    -0
      pigeons/copyright.txt
  54. +56
    -0
      pigeons/messages.dart
  55. +37
    -0
      pubspec.yaml
  56. +370
    -0
      test/url_launcher_ios_test.dart
  57. +89
    -0
      test/url_launcher_ios_test.mocks.dart

+ 66
- 0
AUTHORS View File

@@ -0,0 +1,66 @@
# Below is a list of people and organizations that have contributed
# to the Flutter project. Names should be added to the list like so:
#
# Name/Organization <email address>

Google Inc.
The Chromium Authors
German Saprykin <saprykin.h@gmail.com>
Benjamin Sauer <sauer.benjamin@gmail.com>
larsenthomasj@gmail.com
Ali Bitek <alibitek@protonmail.ch>
Pol Batlló <pol.batllo@gmail.com>
Anatoly Pulyaevskiy
Hayden Flinner <haydenflinner@gmail.com>
Stefano Rodriguez <hlsroddy@gmail.com>
Salvatore Giordano <salvatoregiordanoo@gmail.com>
Brian Armstrong <brian@flutter.institute>
Paul DeMarco <paulmdemarco@gmail.com>
Fabricio Nogueira <feufeu@gmail.com>
Simon Lightfoot <simon@devangels.london>
Ashton Thomas <ashton@acrinta.com>
Thomas Danner <thmsdnnr@gmail.com>
Diego Velásquez <diego.velasquez.lopez@gmail.com>
Hajime Nakamura <nkmrhj@gmail.com>
Tuyển Vũ Xuân <netsoft1985@gmail.com>
Miguel Ruivo <miguel@miguelruivo.com>
Sarthak Verma <sarthak@artiosys.com>
Mike Diarmid <mike@invertase.io>
Invertase <oss@invertase.io>
Elliot Hesp <elliot@invertase.io>
Vince Varga <vince.varga@smaho.com>
Aawaz Gyawali <awazgyawali@gmail.com>
EUI Limited <ian.evans3@admiralgroup.co.uk>
Katarina Sheremet <katarina@sheremet.ch>
Thomas Stockx <thomas@stockxit.com>
Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
Ozkan Eksi <ozeksi@gmail.com>
Rishab Nayak <rishab@bu.edu>
ko2ic <ko2ic.dev@gmail.com>
Jonathan Younger <jonathan@daikini.com>
Jose Sanchez <josesm82@gmail.com>
Debkanchan Samadder <debu.samadder@gmail.com>
Audrius Karosevicius <audrius.karosevicius@gmail.com>
Lukasz Piliszczuk <lukasz@intheloup.io>
SoundReply Solutions GmbH <ch@soundreply.com>
Rafal Wachol <rwachol@gmail.com>
Pau Picas <pau.picas@gmail.com>
Christian Weder <chrstian.weder@yapeal.ch>
Alexandru Tuca <salexandru.tuca@outlook.com>
Christian Weder <chrstian.weder@yapeal.ch>
Rhodes Davis Jr. <rody.davis.jr@gmail.com>
Luigi Agosti <luigi@tengio.com>
Quentin Le Guennec <quentin@tengio.com>
Koushik Ravikumar <koushik@tengio.com>
Nissim Dsilva <nissim@tengio.com>
Giancarlo Rocha <giancarloiff@gmail.com>
Ryo Miyake <ryo@miyake.id>
Théo Champion <contact.theochampion@gmail.com>
Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
Eitan Schwartz <eshvartz@gmail.com>
Chris Rutkowski <chrisrutkowski89@gmail.com>
Juan Alvarez <juan.alvarez@resideo.com>
Aleksandr Yurkovskiy <sanekyy@gmail.com>
Anton Borries <mail@antonborri.es>
Alex Li <google@alexv525.com>
Rahul Raj <64.rahulraj@gmail.com>

+ 90
- 0
CHANGELOG.md View File

@@ -0,0 +1,90 @@
## 6.3.2

* Updates to Pigeon v22.
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.

## 6.3.1

* Fixes a compile error when comforming UIApplication to Launcher in iOS 18 Beta 3.

## 6.3.0

* Adds Swift Package Manager compatibility.

## 6.2.5

* Adds explicit imports for UIKit.
* Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6.

## 6.2.4

* Adds privacy manifest.

## 6.2.3

* Updates minimum required plugin_platform_interface version to 2.1.7.

## 6.2.2

* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
* Fixes lint warnings.

## 6.2.1

* Migrates plugin from Objective-C to Swift.

## 6.2.0

* Implements `supportsMode` and `supportsCloseForMode`.

## 6.1.5

* Adds pub topics to package metadata.
* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.

## 6.1.4

* Updates pigeon to fix warnings with clang 15.

## 6.1.3

* Switches to Pigeon for internal implementation.

## 6.1.2

* Clarifies explanation of endorsement in README.

## 6.1.1

* Updates links for the merge of flutter/plugins into flutter/packages.

## 6.1.0

* Updates minimum Flutter version to 3.3 and iOS 11.

## 6.0.18

* Updates code for stricter lint checks.
* Updates minimum Flutter version to 2.10.

## 6.0.17

* Suppresses warnings for pre-iOS-13 codepaths.

## 6.0.16

* Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors
lint warnings.

## 6.0.15

* Switches to an in-package method channel implementation.

## 6.0.14

* Updates code for new analysis options.
* Removes dependency on `meta`.

## 6.0.13

* Splits from `url_launcher` as a federated implementation.

+ 25
- 0
LICENSE View File

@@ -0,0 +1,25 @@
Copyright 2013 The Flutter Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 15
- 0
README.md View File

@@ -0,0 +1,15 @@
# url\_launcher\_ios

The iOS implementation of [`url_launcher`][1].

## Usage

This package is [endorsed][2], which means you can simply use `url_launcher`
normally. This package will be automatically included in your app when you do,
so you do not need to add it to your `pubspec.yaml`.

However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.

[1]: https://pub.dev/packages/url_launcher
[2]: https://flutter.dev/to/endorsed-federated-plugin

+ 9
- 0
example/README.md View File

@@ -0,0 +1,9 @@
# Platform Implementation Test App

This is a test app for manual testing and automated integration testing
of this platform implementation. It is not intended to demonstrate actual use of
this package, since the intent is that plugin clients use the app-facing
package.

Unless you are making changes to this implementation package, this example is
very unlikely to be relevant.

+ 26
- 0
example/integration_test/url_launcher_test.dart View File

@@ -0,0 +1,26 @@
// 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 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('canLaunch', (WidgetTester _) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;

expect(await launcher.canLaunch('randomstring'), false);

// Generally all devices should have some default browser.
expect(await launcher.canLaunch('http://flutter.dev'), true);

// SMS handling is available by default on test devices.
expect(await launcher.canLaunch('sms:5555555555'), true);

// tel: and mailto: links may not be openable on every device. iOS
// simulators notably can't open these link types.
});
}

+ 30
- 0
example/ios/Flutter/AppFrameworkInfo.plist View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

+ 2
- 0
example/ios/Flutter/Debug.xcconfig View File

@@ -0,0 +1,2 @@
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"

+ 2
- 0
example/ios/Flutter/Release.xcconfig View File

@@ -0,0 +1,2 @@
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"

+ 41
- 0
example/ios/Podfile View File

@@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

+ 771
- 0
example/ios/Runner.xcodeproj/project.pbxproj View File

@@ -0,0 +1,771 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
1D9FF3342A90170000F89C85 /* URLLauncherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FF3332A90170000F89C85 /* URLLauncherTests.swift */; };
1DBE6EDE2A92479000B6525C /* URLLauncherUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBE6EDD2A92479000B6525C /* URLLauncherUITests.swift */; };
2D92223F1EC1DA93007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */; };
2E37D9A274B2EACB147AC51B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 856D0913184F79C678A42603 /* libPods-Runner.a */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
B8140773523F70A044426500 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
F7151F4D26604CFB0028CB91 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
F7151F5B26604D060028CB91 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
1D9FF3332A90170000F89C85 /* URLLauncherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLLauncherTests.swift; sourceTree = "<group>"; };
1DBE6EDD2A92479000B6525C /* URLLauncherUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLLauncherUITests.swift; sourceTree = "<group>"; };
2D92223D1EC1DA93007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GeneratedPluginRegistrant.h; path = Runner/GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GeneratedPluginRegistrant.m; path = Runner/GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
836316F9AEA584411312E29F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
856D0913184F79C678A42603 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A84BFEE343F54B983D1B67EB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
F7151F4826604CFB0028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F7151F4C26604CFB0028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F7151F5626604D060028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F7151F5A26604D060028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
2E37D9A274B2EACB147AC51B /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F7151F4526604CFB0028CB91 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B8140773523F70A044426500 /* libPods-RunnerTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F7151F5326604D060028CB91 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
840012C8B5EDBCF56B0E4AC1 /* Pods */ = {
isa = PBXGroup;
children = (
836316F9AEA584411312E29F /* Pods-Runner.debug.xcconfig */,
A84BFEE343F54B983D1B67EB /* Pods-Runner.release.xcconfig */,
666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */,
D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
2D92223D1EC1DA93007564B0 /* GeneratedPluginRegistrant.h */,
2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
F7151F4926604CFB0028CB91 /* RunnerTests */,
F7151F5726604D060028CB91 /* RunnerUITests */,
97C146EF1CF9000F007C117D /* Products */,
840012C8B5EDBCF56B0E4AC1 /* Pods */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
F7151F4826604CFB0028CB91 /* RunnerTests.xctest */,
F7151F5626604D060028CB91 /* RunnerUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = {
isa = PBXGroup;
children = (
856D0913184F79C678A42603 /* libPods-Runner.a */,
487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
F7151F4926604CFB0028CB91 /* RunnerTests */ = {
isa = PBXGroup;
children = (
F7151F4C26604CFB0028CB91 /* Info.plist */,
1D9FF3332A90170000F89C85 /* URLLauncherTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
F7151F5726604D060028CB91 /* RunnerUITests */ = {
isa = PBXGroup;
children = (
F7151F5A26604D060028CB91 /* Info.plist */,
1DBE6EDD2A92479000B6525C /* URLLauncherUITests.swift */,
);
path = RunnerUITests;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
F7151F4726604CFB0028CB91 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = F7151F5126604CFB0028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
DD4687403C4F35FCD2994FDE /* [CP] Check Pods Manifest.lock */,
F7151F4426604CFB0028CB91 /* Sources */,
F7151F4526604CFB0028CB91 /* Frameworks */,
F7151F4626604CFB0028CB91 /* Resources */,
);
buildRules = (
);
dependencies = (
F7151F4E26604CFB0028CB91 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = F7151F4826604CFB0028CB91 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
F7151F5526604D060028CB91 /* RunnerUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = F7151F5D26604D060028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */;
buildPhases = (
F7151F5226604D060028CB91 /* Sources */,
F7151F5326604D060028CB91 /* Frameworks */,
F7151F5426604D060028CB91 /* Resources */,
);
buildRules = (
);
dependencies = (
F7151F5C26604D060028CB91 /* PBXTargetDependency */,
);
name = RunnerUITests;
productName = RunnerUITests;
productReference = F7151F5626604D060028CB91 /* RunnerUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "The Flutter Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = S8QB4VV633;
};
F7151F4726604CFB0028CB91 = {
CreatedOnToolsVersion = 12.5;
ProvisioningStyle = Automatic;
TestTargetID = 97C146ED1CF9000F007C117D;
};
F7151F5526604D060028CB91 = {
CreatedOnToolsVersion = 12.5;
ProvisioningStyle = Automatic;
TestTargetID = 97C146ED1CF9000F007C117D;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
F7151F4726604CFB0028CB91 /* RunnerTests */,
F7151F5526604D060028CB91 /* RunnerUITests */,
);
};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F7151F4626604CFB0028CB91 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
F7151F5426604D060028CB91 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
};
AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
DD4687403C4F35FCD2994FDE /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
2D92223F1EC1DA93007564B0 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F7151F4426604CFB0028CB91 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1D9FF3342A90170000F89C85 /* URLLauncherTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F7151F5226604D060028CB91 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1DBE6EDE2A92479000B6525C /* URLLauncherUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
F7151F4E26604CFB0028CB91 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = F7151F4D26604CFB0028CB91 /* PBXContainerItemProxy */;
};
F7151F5C26604D060028CB91 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = F7151F5B26604D060028CB91 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncher;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncher;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
F7151F4F26604CFB0028CB91 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
};
name = Debug;
};
F7151F5026604CFB0028CB91 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
};
name = Release;
};
F7151F5E26604D060028CB91 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Runner;
};
name = Debug;
};
F7151F5F26604D060028CB91 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
INFOPLIST_FILE = RunnerUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Runner;
};
name = Release;
};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F7151F5126604CFB0028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F7151F4F26604CFB0028CB91 /* Debug */,
F7151F5026604CFB0028CB91 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F7151F5D26604D060028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F7151F5E26604D060028CB91 /* Debug */,
F7151F5F26604D060028CB91 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

+ 7
- 0
example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

+ 126
- 0
example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F7151F4726604CFB0028CB91"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F7151F5526604D060028CB91"
BuildableName = "RunnerUITests.xctest"
BlueprintName = "RunnerUITests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

+ 10
- 0
example/ios/Runner.xcworkspace/contents.xcworkspacedata View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

+ 8
- 0
example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

+ 10
- 0
example/ios/Runner/AppDelegate.h View File

@@ -0,0 +1,10 @@
// 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 <Flutter/Flutter.h>
#import <UIKit/UIKit.h>

@interface AppDelegate : FlutterAppDelegate

@end

+ 17
- 0
example/ios/Runner/AppDelegate.m View File

@@ -0,0 +1,17 @@
// 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.

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
[super application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}

@end

+ 116
- 0
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json View File

@@ -0,0 +1,116 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png View File

Before After
Width: 20  |  Height: 20  |  Size: 564 B

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.3 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png View File

Before After
Width: 60  |  Height: 60  |  Size: 1.6 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png View File

Before After
Width: 29  |  Height: 29  |  Size: 1.0 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png View File

Before After
Width: 58  |  Height: 58  |  Size: 1.7 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png View File

Before After
Width: 87  |  Height: 87  |  Size: 1.9 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.3 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png View File

Before After
Width: 80  |  Height: 80  |  Size: 1.9 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png View File

Before After
Width: 120  |  Height: 120  |  Size: 2.6 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png View File

Before After
Width: 120  |  Height: 120  |  Size: 2.6 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png View File

Before After
Width: 180  |  Height: 180  |  Size: 3.7 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png View File

Before After
Width: 76  |  Height: 76  |  Size: 1.8 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png View File

Before After
Width: 152  |  Height: 152  |  Size: 3.2 KiB

BIN
example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png View File

Before After
Width: 167  |  Height: 167  |  Size: 3.5 KiB

+ 27
- 0
example/ios/Runner/Base.lproj/LaunchScreen.storyboard View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

+ 26
- 0
example/ios/Runner/Base.lproj/Main.storyboard View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

+ 51
- 0
example/ios/Runner/Info.plist View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>url_launcher_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

+ 13
- 0
example/ios/Runner/main.m View File

@@ -0,0 +1,13 @@
// 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 <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

+ 22
- 0
example/ios/RunnerTests/Info.plist View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

+ 151
- 0
example/ios/RunnerTests/URLLauncherTests.swift View File

@@ -0,0 +1,151 @@
// 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 Flutter
import XCTest

@testable import url_launcher_ios

// Tests whether NSURL parsing is strict. When linking against the iOS 17 SDK or later,
// NSURL uses a more lenient parser which will not return nil.
private func urlParsingIsStrict() -> Bool {
return URL(string: "b a d U R L") == nil
}

final class URLLauncherTests: XCTestCase {

private func createPlugin() -> URLLauncherPlugin {
let launcher = FakeLauncher()
return URLLauncherPlugin(launcher: launcher)
}

private func createPlugin(launcher: FakeLauncher) -> URLLauncherPlugin {
return URLLauncherPlugin(launcher: launcher)
}

func testCanLaunchSuccess() {
let result = createPlugin().canLaunchUrl(url: "good://url")
XCTAssertEqual(result, .success)
}

func testCanLaunchFailure() {
let result = createPlugin().canLaunchUrl(url: "bad://url")
XCTAssertEqual(result, .failure)
}

func testCanLaunchFailureWithInvalidURL() {
let result = createPlugin().canLaunchUrl(url: "urls can't have spaces")

if urlParsingIsStrict() {
XCTAssertEqual(result, .invalidUrl)
} else {
XCTAssertEqual(result, .failure)
}
}

func testLaunchSuccess() {
let expectation = XCTestExpectation(description: "completion called")
createPlugin().launchUrl(url: "good://url", universalLinksOnly: false) { result in
switch result {
case .success(let details):
XCTAssertEqual(details, .success)
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
expectation.fulfill()
}

wait(for: [expectation], timeout: 1)
}

func testLaunchFailure() {
let expectation = XCTestExpectation(description: "completion called")
createPlugin().launchUrl(url: "bad://url", universalLinksOnly: false) { result in
switch result {
case .success(let details):
XCTAssertEqual(details, .failure)
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
expectation.fulfill()
}

wait(for: [expectation], timeout: 1)
}

func testLaunchFailureWithInvalidURL() {
let expectation = XCTestExpectation(description: "completion called")
createPlugin().launchUrl(url: "urls can't have spaces", universalLinksOnly: false) { result in
switch result {
case .success(let details):
if urlParsingIsStrict() {
XCTAssertEqual(details, .invalidUrl)
} else {
XCTAssertEqual(details, .failure)
}
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
expectation.fulfill()
}

wait(for: [expectation], timeout: 1)
}

func testLaunchWithoutUniversalLinks() {
let launcher = FakeLauncher()
let plugin = createPlugin(launcher: launcher)

let expectation = XCTestExpectation(description: "completion called")
plugin.launchUrl(url: "good://url", universalLinksOnly: false) { result in
switch result {
case .success(let details):
XCTAssertEqual(details, .success)
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
expectation.fulfill()
}

wait(for: [expectation], timeout: 1)
XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, false)
}

func testLaunchWithUniversalLinks() {
let launcher = FakeLauncher()
let plugin = createPlugin(launcher: launcher)

let expectation = XCTestExpectation(description: "completion called")
plugin.launchUrl(url: "good://url", universalLinksOnly: true) { result in
switch result {
case .success(let details):
XCTAssertEqual(details, .success)
case .failure(let error):
XCTFail("Unexpected error: \(error)")
}
expectation.fulfill()
}

wait(for: [expectation], timeout: 1)
XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, true)
}

}

final private class FakeLauncher: NSObject, Launcher {
var passedOptions: [UIApplication.OpenExternalURLOptionsKey: Any]?

func canOpenURL(_ url: URL) -> Bool {
url.scheme == "good"
}

func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey: Any],
completionHandler completion: ((Bool) -> Void)?
) {
self.passedOptions = options
completion?(url.scheme == "good")
}
}

+ 22
- 0
example/ios/RunnerUITests/Info.plist View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

+ 42
- 0
example/ios/RunnerUITests/URLLauncherUITests.swift View File

@@ -0,0 +1,42 @@
// 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 XCTest

class URLLauncherUITests: XCTestCase {
var app: XCUIApplication!

override func setUp() {
continueAfterFailure = false

app = XCUIApplication()
app.launch()
}

func testLaunch() {
let app = self.app!

let buttonNames: [String] = [
"Launch in app", "Launch in app(JavaScript ON)", "Launch in app(DOM storage ON)",
"Launch a universal link in a native app, fallback to Safari.(Youtube)",
]
for buttonName in buttonNames {
let button = app.buttons[buttonName]
XCTAssertTrue(button.waitForExistence(timeout: 30.0))
XCTAssertEqual(app.webViews.count, 0)
button.tap()
let webView = app.webViews.firstMatch
XCTAssertTrue(webView.waitForExistence(timeout: 30.0))
XCTAssertTrue(app.buttons["ForwardButton"].waitForExistence(timeout: 30.0))
XCTAssertTrue(app.buttons["Share"].exists)
XCTAssertTrue(app.buttons["OpenInSafariButton"].exists)
let doneButton = app.buttons["Done"]
XCTAssertTrue(doneButton.waitForExistence(timeout: 30.0))
// This should just be doneButton.tap, but for some reason that stopped working in Xcode 15;
// tapping via coordinate works, however.
doneButton.coordinate(withNormalizedOffset: CGVector()).withOffset(CGVector(dx: 10, dy: 10))
.tap()
}
}
}

+ 242
- 0
example/lib/main.dart View File

@@ -0,0 +1,242 @@
// 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.

// ignore_for_file: public_member_api_docs

import 'dart:async';

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

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'URL Launcher',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'URL Launcher'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

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

Future<void> _launchInBrowser(String url) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
if (await launcher.canLaunch(url)) {
await launcher.launch(
url,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw Exception('Could not launch $url');
}
}

Future<void> _launchInWebViewOrVC(String url) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
if (await launcher.canLaunch(url)) {
await launcher.launch(
url,
useSafariVC: true,
useWebView: true,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw Exception('Could not launch $url');
}
}

Future<void> _launchInWebViewWithJavaScript(String url) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
if (await launcher.canLaunch(url)) {
await launcher.launch(
url,
useSafariVC: true,
useWebView: true,
enableJavaScript: true,
enableDomStorage: false,
universalLinksOnly: false,
headers: <String, String>{},
);
} else {
throw Exception('Could not launch $url');
}
}

Future<void> _launchInWebViewWithDomStorage(String url) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
if (await launcher.canLaunch(url)) {
await launcher.launch(
url,
useSafariVC: true,
useWebView: true,
enableJavaScript: false,
enableDomStorage: true,
universalLinksOnly: false,
headers: <String, String>{},
);
} else {
throw Exception('Could not launch $url');
}
}

Future<void> _launchUniversalLinkIos(String url) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
if (await launcher.canLaunch(url)) {
final bool nativeAppLaunchSucceeded = await launcher.launch(
url,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: true,
headers: <String, String>{},
);
if (!nativeAppLaunchSucceeded) {
await launcher.launch(
url,
useSafariVC: true,
useWebView: true,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: true,
headers: <String, String>{},
);
}
}
}

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

Future<void> _makePhoneCall(String url) async {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
if (await launcher.canLaunch(url)) {
await launcher.launch(
url,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: true,
headers: <String, String>{},
);
} else {
throw Exception('Could not launch $url');
}
}

@override
Widget build(BuildContext context) {
const String toLaunch = 'https://www.cylog.org/headers/';
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) => _phone = text,
decoration: const InputDecoration(
hintText: 'Input the phone number to launch')),
),
ElevatedButton(
onPressed: () => setState(() {
_launched = _makePhoneCall('tel:$_phone');
}),
child: const Text('Make phone call'),
),
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(toLaunch),
),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInBrowser(toLaunch);
}),
child: const Text('Launch in browser'),
),
const Padding(padding: EdgeInsets.all(16.0)),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInWebViewOrVC(toLaunch);
}),
child: const Text('Launch in app'),
),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInWebViewWithJavaScript(toLaunch);
}),
child: const Text('Launch in app(JavaScript ON)'),
),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInWebViewWithDomStorage(toLaunch);
}),
child: const Text('Launch in app(DOM storage ON)'),
),
const Padding(padding: EdgeInsets.all(16.0)),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchUniversalLinkIos(toLaunch);
}),
child: const Text(
'Launch a universal link in a native app, fallback to Safari.(Youtube)'),
),
const Padding(padding: EdgeInsets.all(16.0)),
ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInWebViewOrVC(toLaunch);
Timer(const Duration(seconds: 5), () {
UrlLauncherPlatform.instance.closeWebView();
});
}),
child: const Text('Launch in app + close after 5 seconds'),
),
const Padding(padding: EdgeInsets.all(16.0)),
FutureBuilder<void>(future: _launched, builder: _launchStatus),
],
),
],
),
);
}
}

+ 29
- 0
example/pubspec.yaml View File

@@ -0,0 +1,29 @@
name: url_launcher_example
description: Demonstrates how to use the url_launcher plugin.
publish_to: none

environment:
sdk: ^3.3.0
flutter: ">=3.19.0"

dependencies:
flutter:
sdk: flutter
url_launcher_ios:
# When depending on this package from a real application you should use:
# url_launcher_ios: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
url_launcher_platform_interface: ^2.2.0

dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
plugin_platform_interface: ^2.1.7

flutter:
uses-material-design: true

+ 7
- 0
example/test_driver/integration_test.dart View File

@@ -0,0 +1,7 @@
// 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 'package:integration_test/integration_test_driver.dart';

Future<void> main() => integrationDriver();

+ 26
- 0
ios/url_launcher_ios.podspec View File

@@ -0,0 +1,26 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'url_launcher_ios'
s.version = '0.0.1'
s.summary = 'Flutter plugin for launching a URL.'
s.description = <<-DESC
A Flutter plugin for making the underlying platform (Android or iOS) launch a URL.
DESC
s.homepage = 'https://github.com/flutter/packages/tree/main/packages/url_launcher'
s.license = { :type => 'BSD', :file => '../LICENSE' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios' }
s.documentation_url = 'https://pub.dev/packages/url_launcher'
s.swift_version = '5.0'
s.source_files = 'url_launcher_ios/Sources/**/*.swift'
s.xcconfig = {
'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
}
s.dependency 'Flutter'
s.platform = :ios, '12.0'
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.resource_bundles = {'url_launcher_ios_privacy' => ['url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy']}
end

+ 28
- 0
ios/url_launcher_ios/Package.swift View File

@@ -0,0 +1,28 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

// 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 PackageDescription

let package = Package(
name: "url_launcher_ios",
platforms: [
.iOS("12.0")
],
products: [
.library(name: "url-launcher-ios", targets: ["url_launcher_ios"])
],
dependencies: [],
targets: [
.target(
name: "url_launcher_ios",
dependencies: [],
resources: [
.process("Resources")
]
)
]
)

+ 38
- 0
ios/url_launcher_ios/Sources/url_launcher_ios/Launcher.swift View File

@@ -0,0 +1,38 @@
// 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 UIKit

/// Protocol for UIApplication methods relating to launching URLs.
///
/// This protocol exists to allow injecting an alternate implementation for testing.
protocol Launcher {
/// Returns a Boolean value that indicates whether an app is available to handle a URL scheme.
func canOpenURL(_ url: URL) -> Bool

/// Attempts to asynchronously open the resource at the specified URL.
func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey: Any],
completionHandler completion: ((Bool) -> Void)?)
}

// TODO(hellohuanlin): This wrapper is a workaround for iOS 18 Beta 3 where completionHandler is annotated with @MainActor @Sendable, resulting in compile error when conforming UIApplication to Launcher. We should try again in newer betas.
/// A default URL launcher.
final class DefaultLauncher: Launcher {
func canOpenURL(_ url: URL) -> Bool {
return UIApplication.shared.canOpenURL(url)
}

func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey: Any],
completionHandler completion: ((Bool) -> Void)?
) {
UIApplication.shared.open(
url,
options: options,
completionHandler: completion)
}
}

+ 14
- 0
ios/url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

+ 63
- 0
ios/url_launcher_ios/Sources/url_launcher_ios/URLLaunchSession.swift View File

@@ -0,0 +1,63 @@
// 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 Flutter
import SafariServices

typealias OpenInSafariCompletionHandler = (Result<InAppLoadResult, Error>) -> Void

/// A session responsible for launching a URL in Safari and handling its events.
final class URLLaunchSession: NSObject, SFSafariViewControllerDelegate {

private let completion: OpenInSafariCompletionHandler
private let url: URL

/// The Safari view controller used for displaying the URL.
let safariViewController: SFSafariViewController

// A closure to be executed after the Safari view controller finishes.
var didFinish: (() -> Void)?

/// Initializes a new URLLaunchSession with the provided URL and completion handler.
///
/// - Parameters:
/// - url: The URL to be opened in Safari.
/// - completion: The completion handler to be called after attempting to open the URL.
init(url: URL, completion: @escaping OpenInSafariCompletionHandler) {
self.url = url
self.completion = completion
self.safariViewController = SFSafariViewController(url: url)
super.init()
self.safariViewController.delegate = self
}

/// Called when the Safari view controller completes the initial load.
///
/// - Parameters:
/// - controller: The Safari view controller.
/// - didLoadSuccessfully: Indicates if the initial load was successful.
func safariViewController(
_ controller: SFSafariViewController,
didCompleteInitialLoad didLoadSuccessfully: Bool
) {
if didLoadSuccessfully {
completion(.success(.success))
} else {
completion(.success(.failedToLoad))
}
}

/// Called when the user finishes using the Safari view controller.
///
/// - Parameter controller: The Safari view controller.
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
controller.dismiss(animated: true, completion: nil)
didFinish?()
}

/// Closes the Safari view controller.
func close() {
safariViewControllerDidFinish(safariViewController)
}
}

+ 100
- 0
ios/url_launcher_ios/Sources/url_launcher_ios/URLLauncherPlugin.swift View File

@@ -0,0 +1,100 @@
// 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 Flutter
import UIKit

public final class URLLauncherPlugin: NSObject, FlutterPlugin, UrlLauncherApi {

public static func register(with registrar: FlutterPluginRegistrar) {
let plugin = URLLauncherPlugin()
UrlLauncherApiSetup.setUp(binaryMessenger: registrar.messenger(), api: plugin)
registrar.publish(plugin)
}

private var currentSession: URLLaunchSession?
private let launcher: Launcher

private var topViewController: UIViewController? {
// TODO(stuartmorgan) Provide a non-deprecated codepath. See
// https://github.com/flutter/flutter/issues/104117
UIApplication.shared.keyWindow?.rootViewController?.topViewController
}

init(launcher: Launcher = DefaultLauncher()) {
self.launcher = launcher
}

func canLaunchUrl(url: String) -> LaunchResult {
guard let url = URL(string: url) else {
return .invalidUrl
}
let canOpen = launcher.canOpenURL(url)
return canOpen ? .success : .failure
}

func launchUrl(
url: String,
universalLinksOnly: Bool,
completion: @escaping (Result<LaunchResult, Error>) -> Void
) {
guard let url = URL(string: url) else {
completion(.success(.invalidUrl))
return
}
let options = [UIApplication.OpenExternalURLOptionsKey.universalLinksOnly: universalLinksOnly]
launcher.open(url, options: options) { result in
completion(.success(result ? .success : .failure))
}
}

func openUrlInSafariViewController(
url: String,
completion: @escaping (Result<InAppLoadResult, Error>) -> Void
) {
guard let url = URL(string: url) else {
completion(.success(.invalidUrl))
return
}

let session = URLLaunchSession(url: url, completion: completion)
currentSession = session

session.didFinish = { [weak self] in
self?.currentSession = nil
}
topViewController?.present(session.safariViewController, animated: true, completion: nil)
}

func closeSafariViewController() {
currentSession?.close()
}
}

/// This method recursively iterates through the view hierarchy
/// to return the top-most view controller.
///
/// It supports the following scenarios:
///
/// - The view controller is presenting another view.
/// - The view controller is a UINavigationController.
/// - The view controller is a UITabBarController.
///
/// @return The top most view controller.
extension UIViewController {
var topViewController: UIViewController {
if let navigationController = self as? UINavigationController {
return navigationController.viewControllers.last?.topViewController
?? navigationController
.visibleViewController ?? navigationController
}
if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.topViewController ?? tabBarController
}
if let presented = presentedViewController {
return presented.topViewController
}
return self
}
}

+ 242
- 0
ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift View File

@@ -0,0 +1,242 @@
// 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.
// Autogenerated from Pigeon (v22.6.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon

import Foundation

#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif

/// Error class for passing custom error details to Dart side.
final class PigeonError: Error {
let code: String
let message: String?
let details: Any?

init(code: String, message: String?, details: Any?) {
self.code = code
self.message = message
self.details = details
}

var localizedDescription: String {
return
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
}
}

private func wrapResult(_ result: Any?) -> [Any?] {
return [result]
}

private func wrapError(_ error: Any) -> [Any?] {
if let pigeonError = error as? PigeonError {
return [
pigeonError.code,
pigeonError.message,
pigeonError.details,
]
}
if let flutterError = error as? FlutterError {
return [
flutterError.code,
flutterError.message,
flutterError.details,
]
}
return [
"\(error)",
"\(type(of: error))",
"Stacktrace: \(Thread.callStackSymbols)",
]
}

private func isNullish(_ value: Any?) -> Bool {
return value is NSNull || value == nil
}

private func nilOrValue<T>(_ value: Any?) -> T? {
if value is NSNull { return nil }
return value as! T?
}

/// Possible outcomes of launching a URL.
enum LaunchResult: Int {
/// The URL was successfully launched (or could be, for `canLaunchUrl`).
case success = 0
/// There was no handler available for the URL.
case failure = 1
/// The URL could not be launched because it is invalid.
case invalidUrl = 2
}

/// Possible outcomes of handling a URL within the application.
enum InAppLoadResult: Int {
/// The URL was successfully loaded.
case success = 0
/// The URL did not load successfully.
case failedToLoad = 1
/// The URL could not be launched because it is invalid.
case invalidUrl = 2
}

private class messagesPigeonCodecReader: FlutterStandardReader {
override func readValue(ofType type: UInt8) -> Any? {
switch type {
case 129:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return LaunchResult(rawValue: enumResultAsInt)
}
return nil
case 130:
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
if let enumResultAsInt = enumResultAsInt {
return InAppLoadResult(rawValue: enumResultAsInt)
}
return nil
default:
return super.readValue(ofType: type)
}
}
}

private class messagesPigeonCodecWriter: FlutterStandardWriter {
override func writeValue(_ value: Any) {
if let value = value as? LaunchResult {
super.writeByte(129)
super.writeValue(value.rawValue)
} else if let value = value as? InAppLoadResult {
super.writeByte(130)
super.writeValue(value.rawValue)
} else {
super.writeValue(value)
}
}
}

private class messagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
override func reader(with data: Data) -> FlutterStandardReader {
return messagesPigeonCodecReader(data: data)
}

override func writer(with data: NSMutableData) -> FlutterStandardWriter {
return messagesPigeonCodecWriter(data: data)
}
}

class messagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
static let shared = messagesPigeonCodec(readerWriter: messagesPigeonCodecReaderWriter())
}

/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
protocol UrlLauncherApi {
/// Checks whether a URL can be loaded.
func canLaunchUrl(url: String) throws -> LaunchResult
/// Opens the URL externally, returning the status of launching it.
func launchUrl(
url: String, universalLinksOnly: Bool,
completion: @escaping (Result<LaunchResult, Error>) -> Void)
/// Opens the URL in an in-app SFSafariViewController, returning the results
/// of loading it.
func openUrlInSafariViewController(
url: String, completion: @escaping (Result<InAppLoadResult, Error>) -> Void)
/// Closes the view controller opened by [openUrlInSafariViewController].
func closeSafariViewController() throws
}

/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
class UrlLauncherApiSetup {
static var codec: FlutterStandardMessageCodec { messagesPigeonCodec.shared }
/// Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`.
static func setUp(
binaryMessenger: FlutterBinaryMessenger, api: UrlLauncherApi?, messageChannelSuffix: String = ""
) {
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
/// Checks whether a URL can be loaded.
let canLaunchUrlChannel = FlutterBasicMessageChannel(
name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.canLaunchUrl\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
canLaunchUrlChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let urlArg = args[0] as! String
do {
let result = try api.canLaunchUrl(url: urlArg)
reply(wrapResult(result))
} catch {
reply(wrapError(error))
}
}
} else {
canLaunchUrlChannel.setMessageHandler(nil)
}
/// Opens the URL externally, returning the status of launching it.
let launchUrlChannel = FlutterBasicMessageChannel(
name: "dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.launchUrl\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
launchUrlChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let urlArg = args[0] as! String
let universalLinksOnlyArg = args[1] as! Bool
api.launchUrl(url: urlArg, universalLinksOnly: universalLinksOnlyArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
launchUrlChannel.setMessageHandler(nil)
}
/// Opens the URL in an in-app SFSafariViewController, returning the results
/// of loading it.
let openUrlInSafariViewControllerChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.openUrlInSafariViewController\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
openUrlInSafariViewControllerChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let urlArg = args[0] as! String
api.openUrlInSafariViewController(url: urlArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
openUrlInSafariViewControllerChannel.setMessageHandler(nil)
}
/// Closes the view controller opened by [openUrlInSafariViewController].
let closeSafariViewControllerChannel = FlutterBasicMessageChannel(
name:
"dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.closeSafariViewController\(channelSuffix)",
binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
closeSafariViewControllerChannel.setMessageHandler { _, reply in
do {
try api.closeSafariViewController()
reply(wrapResult(nil))
} catch {
reply(wrapError(error))
}
}
} else {
closeSafariViewControllerChannel.setMessageHandler(nil)
}
}
}

+ 208
- 0
lib/src/messages.g.dart View File

@@ -0,0 +1,208 @@
// 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.
// Autogenerated from Pigeon (v22.6.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;

import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

PlatformException _createConnectionError(String channelName) {
return PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel: "$channelName".',
);
}

/// Possible outcomes of launching a URL.
enum LaunchResult {
/// The URL was successfully launched (or could be, for `canLaunchUrl`).
success,

/// There was no handler available for the URL.
failure,

/// The URL could not be launched because it is invalid.
invalidUrl,
}

/// Possible outcomes of handling a URL within the application.
enum InAppLoadResult {
/// The URL was successfully loaded.
success,

/// The URL did not load successfully.
failedToLoad,

/// The URL could not be launched because it is invalid.
invalidUrl,
}

class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
if (value is int) {
buffer.putUint8(4);
buffer.putInt64(value);
} else if (value is LaunchResult) {
buffer.putUint8(129);
writeValue(buffer, value.index);
} else if (value is InAppLoadResult) {
buffer.putUint8(130);
writeValue(buffer, value.index);
} else {
super.writeValue(buffer, value);
}
}

@override
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 129:
final int? value = readValue(buffer) as int?;
return value == null ? null : LaunchResult.values[value];
case 130:
final int? value = readValue(buffer) as int?;
return value == null ? null : InAppLoadResult.values[value];
default:
return super.readValueOfType(type, buffer);
}
}
}

class UrlLauncherApi {
/// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
UrlLauncherApi(
{BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix =
messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;

static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

final String pigeonVar_messageChannelSuffix;

/// Checks whether a URL can be loaded.
Future<LaunchResult> canLaunchUrl(String url) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.canLaunchUrl$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[url]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as LaunchResult?)!;
}
}

/// Opens the URL externally, returning the status of launching it.
Future<LaunchResult> launchUrl(String url, bool universalLinksOnly) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.launchUrl$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList = await pigeonVar_channel
.send(<Object?>[url, universalLinksOnly]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as LaunchResult?)!;
}
}

/// Opens the URL in an in-app SFSafariViewController, returning the results
/// of loading it.
Future<InAppLoadResult> openUrlInSafariViewController(String url) async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.openUrlInSafariViewController$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(<Object?>[url]) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as InAppLoadResult?)!;
}
}

/// Closes the view controller opened by [openUrlInSafariViewController].
Future<void> closeSafariViewController() async {
final String pigeonVar_channelName =
'dev.flutter.pigeon.url_launcher_ios.UrlLauncherApi.closeSafariViewController$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_channel.send(null) as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}

+ 172
- 0
lib/url_launcher_ios.dart View File

@@ -0,0 +1,172 @@
// 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 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

import 'src/messages.g.dart';

/// An implementation of [UrlLauncherPlatform] for iOS.
class UrlLauncherIOS extends UrlLauncherPlatform {
/// Creates a new plugin implementation instance.
UrlLauncherIOS({
@visibleForTesting UrlLauncherApi? api,
}) : _hostApi = api ?? UrlLauncherApi();

final UrlLauncherApi _hostApi;

/// Registers this class as the default instance of [UrlLauncherPlatform].
static void registerWith() {
UrlLauncherPlatform.instance = UrlLauncherIOS();
}

@override
final LinkDelegate? linkDelegate = null;

@override
Future<bool> canLaunch(String url) async {
final LaunchResult result = await _hostApi.canLaunchUrl(url);
return _mapLaunchResult(result);
}

@override
Future<void> closeWebView() {
return _hostApi.closeSafariViewController();
}

@override
Future<bool> launch(
String url, {
required bool useSafariVC,
required bool useWebView,
required bool enableJavaScript,
required bool enableDomStorage,
required bool universalLinksOnly,
required Map<String, String> headers,
String? webOnlyWindowName,
}) async {
final PreferredLaunchMode mode;
if (useSafariVC) {
mode = PreferredLaunchMode.inAppBrowserView;
} else if (universalLinksOnly) {
mode = PreferredLaunchMode.externalNonBrowserApplication;
} else {
mode = PreferredLaunchMode.externalApplication;
}
return launchUrl(
url,
LaunchOptions(
mode: mode,
webViewConfiguration: InAppWebViewConfiguration(
enableDomStorage: enableDomStorage,
enableJavaScript: enableJavaScript,
headers: headers)));
}

@override
Future<bool> launchUrl(String url, LaunchOptions options) async {
final bool inApp;
switch (options.mode) {
case PreferredLaunchMode.inAppWebView:
case PreferredLaunchMode.inAppBrowserView:
// The iOS implementation doesn't distinguish between these two modes;
// both are treated as inAppBrowserView.
inApp = true;
case PreferredLaunchMode.externalApplication:
case PreferredLaunchMode.externalNonBrowserApplication:
inApp = false;
case PreferredLaunchMode.platformDefault:
// Intentionally treat any new values as platformDefault; support for any
// new mode requires intentional opt-in, otherwise falling back is the
// documented behavior.
// ignore: no_default_cases, unreachable_switch_default
default:
// By default, open web URLs in the application.
inApp = url.startsWith('http:') || url.startsWith('https:');
}

if (inApp) {
return _mapInAppLoadResult(
await _hostApi.openUrlInSafariViewController(url),
url: url);
} else {
return _mapLaunchResult(await _hostApi.launchUrl(url,
options.mode == PreferredLaunchMode.externalNonBrowserApplication));
}
}

@override
Future<bool> supportsMode(PreferredLaunchMode mode) async {
switch (mode) {
case PreferredLaunchMode.platformDefault:
case PreferredLaunchMode.inAppWebView:
case PreferredLaunchMode.inAppBrowserView:
case PreferredLaunchMode.externalApplication:
case PreferredLaunchMode.externalNonBrowserApplication:
return true;
// Default is a desired behavior here since support for new modes is
// always opt-in, and the enum lives in a different package, so silently
// adding "false" for new values is the correct behavior.
// ignore: no_default_cases, unreachable_switch_default
default:
return false;
}
}

@override
Future<bool> supportsCloseForMode(PreferredLaunchMode mode) async {
return mode == PreferredLaunchMode.inAppWebView ||
mode == PreferredLaunchMode.inAppBrowserView;
}

bool _mapLaunchResult(LaunchResult result) {
switch (result) {
case LaunchResult.success:
return true;
case LaunchResult.failure:
return false;
case LaunchResult.invalidUrl:
throw _invalidUrlException();
}
}

bool _mapInAppLoadResult(InAppLoadResult result, {required String url}) {
switch (result) {
case InAppLoadResult.success:
return true;
case InAppLoadResult.failedToLoad:
throw _failedSafariViewControllerLoadException(url);
case InAppLoadResult.invalidUrl:
throw _invalidUrlException();
}
}

// TODO(stuartmorgan): Remove this as part of standardizing error handling.
// See https://github.com/flutter/flutter/issues/127665
//
// This PlatformException (including the exact string details, since those
// are a defacto part of the API) is for compatibility with the previous
// native implementation.
PlatformException _invalidUrlException() {
throw PlatformException(
code: 'argument_error',
message: 'Unable to parse URL',
);
}

// TODO(stuartmorgan): Remove this as part of standardizing error handling.
// See https://github.com/flutter/flutter/issues/127665
//
// This PlatformException (including the exact string details, since those
// are a defacto part of the API) is for compatibility with the previous
// native implementation.
PlatformException _failedSafariViewControllerLoadException(String url) {
throw PlatformException(
code: 'Error',
message: 'Error while launching $url',
);
}
}

+ 3
- 0
pigeons/copyright.txt View File

@@ -0,0 +1,3 @@
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.

+ 56
- 0
pigeons/messages.dart View File

@@ -0,0 +1,56 @@
// 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 'package:pigeon/pigeon.dart';

@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
swiftOut: 'ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift',
copyrightHeader: 'pigeons/copyright.txt',
))

/// Possible outcomes of launching a URL.
enum LaunchResult {
/// The URL was successfully launched (or could be, for `canLaunchUrl`).
success,

/// There was no handler available for the URL.
failure,

/// The URL could not be launched because it is invalid.
invalidUrl,
}

/// Possible outcomes of handling a URL within the application.
enum InAppLoadResult {
/// The URL was successfully loaded.
success,

/// The URL did not load successfully.
failedToLoad,

/// The URL could not be launched because it is invalid.
invalidUrl,
}

@HostApi()
abstract class UrlLauncherApi {
/// Checks whether a URL can be loaded.
@ObjCSelector('canLaunchURL:')
LaunchResult canLaunchUrl(String url);

/// Opens the URL externally, returning the status of launching it.
@async
@ObjCSelector('launchURL:universalLinksOnly:')
LaunchResult launchUrl(String url, bool universalLinksOnly);

/// Opens the URL in an in-app SFSafariViewController, returning the results
/// of loading it.
@async
@ObjCSelector('openSafariViewControllerWithURL:')
InAppLoadResult openUrlInSafariViewController(String url);

/// Closes the view controller opened by [openUrlInSafariViewController].
void closeSafariViewController();
}

+ 37
- 0
pubspec.yaml View File

@@ -0,0 +1,37 @@
name: url_launcher_ios
description: iOS implementation of the url_launcher plugin.
repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22
version: 6.3.2

environment:
sdk: ^3.1.0
flutter: ">=3.19.0"

flutter:
plugin:
implements: url_launcher
platforms:
ios:
pluginClass: URLLauncherPlugin
dartPluginClass: UrlLauncherIOS

dependencies:
flutter:
sdk: flutter
url_launcher_platform_interface: ^2.2.0

dev_dependencies:
build_runner: ^2.3.3
flutter_test:
sdk: flutter
mockito: ^5.4.4
pigeon: ^22.6.4
plugin_platform_interface: ^2.1.7
test: ^1.16.3

topics:
- links
- os-integration
- url-launcher
- urls

+ 370
- 0
test/url_launcher_ios_test.dart View File

@@ -0,0 +1,370 @@
// 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 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:url_launcher_ios/src/messages.g.dart';
import 'package:url_launcher_ios/url_launcher_ios.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';

import 'url_launcher_ios_test.mocks.dart';

// A web URL to use in tests where the specifics of the URL don't matter.
const String _webUrl = 'https://example.com/';

@GenerateMocks(<Type>[UrlLauncherApi])
void main() {
late MockUrlLauncherApi api;

setUp(() {
api = MockUrlLauncherApi();
});

test('registers instance', () {
UrlLauncherIOS.registerWith();
expect(UrlLauncherPlatform.instance, isA<UrlLauncherIOS>());
});

group('canLaunch', () {
test('handles success', () async {
when(api.canLaunchUrl(_webUrl))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(await launcher.canLaunch(_webUrl), true);
});

test('handles failure', () async {
when(api.canLaunchUrl(_webUrl))
.thenAnswer((_) async => LaunchResult.failure);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(await launcher.canLaunch(_webUrl), false);
});

test('throws PlatformException for invalid URL', () async {
when(api.canLaunchUrl(_webUrl))
.thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.canLaunch(_webUrl),
throwsA(isA<PlatformException>().having(
(PlatformException e) => e.code, 'code', 'argument_error')));
});
});

group('legacy launch', () {
test('handles success', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
_webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
true);
verifyNever(api.openUrlInSafariViewController(any));
});

test('handles failure', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.failure);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
_webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
false);
verifyNever(api.openUrlInSafariViewController(any));
});

test('throws PlatformException for invalid URL', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launch(
_webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
throwsA(isA<PlatformException>().having(
(PlatformException e) => e.code, 'code', 'argument_error')));
});

test('force SafariVC is handled', () async {
when(api.openUrlInSafariViewController(_webUrl))
.thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
_webUrl,
useSafariVC: true,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
true);
verifyNever(api.launchUrl(any, any));
});

test('universal links only is handled', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
_webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: true,
headers: const <String, String>{},
),
true);
verifyNever(api.openUrlInSafariViewController(any));
});

test('disallowing SafariVC is handled', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.launch(
_webUrl,
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
enableDomStorage: false,
universalLinksOnly: false,
headers: const <String, String>{},
),
true);
verifyNever(api.openUrlInSafariViewController(any));
});
});

test('closeWebView calls through', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await launcher.closeWebView();
verify(api.closeSafariViewController()).called(1);
});

group('launch without webview', () {
test('calls through', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched = await launcher.launchUrl(
_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.externalApplication),
);
expect(launched, true);
verifyNever(api.openUrlInSafariViewController(any));
});

test('throws PlatformException for invalid URL', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launchUrl(
_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.externalApplication),
),
throwsA(isA<PlatformException>().having(
(PlatformException e) => e.code, 'code', 'argument_error')));
});
});

group('launch with Safari view controller', () {
test('calls through with inAppWebView', () async {
when(api.openUrlInSafariViewController(_webUrl))
.thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched = await launcher.launchUrl(
_webUrl, const LaunchOptions(mode: PreferredLaunchMode.inAppWebView));
expect(launched, true);
verifyNever(api.launchUrl(any, any));
});

test('calls through with inAppBrowserView', () async {
when(api.openUrlInSafariViewController(_webUrl))
.thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched = await launcher.launchUrl(_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView));
expect(launched, true);
verifyNever(api.launchUrl(any, any));
});

test('throws PlatformException for invalid URL', () async {
when(api.openUrlInSafariViewController(_webUrl))
.thenAnswer((_) async => InAppLoadResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launchUrl(_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)),
throwsA(isA<PlatformException>().having(
(PlatformException e) => e.code, 'code', 'argument_error')));
});

test('throws PlatformException for load failure', () async {
when(api.openUrlInSafariViewController(_webUrl))
.thenAnswer((_) async => InAppLoadResult.failedToLoad);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launchUrl(_webUrl,
const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)),
throwsA(isA<PlatformException>()
.having((PlatformException e) => e.code, 'code', 'Error')));
});
});

group('launch with universal links', () {
test('calls through', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched = await launcher.launchUrl(
_webUrl,
const LaunchOptions(
mode: PreferredLaunchMode.externalNonBrowserApplication),
);
expect(launched, true);
verifyNever(api.openUrlInSafariViewController(any));
});

test('throws PlatformException for invalid URL', () async {
when(api.launchUrl(_webUrl, any))
.thenAnswer((_) async => LaunchResult.invalidUrl);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
await expectLater(
launcher.launchUrl(
_webUrl,
const LaunchOptions(
mode: PreferredLaunchMode.externalNonBrowserApplication)),
throwsA(isA<PlatformException>().having(
(PlatformException e) => e.code, 'code', 'argument_error')));
});
});

group('launch with platform default', () {
test('uses Safari view controller for http', () async {
const String httpUrl = 'http://example.com/';
when(api.openUrlInSafariViewController(httpUrl))
.thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched =
await launcher.launchUrl(httpUrl, const LaunchOptions());
expect(launched, true);
verifyNever(api.launchUrl(any, any));
});

test('uses Safari view controller for https', () async {
const String httpsUrl = 'https://example.com/';
when(api.openUrlInSafariViewController(httpsUrl))
.thenAnswer((_) async => InAppLoadResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched =
await launcher.launchUrl(httpsUrl, const LaunchOptions());
expect(launched, true);
verifyNever(api.launchUrl(any, any));
});

test('uses standard external for other schemes', () async {
const String nonWebUrl = 'supportedcustomscheme://example.com/';
when(api.launchUrl(nonWebUrl, any))
.thenAnswer((_) async => LaunchResult.success);
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
final bool launched =
await launcher.launchUrl(nonWebUrl, const LaunchOptions());
expect(launched, true);
verifyNever(api.openUrlInSafariViewController(any));
});
});

group('supportsMode', () {
test('returns true for platformDefault', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault),
true);
});

test('returns true for external application', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.supportsMode(PreferredLaunchMode.externalApplication),
true);
});

test('returns true for external non-browser application', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher
.supportsMode(PreferredLaunchMode.externalNonBrowserApplication),
true);
});

test('returns true for in app web view', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true);
});

test('returns true for in app browser view', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView),
true);
});
});

group('supportsCloseForMode', () {
test('returns true for in app web view', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView),
true);
});

test('returns true for in app browser view', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher
.supportsCloseForMode(PreferredLaunchMode.inAppBrowserView),
true);
});

test('returns false for other modes', () async {
final UrlLauncherIOS launcher = UrlLauncherIOS(api: api);
expect(
await launcher
.supportsCloseForMode(PreferredLaunchMode.externalApplication),
false);
expect(
await launcher.supportsCloseForMode(
PreferredLaunchMode.externalNonBrowserApplication),
false);
});
});
}

+ 89
- 0
test/url_launcher_ios_test.mocks.dart View File

@@ -0,0 +1,89 @@
// Mocks generated by Mockito 5.4.4 from annotations
// in url_launcher_ios/test/url_launcher_ios_test.dart.
// Do not manually edit this file.

// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i4;

import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i3;
import 'package:url_launcher_ios/src/messages.g.dart' as _i2;

// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class

/// A class which mocks [UrlLauncherApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockUrlLauncherApi extends _i1.Mock implements _i2.UrlLauncherApi {
MockUrlLauncherApi() {
_i1.throwOnMissingStub(this);
}

@override
String get pigeonVar_messageChannelSuffix => (super.noSuchMethod(
Invocation.getter(#pigeonVar_messageChannelSuffix),
returnValue: _i3.dummyValue<String>(
this,
Invocation.getter(#pigeonVar_messageChannelSuffix),
),
) as String);

@override
_i4.Future<_i2.LaunchResult> canLaunchUrl(String? url) => (super.noSuchMethod(
Invocation.method(
#canLaunchUrl,
[url],
),
returnValue:
_i4.Future<_i2.LaunchResult>.value(_i2.LaunchResult.success),
) as _i4.Future<_i2.LaunchResult>);

@override
_i4.Future<_i2.LaunchResult> launchUrl(
String? url,
bool? universalLinksOnly,
) =>
(super.noSuchMethod(
Invocation.method(
#launchUrl,
[
url,
universalLinksOnly,
],
),
returnValue:
_i4.Future<_i2.LaunchResult>.value(_i2.LaunchResult.success),
) as _i4.Future<_i2.LaunchResult>);

@override
_i4.Future<_i2.InAppLoadResult> openUrlInSafariViewController(String? url) =>
(super.noSuchMethod(
Invocation.method(
#openUrlInSafariViewController,
[url],
),
returnValue:
_i4.Future<_i2.InAppLoadResult>.value(_i2.InAppLoadResult.success),
) as _i4.Future<_i2.InAppLoadResult>);

@override
_i4.Future<void> closeSafariViewController() => (super.noSuchMethod(
Invocation.method(
#closeSafariViewController,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
}

Loading…
Cancel
Save