initial macos implementation
33
.metadata
|
@ -1,10 +1,39 @@
|
||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: c860cba910319332564e1e9d470a17074c1f2dfd
|
revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: plugin
|
project_type: plugin
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
- platform: android
|
||||||
|
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
- platform: macos
|
||||||
|
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
- platform: web
|
||||||
|
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
|
||||||
|
@ -18,9 +19,7 @@ class MyInAppBrowser extends InAppBrowser {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future onLoadStart(url) async {
|
Future onLoadStart(url) async {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future onLoadStop(url) async {
|
Future onLoadStop(url) async {
|
||||||
|
@ -61,26 +60,30 @@ class InAppBrowserExampleScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> {
|
class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> {
|
||||||
late PullToRefreshController pullToRefreshController;
|
PullToRefreshController? pullToRefreshController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
pullToRefreshController = PullToRefreshController(
|
pullToRefreshController = kIsWeb ||
|
||||||
settings: PullToRefreshSettings(
|
![TargetPlatform.iOS, TargetPlatform.android]
|
||||||
color: Colors.black,
|
.contains(defaultTargetPlatform)
|
||||||
),
|
? null
|
||||||
onRefresh: () async {
|
: PullToRefreshController(
|
||||||
if (Platform.isAndroid) {
|
settings: PullToRefreshSettings(
|
||||||
widget.browser.webViewController.reload();
|
color: Colors.black,
|
||||||
} else if (Platform.isIOS) {
|
),
|
||||||
widget.browser.webViewController.loadUrl(
|
onRefresh: () async {
|
||||||
urlRequest: URLRequest(
|
if (Platform.isAndroid) {
|
||||||
url: await widget.browser.webViewController.getUrl()));
|
widget.browser.webViewController.reload();
|
||||||
}
|
} else if (Platform.isIOS) {
|
||||||
},
|
widget.browser.webViewController.loadUrl(
|
||||||
);
|
urlRequest: URLRequest(
|
||||||
|
url: await widget.browser.webViewController.getUrl()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
widget.browser.pullToRefreshController = pullToRefreshController;
|
widget.browser.pullToRefreshController = pullToRefreshController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +106,7 @@ class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> {
|
||||||
URLRequest(url: Uri.parse("https://flutter.dev")),
|
URLRequest(url: Uri.parse("https://flutter.dev")),
|
||||||
settings: InAppBrowserClassSettings(
|
settings: InAppBrowserClassSettings(
|
||||||
browserSettings: InAppBrowserSettings(
|
browserSettings: InAppBrowserSettings(
|
||||||
|
hidden: false,
|
||||||
toolbarTopBackgroundColor: Colors.blue,
|
toolbarTopBackgroundColor: Colors.blue,
|
||||||
presentationStyle: ModalPresentationStyle.POPOVER
|
presentationStyle: ModalPresentationStyle.POPOVER
|
||||||
),
|
),
|
||||||
|
|
|
@ -63,7 +63,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
|
||||||
contextMenuItemClicked.title);
|
contextMenuItemClicked.title);
|
||||||
});
|
});
|
||||||
|
|
||||||
pullToRefreshController = kIsWeb
|
pullToRefreshController = kIsWeb || ![TargetPlatform.iOS, TargetPlatform.android].contains(defaultTargetPlatform)
|
||||||
? null
|
? null
|
||||||
: PullToRefreshController(
|
: PullToRefreshController(
|
||||||
settings: PullToRefreshSettings(
|
settings: PullToRefreshSettings(
|
||||||
|
|
|
@ -33,26 +33,65 @@ Future main() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerInterceptor myDrawer({required BuildContext context}) {
|
PointerInterceptor myDrawer({required BuildContext context}) {
|
||||||
final children = [
|
var children = [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('InAppWebView'),
|
title: Text('InAppWebView'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pushReplacementNamed(context, '/');
|
Navigator.pushReplacementNamed(context, '/');
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('InAppBrowser'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pushReplacementNamed(context, '/InAppBrowser');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('ChromeSafariBrowser'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pushReplacementNamed(context, '/ChromeSafariBrowser');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('WebAuthenticationSession'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pushReplacementNamed(context, '/WebAuthenticationSession');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text('HeadlessInAppWebView'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView');
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
if (!kIsWeb) {
|
if (kIsWeb) {
|
||||||
children.addAll([
|
children = [
|
||||||
|
ListTile(
|
||||||
|
title: Text('InAppWebView'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pushReplacementNamed(context, '/');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
];
|
||||||
|
} else if (defaultTargetPlatform == TargetPlatform.macOS) {
|
||||||
|
children = [
|
||||||
|
// ListTile(
|
||||||
|
// title: Text('InAppWebView'),
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pushReplacementNamed(context, '/');
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ListTile(
|
||||||
|
// title: Text('InAppBrowser'),
|
||||||
|
// onTap: () {
|
||||||
|
// Navigator.pushReplacementNamed(context, '/InAppBrowser');
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('InAppBrowser'),
|
title: Text('InAppBrowser'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pushReplacementNamed(context, '/InAppBrowser');
|
Navigator.pushReplacementNamed(context, '/');
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text('ChromeSafariBrowser'),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.pushReplacementNamed(context, '/ChromeSafariBrowser');
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
@ -67,7 +106,7 @@ PointerInterceptor myDrawer({required BuildContext context}) {
|
||||||
Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView');
|
Navigator.pushReplacementNamed(context, '/HeadlessInAppWebView');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]);
|
];
|
||||||
}
|
}
|
||||||
return PointerInterceptor(
|
return PointerInterceptor(
|
||||||
child: Drawer(
|
child: Drawer(
|
||||||
|
@ -110,6 +149,15 @@ class _MyAppState extends State<MyApp> {
|
||||||
'/': (context) => InAppWebViewExampleScreen(),
|
'/': (context) => InAppWebViewExampleScreen(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.macOS) {
|
||||||
|
return MaterialApp(initialRoute: '/', routes: {
|
||||||
|
// '/': (context) => InAppWebViewExampleScreen(),
|
||||||
|
// '/InAppBrowser': (context) => InAppBrowserExampleScreen(),
|
||||||
|
'/': (context) => InAppBrowserExampleScreen(),
|
||||||
|
'/HeadlessInAppWebView': (context) => HeadlessInAppWebViewExampleScreen(),
|
||||||
|
'/WebAuthenticationSession': (context) => WebAuthenticationSessionExampleScreen(),
|
||||||
|
});
|
||||||
|
}
|
||||||
return MaterialApp(initialRoute: '/', routes: {
|
return MaterialApp(initialRoute: '/', routes: {
|
||||||
'/': (context) => InAppWebViewExampleScreen(),
|
'/': (context) => InAppWebViewExampleScreen(),
|
||||||
'/InAppBrowser': (context) => InAppBrowserExampleScreen(),
|
'/InAppBrowser': (context) => InAppBrowserExampleScreen(),
|
||||||
|
|
|
@ -48,7 +48,8 @@ class _WebAuthenticationSessionExampleScreenState
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (session == null &&
|
if (session == null &&
|
||||||
!kIsWeb &&
|
!kIsWeb &&
|
||||||
defaultTargetPlatform == TargetPlatform.iOS &&
|
[TargetPlatform.iOS, TargetPlatform.macOS]
|
||||||
|
.contains(defaultTargetPlatform) &&
|
||||||
await WebAuthenticationSession.isAvailable()) {
|
await WebAuthenticationSession.isAvailable()) {
|
||||||
session = await WebAuthenticationSession.create(
|
session = await WebAuthenticationSession.create(
|
||||||
url: Uri.parse(
|
url: Uri.parse(
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Flutter-related
|
||||||
|
**/Flutter/ephemeral/
|
||||||
|
**/Pods/
|
||||||
|
|
||||||
|
# Xcode-related
|
||||||
|
**/dgph
|
||||||
|
**/xcuserdata/
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import flutter_inappwebview
|
||||||
|
import path_provider_macos
|
||||||
|
import url_launcher_macos
|
||||||
|
|
||||||
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
platform :osx, '10.11'
|
||||||
|
|
||||||
|
# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_macos_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_macos_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,632 @@
|
||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 51;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXAggregateTarget section */
|
||||||
|
33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
|
||||||
|
isa = PBXAggregateTarget;
|
||||||
|
buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
|
||||||
|
buildPhases = (
|
||||||
|
33CC111E2044C6BF0003C045 /* ShellScript */,
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = "Flutter Assemble";
|
||||||
|
productName = FLX;
|
||||||
|
};
|
||||||
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
|
||||||
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
|
||||||
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||||
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||||
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||||
|
635B80423A5381B66E47F216 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3C8BEF4D338BF1EDD832305 /* Pods_Runner.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 33CC111A2044C6BA0003C045;
|
||||||
|
remoteInfo = FLX;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
33CC110E2044A8840003C045 /* Bundle Framework */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Bundle Framework";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||||
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||||
|
33CC10ED2044A3C60003C045 /* flutter_inappwebview_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flutter_inappwebview_example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||||
|
33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
|
||||||
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
|
||||||
|
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||||
|
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||||
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
7D4269DF6E938B573DA3AB1B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
A3C8BEF4D338BF1EDD832305 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
AC8274DF424C630859617712 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
B44881B5FC807BDF77BD81E9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
33CC10EA2044A3C60003C045 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
635B80423A5381B66E47F216 /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
33BA886A226E78AF003329D5 /* Configs */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33E5194F232828860026EE4D /* AppInfo.xcconfig */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Configs;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC10E42044A3C60003C045 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33FAB671232836740065AC1E /* Runner */,
|
||||||
|
33CEB47122A05771004F2AC0 /* Flutter */,
|
||||||
|
33CC10EE2044A3C60003C045 /* Products */,
|
||||||
|
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||||
|
DE8EF0F1212CA8BCD731BA0F /* Pods */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC10EE2044A3C60003C045 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10ED2044A3C60003C045 /* flutter_inappwebview_example.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CC11242044D66E0003C045 /* Resources */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F22044A3C60003C045 /* Assets.xcassets */,
|
||||||
|
33CC10F42044A3C60003C045 /* MainMenu.xib */,
|
||||||
|
33CC10F72044A3C60003C045 /* Info.plist */,
|
||||||
|
);
|
||||||
|
name = Resources;
|
||||||
|
path = ..;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33CEB47122A05771004F2AC0 /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
|
||||||
|
33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
|
||||||
|
33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
|
||||||
|
33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
33FAB671232836740065AC1E /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F02044A3C60003C045 /* AppDelegate.swift */,
|
||||||
|
33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
|
||||||
|
33E51913231747F40026EE4D /* DebugProfile.entitlements */,
|
||||||
|
33E51914231749380026EE4D /* Release.entitlements */,
|
||||||
|
33CC11242044D66E0003C045 /* Resources */,
|
||||||
|
33BA886A226E78AF003329D5 /* Configs */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A3C8BEF4D338BF1EDD832305 /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
DE8EF0F1212CA8BCD731BA0F /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
AC8274DF424C630859617712 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
7D4269DF6E938B573DA3AB1B /* Pods-Runner.release.xcconfig */,
|
||||||
|
B44881B5FC807BDF77BD81E9 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
33CC10EC2044A3C60003C045 /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
2233BC408EAD04A8B5721F92 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
33CC10E92044A3C60003C045 /* Sources */,
|
||||||
|
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||||
|
33CC10EB2044A3C60003C045 /* Resources */,
|
||||||
|
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||||
|
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||||
|
7E9999C83038C5D12ED26B20 /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
33CC11202044C79F0003C045 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 33CC10ED2044A3C60003C045 /* flutter_inappwebview_example.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
33CC10E52044A3C60003C045 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastSwiftUpdateCheck = 0920;
|
||||||
|
LastUpgradeCheck = 1300;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
33CC10EC2044A3C60003C045 = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
SystemCapabilities = {
|
||||||
|
com.apple.Sandbox = {
|
||||||
|
enabled = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
33CC111A2044C6BA0003C045 = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
ProvisioningStyle = Manual;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 33CC10E42044A3C60003C045;
|
||||||
|
productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
33CC10EC2044A3C60003C045 /* Runner */,
|
||||||
|
33CC111A2044C6BA0003C045 /* Flutter Assemble */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
33CC10EB2044A3C60003C045 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
|
||||||
|
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
2233BC408EAD04A8B5721F92 /* [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-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;
|
||||||
|
};
|
||||||
|
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
|
||||||
|
};
|
||||||
|
33CC111E2044C6BF0003C045 /* ShellScript */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
Flutter/ephemeral/FlutterInputs.xcfilelist,
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
Flutter/ephemeral/tripwire,
|
||||||
|
);
|
||||||
|
outputFileListPaths = (
|
||||||
|
Flutter/ephemeral/FlutterOutputs.xcfilelist,
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||||
|
};
|
||||||
|
7E9999C83038C5D12ED26B20 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
33CC10E92044A3C60003C045 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
|
||||||
|
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
|
||||||
|
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
|
||||||
|
targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
33CC10F52044A3C60003C045 /* Base */,
|
||||||
|
);
|
||||||
|
name = MainMenu.xib;
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
338D0CE9231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
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_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
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_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CEA231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
338D0CEB231458BD00FA5F75 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
33CC10F92044A3C60003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
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_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
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_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC10FA2044A3C60003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
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_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
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_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
33CC10FC2044A3C60003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC10FD2044A3C60003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
);
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
33CC111C2044C6BA0003C045 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Manual;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
33CC111D2044C6BA0003C045 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC10F92044A3C60003C045 /* Debug */,
|
||||||
|
33CC10FA2044A3C60003C045 /* Release */,
|
||||||
|
338D0CE9231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC10FC2044A3C60003C045 /* Debug */,
|
||||||
|
33CC10FD2044A3C60003C045 /* Release */,
|
||||||
|
338D0CEA231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
33CC111C2044C6BA0003C045 /* Debug */,
|
||||||
|
33CC111D2044C6BA0003C045 /* Release */,
|
||||||
|
338D0CEB231458BD00FA5F75 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 33CC10E52044A3C60003C045 /* Project object */;
|
||||||
|
}
|
|
@ -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>
|
|
@ -0,0 +1,87 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1300"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "flutter_inappwebview_example.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 = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "flutter_inappwebview_example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</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"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "flutter_inappwebview_example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
|
||||||
|
BuildableName = "flutter_inappwebview_example.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
@NSApplicationMain
|
||||||
|
class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "16x16",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_16.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "16x16",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_32.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "32x32",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_32.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "32x32",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_64.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "128x128",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_128.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "128x128",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_256.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "256x256",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_256.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "256x256",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_512.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "512x512",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_512.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "512x512",
|
||||||
|
"idiom" : "mac",
|
||||||
|
"filename" : "app_icon_1024.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 520 B |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,343 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
||||||
|
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||||
|
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||||
|
<items>
|
||||||
|
<menuItem title="APP_NAME" id="1Xt-HY-uBw">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
|
||||||
|
<items>
|
||||||
|
<menuItem title="About APP_NAME" id="5kV-Vb-QxS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||||
|
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||||
|
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||||
|
<menuItem title="Services" id="NMo-om-nkz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
|
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
|
||||||
|
<connections>
|
||||||
|
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||||
|
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
|
||||||
|
<connections>
|
||||||
|
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
|
||||||
|
<connections>
|
||||||
|
<action selector="undo:" target="-1" id="M6e-cu-g7V"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
|
||||||
|
<connections>
|
||||||
|
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
|
||||||
|
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
|
||||||
|
<connections>
|
||||||
|
<action selector="cut:" target="-1" id="YJe-68-I9s"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||||
|
<connections>
|
||||||
|
<action selector="copy:" target="-1" id="G1f-GL-Joy"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||||
|
<connections>
|
||||||
|
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Delete" id="pa3-QI-u2k">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
|
||||||
|
<connections>
|
||||||
|
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
|
||||||
|
<menuItem title="Find" id="4EN-yA-p0u">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Find" id="1b7-l0-nxx">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
|
||||||
|
<connections>
|
||||||
|
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
|
||||||
|
<connections>
|
||||||
|
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
|
||||||
|
<connections>
|
||||||
|
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
|
||||||
|
<connections>
|
||||||
|
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
|
||||||
|
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Substitutions" id="9ic-FL-obx">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
|
||||||
|
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Smart Links" id="cwL-P1-jid">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Data Detectors" id="tRr-pd-1PS">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Transformations" id="2oI-Rn-ZJC">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Speech" id="xrE-MZ-jX0">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="View" id="H8h-7b-M4v">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Window" id="aUF-d1-5bR">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||||
|
<connections>
|
||||||
|
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||||
|
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Help" id="EPT-qC-fAb">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
<point key="canvasLocation" x="142" y="-258"/>
|
||||||
|
</menu>
|
||||||
|
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
|
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||||
|
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</view>
|
||||||
|
</window>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Application-level settings for the Runner target.
|
||||||
|
//
|
||||||
|
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
|
||||||
|
// future. If not, the values below would default to using the project name when this becomes a
|
||||||
|
// 'flutter create' template.
|
||||||
|
|
||||||
|
// The application's name. By default this is also the title of the Flutter window.
|
||||||
|
PRODUCT_NAME = flutter_inappwebview_example
|
||||||
|
|
||||||
|
// The application's bundle identifier
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.pichillilorenzo.flutterInappwebviewExample
|
||||||
|
|
||||||
|
// The copyright displayed in application information
|
||||||
|
PRODUCT_COPYRIGHT = Copyright © 2022 com.pichillilorenzo. All rights reserved.
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include "../../Flutter/Flutter-Debug.xcconfig"
|
||||||
|
#include "Warnings.xcconfig"
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include "../../Flutter/Flutter-Release.xcconfig"
|
||||||
|
#include "Warnings.xcconfig"
|
|
@ -0,0 +1,13 @@
|
||||||
|
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES
|
||||||
|
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
|
||||||
|
CLANG_WARN_PRAGMA_PACK = YES
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES
|
||||||
|
CLANG_WARN_COMMA = YES
|
||||||
|
GCC_WARN_STRICT_SELECTOR_MATCH = YES
|
||||||
|
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
|
||||||
|
GCC_WARN_SHADOW = YES
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?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>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.print</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?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>CFBundleIconFile</key>
|
||||||
|
<string></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>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>$(PRODUCT_COPYRIGHT)</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>MainMenu</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
class MainFlutterWindow: NSWindow {
|
||||||
|
override func awakeFromNib() {
|
||||||
|
let flutterViewController = FlutterViewController.init()
|
||||||
|
let windowFrame = self.frame
|
||||||
|
self.contentViewController = flutterViewController
|
||||||
|
self.setFrame(windowFrame, display: true)
|
||||||
|
|
||||||
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
|
||||||
|
super.awakeFromNib()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?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>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.print</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -110,7 +110,7 @@ public class InAppBrowserManager: ChannelDelegate {
|
||||||
navController.tmpWindow = tmpWindow
|
navController.tmpWindow = tmpWindow
|
||||||
|
|
||||||
var animated = true
|
var animated = true
|
||||||
if let browserOptions = webViewController.browserSettings, browserOptions.hidden {
|
if let browserSettings = webViewController.browserSettings, browserSettings.hidden {
|
||||||
tmpWindow.isHidden = true
|
tmpWindow.isHidden = true
|
||||||
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
|
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
|
||||||
animated = false
|
animated = false
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class InAppBrowserSettings: ISettings<InAppBrowserWebViewController> {
|
||||||
var realOptions: [String: Any?] = toMap()
|
var realOptions: [String: Any?] = toMap()
|
||||||
if let inAppBrowserWebViewController = obj {
|
if let inAppBrowserWebViewController = obj {
|
||||||
realOptions["hideUrlBar"] = inAppBrowserWebViewController.searchBar.isHidden
|
realOptions["hideUrlBar"] = inAppBrowserWebViewController.searchBar.isHidden
|
||||||
realOptions["hideUrlBar"] = inAppBrowserWebViewController.progressBar.isHidden
|
realOptions["progressBar"] = inAppBrowserWebViewController.progressBar.isHidden
|
||||||
realOptions["closeButtonCaption"] = inAppBrowserWebViewController.closeButton.title
|
realOptions["closeButtonCaption"] = inAppBrowserWebViewController.closeButton.title
|
||||||
realOptions["closeButtonColor"] = inAppBrowserWebViewController.closeButton.tintColor?.hexString
|
realOptions["closeButtonColor"] = inAppBrowserWebViewController.closeButton.tintColor?.hexString
|
||||||
if let navController = inAppBrowserWebViewController.navigationController {
|
if let navController = inAppBrowserWebViewController.navigationController {
|
||||||
|
|
|
@ -254,7 +254,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
|
||||||
navigationController?.navigationBar.barTintColor = UIColor(hexString: barTintColor)
|
navigationController?.navigationBar.barTintColor = UIColor(hexString: barTintColor)
|
||||||
}
|
}
|
||||||
if let tintColor = browserOptions.toolbarTopTintColor, !tintColor.isEmpty {
|
if let tintColor = browserOptions.toolbarTopTintColor, !tintColor.isEmpty {
|
||||||
navigationController?.navigationBar.barTintColor = UIColor(hexString: tintColor)
|
navigationController?.navigationBar.tintColor = UIColor(hexString: tintColor)
|
||||||
}
|
}
|
||||||
navigationController?.navigationBar.isTranslucent = browserOptions.toolbarTopTranslucent
|
navigationController?.navigationBar.isTranslucent = browserOptions.toolbarTopTranslucent
|
||||||
}
|
}
|
||||||
|
|
|
@ -688,7 +688,7 @@ class _InAppWebViewState extends State<InAppWebView> {
|
||||||
creationParamsCodec: const StandardMessageCodec(),
|
creationParamsCodec: const StandardMessageCodec(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
|
} else if (defaultTargetPlatform == TargetPlatform.iOS/* || defaultTargetPlatform == TargetPlatform.macOS*/) {
|
||||||
return UiKitView(
|
return UiKitView(
|
||||||
viewType: 'com.pichillilorenzo/flutter_inappwebview',
|
viewType: 'com.pichillilorenzo/flutter_inappwebview',
|
||||||
onPlatformViewCreated: _onPlatformViewCreated,
|
onPlatformViewCreated: _onPlatformViewCreated,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
|
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
|
||||||
|
|
||||||
import '../print_job/main.dart';
|
import '../print_job/main.dart';
|
||||||
|
@ -9,11 +10,17 @@ part 'print_job_color_mode.g.dart';
|
||||||
class PrintJobColorMode_ {
|
class PrintJobColorMode_ {
|
||||||
// ignore: unused_field
|
// ignore: unused_field
|
||||||
final int _value;
|
final int _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final dynamic _nativeValue = null;
|
||||||
const PrintJobColorMode_._internal(this._value);
|
const PrintJobColorMode_._internal(this._value);
|
||||||
|
|
||||||
///Monochrome color scheme, for example one color is used.
|
///Monochrome color scheme, for example one color is used.
|
||||||
|
@EnumSupportedPlatforms(
|
||||||
|
platforms: [EnumAndroidPlatform(value: 1), EnumMacOSPlatform(value: "Gray")])
|
||||||
static const MONOCHROME = const PrintJobColorMode_._internal(1);
|
static const MONOCHROME = const PrintJobColorMode_._internal(1);
|
||||||
|
|
||||||
///Color color scheme, for example many colors are used.
|
///Color color scheme, for example many colors are used.
|
||||||
|
@EnumSupportedPlatforms(
|
||||||
|
platforms: [EnumAndroidPlatform(value: 1), EnumMacOSPlatform(value: "RGB")])
|
||||||
static const COLOR = const PrintJobColorMode_._internal(2);
|
static const COLOR = const PrintJobColorMode_._internal(2);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ part of 'print_job_color_mode.dart';
|
||||||
///Class representing how the printed content of a [PrintJobController] should be laid out.
|
///Class representing how the printed content of a [PrintJobController] should be laid out.
|
||||||
class PrintJobColorMode {
|
class PrintJobColorMode {
|
||||||
final int _value;
|
final int _value;
|
||||||
final int _nativeValue;
|
final dynamic _nativeValue;
|
||||||
const PrintJobColorMode._internal(this._value, this._nativeValue);
|
const PrintJobColorMode._internal(this._value, this._nativeValue);
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
factory PrintJobColorMode._internalMultiPlatform(
|
factory PrintJobColorMode._internalMultiPlatform(
|
||||||
|
@ -17,10 +17,38 @@ class PrintJobColorMode {
|
||||||
PrintJobColorMode._internal(value, nativeValue());
|
PrintJobColorMode._internal(value, nativeValue());
|
||||||
|
|
||||||
///Monochrome color scheme, for example one color is used.
|
///Monochrome color scheme, for example one color is used.
|
||||||
static const MONOCHROME = PrintJobColorMode._internal(1, 1);
|
///
|
||||||
|
///**Supported Platforms/Implementations**:
|
||||||
|
///- Android native WebView
|
||||||
|
///- MacOS
|
||||||
|
static final MONOCHROME = PrintJobColorMode._internalMultiPlatform(1, () {
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return 1;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 'Gray';
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
///Color color scheme, for example many colors are used.
|
///Color color scheme, for example many colors are used.
|
||||||
static const COLOR = PrintJobColorMode._internal(2, 2);
|
///
|
||||||
|
///**Supported Platforms/Implementations**:
|
||||||
|
///- Android native WebView
|
||||||
|
///- MacOS
|
||||||
|
static final COLOR = PrintJobColorMode._internalMultiPlatform(2, () {
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return 1;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 'RGB';
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
///Set of all values of [PrintJobColorMode].
|
///Set of all values of [PrintJobColorMode].
|
||||||
static final Set<PrintJobColorMode> values = [
|
static final Set<PrintJobColorMode> values = [
|
||||||
|
@ -42,7 +70,7 @@ class PrintJobColorMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
///Gets a possible [PrintJobColorMode] instance from a native value.
|
///Gets a possible [PrintJobColorMode] instance from a native value.
|
||||||
static PrintJobColorMode? fromNativeValue(int? value) {
|
static PrintJobColorMode? fromNativeValue(dynamic value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
try {
|
try {
|
||||||
return PrintJobColorMode.values
|
return PrintJobColorMode.values
|
||||||
|
@ -57,8 +85,8 @@ class PrintJobColorMode {
|
||||||
///Gets [int] value.
|
///Gets [int] value.
|
||||||
int toValue() => _value;
|
int toValue() => _value;
|
||||||
|
|
||||||
///Gets [int] native value.
|
///Gets [dynamic] native value.
|
||||||
int toNativeValue() => _nativeValue;
|
dynamic toNativeValue() => _nativeValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => _value.hashCode;
|
int get hashCode => _value.hashCode;
|
||||||
|
|
|
@ -17,18 +17,18 @@ class PrintJobDuplexMode_ {
|
||||||
|
|
||||||
///No double-sided (duplex) printing; single-sided printing only.
|
///No double-sided (duplex) printing; single-sided printing only.
|
||||||
@EnumSupportedPlatforms(
|
@EnumSupportedPlatforms(
|
||||||
platforms: [EnumAndroidPlatform(value: 1), EnumIOSPlatform(value: 0)])
|
platforms: [EnumAndroidPlatform(value: 1), EnumIOSPlatform(value: 0), EnumMacOSPlatform(value: 1)])
|
||||||
static const NONE = PrintJobDuplexMode_._internal('NONE');
|
static const NONE = PrintJobDuplexMode_._internal('NONE');
|
||||||
|
|
||||||
///Duplex printing that flips the back page along the long edge of the paper.
|
///Duplex printing that flips the back page along the long edge of the paper.
|
||||||
///Pages are turned sideways along the long edge - like a book.
|
///Pages are turned sideways along the long edge - like a book.
|
||||||
@EnumSupportedPlatforms(
|
@EnumSupportedPlatforms(
|
||||||
platforms: [EnumAndroidPlatform(value: 2), EnumIOSPlatform(value: 1)])
|
platforms: [EnumAndroidPlatform(value: 2), EnumIOSPlatform(value: 1), EnumMacOSPlatform(value: 2)])
|
||||||
static const LONG_EDGE = PrintJobDuplexMode_._internal('LONG_EDGE');
|
static const LONG_EDGE = PrintJobDuplexMode_._internal('LONG_EDGE');
|
||||||
|
|
||||||
///Duplex print that flips the back page along the short edge of the paper.
|
///Duplex print that flips the back page along the short edge of the paper.
|
||||||
///Pages are turned upwards along the short edge - like a notepad.
|
///Pages are turned upwards along the short edge - like a notepad.
|
||||||
@EnumSupportedPlatforms(
|
@EnumSupportedPlatforms(
|
||||||
platforms: [EnumAndroidPlatform(value: 4), EnumIOSPlatform(value: 2)])
|
platforms: [EnumAndroidPlatform(value: 4), EnumIOSPlatform(value: 2), EnumMacOSPlatform(value: 3)])
|
||||||
static const SHORT_EDGE = PrintJobDuplexMode_._internal('SHORT_EDGE');
|
static const SHORT_EDGE = PrintJobDuplexMode_._internal('SHORT_EDGE');
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,15 @@ class PrintJobDuplexMode {
|
||||||
///**Supported Platforms/Implementations**:
|
///**Supported Platforms/Implementations**:
|
||||||
///- Android native WebView
|
///- Android native WebView
|
||||||
///- iOS
|
///- iOS
|
||||||
|
///- MacOS
|
||||||
static final NONE = PrintJobDuplexMode._internalMultiPlatform('NONE', () {
|
static final NONE = PrintJobDuplexMode._internalMultiPlatform('NONE', () {
|
||||||
switch (defaultTargetPlatform) {
|
switch (defaultTargetPlatform) {
|
||||||
case TargetPlatform.android:
|
case TargetPlatform.android:
|
||||||
return 1;
|
return 1;
|
||||||
case TargetPlatform.iOS:
|
case TargetPlatform.iOS:
|
||||||
return 0;
|
return 0;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 1;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +42,7 @@ class PrintJobDuplexMode {
|
||||||
///**Supported Platforms/Implementations**:
|
///**Supported Platforms/Implementations**:
|
||||||
///- Android native WebView
|
///- Android native WebView
|
||||||
///- iOS
|
///- iOS
|
||||||
|
///- MacOS
|
||||||
static final LONG_EDGE =
|
static final LONG_EDGE =
|
||||||
PrintJobDuplexMode._internalMultiPlatform('LONG_EDGE', () {
|
PrintJobDuplexMode._internalMultiPlatform('LONG_EDGE', () {
|
||||||
switch (defaultTargetPlatform) {
|
switch (defaultTargetPlatform) {
|
||||||
|
@ -46,6 +50,8 @@ class PrintJobDuplexMode {
|
||||||
return 2;
|
return 2;
|
||||||
case TargetPlatform.iOS:
|
case TargetPlatform.iOS:
|
||||||
return 1;
|
return 1;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 2;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -58,6 +64,7 @@ class PrintJobDuplexMode {
|
||||||
///**Supported Platforms/Implementations**:
|
///**Supported Platforms/Implementations**:
|
||||||
///- Android native WebView
|
///- Android native WebView
|
||||||
///- iOS
|
///- iOS
|
||||||
|
///- MacOS
|
||||||
static final SHORT_EDGE =
|
static final SHORT_EDGE =
|
||||||
PrintJobDuplexMode._internalMultiPlatform('SHORT_EDGE', () {
|
PrintJobDuplexMode._internalMultiPlatform('SHORT_EDGE', () {
|
||||||
switch (defaultTargetPlatform) {
|
switch (defaultTargetPlatform) {
|
||||||
|
@ -65,6 +72,8 @@ class PrintJobDuplexMode {
|
||||||
return 4;
|
return 4;
|
||||||
case TargetPlatform.iOS:
|
case TargetPlatform.iOS:
|
||||||
return 2;
|
return 2;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 3;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
|
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
|
||||||
|
|
||||||
import '../print_job/main.dart';
|
import '../print_job/main.dart';
|
||||||
|
@ -12,8 +13,24 @@ class PrintJobOrientation_ {
|
||||||
const PrintJobOrientation_._internal(this._value);
|
const PrintJobOrientation_._internal(this._value);
|
||||||
|
|
||||||
///Pages are printed in portrait orientation.
|
///Pages are printed in portrait orientation.
|
||||||
|
@EnumSupportedPlatforms(platforms: [
|
||||||
|
EnumIOSPlatform(
|
||||||
|
value: 0
|
||||||
|
),
|
||||||
|
EnumMacOSPlatform(
|
||||||
|
value: 0
|
||||||
|
)
|
||||||
|
])
|
||||||
static const PORTRAIT = const PrintJobOrientation_._internal(0);
|
static const PORTRAIT = const PrintJobOrientation_._internal(0);
|
||||||
|
|
||||||
///Pages are printed in landscape orientation.
|
///Pages are printed in landscape orientation.
|
||||||
|
@EnumSupportedPlatforms(platforms: [
|
||||||
|
EnumIOSPlatform(
|
||||||
|
value: 1
|
||||||
|
),
|
||||||
|
EnumMacOSPlatform(
|
||||||
|
value: 1
|
||||||
|
)
|
||||||
|
])
|
||||||
static const LANDSCAPE = const PrintJobOrientation_._internal(1);
|
static const LANDSCAPE = const PrintJobOrientation_._internal(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,38 @@ class PrintJobOrientation {
|
||||||
PrintJobOrientation._internal(value, nativeValue());
|
PrintJobOrientation._internal(value, nativeValue());
|
||||||
|
|
||||||
///Pages are printed in portrait orientation.
|
///Pages are printed in portrait orientation.
|
||||||
static const PORTRAIT = PrintJobOrientation._internal(0, 0);
|
///
|
||||||
|
///**Supported Platforms/Implementations**:
|
||||||
|
///- iOS
|
||||||
|
///- MacOS
|
||||||
|
static final PORTRAIT = PrintJobOrientation._internalMultiPlatform(0, () {
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return 0;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
///Pages are printed in landscape orientation.
|
///Pages are printed in landscape orientation.
|
||||||
static const LANDSCAPE = PrintJobOrientation._internal(1, 1);
|
///
|
||||||
|
///**Supported Platforms/Implementations**:
|
||||||
|
///- iOS
|
||||||
|
///- MacOS
|
||||||
|
static final LANDSCAPE = PrintJobOrientation._internalMultiPlatform(1, () {
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return 1;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
///Set of all values of [PrintJobOrientation].
|
///Set of all values of [PrintJobOrientation].
|
||||||
static final Set<PrintJobOrientation> values = [
|
static final Set<PrintJobOrientation> values = [
|
||||||
|
|
|
@ -85,10 +85,12 @@ class SslCertificate_ {
|
||||||
SslCertificateDName.fromMap(map["issuedBy"]?.cast<String, dynamic>()),
|
SslCertificateDName.fromMap(map["issuedBy"]?.cast<String, dynamic>()),
|
||||||
issuedTo:
|
issuedTo:
|
||||||
SslCertificateDName.fromMap(map["issuedTo"]?.cast<String, dynamic>()),
|
SslCertificateDName.fromMap(map["issuedTo"]?.cast<String, dynamic>()),
|
||||||
validNotAfterDate:
|
validNotAfterDate: map["validNotAfterDate"] != null
|
||||||
DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]),
|
? DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"])
|
||||||
validNotBeforeDate:
|
: null,
|
||||||
DateTime.fromMillisecondsSinceEpoch(map["validNotBeforeDate"]),
|
validNotBeforeDate: map["validNotBeforeDate"] != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(map["validNotBeforeDate"])
|
||||||
|
: null,
|
||||||
x509Certificate: x509Certificate,
|
x509Certificate: x509Certificate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,10 +75,12 @@ class SslCertificate {
|
||||||
map["issuedBy"]?.cast<String, dynamic>()),
|
map["issuedBy"]?.cast<String, dynamic>()),
|
||||||
issuedTo: SslCertificateDName.fromMap(
|
issuedTo: SslCertificateDName.fromMap(
|
||||||
map["issuedTo"]?.cast<String, dynamic>()),
|
map["issuedTo"]?.cast<String, dynamic>()),
|
||||||
validNotAfterDate:
|
validNotAfterDate: map["validNotAfterDate"] != null
|
||||||
DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"]),
|
? DateTime.fromMillisecondsSinceEpoch(map["validNotAfterDate"])
|
||||||
validNotBeforeDate:
|
: null,
|
||||||
DateTime.fromMillisecondsSinceEpoch(map["validNotBeforeDate"]),
|
validNotBeforeDate: map["validNotBeforeDate"] != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(map["validNotBeforeDate"])
|
||||||
|
: null,
|
||||||
x509Certificate: x509Certificate);
|
x509Certificate: x509Certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
//
|
||||||
|
// CredentialDatabase.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 29/10/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class CredentialDatabase: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_credential_database"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
static var credentialStore: URLCredentialStorage?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: CredentialDatabase.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
CredentialDatabase.registrar = registrar
|
||||||
|
CredentialDatabase.credentialStore = URLCredentialStorage.shared
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
switch call.method {
|
||||||
|
case "getAllAuthCredentials":
|
||||||
|
var allCredentials: [[String: Any?]] = []
|
||||||
|
guard let credentialStore = CredentialDatabase.credentialStore else {
|
||||||
|
result(allCredentials)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (protectionSpace, credentials) in credentialStore.allCredentials {
|
||||||
|
var crendentials: [[String: Any?]] = []
|
||||||
|
for c in credentials {
|
||||||
|
let credential: [String: Any?] = c.value.toMap()
|
||||||
|
crendentials.append(credential)
|
||||||
|
}
|
||||||
|
if crendentials.count > 0 {
|
||||||
|
let dict: [String : Any] = [
|
||||||
|
"protectionSpace": protectionSpace.toMap(),
|
||||||
|
"credentials": crendentials
|
||||||
|
]
|
||||||
|
allCredentials.append(dict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result(allCredentials)
|
||||||
|
break
|
||||||
|
case "getHttpAuthCredentials":
|
||||||
|
var crendentials: [[String: Any?]] = []
|
||||||
|
guard let credentialStore = CredentialDatabase.credentialStore else {
|
||||||
|
result(crendentials)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = arguments!["host"] as! String
|
||||||
|
let urlProtocol = arguments!["protocol"] as? String
|
||||||
|
let urlPort = arguments!["port"] as? Int ?? 0
|
||||||
|
var realm = arguments!["realm"] as? String;
|
||||||
|
if let r = realm, r.isEmpty {
|
||||||
|
realm = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for (protectionSpace, credentials) in credentialStore.allCredentials {
|
||||||
|
if protectionSpace.host == host && protectionSpace.realm == realm &&
|
||||||
|
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
|
||||||
|
for c in credentials {
|
||||||
|
crendentials.append(c.value.toMap())
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result(crendentials)
|
||||||
|
break
|
||||||
|
case "setHttpAuthCredential":
|
||||||
|
guard let credentialStore = CredentialDatabase.credentialStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = arguments!["host"] as! String
|
||||||
|
let urlProtocol = arguments!["protocol"] as? String
|
||||||
|
let urlPort = arguments!["port"] as? Int ?? 0
|
||||||
|
var realm = arguments!["realm"] as? String;
|
||||||
|
if let r = realm, r.isEmpty {
|
||||||
|
realm = nil
|
||||||
|
}
|
||||||
|
let username = arguments!["username"] as! String
|
||||||
|
let password = arguments!["password"] as! String
|
||||||
|
let credential = URLCredential(user: username, password: password, persistence: .permanent)
|
||||||
|
credentialStore.set(credential,
|
||||||
|
for: URLProtectionSpace(host: host, port: urlPort, protocol: urlProtocol,
|
||||||
|
realm: realm, authenticationMethod: NSURLAuthenticationMethodHTTPBasic))
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
case "removeHttpAuthCredential":
|
||||||
|
guard let credentialStore = CredentialDatabase.credentialStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = arguments!["host"] as! String
|
||||||
|
let urlProtocol = arguments!["protocol"] as? String
|
||||||
|
let urlPort = arguments!["port"] as? Int ?? 0
|
||||||
|
var realm = arguments!["realm"] as? String;
|
||||||
|
if let r = realm, r.isEmpty {
|
||||||
|
realm = nil
|
||||||
|
}
|
||||||
|
let username = arguments!["username"] as! String
|
||||||
|
let password = arguments!["password"] as! String
|
||||||
|
|
||||||
|
var credential: URLCredential? = nil;
|
||||||
|
var protectionSpaceCredential: URLProtectionSpace? = nil
|
||||||
|
|
||||||
|
for (protectionSpace, credentials) in credentialStore.allCredentials {
|
||||||
|
if protectionSpace.host == host && protectionSpace.realm == realm &&
|
||||||
|
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
|
||||||
|
for c in credentials {
|
||||||
|
if c.value.user == username, c.value.password == password {
|
||||||
|
credential = c.value
|
||||||
|
protectionSpaceCredential = protectionSpace
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if credential != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let c = credential, let protectionSpace = protectionSpaceCredential {
|
||||||
|
credentialStore.remove(c, for: protectionSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
case "removeHttpAuthCredentials":
|
||||||
|
guard let credentialStore = CredentialDatabase.credentialStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = arguments!["host"] as! String
|
||||||
|
let urlProtocol = arguments!["protocol"] as? String
|
||||||
|
let urlPort = arguments!["port"] as? Int ?? 0
|
||||||
|
var realm = arguments!["realm"] as? String;
|
||||||
|
if let r = realm, r.isEmpty {
|
||||||
|
realm = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var credentialsToRemove: [URLCredential] = [];
|
||||||
|
var protectionSpaceCredential: URLProtectionSpace? = nil
|
||||||
|
|
||||||
|
for (protectionSpace, credentials) in credentialStore.allCredentials {
|
||||||
|
if protectionSpace.host == host && protectionSpace.realm == realm &&
|
||||||
|
protectionSpace.protocol == urlProtocol && protectionSpace.port == urlPort {
|
||||||
|
protectionSpaceCredential = protectionSpace
|
||||||
|
for c in credentials {
|
||||||
|
if let _ = c.value.user, let _ = c.value.password {
|
||||||
|
credentialsToRemove.append(c.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let protectionSpace = protectionSpaceCredential {
|
||||||
|
for credential in credentialsToRemove {
|
||||||
|
credentialStore.remove(credential, for: protectionSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
case "clearAllAuthCredentials":
|
||||||
|
guard let credentialStore = CredentialDatabase.credentialStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (protectionSpace, credentials) in credentialStore.allCredentials {
|
||||||
|
for credential in credentials {
|
||||||
|
credentialStore.remove(credential.value, for: protectionSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
CredentialDatabase.registrar = nil
|
||||||
|
CredentialDatabase.credentialStore = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// HeadlessInAppWebView.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 26/03/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class HeadlessInAppWebView : Disposable {
|
||||||
|
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_headless_inappwebview_"
|
||||||
|
var id: String
|
||||||
|
var channelDelegate: HeadlessWebViewChannelDelegate?
|
||||||
|
var flutterWebView: FlutterWebViewController?
|
||||||
|
|
||||||
|
public init(id: String, flutterWebView: FlutterWebViewController) {
|
||||||
|
self.id = id
|
||||||
|
self.flutterWebView = flutterWebView
|
||||||
|
let channel = FlutterMethodChannel(name: HeadlessInAppWebView.METHOD_CHANNEL_NAME_PREFIX + id,
|
||||||
|
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger)
|
||||||
|
self.channelDelegate = HeadlessWebViewChannelDelegate(headlessWebView: self, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onWebViewCreated() {
|
||||||
|
channelDelegate?.onWebViewCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public func prepare(params: NSDictionary) {
|
||||||
|
if let view = flutterWebView?.view() {
|
||||||
|
view.alphaValue = 0.01
|
||||||
|
let initialSize = params["initialSize"] as? [String: Any?]
|
||||||
|
if let size = Size2D.fromMap(map: initialSize) {
|
||||||
|
setSize(size: size)
|
||||||
|
} else {
|
||||||
|
view.frame = CGRect(x: 0.0, y: 0.0, width: NSApplication.shared.mainWindow?.contentView?.bounds.width ?? 0.0,
|
||||||
|
height: NSApplication.shared.mainWindow?.contentView?.bounds.height ?? 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setSize(size: Size2D) {
|
||||||
|
if let view = flutterWebView?.view() {
|
||||||
|
let width = size.width == -1.0 ? NSApplication.shared.mainWindow?.contentView?.bounds.width ?? 0.0 : CGFloat(size.width)
|
||||||
|
let height = size.height == -1.0 ? NSApplication.shared.mainWindow?.contentView?.bounds.height ?? 0.0 : CGFloat(size.height)
|
||||||
|
view.frame = CGRect(x: 0.0, y: 0.0, width: width, height: height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getSize() -> Size2D? {
|
||||||
|
if let view = flutterWebView?.view() {
|
||||||
|
return Size2D(width: Double(view.frame.width), height: Double(view.frame.height))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
channelDelegate?.dispose()
|
||||||
|
channelDelegate = nil
|
||||||
|
HeadlessInAppWebViewManager.webViews[id] = nil
|
||||||
|
flutterWebView = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("HeadlessInAppWebView - dealloc")
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// HeadlessInAppWebViewManager.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/05/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import AppKit
|
||||||
|
import WebKit
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
public class HeadlessInAppWebViewManager: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_headless_inappwebview"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
static var webViews: [String: HeadlessInAppWebView?] = [:]
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: HeadlessInAppWebViewManager.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
HeadlessInAppWebViewManager.registrar = registrar
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
let id: String = arguments!["id"] as! String
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "run":
|
||||||
|
let params = arguments!["params"] as! [String: Any?]
|
||||||
|
HeadlessInAppWebViewManager.run(id: id, params: params)
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func run(id: String, params: [String: Any?]) {
|
||||||
|
let flutterWebView = FlutterWebViewController(registrar: HeadlessInAppWebViewManager.registrar!,
|
||||||
|
withFrame: CGRect.zero,
|
||||||
|
viewIdentifier: id,
|
||||||
|
params: params as NSDictionary)
|
||||||
|
let headlessInAppWebView = HeadlessInAppWebView(id: id, flutterWebView: flutterWebView)
|
||||||
|
HeadlessInAppWebViewManager.webViews[id] = headlessInAppWebView
|
||||||
|
|
||||||
|
headlessInAppWebView.prepare(params: params as NSDictionary)
|
||||||
|
headlessInAppWebView.onWebViewCreated()
|
||||||
|
flutterWebView.makeInitialLoad(params: params as NSDictionary)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
HeadlessInAppWebViewManager.registrar = nil
|
||||||
|
let headlessWebViews = HeadlessInAppWebViewManager.webViews.values
|
||||||
|
headlessWebViews.forEach { (headlessWebView: HeadlessInAppWebView?) in
|
||||||
|
headlessWebView?.dispose()
|
||||||
|
}
|
||||||
|
HeadlessInAppWebViewManager.webViews.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// HeadlessWebViewChannelDelegate.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 05/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class HeadlessWebViewChannelDelegate : ChannelDelegate {
|
||||||
|
private weak var headlessWebView: HeadlessInAppWebView?
|
||||||
|
|
||||||
|
public init(headlessWebView: HeadlessInAppWebView, channel: FlutterMethodChannel) {
|
||||||
|
super.init(channel: channel)
|
||||||
|
self.headlessWebView = headlessWebView
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "dispose":
|
||||||
|
if let headlessWebView = headlessWebView {
|
||||||
|
headlessWebView.dispose()
|
||||||
|
result(true)
|
||||||
|
} else {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "setSize":
|
||||||
|
if let headlessWebView = headlessWebView {
|
||||||
|
let sizeMap = arguments!["size"] as? [String: Any?]
|
||||||
|
if let size = Size2D.fromMap(map: sizeMap) {
|
||||||
|
headlessWebView.setSize(size: size)
|
||||||
|
}
|
||||||
|
result(true)
|
||||||
|
} else {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "getSize":
|
||||||
|
result(headlessWebView?.getSize()?.toMap())
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onWebViewCreated() {
|
||||||
|
let arguments: [String: Any?] = [:]
|
||||||
|
channel?.invokeMethod("onWebViewCreated", arguments: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
headlessWebView = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// Options.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 26/09/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objcMembers
|
||||||
|
public class ISettings<T>: NSObject {
|
||||||
|
|
||||||
|
override init(){
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(settings: [String: Any?]) -> ISettings<T> {
|
||||||
|
for (key, value) in settings {
|
||||||
|
if !(value is NSNull), value != nil {
|
||||||
|
if self.responds(to: Selector(key)) {
|
||||||
|
self.setValue(value, forKey: key)
|
||||||
|
} else if self.responds(to: Selector("_" + key)) {
|
||||||
|
self.setValue(value, forKey: "_" + key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMap() -> [String: Any?] {
|
||||||
|
var settings: [String: Any?] = [:]
|
||||||
|
var counts = UInt32()
|
||||||
|
let properties = class_copyPropertyList(object_getClass(self), &counts)
|
||||||
|
for i in 0..<counts {
|
||||||
|
if let property = properties?.advanced(by: Int(i)).pointee {
|
||||||
|
let cName = property_getName(property)
|
||||||
|
let name = String(cString: cName)
|
||||||
|
let key = !name.hasPrefix("_") ? name : String(name.suffix(from: name.index(name.startIndex, offsetBy: 1)))
|
||||||
|
settings[key] = self.value(forKey: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(properties)
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRealSettings(obj: T?) -> [String: Any?] {
|
||||||
|
let realSettings: [String: Any?] = toMap()
|
||||||
|
return realSettings
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// InAppBrowserChannelDelegate.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 05/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class InAppBrowserChannelDelegate : ChannelDelegate {
|
||||||
|
public override init(channel: FlutterMethodChannel) {
|
||||||
|
super.init(channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onBrowserCreated() {
|
||||||
|
let arguments: [String: Any?] = [:]
|
||||||
|
channel?.invokeMethod("onBrowserCreated", arguments: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onExit() {
|
||||||
|
let arguments: [String: Any?] = [:]
|
||||||
|
channel?.invokeMethod("onExit", arguments: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// InAppBrowserDelegate.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 14/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public protocol InAppBrowserDelegate {
|
||||||
|
func didChangeTitle(title: String?)
|
||||||
|
func didStartNavigation(url: URL?)
|
||||||
|
func didUpdateVisitedHistory(url: URL?)
|
||||||
|
func didFinishNavigation(url: URL?)
|
||||||
|
func didFailNavigation(url: URL?, error: Error)
|
||||||
|
func didChangeProgress(progress: Double)
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
//
|
||||||
|
// InAppBrowserManager.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 18/12/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import AppKit
|
||||||
|
import WebKit
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
public class InAppBrowserManager: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappbrowser"
|
||||||
|
static let WEBVIEW_STORYBOARD = "WebView"
|
||||||
|
static let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController"
|
||||||
|
static let NAV_STORYBOARD_CONTROLLER_ID = "navController"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: InAppBrowserManager.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
InAppBrowserManager.registrar = registrar
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "open":
|
||||||
|
open(arguments: arguments!)
|
||||||
|
result(true)
|
||||||
|
break
|
||||||
|
case "openWithSystemBrowser":
|
||||||
|
let url = arguments!["url"] as! String
|
||||||
|
openWithSystemBrowser(url: url, result: result)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func open(arguments: NSDictionary) {
|
||||||
|
let id = arguments["id"] as! String
|
||||||
|
let urlRequest = arguments["urlRequest"] as? [String:Any?]
|
||||||
|
let assetFilePath = arguments["assetFilePath"] as? String
|
||||||
|
let data = arguments["data"] as? String
|
||||||
|
let mimeType = arguments["mimeType"] as? String
|
||||||
|
let encoding = arguments["encoding"] as? String
|
||||||
|
let baseUrl = arguments["baseUrl"] as? String
|
||||||
|
let settings = arguments["settings"] as! [String: Any?]
|
||||||
|
let contextMenu = arguments["contextMenu"] as! [String: Any]
|
||||||
|
let windowId = arguments["windowId"] as? Int64
|
||||||
|
let initialUserScripts = arguments["initialUserScripts"] as? [[String: Any]]
|
||||||
|
|
||||||
|
let browserSettings = InAppBrowserSettings()
|
||||||
|
let _ = browserSettings.parse(settings: settings)
|
||||||
|
|
||||||
|
let webViewSettings = InAppWebViewSettings()
|
||||||
|
let _ = webViewSettings.parse(settings: settings)
|
||||||
|
|
||||||
|
let webViewController = InAppBrowserWebViewController()
|
||||||
|
webViewController.browserSettings = browserSettings
|
||||||
|
webViewController.webViewSettings = webViewSettings
|
||||||
|
|
||||||
|
webViewController.id = id
|
||||||
|
webViewController.initialUrlRequest = urlRequest != nil ? URLRequest.init(fromPluginMap: urlRequest!) : nil
|
||||||
|
webViewController.initialFile = assetFilePath
|
||||||
|
webViewController.initialData = data
|
||||||
|
webViewController.initialMimeType = mimeType
|
||||||
|
webViewController.initialEncoding = encoding
|
||||||
|
webViewController.initialBaseUrl = baseUrl
|
||||||
|
webViewController.contextMenu = contextMenu
|
||||||
|
webViewController.windowId = windowId
|
||||||
|
webViewController.initialUserScripts = initialUserScripts ?? []
|
||||||
|
|
||||||
|
let window = InAppBrowserWindow(contentViewController: webViewController)
|
||||||
|
window.browserSettings = browserSettings
|
||||||
|
window.contentViewController = webViewController
|
||||||
|
window.prepare()
|
||||||
|
|
||||||
|
NSApplication.shared.mainWindow?.addChildWindow(window, ordered: .above)
|
||||||
|
|
||||||
|
if browserSettings.hidden {
|
||||||
|
window.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func openWithSystemBrowser(url: String, result: @escaping FlutterResult) {
|
||||||
|
let absoluteUrl = URL(string: url)!.absoluteURL
|
||||||
|
if !NSWorkspace.shared.open(absoluteUrl) {
|
||||||
|
result(FlutterError(code: "InAppBrowserManager", message: url + " cannot be opened!", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
InAppBrowserManager.registrar = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// InAppBrowserOptions.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 17/09/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objcMembers
|
||||||
|
public class InAppBrowserSettings: ISettings<InAppBrowserWebViewController> {
|
||||||
|
|
||||||
|
var hidden = false
|
||||||
|
var hideToolbarTop = true
|
||||||
|
var toolbarTopBackgroundColor: String?
|
||||||
|
var hideUrlBar = false
|
||||||
|
var hideProgressBar = false
|
||||||
|
|
||||||
|
var toolbarTopTranslucent = true
|
||||||
|
var toolbarTopBarTintColor: String?
|
||||||
|
var toolbarTopTintColor: String?
|
||||||
|
var hideToolbarBottom = true
|
||||||
|
var toolbarBottomBackgroundColor: String?
|
||||||
|
var toolbarBottomTintColor: String?
|
||||||
|
var toolbarBottomTranslucent = true
|
||||||
|
var closeButtonCaption: String?
|
||||||
|
var closeButtonColor: String?
|
||||||
|
var presentationStyle = 0 //fullscreen
|
||||||
|
var transitionStyle = 0 //crossDissolve
|
||||||
|
|
||||||
|
override init(){
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func getRealSettings(obj: InAppBrowserWebViewController?) -> [String: Any?] {
|
||||||
|
var realOptions: [String: Any?] = toMap()
|
||||||
|
if let inAppBrowserWebViewController = obj {
|
||||||
|
realOptions["hideUrlBar"] = inAppBrowserWebViewController.window?.searchBar?.isHidden
|
||||||
|
realOptions["progressBar"] = inAppBrowserWebViewController.progressBar.isHidden
|
||||||
|
realOptions["hideToolbarTop"] = !(inAppBrowserWebViewController.window?.toolbar?.isVisible ?? true)
|
||||||
|
realOptions["toolbarTopBackgroundColor"] = inAppBrowserWebViewController.window?.backgroundColor
|
||||||
|
}
|
||||||
|
return realOptions
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
//
|
||||||
|
// InAppBrowserWebViewController.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 17/09/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import AppKit
|
||||||
|
import WebKit
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelegate, Disposable {
|
||||||
|
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_";
|
||||||
|
|
||||||
|
var progressBar: NSProgressIndicator!
|
||||||
|
|
||||||
|
var window: InAppBrowserWindow?
|
||||||
|
var id: String = ""
|
||||||
|
var windowId: Int64?
|
||||||
|
var webView: InAppWebView?
|
||||||
|
var channelDelegate: InAppBrowserChannelDelegate?
|
||||||
|
var initialUrlRequest: URLRequest?
|
||||||
|
var initialFile: String?
|
||||||
|
var contextMenu: [String: Any]?
|
||||||
|
var browserSettings: InAppBrowserSettings?
|
||||||
|
var webViewSettings: InAppWebViewSettings?
|
||||||
|
var initialData: String?
|
||||||
|
var initialMimeType: String?
|
||||||
|
var initialEncoding: String?
|
||||||
|
var initialBaseUrl: String?
|
||||||
|
var initialUserScripts: [[String: Any]] = []
|
||||||
|
|
||||||
|
public override func loadView() {
|
||||||
|
let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger)
|
||||||
|
channelDelegate = InAppBrowserChannelDelegate(channel: channel)
|
||||||
|
|
||||||
|
var userScripts: [UserScript] = []
|
||||||
|
for intialUserScript in initialUserScripts {
|
||||||
|
userScripts.append(UserScript.fromMap(map: intialUserScript, windowId: windowId)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings)
|
||||||
|
if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] {
|
||||||
|
webView = webViewTransport.webView
|
||||||
|
webView!.contextMenu = contextMenu
|
||||||
|
webView!.initialUserScripts = userScripts
|
||||||
|
} else {
|
||||||
|
webView = InAppWebView(id: nil,
|
||||||
|
registrar: nil,
|
||||||
|
frame: .zero,
|
||||||
|
configuration: preWebviewConfiguration,
|
||||||
|
contextMenu: contextMenu,
|
||||||
|
userScripts: userScripts)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let webView = webView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
webView.inAppBrowserDelegate = self
|
||||||
|
webView.id = id
|
||||||
|
webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel)
|
||||||
|
|
||||||
|
prepareWebView()
|
||||||
|
webView.windowCreated = true
|
||||||
|
|
||||||
|
progressBar = NSProgressIndicator()
|
||||||
|
progressBar.style = .bar
|
||||||
|
progressBar.isIndeterminate = false
|
||||||
|
progressBar.startAnimation(self)
|
||||||
|
|
||||||
|
view = NSView(frame: NSApplication.shared.mainWindow?.frame ?? .zero)
|
||||||
|
view.addSubview(webView)
|
||||||
|
view.addSubview(progressBar, positioned: .above, relativeTo: webView)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
webView?.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
progressBar.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
webView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true
|
||||||
|
webView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
|
||||||
|
webView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
|
||||||
|
webView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
|
||||||
|
|
||||||
|
progressBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: -6.0).isActive = true
|
||||||
|
progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
|
||||||
|
progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
|
||||||
|
|
||||||
|
if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] {
|
||||||
|
webView?.load(webViewTransport.request)
|
||||||
|
channelDelegate?.onBrowserCreated()
|
||||||
|
} else {
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
if let contentBlockers = webView?.settings?.contentBlockers, contentBlockers.count > 0 {
|
||||||
|
do {
|
||||||
|
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
|
||||||
|
let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
|
||||||
|
WKContentRuleListStore.default().compileContentRuleList(
|
||||||
|
forIdentifier: "ContentBlockingRules",
|
||||||
|
encodedContentRuleList: blockRules) { (contentRuleList, error) in
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let configuration = self.webView!.configuration
|
||||||
|
configuration.userContentController.add(contentRuleList!)
|
||||||
|
|
||||||
|
self.initLoad()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initLoad()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func viewDidAppear() {
|
||||||
|
super.viewDidAppear()
|
||||||
|
window = view.window as? InAppBrowserWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
public func initLoad() {
|
||||||
|
if let initialFile = initialFile {
|
||||||
|
do {
|
||||||
|
try webView?.loadFile(assetFilePath: initialFile)
|
||||||
|
}
|
||||||
|
catch let error as NSError {
|
||||||
|
dump(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if let initialData = initialData {
|
||||||
|
let baseUrl = URL(string: initialBaseUrl ?? "about:blank")!
|
||||||
|
var allowingReadAccessToURL: URL? = nil
|
||||||
|
if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
|
||||||
|
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
|
||||||
|
if allowingReadAccessToURL?.scheme != "file" {
|
||||||
|
allowingReadAccessToURL = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView?.loadData(data: initialData, mimeType: initialMimeType!, encoding: initialEncoding!, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL)
|
||||||
|
}
|
||||||
|
else if let initialUrlRequest = initialUrlRequest {
|
||||||
|
var allowingReadAccessToURL: URL? = nil
|
||||||
|
if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, let url = initialUrlRequest.url, url.scheme == "file" {
|
||||||
|
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
|
||||||
|
if allowingReadAccessToURL?.scheme != "file" {
|
||||||
|
allowingReadAccessToURL = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView?.loadUrl(urlRequest: initialUrlRequest, allowingReadAccessTo: allowingReadAccessToURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
channelDelegate?.onBrowserCreated()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func prepareWebView() {
|
||||||
|
webView?.settings = webViewSettings
|
||||||
|
webView?.prepare()
|
||||||
|
|
||||||
|
if let browserSettings = browserSettings {
|
||||||
|
if browserSettings.hideProgressBar {
|
||||||
|
progressBar.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didChangeTitle(title: String?) {
|
||||||
|
guard let title = title else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window?.title = title
|
||||||
|
window?.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didStartNavigation(url: URL?) {
|
||||||
|
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
|
||||||
|
window?.backButton?.isEnabled = webView?.canGoBack ?? false
|
||||||
|
progressBar.doubleValue = 0.0
|
||||||
|
progressBar.isHidden = false
|
||||||
|
guard let url = url else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window?.searchBar?.stringValue = url.absoluteString
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didUpdateVisitedHistory(url: URL?) {
|
||||||
|
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
|
||||||
|
window?.backButton?.isEnabled = webView?.canGoBack ?? false
|
||||||
|
guard let url = url else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window?.searchBar?.stringValue = url.absoluteString
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didFinishNavigation(url: URL?) {
|
||||||
|
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
|
||||||
|
window?.backButton?.isEnabled = webView?.canGoBack ?? false
|
||||||
|
progressBar.doubleValue = 0.0
|
||||||
|
progressBar.isHidden = true
|
||||||
|
guard let url = url else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window?.searchBar?.stringValue = url.absoluteString
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didFailNavigation(url: URL?, error: Error) {
|
||||||
|
window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
|
||||||
|
window?.backButton?.isEnabled = webView?.canGoBack ?? false
|
||||||
|
progressBar.doubleValue = 0.0
|
||||||
|
progressBar.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func didChangeProgress(progress: Double) {
|
||||||
|
progressBar.isHidden = false
|
||||||
|
progressBar.doubleValue = progress * 100
|
||||||
|
if progress == 100.0 {
|
||||||
|
progressBar.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func reload() {
|
||||||
|
webView?.reload()
|
||||||
|
didUpdateVisitedHistory(url: webView?.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func goBack() {
|
||||||
|
if let webView = webView, webView.canGoBack {
|
||||||
|
webView.goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func goForward() {
|
||||||
|
if let webView = webView, webView.canGoForward {
|
||||||
|
webView.goForward()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func goBackOrForward(steps: Int) {
|
||||||
|
webView?.goBackOrForward(steps: steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
|
||||||
|
window?.setSettings(newSettings: newSettings, newSettingsMap: newSettingsMap)
|
||||||
|
|
||||||
|
let newInAppWebViewSettings = InAppWebViewSettings()
|
||||||
|
let _ = newInAppWebViewSettings.parse(settings: newSettingsMap)
|
||||||
|
webView?.setSettings(newSettings: newInAppWebViewSettings, newSettingsMap: newSettingsMap)
|
||||||
|
|
||||||
|
if newSettingsMap["hideProgressBar"] != nil, browserSettings?.hideProgressBar != newSettings.hideProgressBar {
|
||||||
|
progressBar.isHidden = newSettings.hideProgressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
browserSettings = newSettings
|
||||||
|
webViewSettings = newInAppWebViewSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getSettings() -> [String: Any?]? {
|
||||||
|
let webViewSettingsMap = webView?.getSettings()
|
||||||
|
if (self.browserSettings == nil || webViewSettingsMap == nil) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var settingsMap = self.browserSettings!.getRealSettings(obj: self)
|
||||||
|
settingsMap.merge(webViewSettingsMap!, uniquingKeysWith: { (current, _) in current })
|
||||||
|
return settingsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hide() {
|
||||||
|
window?.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func show() {
|
||||||
|
window?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func close() {
|
||||||
|
window?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
channelDelegate?.onExit()
|
||||||
|
channelDelegate?.dispose()
|
||||||
|
channelDelegate = nil
|
||||||
|
webView?.dispose()
|
||||||
|
webView = nil
|
||||||
|
window = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("InAppBrowserWebViewController - dealloc")
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,276 @@
|
||||||
|
//
|
||||||
|
// InAppBrowserNavigationController.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 14/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ToolbarIdentifiers {
|
||||||
|
static let searchBar = NSToolbarItem.Identifier(rawValue: "SearchBar")
|
||||||
|
static let backButton = NSToolbarItem.Identifier(rawValue: "BackButton")
|
||||||
|
static let forwardButton = NSToolbarItem.Identifier(rawValue: "ForwardButton")
|
||||||
|
static let reloadButton = NSToolbarItem.Identifier(rawValue: "ReloadButton")
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InAppBrowserWindow : NSWindow, NSWindowDelegate, NSToolbarDelegate, NSSearchFieldDelegate {
|
||||||
|
var searchItem: NSToolbarItem?
|
||||||
|
var backItem: NSToolbarItem?
|
||||||
|
var forwardItem: NSToolbarItem?
|
||||||
|
var reloadItem: NSToolbarItem?
|
||||||
|
|
||||||
|
var reloadButton: NSButton? {
|
||||||
|
get {
|
||||||
|
return reloadItem?.view as? NSButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var backButton: NSButton? {
|
||||||
|
get {
|
||||||
|
return backItem?.view as? NSButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var forwardButton: NSButton? {
|
||||||
|
get {
|
||||||
|
return forwardItem?.view as? NSButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var searchBar: NSSearchField? {
|
||||||
|
get {
|
||||||
|
if #available(macOS 11.0, *), let searchItem = searchItem as? NSSearchToolbarItem {
|
||||||
|
return searchItem.searchField
|
||||||
|
} else {
|
||||||
|
return searchItem?.view as? NSSearchField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var browserSettings: InAppBrowserSettings?
|
||||||
|
|
||||||
|
public func prepare() {
|
||||||
|
title = ""
|
||||||
|
delegate = self
|
||||||
|
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
let windowToolbar = NSToolbar()
|
||||||
|
windowToolbar.delegate = self
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
searchItem = NSSearchToolbarItem(itemIdentifier: ToolbarIdentifiers.searchBar)
|
||||||
|
(searchItem as! NSSearchToolbarItem).searchField.delegate = self
|
||||||
|
toolbarStyle = .expanded
|
||||||
|
} else {
|
||||||
|
searchItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.searchBar)
|
||||||
|
let textField = NSSearchField()
|
||||||
|
textField.usesSingleLineMode = true
|
||||||
|
textField.delegate = self
|
||||||
|
searchItem?.view = textField
|
||||||
|
}
|
||||||
|
searchItem?.label = ""
|
||||||
|
windowToolbar.displayMode = .default
|
||||||
|
|
||||||
|
backItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.backButton)
|
||||||
|
backItem?.label = ""
|
||||||
|
if let webViewController = contentViewController as? InAppBrowserWebViewController {
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
backItem?.view = NSButton(image: NSImage(systemSymbolName: "chevron.left",
|
||||||
|
accessibilityDescription: "Go Back")!,
|
||||||
|
target: webViewController,
|
||||||
|
action: #selector(InAppBrowserWebViewController.goBack))
|
||||||
|
} else {
|
||||||
|
backItem?.view = NSButton(title: "\u{2039}",
|
||||||
|
target: webViewController,
|
||||||
|
action: #selector(InAppBrowserWebViewController.goBack))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.forwardButton)
|
||||||
|
forwardItem?.label = ""
|
||||||
|
if let webViewController = contentViewController as? InAppBrowserWebViewController {
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
forwardItem?.view = NSButton(image: NSImage(systemSymbolName: "chevron.right",
|
||||||
|
accessibilityDescription: "Go Forward")!,
|
||||||
|
target: webViewController,
|
||||||
|
action: #selector(InAppBrowserWebViewController.goForward))
|
||||||
|
} else {
|
||||||
|
forwardItem?.view = NSButton(title: "\u{203A}",
|
||||||
|
target: webViewController,
|
||||||
|
action: #selector(InAppBrowserWebViewController.goForward))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadItem = NSToolbarItem(itemIdentifier: ToolbarIdentifiers.reloadButton)
|
||||||
|
reloadItem?.label = ""
|
||||||
|
if let webViewController = contentViewController as? InAppBrowserWebViewController {
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
reloadItem?.view = NSButton(image: NSImage(systemSymbolName: "arrow.counterclockwise",
|
||||||
|
accessibilityDescription: "Reload")!,
|
||||||
|
target: webViewController,
|
||||||
|
action: #selector(InAppBrowserWebViewController.reload))
|
||||||
|
} else {
|
||||||
|
reloadItem?.view = NSButton(title: "Reload",
|
||||||
|
target: webViewController,
|
||||||
|
action: #selector(InAppBrowserWebViewController.reload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if #available(macOS 10.14, *) {
|
||||||
|
windowToolbar.centeredItemIdentifier = ToolbarIdentifiers.searchBar
|
||||||
|
}
|
||||||
|
toolbar = windowToolbar
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardButton?.isEnabled = false
|
||||||
|
backButton?.isEnabled = false
|
||||||
|
|
||||||
|
if let browserSettings = browserSettings {
|
||||||
|
if !browserSettings.hideToolbarTop {
|
||||||
|
toolbar?.isVisible = true
|
||||||
|
if browserSettings.hideUrlBar {
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
(searchItem as! NSSearchToolbarItem).searchField.isHidden = true
|
||||||
|
} else {
|
||||||
|
searchItem?.view?.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let bgColor = browserSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
|
||||||
|
backgroundColor = NSColor(hexString: bgColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toolbar?.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
|
return [ ToolbarIdentifiers.searchBar,
|
||||||
|
ToolbarIdentifiers.backButton,
|
||||||
|
ToolbarIdentifiers.forwardButton,
|
||||||
|
ToolbarIdentifiers.reloadButton,
|
||||||
|
.flexibleSpace ]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
|
return [.flexibleSpace,
|
||||||
|
ToolbarIdentifiers.searchBar,
|
||||||
|
.flexibleSpace,
|
||||||
|
ToolbarIdentifiers.reloadButton,
|
||||||
|
ToolbarIdentifiers.backButton,
|
||||||
|
ToolbarIdentifiers.forwardButton]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||||
|
switch(itemIdentifier) {
|
||||||
|
case ToolbarIdentifiers.searchBar:
|
||||||
|
return searchItem
|
||||||
|
case ToolbarIdentifiers.backButton:
|
||||||
|
return backItem
|
||||||
|
case ToolbarIdentifiers.forwardButton:
|
||||||
|
return forwardItem
|
||||||
|
case ToolbarIdentifiers.reloadButton:
|
||||||
|
return reloadItem
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
|
||||||
|
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
|
||||||
|
// ENTER key
|
||||||
|
var searchField: NSSearchField? = nil
|
||||||
|
if #available(macOS 11.0, *), let searchBar = searchItem as? NSSearchToolbarItem {
|
||||||
|
searchField = searchBar.searchField
|
||||||
|
} else if let searchBar = searchItem {
|
||||||
|
searchField = searchBar.view as? NSSearchField
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let searchField,
|
||||||
|
let urlEncoded = searchField.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
|
||||||
|
let url = URL(string: urlEncoded) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = URLRequest(url: url)
|
||||||
|
(contentViewController as? InAppBrowserWebViewController)?.webView?.load(request)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hide() {
|
||||||
|
orderOut(self)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func show() {
|
||||||
|
if !(NSApplication.shared.mainWindow?.childWindows?.contains(self) ?? false) {
|
||||||
|
NSApplication.shared.mainWindow?.addChildWindow(self, ordered: .above)
|
||||||
|
} else {
|
||||||
|
orderFront(self)
|
||||||
|
}
|
||||||
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
|
||||||
|
if newSettingsMap["hidden"] != nil, browserSettings?.hidden != newSettings.hidden {
|
||||||
|
if newSettings.hidden {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if newSettingsMap["hideUrlBar"] != nil, browserSettings?.hideUrlBar != newSettings.hideUrlBar {
|
||||||
|
searchBar?.isHidden = newSettings.hideUrlBar
|
||||||
|
}
|
||||||
|
|
||||||
|
if newSettingsMap["hideToolbarTop"] != nil, browserSettings?.hideToolbarTop != newSettings.hideToolbarTop {
|
||||||
|
toolbar?.isVisible = !newSettings.hideToolbarTop
|
||||||
|
}
|
||||||
|
|
||||||
|
if newSettingsMap["toolbarTopBackgroundColor"] != nil, browserSettings?.toolbarTopBackgroundColor != newSettings.toolbarTopBackgroundColor {
|
||||||
|
if let bgColor = newSettings.toolbarTopBackgroundColor, !bgColor.isEmpty {
|
||||||
|
backgroundColor = NSColor(hexString: bgColor)
|
||||||
|
} else {
|
||||||
|
backgroundColor = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browserSettings = newSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
public func windowWillClose(_ notification: Notification) {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
delegate = nil
|
||||||
|
if let webViewController = contentViewController as? InAppBrowserWebViewController {
|
||||||
|
webViewController.dispose()
|
||||||
|
}
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
(searchItem as? NSSearchToolbarItem)?.searchField.delegate = nil
|
||||||
|
} else {
|
||||||
|
(searchItem?.view as? NSTextField)?.delegate = nil
|
||||||
|
searchItem?.view = nil
|
||||||
|
}
|
||||||
|
searchItem = nil
|
||||||
|
(backItem?.view as? NSButton)?.target = nil
|
||||||
|
backItem?.view = nil
|
||||||
|
backItem = nil
|
||||||
|
(forwardItem?.view as? NSButton)?.target = nil
|
||||||
|
forwardItem?.view = nil
|
||||||
|
forwardItem = nil
|
||||||
|
(reloadItem?.view as? NSButton)?.target = nil
|
||||||
|
reloadItem?.view = nil
|
||||||
|
reloadItem = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("InAppBrowserWindow - dealloc")
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// ContextMenuOptions.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 30/05/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class ContextMenuSettings: ISettings<NSObject> {
|
||||||
|
|
||||||
|
var hideDefaultSystemContextMenuItems = false;
|
||||||
|
|
||||||
|
override init(){
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// CustomeSchemeHandler.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 25/10/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FlutterMacOS
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
|
||||||
|
@available(macOS 10.13, *)
|
||||||
|
public class CustomSchemeHandler : NSObject, WKURLSchemeHandler {
|
||||||
|
var schemeHandlers: [Int:WKURLSchemeTask] = [:]
|
||||||
|
|
||||||
|
public func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
|
||||||
|
schemeHandlers[urlSchemeTask.hash] = urlSchemeTask
|
||||||
|
let inAppWebView = webView as! InAppWebView
|
||||||
|
let request = WebResourceRequest.init(fromURLRequest: urlSchemeTask.request)
|
||||||
|
let callback = WebViewChannelDelegate.LoadResourceWithCustomSchemeCallback()
|
||||||
|
callback.nonNullSuccess = { (response: CustomSchemeResponse) in
|
||||||
|
if (self.schemeHandlers[urlSchemeTask.hash] != nil) {
|
||||||
|
let urlResponse = URLResponse(url: request.url, mimeType: response.contentType, expectedContentLength: -1, textEncodingName: response.contentEncoding)
|
||||||
|
urlSchemeTask.didReceive(urlResponse)
|
||||||
|
urlSchemeTask.didReceive(response.data)
|
||||||
|
urlSchemeTask.didFinish()
|
||||||
|
self.schemeHandlers.removeValue(forKey: urlSchemeTask.hash)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
callback.error = { (code: String, message: String?, details: Any?) in
|
||||||
|
print(code + ", " + (message ?? ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let channelDelegate = inAppWebView.channelDelegate {
|
||||||
|
channelDelegate.onLoadResourceWithCustomScheme(request: request, callback: callback)
|
||||||
|
} else {
|
||||||
|
callback.defaultBehaviour(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
|
||||||
|
schemeHandlers.removeValue(forKey: urlSchemeTask.hash)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
//
|
||||||
|
// FlutterWebViewController.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 13/11/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class FlutterWebViewController: NSObject, /*FlutterPlatformView,*/ Disposable {
|
||||||
|
|
||||||
|
var myView: NSView?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
myView = NSView(frame: frame)
|
||||||
|
|
||||||
|
let initialSettings = params["initialSettings"] as! [String: Any?]
|
||||||
|
let contextMenu = params["contextMenu"] as? [String: Any]
|
||||||
|
let windowId = params["windowId"] as? Int64
|
||||||
|
let initialUserScripts = params["initialUserScripts"] as? [[String: Any]]
|
||||||
|
|
||||||
|
var userScripts: [UserScript] = []
|
||||||
|
if let initialUserScripts = initialUserScripts {
|
||||||
|
for intialUserScript in initialUserScripts {
|
||||||
|
userScripts.append(UserScript.fromMap(map: intialUserScript, windowId: windowId)!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings = InAppWebViewSettings()
|
||||||
|
let _ = settings.parse(settings: initialSettings)
|
||||||
|
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: settings)
|
||||||
|
|
||||||
|
var webView: InAppWebView?
|
||||||
|
|
||||||
|
if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] {
|
||||||
|
webView = webViewTransport.webView
|
||||||
|
webView!.id = viewId
|
||||||
|
let channel = FlutterMethodChannel(name: InAppWebView.METHOD_CHANNEL_NAME_PREFIX + String(describing: viewId),
|
||||||
|
binaryMessenger: registrar.messenger)
|
||||||
|
webView!.channelDelegate = WebViewChannelDelegate(webView: webView!, channel: channel)
|
||||||
|
webView!.frame = myView!.bounds
|
||||||
|
webView!.contextMenu = contextMenu
|
||||||
|
webView!.initialUserScripts = userScripts
|
||||||
|
} else {
|
||||||
|
webView = InAppWebView(id: viewId,
|
||||||
|
registrar: registrar,
|
||||||
|
frame: myView!.bounds,
|
||||||
|
configuration: preWebviewConfiguration,
|
||||||
|
contextMenu: contextMenu,
|
||||||
|
userScripts: userScripts)
|
||||||
|
}
|
||||||
|
|
||||||
|
webView!.autoresizingMask = [.width, .height]
|
||||||
|
myView!.autoresizesSubviews = true
|
||||||
|
myView!.autoresizingMask = [.width, .height]
|
||||||
|
myView!.addSubview(webView!)
|
||||||
|
|
||||||
|
webView!.settings = settings
|
||||||
|
webView!.prepare()
|
||||||
|
webView!.windowCreated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func webView() -> InAppWebView? {
|
||||||
|
for subview in myView?.subviews ?? []
|
||||||
|
{
|
||||||
|
if let item = subview as? InAppWebView
|
||||||
|
{
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func view() -> NSView {
|
||||||
|
return myView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeInitialLoad(params: NSDictionary) {
|
||||||
|
guard let webView = webView() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let windowId = params["windowId"] as? Int64
|
||||||
|
let initialUrlRequest = params["initialUrlRequest"] as? [String: Any?]
|
||||||
|
let initialFile = params["initialFile"] as? String
|
||||||
|
let initialData = params["initialData"] as? [String: String?]
|
||||||
|
|
||||||
|
if windowId == nil {
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
webView.configuration.userContentController.removeAllContentRuleLists()
|
||||||
|
if let contentBlockers = webView.settings?.contentBlockers, contentBlockers.count > 0 {
|
||||||
|
do {
|
||||||
|
let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
|
||||||
|
let blockRules = String(data: jsonData, encoding: String.Encoding.utf8)
|
||||||
|
WKContentRuleListStore.default().compileContentRuleList(
|
||||||
|
forIdentifier: "ContentBlockingRules",
|
||||||
|
encodedContentRuleList: blockRules) { (contentRuleList, error) in
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let configuration = webView.configuration
|
||||||
|
configuration.userContentController.add(contentRuleList!)
|
||||||
|
|
||||||
|
self.load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load(initialUrlRequest: initialUrlRequest, initialFile: initialFile, initialData: initialData)
|
||||||
|
}
|
||||||
|
else if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] {
|
||||||
|
webView.load(webViewTransport.request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func load(initialUrlRequest: [String:Any?]?, initialFile: String?, initialData: [String: String?]?) {
|
||||||
|
guard let webView = webView() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let initialFile = initialFile {
|
||||||
|
do {
|
||||||
|
try webView.loadFile(assetFilePath: initialFile)
|
||||||
|
}
|
||||||
|
catch let error as NSError {
|
||||||
|
dump(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if let initialData = initialData, let data = initialData["data"]!,
|
||||||
|
let mimeType = initialData["mimeType"]!, let encoding = initialData["encoding"]!,
|
||||||
|
let baseUrl = URL(string: initialData["baseUrl"]! ?? "about:blank") {
|
||||||
|
var allowingReadAccessToURL: URL? = nil
|
||||||
|
if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
|
||||||
|
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
|
||||||
|
if allowingReadAccessToURL?.scheme != "file" {
|
||||||
|
allowingReadAccessToURL = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView.loadData(data: data,
|
||||||
|
mimeType: mimeType,
|
||||||
|
encoding: encoding,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
allowingReadAccessTo: allowingReadAccessToURL)
|
||||||
|
}
|
||||||
|
else if let initialUrlRequest = initialUrlRequest {
|
||||||
|
let urlRequest = URLRequest.init(fromPluginMap: initialUrlRequest)
|
||||||
|
var allowingReadAccessToURL: URL? = nil
|
||||||
|
if let allowingReadAccessTo = webView.settings?.allowingReadAccessTo, let url = urlRequest.url, url.scheme == "file" {
|
||||||
|
allowingReadAccessToURL = URL(string: allowingReadAccessTo)
|
||||||
|
if allowingReadAccessToURL?.scheme != "file" {
|
||||||
|
allowingReadAccessToURL = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webView.loadUrl(urlRequest: urlRequest, allowingReadAccessTo: allowingReadAccessToURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
if let webView = webView() {
|
||||||
|
webView.dispose()
|
||||||
|
}
|
||||||
|
myView = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("FlutterWebViewController - dealloc")
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// FlutterWebViewFactory.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 13/11/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
|
||||||
|
static let VIEW_TYPE_ID = "com.pichillilorenzo/flutter_inappwebview"
|
||||||
|
private var registrar: FlutterPluginRegistrar?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar?) {
|
||||||
|
super.init()
|
||||||
|
self.registrar = registrar
|
||||||
|
}
|
||||||
|
|
||||||
|
public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
|
||||||
|
return FlutterStandardMessageCodec.sharedInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func create(withViewIdentifier viewId: Int64, arguments args: Any?) -> NSView {
|
||||||
|
let arguments = args as? NSDictionary
|
||||||
|
let webviewController = FlutterWebViewController(registrar: registrar!,
|
||||||
|
withFrame: .zero,
|
||||||
|
viewIdentifier: viewId,
|
||||||
|
params: arguments!)
|
||||||
|
webviewController.makeInitialLoad(params: arguments!)
|
||||||
|
return webviewController.view()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
//
|
||||||
|
// InAppWebViewSettings.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 21/10/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
|
||||||
|
@objcMembers
|
||||||
|
public class InAppWebViewSettings: ISettings<InAppWebView> {
|
||||||
|
|
||||||
|
var useShouldOverrideUrlLoading = false
|
||||||
|
var useOnLoadResource = false
|
||||||
|
var useOnDownloadStart = false
|
||||||
|
var clearCache = false
|
||||||
|
var userAgent = ""
|
||||||
|
var applicationNameForUserAgent = ""
|
||||||
|
var javaScriptEnabled = true
|
||||||
|
var javaScriptCanOpenWindowsAutomatically = false
|
||||||
|
var mediaPlaybackRequiresUserGesture = true
|
||||||
|
var verticalScrollBarEnabled = true
|
||||||
|
var horizontalScrollBarEnabled = true
|
||||||
|
var resourceCustomSchemes: [String] = []
|
||||||
|
var contentBlockers: [[String: [String : Any]]] = []
|
||||||
|
var minimumFontSize = 0
|
||||||
|
var useShouldInterceptAjaxRequest = false
|
||||||
|
var useShouldInterceptFetchRequest = false
|
||||||
|
var incognito = false
|
||||||
|
var cacheEnabled = true
|
||||||
|
var transparentBackground = false
|
||||||
|
var disableVerticalScroll = false
|
||||||
|
var disableHorizontalScroll = false
|
||||||
|
var disableContextMenu = false
|
||||||
|
var supportZoom = true
|
||||||
|
var allowUniversalAccessFromFileURLs = false
|
||||||
|
var allowFileAccessFromFileURLs = false
|
||||||
|
|
||||||
|
var disallowOverScroll = false
|
||||||
|
var enableViewportScale = false
|
||||||
|
var suppressesIncrementalRendering = false
|
||||||
|
var allowsAirPlayForMediaPlayback = true
|
||||||
|
var allowsBackForwardNavigationGestures = true
|
||||||
|
var allowsLinkPreview = true
|
||||||
|
var ignoresViewportScaleLimits = false
|
||||||
|
var allowsInlineMediaPlayback = false
|
||||||
|
var allowsPictureInPictureMediaPlayback = true
|
||||||
|
var isFraudulentWebsiteWarningEnabled = true
|
||||||
|
var selectionGranularity = 0
|
||||||
|
var dataDetectorTypes: [String] = ["NONE"] // WKDataDetectorTypeNone
|
||||||
|
var preferredContentMode = 0
|
||||||
|
var sharedCookiesEnabled = false
|
||||||
|
var automaticallyAdjustsScrollIndicatorInsets = false
|
||||||
|
var accessibilityIgnoresInvertColors = false
|
||||||
|
var decelerationRate = "NORMAL" // UIScrollView.DecelerationRate.normal
|
||||||
|
var alwaysBounceVertical = false
|
||||||
|
var alwaysBounceHorizontal = false
|
||||||
|
var scrollsToTop = true
|
||||||
|
var isPagingEnabled = false
|
||||||
|
var maximumZoomScale = 1.0
|
||||||
|
var minimumZoomScale = 1.0
|
||||||
|
var contentInsetAdjustmentBehavior = 2 // UIScrollView.ContentInsetAdjustmentBehavior.never
|
||||||
|
var isDirectionalLockEnabled = false
|
||||||
|
var mediaType: String? = nil
|
||||||
|
var pageZoom = 1.0
|
||||||
|
var limitsNavigationsToAppBoundDomains = false
|
||||||
|
var useOnNavigationResponse = false
|
||||||
|
var applePayAPIEnabled = false
|
||||||
|
var allowingReadAccessTo: String? = nil
|
||||||
|
var disableLongPressContextMenuOnLinks = false
|
||||||
|
var disableInputAccessoryView = false
|
||||||
|
var underPageBackgroundColor: String?
|
||||||
|
var isTextInteractionEnabled = true
|
||||||
|
var isSiteSpecificQuirksModeEnabled = true
|
||||||
|
var upgradeKnownHostsToHTTPS = true
|
||||||
|
var isElementFullscreenEnabled = true
|
||||||
|
var isFindInteractionEnabled = false
|
||||||
|
var minimumViewportInset: NSEdgeInsets? = nil
|
||||||
|
var maximumViewportInset: NSEdgeInsets? = nil
|
||||||
|
|
||||||
|
override init(){
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func parse(settings: [String: Any?]) -> InAppWebViewSettings {
|
||||||
|
let _ = super.parse(settings: settings)
|
||||||
|
if #available(iOS 13.0, *) {} else {
|
||||||
|
applePayAPIEnabled = false
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
override func getRealSettings(obj: InAppWebView?) -> [String: Any?] {
|
||||||
|
var realSettings: [String: Any?] = toMap()
|
||||||
|
if let webView = obj {
|
||||||
|
let configuration = webView.configuration
|
||||||
|
if #available(iOS 9.0, *) {
|
||||||
|
realSettings["userAgent"] = webView.customUserAgent
|
||||||
|
realSettings["applicationNameForUserAgent"] = configuration.applicationNameForUserAgent
|
||||||
|
realSettings["allowsAirPlayForMediaPlayback"] = configuration.allowsAirPlayForMediaPlayback
|
||||||
|
realSettings["allowsLinkPreview"] = webView.allowsLinkPreview
|
||||||
|
}
|
||||||
|
realSettings["javaScriptCanOpenWindowsAutomatically"] = configuration.preferences.javaScriptCanOpenWindowsAutomatically
|
||||||
|
if #available(macOS 10.12, *) {
|
||||||
|
realSettings["mediaPlaybackRequiresUserGesture"] = configuration.mediaTypesRequiringUserActionForPlayback == .all
|
||||||
|
}
|
||||||
|
realSettings["minimumFontSize"] = configuration.preferences.minimumFontSize
|
||||||
|
realSettings["suppressesIncrementalRendering"] = configuration.suppressesIncrementalRendering
|
||||||
|
realSettings["allowsBackForwardNavigationGestures"] = webView.allowsBackForwardNavigationGestures
|
||||||
|
if #available(macOS 10.15, *) {
|
||||||
|
realSettings["isFraudulentWebsiteWarningEnabled"] = configuration.preferences.isFraudulentWebsiteWarningEnabled
|
||||||
|
realSettings["preferredContentMode"] = configuration.defaultWebpagePreferences.preferredContentMode.rawValue
|
||||||
|
}
|
||||||
|
realSettings["allowUniversalAccessFromFileURLs"] = configuration.value(forKey: "allowUniversalAccessFromFileURLs")
|
||||||
|
realSettings["allowFileAccessFromFileURLs"] = configuration.preferences.value(forKey: "allowFileAccessFromFileURLs")
|
||||||
|
realSettings["javaScriptEnabled"] = configuration.preferences.javaScriptEnabled
|
||||||
|
if #available(macOS 11.0, *) {
|
||||||
|
realSettings["mediaType"] = webView.mediaType
|
||||||
|
realSettings["pageZoom"] = Float(webView.pageZoom)
|
||||||
|
realSettings["limitsNavigationsToAppBoundDomains"] = configuration.limitsNavigationsToAppBoundDomains
|
||||||
|
realSettings["javaScriptEnabled"] = configuration.defaultWebpagePreferences.allowsContentJavaScript
|
||||||
|
}
|
||||||
|
if #available(macOS 11.3, *) {
|
||||||
|
realSettings["isTextInteractionEnabled"] = configuration.preferences.isTextInteractionEnabled
|
||||||
|
realSettings["upgradeKnownHostsToHTTPS"] = configuration.upgradeKnownHostsToHTTPS
|
||||||
|
}
|
||||||
|
if #available(macOS 12.0, *) {
|
||||||
|
realSettings["underPageBackgroundColor"] = webView.underPageBackgroundColor.hexString
|
||||||
|
realSettings["isSiteSpecificQuirksModeEnabled"] = configuration.preferences.isSiteSpecificQuirksModeEnabled
|
||||||
|
realSettings["isElementFullscreenEnabled"] = configuration.preferences.isElementFullscreenEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return realSettings
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
//
|
||||||
|
// WebMessageChannel.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/03/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class WebMessageChannel : FlutterMethodCallDelegate {
|
||||||
|
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_channel_"
|
||||||
|
var id: String
|
||||||
|
var channelDelegate: WebMessageChannelChannelDelegate?
|
||||||
|
weak var webView: InAppWebView?
|
||||||
|
var ports: [WebMessagePort] = []
|
||||||
|
|
||||||
|
public init(id: String) {
|
||||||
|
self.id = id
|
||||||
|
super.init()
|
||||||
|
let channel = FlutterMethodChannel(name: WebMessageChannel.METHOD_CHANNEL_NAME_PREFIX + id,
|
||||||
|
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger)
|
||||||
|
self.channelDelegate = WebMessageChannelChannelDelegate(webMessageChannel: self, channel: channel)
|
||||||
|
self.ports = [
|
||||||
|
WebMessagePort(name: "port1", webMessageChannel: self),
|
||||||
|
WebMessagePort(name: "port2", webMessageChannel: self)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func initJsInstance(webView: InAppWebView, completionHandler: ((WebMessageChannel) -> Void)? = nil) {
|
||||||
|
self.webView = webView
|
||||||
|
if let webView = self.webView {
|
||||||
|
webView.evaluateJavascript(source: """
|
||||||
|
(function() {
|
||||||
|
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"] = new MessageChannel();
|
||||||
|
})();
|
||||||
|
""") { (_) in
|
||||||
|
completionHandler?(self)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completionHandler?(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toMap() -> [String:Any?] {
|
||||||
|
return [
|
||||||
|
"id": id
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
channelDelegate?.dispose()
|
||||||
|
channelDelegate = nil
|
||||||
|
for port in ports {
|
||||||
|
port.dispose()
|
||||||
|
}
|
||||||
|
ports.removeAll()
|
||||||
|
webView?.evaluateJavascript(source: """
|
||||||
|
(function() {
|
||||||
|
var webMessageChannel = \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
|
||||||
|
if (webMessageChannel != null) {
|
||||||
|
webMessageChannel.port1.close();
|
||||||
|
webMessageChannel.port2.close();
|
||||||
|
delete \(WEB_MESSAGE_CHANNELS_VARIABLE_NAME)["\(id)"];
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
""")
|
||||||
|
webView = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("WebMessageChannel - dealloc")
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
//
|
||||||
|
// WebMessageChannelChannelDelegate.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 07/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class WebMessageChannelChannelDelegate : ChannelDelegate {
|
||||||
|
private weak var webMessageChannel: WebMessageChannel?
|
||||||
|
|
||||||
|
public init(webMessageChannel: WebMessageChannel, channel: FlutterMethodChannel) {
|
||||||
|
super.init(channel: channel)
|
||||||
|
self.webMessageChannel = webMessageChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "setWebMessageCallback":
|
||||||
|
if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
|
||||||
|
let index = arguments!["index"] as! Int
|
||||||
|
let port = ports[index]
|
||||||
|
do {
|
||||||
|
try port.setWebMessageCallback { (_) in
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
} catch let error as NSError {
|
||||||
|
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "postMessage":
|
||||||
|
if let webView = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
|
||||||
|
let index = arguments!["index"] as! Int
|
||||||
|
let port = ports[index]
|
||||||
|
let message = arguments!["message"] as! [String: Any?]
|
||||||
|
|
||||||
|
var webMessagePorts: [WebMessagePort] = []
|
||||||
|
let portsMap = message["ports"] as? [[String: Any?]]
|
||||||
|
if let portsMap = portsMap {
|
||||||
|
for portMap in portsMap {
|
||||||
|
let webMessageChannelId = portMap["webMessageChannelId"] as! String
|
||||||
|
let index = portMap["index"] as! Int
|
||||||
|
if let webMessageChannel = webView.webMessageChannels[webMessageChannelId] {
|
||||||
|
webMessagePorts.append(webMessageChannel.ports[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let webMessage = WebMessage(data: message["data"] as? String, ports: webMessagePorts)
|
||||||
|
do {
|
||||||
|
try port.postMessage(message: webMessage) { (_) in
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
} catch let error as NSError {
|
||||||
|
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "close":
|
||||||
|
if let _ = webMessageChannel?.webView, let ports = webMessageChannel?.ports, ports.count > 0 {
|
||||||
|
let index = arguments!["index"] as! Int
|
||||||
|
let port = ports[index]
|
||||||
|
do {
|
||||||
|
try port.close { (_) in
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
} catch let error as NSError {
|
||||||
|
result(FlutterError(code: "WebMessageChannel", message: error.domain, details: nil))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onMessage(index: Int64, message: String?) {
|
||||||
|
let arguments: [String:Any?] = [
|
||||||
|
"index": index,
|
||||||
|
"message": message
|
||||||
|
]
|
||||||
|
channel?.invokeMethod("onMessage", arguments: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
webMessageChannel = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
//
|
||||||
|
// WebMessageListener.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/03/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class WebMessageListener : FlutterMethodCallDelegate {
|
||||||
|
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_web_message_listener_"
|
||||||
|
var jsObjectName: String
|
||||||
|
var allowedOriginRules: Set<String>
|
||||||
|
var channelDelegate: WebMessageListenerChannelDelegate?
|
||||||
|
weak var webView: InAppWebView?
|
||||||
|
|
||||||
|
public init(jsObjectName: String, allowedOriginRules: Set<String>) {
|
||||||
|
self.jsObjectName = jsObjectName
|
||||||
|
self.allowedOriginRules = allowedOriginRules
|
||||||
|
super.init()
|
||||||
|
let channel = FlutterMethodChannel(name: WebMessageListener.METHOD_CHANNEL_NAME_PREFIX + self.jsObjectName,
|
||||||
|
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger)
|
||||||
|
self.channelDelegate = WebMessageListenerChannelDelegate(webMessageListener: self, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func assertOriginRulesValid() throws {
|
||||||
|
for (index, originRule) in allowedOriginRules.enumerated() {
|
||||||
|
if originRule.isEmpty {
|
||||||
|
throw NSError(domain: "allowedOriginRules[\(index)] is empty", code: 0)
|
||||||
|
}
|
||||||
|
if originRule == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let url = URL(string: originRule) {
|
||||||
|
guard let scheme = url.scheme else {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
if scheme == "http" || scheme == "https", url.host == nil || url.host!.isEmpty {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
if scheme != "http", scheme != "https", url.host != nil || url.port != nil {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
if url.host == nil || url.host!.isEmpty, url.port != nil {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
if !url.path.isEmpty {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
if let hostname = url.host {
|
||||||
|
if let firstIndex = hostname.firstIndex(of: "*") {
|
||||||
|
let distance = hostname.distance(from: hostname.startIndex, to: firstIndex)
|
||||||
|
if distance != 0 || (distance == 0 && hostname.prefix(2) != "*.") {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hostname.hasPrefix("[") {
|
||||||
|
if !hostname.hasSuffix("]") {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
|
||||||
|
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
|
||||||
|
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
|
||||||
|
let ipv6 = String(hostname[indexRange])
|
||||||
|
if !Util.isIPv6(address: ipv6) {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw NSError(domain: "allowedOriginRules \(originRule) is invalid", code: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func initJsInstance(webView: InAppWebView) {
|
||||||
|
self.webView = webView
|
||||||
|
if let webView = self.webView {
|
||||||
|
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
|
||||||
|
let allowedOriginRulesString = allowedOriginRules.map { (allowedOriginRule) -> String in
|
||||||
|
if allowedOriginRule == "*" {
|
||||||
|
return "'*'"
|
||||||
|
}
|
||||||
|
let rule = URL(string: allowedOriginRule)!
|
||||||
|
let host = rule.host != nil ? "'" + rule.host!.replacingOccurrences(of: "\'", with: "\\'") + "'" : "null"
|
||||||
|
return """
|
||||||
|
{scheme: '\(rule.scheme!)', host: \(host), port: \(rule.port != nil ? String(rule.port!) : "null")}
|
||||||
|
"""
|
||||||
|
}.joined(separator: ", ")
|
||||||
|
let source = """
|
||||||
|
(function() {
|
||||||
|
var allowedOriginRules = [\(allowedOriginRulesString)];
|
||||||
|
var isPageBlank = window.location.href === "about:blank";
|
||||||
|
var scheme = !isPageBlank ? window.location.protocol.replace(":", "") : null;
|
||||||
|
var host = !isPageBlank ? window.location.hostname : null;
|
||||||
|
var port = !isPageBlank ? window.location.port : null;
|
||||||
|
if (window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed(allowedOriginRules, scheme, host, port)) {
|
||||||
|
window['\(jsObjectNameEscaped)'] = new FlutterInAppWebViewWebMessageListener('\(jsObjectNameEscaped)');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
"""
|
||||||
|
webView.configuration.userContentController.addPluginScript(PluginScript(
|
||||||
|
groupName: "WebMessageListener-" + jsObjectName,
|
||||||
|
source: source,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: ["onWebMessageListenerPostMessageReceived"]
|
||||||
|
))
|
||||||
|
webView.configuration.userContentController.sync(scriptMessageHandler: webView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fromMap(map: [String:Any?]?) -> WebMessageListener? {
|
||||||
|
guard let map = map else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return WebMessageListener(
|
||||||
|
jsObjectName: map["jsObjectName"] as! String,
|
||||||
|
allowedOriginRules: Set(map["allowedOriginRules"] as! [String])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isOriginAllowed(scheme: String?, host: String?, port: Int?) -> Bool {
|
||||||
|
for allowedOriginRule in allowedOriginRules {
|
||||||
|
if allowedOriginRule == "*" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if scheme == nil || scheme!.isEmpty {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if scheme == nil || scheme!.isEmpty, host == nil || host!.isEmpty, port == nil || port == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let rule = URL(string: allowedOriginRule) {
|
||||||
|
let rulePort = rule.port == nil || rule.port == 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port!
|
||||||
|
let currentPort = port == nil || port == 0 ? (scheme == "https" ? 443 : 80) : port!
|
||||||
|
var IPv6: String? = nil
|
||||||
|
if let hostname = rule.host, hostname.hasPrefix("[") {
|
||||||
|
let fromIndex = hostname.index(hostname.startIndex, offsetBy: 1)
|
||||||
|
let toIndex = hostname.index(hostname.startIndex, offsetBy: hostname.count - 1)
|
||||||
|
let indexRange = Range<String.Index>(uncheckedBounds: (lower: fromIndex, upper: toIndex))
|
||||||
|
do {
|
||||||
|
IPv6 = try Util.normalizeIPv6(address: String(hostname[indexRange]))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
var hostIPv6: String? = nil
|
||||||
|
if let host = host, Util.isIPv6(address: host) {
|
||||||
|
do {
|
||||||
|
hostIPv6 = try Util.normalizeIPv6(address: host)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schemeAllowed = scheme != nil && !scheme!.isEmpty && scheme == rule.scheme
|
||||||
|
|
||||||
|
let hostAllowed = rule.host == nil ||
|
||||||
|
rule.host!.isEmpty ||
|
||||||
|
host == rule.host ||
|
||||||
|
(rule.host!.hasPrefix("*") && host != nil && host!.hasSuffix(rule.host!.split(separator: "*", omittingEmptySubsequences: false)[1])) ||
|
||||||
|
(hostIPv6 != nil && IPv6 != nil && hostIPv6 == IPv6)
|
||||||
|
|
||||||
|
let portAllowed = rulePort == currentPort
|
||||||
|
|
||||||
|
if schemeAllowed, hostAllowed, portAllowed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
channelDelegate?.dispose()
|
||||||
|
channelDelegate = nil
|
||||||
|
webView = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("WebMessageListener - dealloc")
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
//
|
||||||
|
// WebMessageListenerChannelDelegate.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 07/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class WebMessageListenerChannelDelegate : ChannelDelegate {
|
||||||
|
private weak var webMessageListener: WebMessageListener?
|
||||||
|
|
||||||
|
public init(webMessageListener: WebMessageListener, channel: FlutterMethodChannel) {
|
||||||
|
super.init(channel: channel)
|
||||||
|
self.webMessageListener = webMessageListener
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "postMessage":
|
||||||
|
if let webView = webMessageListener?.webView, let jsObjectName = webMessageListener?.jsObjectName {
|
||||||
|
let jsObjectNameEscaped = jsObjectName.replacingOccurrences(of: "\'", with: "\\'")
|
||||||
|
let messageEscaped = (arguments!["message"] as! String).replacingOccurrences(of: "\'", with: "\\'")
|
||||||
|
let source = """
|
||||||
|
(function() {
|
||||||
|
var webMessageListener = window['\(jsObjectNameEscaped)'];
|
||||||
|
if (webMessageListener != null) {
|
||||||
|
var event = {data: '\(messageEscaped)'};
|
||||||
|
if (webMessageListener.onmessage != null) {
|
||||||
|
webMessageListener.onmessage(event);
|
||||||
|
}
|
||||||
|
for (var listener of webMessageListener.listeners) {
|
||||||
|
listener(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
"""
|
||||||
|
webView.evaluateJavascript(source: source) { (_) in
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onPostMessage(message: String?, sourceOrigin: URL?, isMainFrame: Bool) {
|
||||||
|
let arguments: [String:Any?] = [
|
||||||
|
"message": message,
|
||||||
|
"sourceOrigin": sourceOrigin?.absoluteString,
|
||||||
|
"isMainFrame": isMainFrame
|
||||||
|
]
|
||||||
|
channel?.invokeMethod("onPostMessage", arguments: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
webMessageListener = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// WebViewChannelDelegateMethods.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 08/10/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum WebViewChannelDelegateMethods: String {
|
||||||
|
case getUrl = "getUrl"
|
||||||
|
case getTitle = "getTitle"
|
||||||
|
case getProgress = "getProgress"
|
||||||
|
case loadUrl = "loadUrl"
|
||||||
|
case postUrl = "postUrl"
|
||||||
|
case loadData = "loadData"
|
||||||
|
case loadFile = "loadFile"
|
||||||
|
case evaluateJavascript = "evaluateJavascript"
|
||||||
|
case injectJavascriptFileFromUrl = "injectJavascriptFileFromUrl"
|
||||||
|
case injectCSSCode = "injectCSSCode"
|
||||||
|
case injectCSSFileFromUrl = "injectCSSFileFromUrl"
|
||||||
|
case reload = "reload"
|
||||||
|
case goBack = "goBack"
|
||||||
|
case canGoBack = "canGoBack"
|
||||||
|
case goForward = "goForward"
|
||||||
|
case canGoForward = "canGoForward"
|
||||||
|
case goBackOrForward = "goBackOrForward"
|
||||||
|
case canGoBackOrForward = "canGoBackOrForward"
|
||||||
|
case stopLoading = "stopLoading"
|
||||||
|
case isLoading = "isLoading"
|
||||||
|
case takeScreenshot = "takeScreenshot"
|
||||||
|
case setSettings = "setSettings"
|
||||||
|
case getSettings = "getSettings"
|
||||||
|
case close = "close"
|
||||||
|
case show = "show"
|
||||||
|
case hide = "hide"
|
||||||
|
case getCopyBackForwardList = "getCopyBackForwardList"
|
||||||
|
case clearCache = "clearCache"
|
||||||
|
case scrollTo = "scrollTo"
|
||||||
|
case scrollBy = "scrollBy"
|
||||||
|
case pauseTimers = "pauseTimers"
|
||||||
|
case resumeTimers = "resumeTimers"
|
||||||
|
case printCurrentPage = "printCurrentPage"
|
||||||
|
case getContentHeight = "getContentHeight"
|
||||||
|
case zoomBy = "zoomBy"
|
||||||
|
case reloadFromOrigin = "reloadFromOrigin"
|
||||||
|
case getOriginalUrl = "getOriginalUrl"
|
||||||
|
case getZoomScale = "getZoomScale"
|
||||||
|
case hasOnlySecureContent = "hasOnlySecureContent"
|
||||||
|
case getSelectedText = "getSelectedText"
|
||||||
|
case getHitTestResult = "getHitTestResult"
|
||||||
|
case clearFocus = "clearFocus"
|
||||||
|
case setContextMenu = "setContextMenu"
|
||||||
|
case requestFocusNodeHref = "requestFocusNodeHref"
|
||||||
|
case requestImageRef = "requestImageRef"
|
||||||
|
case getScrollX = "getScrollX"
|
||||||
|
case getScrollY = "getScrollY"
|
||||||
|
case getCertificate = "getCertificate"
|
||||||
|
case addUserScript = "addUserScript"
|
||||||
|
case removeUserScript = "removeUserScript"
|
||||||
|
case removeUserScriptsByGroupName = "removeUserScriptsByGroupName"
|
||||||
|
case removeAllUserScripts = "removeAllUserScripts"
|
||||||
|
case callAsyncJavaScript = "callAsyncJavaScript"
|
||||||
|
case createPdf = "createPdf"
|
||||||
|
case createWebArchiveData = "createWebArchiveData"
|
||||||
|
case saveWebArchive = "saveWebArchive"
|
||||||
|
case isSecureContext = "isSecureContext"
|
||||||
|
case createWebMessageChannel = "createWebMessageChannel"
|
||||||
|
case postWebMessage = "postWebMessage"
|
||||||
|
case addWebMessageListener = "addWebMessageListener"
|
||||||
|
case canScrollVertically = "canScrollVertically"
|
||||||
|
case canScrollHorizontally = "canScrollHorizontally"
|
||||||
|
case pauseAllMediaPlayback = "pauseAllMediaPlayback"
|
||||||
|
case setAllMediaPlaybackSuspended = "setAllMediaPlaybackSuspended"
|
||||||
|
case closeAllMediaPresentations = "closeAllMediaPresentations"
|
||||||
|
case requestMediaPlaybackState = "requestMediaPlaybackState"
|
||||||
|
case getMetaThemeColor = "getMetaThemeColor"
|
||||||
|
case isInFullscreen = "isInFullscreen"
|
||||||
|
case getCameraCaptureState = "getCameraCaptureState"
|
||||||
|
case setCameraCaptureState = "setCameraCaptureState"
|
||||||
|
case getMicrophoneCaptureState = "getMicrophoneCaptureState"
|
||||||
|
case setMicrophoneCaptureState = "setMicrophoneCaptureState"
|
||||||
|
case loadSimulatedRequest = "loadSimulatedRequest"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Cocoa
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class InAppWebViewFlutterPlugin: NSObject, FlutterPlugin {
|
||||||
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
|
SwiftFlutterPlugin.register(with: registrar)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
//
|
||||||
|
// InAppWebViewStatic.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 08/12/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class InAppWebViewStatic: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_static"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
static var webViewForUserAgent: WKWebView?
|
||||||
|
static var defaultUserAgent: String?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: InAppWebViewStatic.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
InAppWebViewStatic.registrar = registrar
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "getDefaultUserAgent":
|
||||||
|
InAppWebViewStatic.getDefaultUserAgent(completionHandler: { (value) in
|
||||||
|
result(value)
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case "handlesURLScheme":
|
||||||
|
let urlScheme = arguments!["urlScheme"] as! String
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
result(WKWebView.handlesURLScheme(urlScheme))
|
||||||
|
} else {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public func getDefaultUserAgent(completionHandler: @escaping (_ value: String?) -> Void) {
|
||||||
|
if defaultUserAgent == nil {
|
||||||
|
InAppWebViewStatic.webViewForUserAgent = WKWebView()
|
||||||
|
InAppWebViewStatic.webViewForUserAgent?.evaluateJavaScript("navigator.userAgent") { (value, error) in
|
||||||
|
|
||||||
|
if error != nil {
|
||||||
|
print("Error occured to get userAgent")
|
||||||
|
self.webViewForUserAgent = nil
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let unwrappedUserAgent = value as? String {
|
||||||
|
InAppWebViewStatic.defaultUserAgent = unwrappedUserAgent
|
||||||
|
completionHandler(defaultUserAgent)
|
||||||
|
} else {
|
||||||
|
print("Failed to get userAgent")
|
||||||
|
}
|
||||||
|
self.webViewForUserAgent = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completionHandler(defaultUserAgent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
InAppWebViewStatic.registrar = nil
|
||||||
|
InAppWebViewStatic.webViewForUserAgent = nil
|
||||||
|
InAppWebViewStatic.defaultUserAgent = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// LeakAvoider.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 15/12/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class LeakAvoider: NSObject {
|
||||||
|
weak var delegate : FlutterMethodCallDelegate?
|
||||||
|
|
||||||
|
init(delegate: FlutterMethodCallDelegate) {
|
||||||
|
super.init()
|
||||||
|
self.delegate = delegate
|
||||||
|
}
|
||||||
|
|
||||||
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
self.delegate?.handle(call, result: result)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
debugPrint("LeakAvoider - dealloc")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,309 @@
|
||||||
|
//
|
||||||
|
// MyCookieManager.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo on 26/10/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
@available(macOS 10.13, *)
|
||||||
|
public class MyCookieManager: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_cookiemanager"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
static var httpCookieStore: WKHTTPCookieStore?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: MyCookieManager.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
MyCookieManager.registrar = registrar
|
||||||
|
MyCookieManager.httpCookieStore = WKWebsiteDataStore.default().httpCookieStore
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
switch call.method {
|
||||||
|
case "setCookie":
|
||||||
|
let url = arguments!["url"] as! String
|
||||||
|
let name = arguments!["name"] as! String
|
||||||
|
let value = arguments!["value"] as! String
|
||||||
|
let path = arguments!["path"] as! String
|
||||||
|
|
||||||
|
var expiresDate: Int64?
|
||||||
|
if let expiresDateString = arguments!["expiresDate"] as? String {
|
||||||
|
expiresDate = Int64(expiresDateString)
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxAge = arguments!["maxAge"] as? Int64
|
||||||
|
let isSecure = arguments!["isSecure"] as? Bool
|
||||||
|
let isHttpOnly = arguments!["isHttpOnly"] as? Bool
|
||||||
|
let sameSite = arguments!["sameSite"] as? String
|
||||||
|
let domain = arguments!["domain"] as? String
|
||||||
|
|
||||||
|
MyCookieManager.setCookie(url: url,
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
path: path,
|
||||||
|
domain: domain,
|
||||||
|
expiresDate: expiresDate,
|
||||||
|
maxAge: maxAge,
|
||||||
|
isSecure: isSecure,
|
||||||
|
isHttpOnly: isHttpOnly,
|
||||||
|
sameSite: sameSite,
|
||||||
|
result: result)
|
||||||
|
break
|
||||||
|
case "getCookies":
|
||||||
|
let url = arguments!["url"] as! String
|
||||||
|
MyCookieManager.getCookies(url: url, result: result)
|
||||||
|
break
|
||||||
|
case "getAllCookies":
|
||||||
|
MyCookieManager.getAllCookies(result: result)
|
||||||
|
break
|
||||||
|
case "deleteCookie":
|
||||||
|
let url = arguments!["url"] as! String
|
||||||
|
let name = arguments!["name"] as! String
|
||||||
|
let path = arguments!["path"] as! String
|
||||||
|
let domain = arguments!["domain"] as? String
|
||||||
|
MyCookieManager.deleteCookie(url: url, name: name, path: path, domain: domain, result: result)
|
||||||
|
break;
|
||||||
|
case "deleteCookies":
|
||||||
|
let url = arguments!["url"] as! String
|
||||||
|
let path = arguments!["path"] as! String
|
||||||
|
let domain = arguments!["domain"] as? String
|
||||||
|
MyCookieManager.deleteCookies(url: url, path: path, domain: domain, result: result)
|
||||||
|
break;
|
||||||
|
case "deleteAllCookies":
|
||||||
|
MyCookieManager.deleteAllCookies(result: result)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func setCookie(url: String,
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
path: String,
|
||||||
|
domain: String?,
|
||||||
|
expiresDate: Int64?,
|
||||||
|
maxAge: Int64?,
|
||||||
|
isSecure: Bool?,
|
||||||
|
isHttpOnly: Bool?,
|
||||||
|
sameSite: String?,
|
||||||
|
result: @escaping FlutterResult) {
|
||||||
|
guard let httpCookieStore = MyCookieManager.httpCookieStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var properties: [HTTPCookiePropertyKey: Any] = [:]
|
||||||
|
properties[.originURL] = url
|
||||||
|
properties[.name] = name
|
||||||
|
properties[.value] = value
|
||||||
|
properties[.path] = path
|
||||||
|
|
||||||
|
if domain != nil {
|
||||||
|
properties[.domain] = domain
|
||||||
|
}
|
||||||
|
|
||||||
|
if expiresDate != nil {
|
||||||
|
// convert from milliseconds
|
||||||
|
properties[.expires] = Date(timeIntervalSince1970: TimeInterval(Double(expiresDate!)/1000))
|
||||||
|
}
|
||||||
|
if maxAge != nil {
|
||||||
|
properties[.maximumAge] = String(maxAge!)
|
||||||
|
}
|
||||||
|
if isSecure != nil && isSecure! {
|
||||||
|
properties[.secure] = "TRUE"
|
||||||
|
}
|
||||||
|
if isHttpOnly != nil && isHttpOnly! {
|
||||||
|
properties[.init("HttpOnly")] = "YES"
|
||||||
|
}
|
||||||
|
if sameSite != nil {
|
||||||
|
if #available(macOS 10.15, *) {
|
||||||
|
var sameSiteValue = HTTPCookieStringPolicy(rawValue: "None")
|
||||||
|
switch sameSite {
|
||||||
|
case "Lax":
|
||||||
|
sameSiteValue = HTTPCookieStringPolicy.sameSiteLax
|
||||||
|
case "Strict":
|
||||||
|
sameSiteValue = HTTPCookieStringPolicy.sameSiteStrict
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
properties[.sameSitePolicy] = sameSiteValue
|
||||||
|
} else {
|
||||||
|
properties[.init("SameSite")] = sameSite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookie = HTTPCookie(properties: properties)!
|
||||||
|
|
||||||
|
httpCookieStore.setCookie(cookie, completionHandler: {() in
|
||||||
|
result(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getCookies(url: String, result: @escaping FlutterResult) {
|
||||||
|
var cookieList: [[String: Any?]] = []
|
||||||
|
|
||||||
|
guard let httpCookieStore = MyCookieManager.httpCookieStore else {
|
||||||
|
result(cookieList)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let urlHost = URL(string: url)?.host {
|
||||||
|
httpCookieStore.getAllCookies { (cookies) in
|
||||||
|
for cookie in cookies {
|
||||||
|
if urlHost.hasSuffix(cookie.domain) || ".\(urlHost)".hasSuffix(cookie.domain) {
|
||||||
|
var sameSite: String? = nil
|
||||||
|
if #available(macOS 10.15, *) {
|
||||||
|
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
|
||||||
|
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresDateTimestamp: Int64 = -1
|
||||||
|
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
|
||||||
|
// convert to milliseconds
|
||||||
|
expiresDateTimestamp = Int64(expiresDate * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieList.append([
|
||||||
|
"name": cookie.name,
|
||||||
|
"value": cookie.value,
|
||||||
|
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
|
||||||
|
"isSessionOnly": cookie.isSessionOnly,
|
||||||
|
"domain": cookie.domain,
|
||||||
|
"sameSite": sameSite,
|
||||||
|
"isSecure": cookie.isSecure,
|
||||||
|
"isHttpOnly": cookie.isHTTPOnly,
|
||||||
|
"path": cookie.path,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result(cookieList)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
print("Cannot get WebView cookies. No HOST found for URL: \(url)")
|
||||||
|
}
|
||||||
|
|
||||||
|
result(cookieList)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getAllCookies(result: @escaping FlutterResult) {
|
||||||
|
var cookieList: [[String: Any?]] = []
|
||||||
|
|
||||||
|
guard let httpCookieStore = MyCookieManager.httpCookieStore else {
|
||||||
|
result(cookieList)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpCookieStore.getAllCookies { (cookies) in
|
||||||
|
for cookie in cookies {
|
||||||
|
var sameSite: String? = nil
|
||||||
|
if #available(macOS 10.15, *) {
|
||||||
|
if let sameSiteValue = cookie.sameSitePolicy?.rawValue {
|
||||||
|
sameSite = sameSiteValue.prefix(1).capitalized + sameSiteValue.dropFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresDateTimestamp: Int64 = -1
|
||||||
|
if let expiresDate = cookie.expiresDate?.timeIntervalSince1970 {
|
||||||
|
// convert to milliseconds
|
||||||
|
expiresDateTimestamp = Int64(expiresDate * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieList.append([
|
||||||
|
"name": cookie.name,
|
||||||
|
"value": cookie.value,
|
||||||
|
"expiresDate": expiresDateTimestamp != -1 ? expiresDateTimestamp : nil,
|
||||||
|
"isSessionOnly": cookie.isSessionOnly,
|
||||||
|
"domain": cookie.domain,
|
||||||
|
"sameSite": sameSite,
|
||||||
|
"isSecure": cookie.isSecure,
|
||||||
|
"isHttpOnly": cookie.isHTTPOnly,
|
||||||
|
"path": cookie.path,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
result(cookieList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func deleteCookie(url: String, name: String, path: String, domain: String?, result: @escaping FlutterResult) {
|
||||||
|
guard let httpCookieStore = MyCookieManager.httpCookieStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var domain = domain
|
||||||
|
httpCookieStore.getAllCookies { (cookies) in
|
||||||
|
for cookie in cookies {
|
||||||
|
var originURL = url
|
||||||
|
if cookie.properties![.originURL] is String {
|
||||||
|
originURL = cookie.properties![.originURL] as! String
|
||||||
|
}
|
||||||
|
else if cookie.properties![.originURL] is URL {
|
||||||
|
originURL = (cookie.properties![.originURL] as! URL).absoluteString
|
||||||
|
}
|
||||||
|
if domain == nil, let domainUrl = URL(string: originURL) {
|
||||||
|
domain = domainUrl.host
|
||||||
|
}
|
||||||
|
if let domain = domain, cookie.domain == domain, cookie.name == name, cookie.path == path {
|
||||||
|
httpCookieStore.delete(cookie, completionHandler: {
|
||||||
|
result(true)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func deleteCookies(url: String, path: String, domain: String?, result: @escaping FlutterResult) {
|
||||||
|
guard let httpCookieStore = MyCookieManager.httpCookieStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var domain = domain
|
||||||
|
httpCookieStore.getAllCookies { (cookies) in
|
||||||
|
for cookie in cookies {
|
||||||
|
var originURL = url
|
||||||
|
if cookie.properties![.originURL] is String {
|
||||||
|
originURL = cookie.properties![.originURL] as! String
|
||||||
|
}
|
||||||
|
else if cookie.properties![.originURL] is URL {
|
||||||
|
originURL = (cookie.properties![.originURL] as! URL).absoluteString
|
||||||
|
}
|
||||||
|
if domain == nil, let domainUrl = URL(string: originURL) {
|
||||||
|
domain = domainUrl.host
|
||||||
|
}
|
||||||
|
if let domain = domain, cookie.domain == domain, cookie.path == path {
|
||||||
|
httpCookieStore.delete(cookie, completionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func deleteAllCookies(result: @escaping FlutterResult) {
|
||||||
|
let websiteDataTypes = NSSet(array: [WKWebsiteDataTypeCookies])
|
||||||
|
let date = NSDate(timeIntervalSince1970: 0)
|
||||||
|
WKWebsiteDataStore.default().removeData(ofTypes: websiteDataTypes as! Set<String>, modifiedSince: date as Date, completionHandler:{
|
||||||
|
result(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
MyCookieManager.registrar = nil
|
||||||
|
MyCookieManager.httpCookieStore = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
//
|
||||||
|
// MyWebStorageManager.swift
|
||||||
|
// connectivity
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/12/2019.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import WebKit
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class MyWebStorageManager: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_webstoragemanager"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
static var websiteDataStore: WKWebsiteDataStore?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: MyWebStorageManager.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
MyWebStorageManager.registrar = registrar
|
||||||
|
MyWebStorageManager.websiteDataStore = WKWebsiteDataStore.default()
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
switch call.method {
|
||||||
|
case "fetchDataRecords":
|
||||||
|
let dataTypes = Set(arguments!["dataTypes"] as! [String])
|
||||||
|
MyWebStorageManager.fetchDataRecords(dataTypes: dataTypes, result: result)
|
||||||
|
break
|
||||||
|
case "removeDataFor":
|
||||||
|
let dataTypes = Set(arguments!["dataTypes"] as! [String])
|
||||||
|
let recordList = arguments!["recordList"] as! [[String: Any?]]
|
||||||
|
MyWebStorageManager.removeDataFor(dataTypes: dataTypes, recordList: recordList, result: result)
|
||||||
|
break
|
||||||
|
case "removeDataModifiedSince":
|
||||||
|
let dataTypes = Set(arguments!["dataTypes"] as! [String])
|
||||||
|
let timestamp = arguments!["timestamp"] as! Int64
|
||||||
|
MyWebStorageManager.removeDataModifiedSince(dataTypes: dataTypes, timestamp: timestamp, result: result)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func fetchDataRecords(dataTypes: Set<String>, result: @escaping FlutterResult) {
|
||||||
|
var recordList: [[String: Any?]] = []
|
||||||
|
|
||||||
|
guard let websiteDataStore = MyWebStorageManager.websiteDataStore else {
|
||||||
|
result(recordList)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in
|
||||||
|
for record in data {
|
||||||
|
recordList.append([
|
||||||
|
"displayName": record.displayName,
|
||||||
|
"dataTypes": record.dataTypes.map({ (dataType) -> String in
|
||||||
|
return dataType
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
result(recordList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func removeDataFor(dataTypes: Set<String>, recordList: [[String: Any?]], result: @escaping FlutterResult) {
|
||||||
|
var records: [WKWebsiteDataRecord] = []
|
||||||
|
|
||||||
|
guard let websiteDataStore = MyWebStorageManager.websiteDataStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
websiteDataStore.fetchDataRecords(ofTypes: dataTypes) { (data) in
|
||||||
|
for record in data {
|
||||||
|
for r in recordList {
|
||||||
|
let displayName = r["displayName"] as! String
|
||||||
|
if (record.displayName == displayName) {
|
||||||
|
records.append(record)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
websiteDataStore.removeData(ofTypes: dataTypes, for: records) {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func removeDataModifiedSince(dataTypes: Set<String>, timestamp: Int64, result: @escaping FlutterResult) {
|
||||||
|
guard let websiteDataStore = MyWebStorageManager.websiteDataStore else {
|
||||||
|
result(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp))
|
||||||
|
websiteDataStore.removeData(ofTypes: dataTypes, modifiedSince: date as Date) {
|
||||||
|
result(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
MyWebStorageManager.registrar = nil
|
||||||
|
MyWebStorageManager.websiteDataStore = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
//
|
||||||
|
// PlatformUtil.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 01/03/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class PlatformUtil: ChannelDelegate {
|
||||||
|
static let METHOD_CHANNEL_NAME = "com.pichillilorenzo/flutter_inappwebview_platformutil"
|
||||||
|
static var registrar: FlutterPluginRegistrar?
|
||||||
|
|
||||||
|
init(registrar: FlutterPluginRegistrar) {
|
||||||
|
super.init(channel: FlutterMethodChannel(name: PlatformUtil.METHOD_CHANNEL_NAME, binaryMessenger: registrar.messenger))
|
||||||
|
InAppWebViewStatic.registrar = registrar
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "getSystemVersion":
|
||||||
|
result(ProcessInfo.processInfo.operatingSystemVersionString)
|
||||||
|
break
|
||||||
|
case "formatDate":
|
||||||
|
let date = arguments!["date"] as! Int64
|
||||||
|
let format = arguments!["format"] as! String
|
||||||
|
let locale = PlatformUtil.getLocaleFromString(locale: arguments!["locale"] as? String)
|
||||||
|
let timezone = TimeZone.init(abbreviation: arguments!["timezone"] as? String ?? "UTC")!
|
||||||
|
result(PlatformUtil.formatDate(date: date, format: format, locale: locale, timezone: timezone))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static public func getLocaleFromString(locale: String?) -> Locale {
|
||||||
|
guard let locale = locale else {
|
||||||
|
return Locale.init(identifier: "en_US")
|
||||||
|
}
|
||||||
|
return Locale.init(identifier: locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
static public func getDateFromMilliseconds(date: Int64) -> Date {
|
||||||
|
return Date(timeIntervalSince1970: TimeInterval(Double(date)/1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
static public func formatDate(date: Int64, format: String, locale: Locale, timezone: TimeZone) -> String {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = format
|
||||||
|
formatter.timeZone = timezone
|
||||||
|
return formatter.string(from: PlatformUtil.getDateFromMilliseconds(date: date))
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
PlatformUtil.registrar = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// CallAsyncJavaScriptBelowIOS14WrapperJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let CALL_ASYNC_JAVASCRIPT_BELOW_IOS_14_WRAPPER_JS = """
|
||||||
|
(function(obj) {
|
||||||
|
(async function(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_NAMES) {
|
||||||
|
\(PluginScriptsUtil.VAR_FUNCTION_BODY)
|
||||||
|
})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENT_VALUES)).then(function(value) {
|
||||||
|
window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': value, 'error': null, 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'});
|
||||||
|
}).catch(function(error) {
|
||||||
|
window.webkit.messageHandlers['onCallAsyncJavaScriptResultBelowIOS14Received'].postMessage({'value': null, 'error': error + '', 'resultUuid': '\(PluginScriptsUtil.VAR_RESULT_UUID)'});
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
})(\(PluginScriptsUtil.VAR_FUNCTION_ARGUMENTS_OBJ));
|
||||||
|
"""
|
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// ConsoleLogJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_CONSOLE_LOG_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let CONSOLE_LOG_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: CONSOLE_LOG_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: CONSOLE_LOG_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: true,
|
||||||
|
messageHandlerNames: ["consoleLog", "consoleDebug", "consoleError", "consoleInfo", "consoleWarn"])
|
||||||
|
|
||||||
|
// the message needs to be concatenated with '' in order to have the same behavior like on Android
|
||||||
|
let CONSOLE_LOG_JS_SOURCE = """
|
||||||
|
(function(console) {
|
||||||
|
|
||||||
|
var oldLogs = {
|
||||||
|
'consoleLog': console.log,
|
||||||
|
'consoleDebug': console.debug,
|
||||||
|
'consoleError': console.error,
|
||||||
|
'consoleInfo': console.info,
|
||||||
|
'consoleWarn': console.warn
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var k in oldLogs) {
|
||||||
|
(function(oldLog) {
|
||||||
|
console[oldLog.replace('console', '').toLowerCase()] = function() {
|
||||||
|
oldLogs[oldLog].apply(null, arguments);
|
||||||
|
var args = arguments;
|
||||||
|
|
||||||
|
// on iOS, for some reason, accessing the arguments object synchronously can throw some errors, such as "TypeError"
|
||||||
|
// see https://github.com/pichillilorenzo/flutter_inappwebview/issues/776
|
||||||
|
setTimeout(function() {
|
||||||
|
var message = '';
|
||||||
|
for (var i in args) {
|
||||||
|
if (message == '') {
|
||||||
|
message += args[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message += ' ' + args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
window.webkit.messageHandlers[oldLog].postMessage({'message': message, '_windowId': _windowId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(k);
|
||||||
|
}
|
||||||
|
})(window.console);
|
||||||
|
"""
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// EnableViewportScaleJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: ENABLE_VIEWPORT_SCALE_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: ENABLE_VIEWPORT_SCALE_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentEnd,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let ENABLE_VIEWPORT_SCALE_JS_SOURCE = """
|
||||||
|
(function() {
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.setAttribute('name', 'viewport');
|
||||||
|
meta.setAttribute('content', 'width=device-width');
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
})()
|
||||||
|
"""
|
||||||
|
|
||||||
|
let NOT_ENABLE_VIEWPORT_SCALE_JS_SOURCE = """
|
||||||
|
(function() {
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.setAttribute('name', 'viewport');
|
||||||
|
meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent);
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
})()
|
||||||
|
"""
|
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// FindElementsAtPointJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: FIND_ELEMENTS_AT_POINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: FIND_ELEMENTS_AT_POINT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
/**
|
||||||
|
https://developer.android.com/reference/android/webkit/WebView.HitTestResult
|
||||||
|
*/
|
||||||
|
let FIND_ELEMENTS_AT_POINT_JS_SOURCE = """
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
|
||||||
|
var hitTestResultType = {
|
||||||
|
UNKNOWN_TYPE: 0,
|
||||||
|
PHONE_TYPE: 2,
|
||||||
|
GEO_TYPE: 3,
|
||||||
|
EMAIL_TYPE: 4,
|
||||||
|
IMAGE_TYPE: 5,
|
||||||
|
SRC_ANCHOR_TYPE: 7,
|
||||||
|
SRC_IMAGE_ANCHOR_TYPE: 8,
|
||||||
|
EDIT_TEXT_TYPE: 9
|
||||||
|
};
|
||||||
|
var element = document.elementFromPoint(x, y);
|
||||||
|
var data = {
|
||||||
|
type: 0,
|
||||||
|
extra: null
|
||||||
|
};
|
||||||
|
while (element) {
|
||||||
|
if (element.tagName === 'IMG' && element.src) {
|
||||||
|
if (element.parentNode && element.parentNode.tagName === 'A' && element.parentNode.href) {
|
||||||
|
data.type = hitTestResultType.SRC_IMAGE_ANCHOR_TYPE;
|
||||||
|
} else {
|
||||||
|
data.type = hitTestResultType.IMAGE_TYPE;
|
||||||
|
}
|
||||||
|
data.extra = element.src;
|
||||||
|
break;
|
||||||
|
} else if (element.tagName === 'A' && element.href) {
|
||||||
|
if (element.href.indexOf('mailto:') === 0) {
|
||||||
|
data.type = hitTestResultType.EMAIL_TYPE;
|
||||||
|
data.extra = element.href.replace('mailto:', '');
|
||||||
|
} else if (element.href.indexOf('tel:') === 0) {
|
||||||
|
data.type = hitTestResultType.PHONE_TYPE;
|
||||||
|
data.extra = element.href.replace('tel:', '');
|
||||||
|
} else if (element.href.indexOf('geo:') === 0) {
|
||||||
|
data.type = hitTestResultType.GEO_TYPE;
|
||||||
|
data.extra = element.href.replace('geo:', '');
|
||||||
|
} else {
|
||||||
|
data.type = hitTestResultType.SRC_ANCHOR_TYPE;
|
||||||
|
data.extra = element.href;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (
|
||||||
|
(element.tagName === 'INPUT' && ['text', 'email', 'password', 'number', 'search', 'tel', 'url'].indexOf(element.type) >= 0) ||
|
||||||
|
element.tagName === 'TEXTAREA') {
|
||||||
|
data.type = hitTestResultType.EDIT_TEXT_TYPE
|
||||||
|
}
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
"""
|
|
@ -0,0 +1,187 @@
|
||||||
|
//
|
||||||
|
// FindTextHighlightJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT"
|
||||||
|
let FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._searchResultCount"
|
||||||
|
let FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._currentHighlight"
|
||||||
|
let FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._isDoneCounting"
|
||||||
|
|
||||||
|
let FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: FIND_TEXT_HIGHLIGHT_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: FIND_TEXT_HIGHLIGHT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: ["onFindResultReceived"])
|
||||||
|
|
||||||
|
let FIND_TEXT_HIGHLIGHT_JS_SOURCE = """
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0;
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0;
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false;
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, keyword) {
|
||||||
|
if (element) {
|
||||||
|
if (element.nodeType == 3) {
|
||||||
|
// Text node
|
||||||
|
|
||||||
|
var elementTmp = element;
|
||||||
|
while (true) {
|
||||||
|
var value = elementTmp.nodeValue; // Search for keyword in text node
|
||||||
|
var idx = value.toLowerCase().indexOf(keyword);
|
||||||
|
|
||||||
|
if (idx < 0) break;
|
||||||
|
|
||||||
|
var span = document.createElement("span");
|
||||||
|
var text = document.createTextNode(value.substr(idx, keyword.length));
|
||||||
|
span.appendChild(text);
|
||||||
|
|
||||||
|
span.setAttribute(
|
||||||
|
"id",
|
||||||
|
"\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
|
||||||
|
);
|
||||||
|
span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight");
|
||||||
|
var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00";
|
||||||
|
span.setAttribute("style", "color: #000 !important; background: " + backgroundColor + " !important; padding: 0px !important; margin: 0px !important; border: 0px !important;");
|
||||||
|
|
||||||
|
text = document.createTextNode(value.substr(idx + keyword.length));
|
||||||
|
element.deleteData(idx, value.length - idx);
|
||||||
|
|
||||||
|
var next = element.nextSibling;
|
||||||
|
element.parentNode.insertBefore(span, next);
|
||||||
|
element.parentNode.insertBefore(text, next);
|
||||||
|
element = text;
|
||||||
|
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)++;
|
||||||
|
elementTmp = document.createTextNode(
|
||||||
|
value.substr(idx + keyword.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
|
||||||
|
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
|
||||||
|
{
|
||||||
|
'findResult': {
|
||||||
|
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
|
||||||
|
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
|
||||||
|
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
|
||||||
|
},
|
||||||
|
'_windowId': _windowId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (element.nodeType == 1) {
|
||||||
|
// Element node
|
||||||
|
if (
|
||||||
|
element.style.display != "none" &&
|
||||||
|
element.nodeName.toLowerCase() != "select"
|
||||||
|
) {
|
||||||
|
for (var i = element.childNodes.length - 1; i >= 0; i--) {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(
|
||||||
|
element.childNodes[element.childNodes.length - 1 - i],
|
||||||
|
keyword
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the main entry point to start the search
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync = function(keyword) {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement(document.body, keyword.toLowerCase());
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = true;
|
||||||
|
|
||||||
|
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
|
||||||
|
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
|
||||||
|
{
|
||||||
|
'findResult': {
|
||||||
|
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
|
||||||
|
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
|
||||||
|
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
|
||||||
|
},
|
||||||
|
'_windowId': _windowId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function, recursively removes the highlights in elements and their childs
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement = function(element) {
|
||||||
|
if (element) {
|
||||||
|
if (element.nodeType == 1) {
|
||||||
|
if (element.getAttribute("class") == "\(JAVASCRIPT_BRIDGE_NAME)_Highlight") {
|
||||||
|
var text = element.removeChild(element.firstChild);
|
||||||
|
element.parentNode.insertBefore(text, element);
|
||||||
|
element.parentNode.removeChild(element);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
var normalize = false;
|
||||||
|
for (var i = element.childNodes.length - 1; i >= 0; i--) {
|
||||||
|
if (window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(element.childNodes[i])) {
|
||||||
|
normalize = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (normalize) {
|
||||||
|
element.normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the main entry point to remove the highlights
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches = function() {
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) = 0;
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = 0;
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE) = false;
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatchesForElement(document.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._findNext = function(forward) {
|
||||||
|
if (\(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) <= 0) return;
|
||||||
|
|
||||||
|
var idx = \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) + (forward ? +1 : -1);
|
||||||
|
idx =
|
||||||
|
idx < 0
|
||||||
|
? \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) - 1
|
||||||
|
: idx >= \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
|
||||||
|
? 0
|
||||||
|
: idx;
|
||||||
|
\(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE) = idx;
|
||||||
|
|
||||||
|
var scrollTo = document.getElementById("\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + idx);
|
||||||
|
if (scrollTo) {
|
||||||
|
var highlights = document.getElementsByClassName("\(JAVASCRIPT_BRIDGE_NAME)_Highlight");
|
||||||
|
for (var i = 0; i < highlights.length; i++) {
|
||||||
|
var span = highlights[i];
|
||||||
|
span.style.backgroundColor = "#FFFF00";
|
||||||
|
}
|
||||||
|
scrollTo.style.backgroundColor = "#FF9732";
|
||||||
|
|
||||||
|
scrollTo.scrollIntoView({
|
||||||
|
behavior: "auto",
|
||||||
|
block: "center"
|
||||||
|
});
|
||||||
|
|
||||||
|
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
|
||||||
|
window.webkit.messageHandlers["onFindResultReceived"].postMessage(
|
||||||
|
{
|
||||||
|
'findResult': {
|
||||||
|
'activeMatchOrdinal': \(FIND_TEXT_HIGHLIGHT_CURRENT_HIGHLIGHT_JS_SOURCE),
|
||||||
|
'numberOfMatches': \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE),
|
||||||
|
'isDoneCounting': \(FIND_TEXT_HIGHLIGHT_IS_DONE_COUNTING_JS_SOURCE)
|
||||||
|
},
|
||||||
|
'_windowId': _windowId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
|
@ -0,0 +1,251 @@
|
||||||
|
//
|
||||||
|
// InterceptAjaxRequestsJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT"
|
||||||
|
let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptAjaxRequest"
|
||||||
|
|
||||||
|
let INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: INTERCEPT_AJAX_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: INTERCEPT_AJAX_REQUEST_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: true,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let INTERCEPT_AJAX_REQUEST_JS_SOURCE = """
|
||||||
|
\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) = true;
|
||||||
|
(function(ajax) {
|
||||||
|
var send = ajax.prototype.send;
|
||||||
|
var open = ajax.prototype.open;
|
||||||
|
var setRequestHeader = ajax.prototype.setRequestHeader;
|
||||||
|
ajax.prototype._flutter_inappwebview_url = null;
|
||||||
|
ajax.prototype._flutter_inappwebview_method = null;
|
||||||
|
ajax.prototype._flutter_inappwebview_isAsync = null;
|
||||||
|
ajax.prototype._flutter_inappwebview_user = null;
|
||||||
|
ajax.prototype._flutter_inappwebview_password = null;
|
||||||
|
ajax.prototype._flutter_inappwebview_password = null;
|
||||||
|
ajax.prototype._flutter_inappwebview_already_onreadystatechange_wrapped = false;
|
||||||
|
ajax.prototype._flutter_inappwebview_request_headers = {};
|
||||||
|
function convertRequestResponse(request, callback) {
|
||||||
|
if (request.response != null && request.responseType != null) {
|
||||||
|
switch (request.responseType) {
|
||||||
|
case 'arraybuffer':
|
||||||
|
callback(new Uint8Array(request.response));
|
||||||
|
return;
|
||||||
|
case 'blob':
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener('loadend', function() {
|
||||||
|
callback(new Uint8Array(reader.result));
|
||||||
|
});
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
return;
|
||||||
|
case 'document':
|
||||||
|
callback(request.response.documentElement.outerHTML);
|
||||||
|
return;
|
||||||
|
case 'json':
|
||||||
|
callback(request.response);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
};
|
||||||
|
ajax.prototype.open = function(method, url, isAsync, user, password) {
|
||||||
|
isAsync = (isAsync != null) ? isAsync : true;
|
||||||
|
this._flutter_inappwebview_url = url;
|
||||||
|
this._flutter_inappwebview_method = method;
|
||||||
|
this._flutter_inappwebview_isAsync = isAsync;
|
||||||
|
this._flutter_inappwebview_user = user;
|
||||||
|
this._flutter_inappwebview_password = password;
|
||||||
|
this._flutter_inappwebview_request_headers = {};
|
||||||
|
open.call(this, method, url, isAsync, user, password);
|
||||||
|
};
|
||||||
|
ajax.prototype.setRequestHeader = function(header, value) {
|
||||||
|
this._flutter_inappwebview_request_headers[header] = value;
|
||||||
|
setRequestHeader.call(this, header, value);
|
||||||
|
};
|
||||||
|
function handleEvent(e) {
|
||||||
|
var self = this;
|
||||||
|
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) {
|
||||||
|
var headers = this.getAllResponseHeaders();
|
||||||
|
var responseHeaders = {};
|
||||||
|
if (headers != null) {
|
||||||
|
var arr = headers.trim().split(/[\\r\\n]+/);
|
||||||
|
arr.forEach(function (line) {
|
||||||
|
var parts = line.split(': ');
|
||||||
|
var header = parts.shift();
|
||||||
|
var value = parts.join(': ');
|
||||||
|
responseHeaders[header] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
convertRequestResponse(this, function(response) {
|
||||||
|
var ajaxRequest = {
|
||||||
|
method: self._flutter_inappwebview_method,
|
||||||
|
url: self._flutter_inappwebview_url,
|
||||||
|
isAsync: self._flutter_inappwebview_isAsync,
|
||||||
|
user: self._flutter_inappwebview_user,
|
||||||
|
password: self._flutter_inappwebview_password,
|
||||||
|
withCredentials: self.withCredentials,
|
||||||
|
headers: self._flutter_inappwebview_request_headers,
|
||||||
|
readyState: self.readyState,
|
||||||
|
status: self.status,
|
||||||
|
responseURL: self.responseURL,
|
||||||
|
responseType: self.responseType,
|
||||||
|
response: response,
|
||||||
|
responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
|
||||||
|
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
|
||||||
|
statusText: self.statusText,
|
||||||
|
responseHeaders, responseHeaders,
|
||||||
|
event: {
|
||||||
|
type: e.type,
|
||||||
|
loaded: e.loaded,
|
||||||
|
lengthComputable: e.lengthComputable,
|
||||||
|
total: e.total
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxProgress', ajaxRequest).then(function(result) {
|
||||||
|
if (result != null) {
|
||||||
|
switch (result) {
|
||||||
|
case 0:
|
||||||
|
self.abort();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ajax.prototype.send = function(data) {
|
||||||
|
var self = this;
|
||||||
|
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) {
|
||||||
|
if (!this._flutter_inappwebview_already_onreadystatechange_wrapped) {
|
||||||
|
this._flutter_inappwebview_already_onreadystatechange_wrapped = true;
|
||||||
|
var onreadystatechange = this.onreadystatechange;
|
||||||
|
this.onreadystatechange = function() {
|
||||||
|
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_AJAX_REQUEST_JS_SOURCE) == true) {
|
||||||
|
var headers = this.getAllResponseHeaders();
|
||||||
|
var responseHeaders = {};
|
||||||
|
if (headers != null) {
|
||||||
|
var arr = headers.trim().split(/[\\r\\n]+/);
|
||||||
|
arr.forEach(function (line) {
|
||||||
|
var parts = line.split(': ');
|
||||||
|
var header = parts.shift();
|
||||||
|
var value = parts.join(': ');
|
||||||
|
responseHeaders[header] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
convertRequestResponse(this, function(response) {
|
||||||
|
var ajaxRequest = {
|
||||||
|
method: self._flutter_inappwebview_method,
|
||||||
|
url: self._flutter_inappwebview_url,
|
||||||
|
isAsync: self._flutter_inappwebview_isAsync,
|
||||||
|
user: self._flutter_inappwebview_user,
|
||||||
|
password: self._flutter_inappwebview_password,
|
||||||
|
withCredentials: self.withCredentials,
|
||||||
|
headers: self._flutter_inappwebview_request_headers,
|
||||||
|
readyState: self.readyState,
|
||||||
|
status: self.status,
|
||||||
|
responseURL: self.responseURL,
|
||||||
|
responseType: self.responseType,
|
||||||
|
response: response,
|
||||||
|
responseText: (self.responseType == 'text' || self.responseType == '') ? self.responseText : null,
|
||||||
|
responseXML: (self.responseType == 'document' && self.responseXML != null) ? self.responseXML.documentElement.outerHTML : null,
|
||||||
|
statusText: self.statusText,
|
||||||
|
responseHeaders: responseHeaders
|
||||||
|
};
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onAjaxReadyStateChange', ajaxRequest).then(function(result) {
|
||||||
|
if (result != null) {
|
||||||
|
switch (result) {
|
||||||
|
case 0:
|
||||||
|
self.abort();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (onreadystatechange != null) {
|
||||||
|
onreadystatechange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (onreadystatechange != null) {
|
||||||
|
onreadystatechange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.addEventListener('loadstart', handleEvent);
|
||||||
|
this.addEventListener('load', handleEvent);
|
||||||
|
this.addEventListener('loadend', handleEvent);
|
||||||
|
this.addEventListener('progress', handleEvent);
|
||||||
|
this.addEventListener('error', handleEvent);
|
||||||
|
this.addEventListener('abort', handleEvent);
|
||||||
|
this.addEventListener('timeout', handleEvent);
|
||||||
|
\(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(data).then(function(data) {
|
||||||
|
var ajaxRequest = {
|
||||||
|
data: data,
|
||||||
|
method: self._flutter_inappwebview_method,
|
||||||
|
url: self._flutter_inappwebview_url,
|
||||||
|
isAsync: self._flutter_inappwebview_isAsync,
|
||||||
|
user: self._flutter_inappwebview_user,
|
||||||
|
password: self._flutter_inappwebview_password,
|
||||||
|
withCredentials: self.withCredentials,
|
||||||
|
headers: self._flutter_inappwebview_request_headers,
|
||||||
|
responseType: self.responseType
|
||||||
|
};
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptAjaxRequest', ajaxRequest).then(function(result) {
|
||||||
|
if (result != null) {
|
||||||
|
switch (result) {
|
||||||
|
case 0:
|
||||||
|
self.abort();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (result.data != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) && result.data.length > 0) {
|
||||||
|
var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.data);
|
||||||
|
if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) {
|
||||||
|
var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString);
|
||||||
|
if (result.headers != null) {
|
||||||
|
result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];
|
||||||
|
} else {
|
||||||
|
result.headers = { 'Content-Type': formDataContentType };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.data) || result.data == null) {
|
||||||
|
data = result.data;
|
||||||
|
} else if (result.data.length > 0) {
|
||||||
|
data = new Uint8Array(result.data);
|
||||||
|
}
|
||||||
|
self.withCredentials = result.withCredentials;
|
||||||
|
if (result.responseType != null) {
|
||||||
|
self.responseType = result.responseType;
|
||||||
|
};
|
||||||
|
if (result.headers != null) {
|
||||||
|
for (var header in result.headers) {
|
||||||
|
var value = result.headers[header];
|
||||||
|
var flutter_inappwebview_value = self._flutter_inappwebview_request_headers[header];
|
||||||
|
if (flutter_inappwebview_value == null) {
|
||||||
|
self._flutter_inappwebview_request_headers[header] = value;
|
||||||
|
} else {
|
||||||
|
self._flutter_inappwebview_request_headers[header] += ', ' + value;
|
||||||
|
}
|
||||||
|
setRequestHeader.call(self, header, value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ((self._flutter_inappwebview_method != result.method && result.method != null) || (self._flutter_inappwebview_url != result.url && result.url != null)) {
|
||||||
|
self.abort();
|
||||||
|
self.open(result.method, result.url, result.isAsync, result.user, result.password);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send.call(self, data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
send.call(this, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(window.XMLHttpRequest);
|
||||||
|
"""
|
|
@ -0,0 +1,153 @@
|
||||||
|
//
|
||||||
|
// InterceptFetchRequestsJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT"
|
||||||
|
let FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useShouldInterceptFetchRequest"
|
||||||
|
|
||||||
|
let INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: INTERCEPT_FETCH_REQUEST_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: INTERCEPT_FETCH_REQUEST_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: true,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let INTERCEPT_FETCH_REQUEST_JS_SOURCE = """
|
||||||
|
\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) = true;
|
||||||
|
(function(fetch) {
|
||||||
|
if (fetch == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.fetch = async function(resource, init) {
|
||||||
|
if (\(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_SHOULD_INTERCEPT_FETCH_REQUEST_JS_SOURCE) == true) {
|
||||||
|
var fetchRequest = {
|
||||||
|
url: null,
|
||||||
|
method: null,
|
||||||
|
headers: null,
|
||||||
|
body: null,
|
||||||
|
mode: null,
|
||||||
|
credentials: null,
|
||||||
|
cache: null,
|
||||||
|
redirect: null,
|
||||||
|
referrer: null,
|
||||||
|
referrerPolicy: null,
|
||||||
|
integrity: null,
|
||||||
|
keepalive: null
|
||||||
|
};
|
||||||
|
if (resource instanceof Request) {
|
||||||
|
fetchRequest.url = resource.url;
|
||||||
|
fetchRequest.method = resource.method;
|
||||||
|
fetchRequest.headers = resource.headers;
|
||||||
|
fetchRequest.body = resource.body;
|
||||||
|
fetchRequest.mode = resource.mode;
|
||||||
|
fetchRequest.credentials = resource.credentials;
|
||||||
|
fetchRequest.cache = resource.cache;
|
||||||
|
fetchRequest.redirect = resource.redirect;
|
||||||
|
fetchRequest.referrer = resource.referrer;
|
||||||
|
fetchRequest.referrerPolicy = resource.referrerPolicy;
|
||||||
|
fetchRequest.integrity = resource.integrity;
|
||||||
|
fetchRequest.keepalive = resource.keepalive;
|
||||||
|
} else {
|
||||||
|
fetchRequest.url = resource != null ? resource.toString() : null;
|
||||||
|
if (init != null) {
|
||||||
|
fetchRequest.method = init.method;
|
||||||
|
fetchRequest.headers = init.headers;
|
||||||
|
fetchRequest.body = init.body;
|
||||||
|
fetchRequest.mode = init.mode;
|
||||||
|
fetchRequest.credentials = init.credentials;
|
||||||
|
fetchRequest.cache = init.cache;
|
||||||
|
fetchRequest.redirect = init.redirect;
|
||||||
|
fetchRequest.referrer = init.referrer;
|
||||||
|
fetchRequest.referrerPolicy = init.referrerPolicy;
|
||||||
|
fetchRequest.integrity = init.integrity;
|
||||||
|
fetchRequest.keepalive = init.keepalive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fetchRequest.headers instanceof Headers) {
|
||||||
|
fetchRequest.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertHeadersToJson(fetchRequest.headers);
|
||||||
|
}
|
||||||
|
fetchRequest.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertCredentialsToJson(fetchRequest.credentials);
|
||||||
|
return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyRequest(fetchRequest.body).then(function(body) {
|
||||||
|
fetchRequest.body = body;
|
||||||
|
return window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('shouldInterceptFetchRequest', fetchRequest).then(function(result) {
|
||||||
|
if (result != null) {
|
||||||
|
switch (result.action) {
|
||||||
|
case 0:
|
||||||
|
var controller = new AbortController();
|
||||||
|
if (init != null) {
|
||||||
|
init.signal = controller.signal;
|
||||||
|
} else {
|
||||||
|
init = {
|
||||||
|
signal: controller.signal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
controller.abort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result.body != null && !\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) && result.body.length > 0) {
|
||||||
|
var bodyString = \(JAVASCRIPT_UTIL_VAR_NAME).arrayBufferToString(result.body);
|
||||||
|
if (\(JAVASCRIPT_UTIL_VAR_NAME).isBodyFormData(bodyString)) {
|
||||||
|
var formDataContentType = \(JAVASCRIPT_UTIL_VAR_NAME).getFormDataContentType(bodyString);
|
||||||
|
if (result.headers != null) {
|
||||||
|
result.headers['Content-Type'] = result.headers['Content-Type'] == null ? formDataContentType : result.headers['Content-Type'];
|
||||||
|
} else {
|
||||||
|
result.headers = { 'Content-Type': formDataContentType };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource = result.url;
|
||||||
|
if (init == null) {
|
||||||
|
init = {};
|
||||||
|
}
|
||||||
|
if (result.method != null && result.method.length > 0) {
|
||||||
|
init.method = result.method;
|
||||||
|
}
|
||||||
|
if (result.headers != null && Object.keys(result.headers).length > 0) {
|
||||||
|
init.headers = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToHeaders(result.headers);
|
||||||
|
}
|
||||||
|
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(result.body) || result.body == null) {
|
||||||
|
init.body = result.body;
|
||||||
|
} else if (result.body.length > 0) {
|
||||||
|
init.body = new Uint8Array(result.body);
|
||||||
|
}
|
||||||
|
if (result.mode != null && result.mode.length > 0) {
|
||||||
|
init.mode = result.mode;
|
||||||
|
}
|
||||||
|
if (result.credentials != null) {
|
||||||
|
init.credentials = \(JAVASCRIPT_UTIL_VAR_NAME).convertJsonToCredential(result.credentials);
|
||||||
|
}
|
||||||
|
if (result.cache != null && result.cache.length > 0) {
|
||||||
|
init.cache = result.cache;
|
||||||
|
}
|
||||||
|
if (result.redirect != null && result.redirect.length > 0) {
|
||||||
|
init.redirect = result.redirect;
|
||||||
|
}
|
||||||
|
if (result.referrer != null && result.referrer.length > 0) {
|
||||||
|
init.referrer = result.referrer;
|
||||||
|
}
|
||||||
|
if (result.referrerPolicy != null && result.referrerPolicy.length > 0) {
|
||||||
|
init.referrerPolicy = result.referrerPolicy;
|
||||||
|
}
|
||||||
|
if (result.integrity != null && result.integrity.length > 0) {
|
||||||
|
init.integrity = result.integrity;
|
||||||
|
}
|
||||||
|
if (result.keepalive != null) {
|
||||||
|
init.keepalive = result.keepalive;
|
||||||
|
}
|
||||||
|
return fetch(resource, init);
|
||||||
|
}
|
||||||
|
return fetch(resource, init);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return fetch(resource, init);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})(window.fetch);
|
||||||
|
"""
|
|
@ -0,0 +1,243 @@
|
||||||
|
//
|
||||||
|
// javaScriptBridgeJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview"
|
||||||
|
let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: JAVASCRIPT_BRIDGE_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: JAVASCRIPT_BRIDGE_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: true,
|
||||||
|
messageHandlerNames: ["callHandler"])
|
||||||
|
|
||||||
|
let JAVASCRIPT_BRIDGE_JS_SOURCE = """
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME) = {};
|
||||||
|
\(WEB_MESSAGE_CHANNELS_VARIABLE_NAME) = {};
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler = function() {
|
||||||
|
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
var _callHandlerID = setTimeout(function(){});
|
||||||
|
window.webkit.messageHandlers['callHandler'].postMessage( {'handlerName': arguments[0], '_callHandlerID': _callHandlerID, 'args': JSON.stringify(Array.prototype.slice.call(arguments, 1)), '_windowId': _windowId} );
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)[_callHandlerID] = resolve;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
\(WEB_MESSAGE_LISTENER_JS_SOURCE)
|
||||||
|
\(UTIL_JS_SOURCE)
|
||||||
|
"""
|
||||||
|
|
||||||
|
let PLATFORM_READY_JS_SOURCE = "window.dispatchEvent(new Event('flutterInAppWebViewPlatformReady'));";
|
||||||
|
|
||||||
|
let JAVASCRIPT_UTIL_VAR_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._Util"
|
||||||
|
|
||||||
|
/*
|
||||||
|
https://github.com/github/fetch/blob/master/fetch.js
|
||||||
|
*/
|
||||||
|
let UTIL_JS_SOURCE = """
|
||||||
|
\(JAVASCRIPT_UTIL_VAR_NAME) = {
|
||||||
|
support: {
|
||||||
|
searchParams: 'URLSearchParams' in window,
|
||||||
|
iterable: 'Symbol' in window && 'iterator' in Symbol,
|
||||||
|
blob:
|
||||||
|
'FileReader' in window &&
|
||||||
|
'Blob' in window &&
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
new Blob();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
formData: 'FormData' in window,
|
||||||
|
arrayBuffer: 'ArrayBuffer' in window
|
||||||
|
},
|
||||||
|
isDataView: function(obj) {
|
||||||
|
return obj && DataView.prototype.isPrototypeOf(obj);
|
||||||
|
},
|
||||||
|
fileReaderReady: function(reader) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
reader.onload = function() {
|
||||||
|
resolve(reader.result);
|
||||||
|
};
|
||||||
|
reader.onerror = function() {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
readBlobAsArrayBuffer: function(blob) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
var promise = \(JAVASCRIPT_UTIL_VAR_NAME).fileReaderReady(reader);
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
convertBodyToArrayBuffer: function(body) {
|
||||||
|
var viewClasses = [
|
||||||
|
'[object Int8Array]',
|
||||||
|
'[object Uint8Array]',
|
||||||
|
'[object Uint8ClampedArray]',
|
||||||
|
'[object Int16Array]',
|
||||||
|
'[object Uint16Array]',
|
||||||
|
'[object Int32Array]',
|
||||||
|
'[object Uint32Array]',
|
||||||
|
'[object Float32Array]',
|
||||||
|
'[object Float64Array]'
|
||||||
|
];
|
||||||
|
var isArrayBufferView = null;
|
||||||
|
if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer) {
|
||||||
|
isArrayBufferView =
|
||||||
|
ArrayBuffer.isView ||
|
||||||
|
function(obj) {
|
||||||
|
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyUsed = false;
|
||||||
|
|
||||||
|
this._bodyInit = body;
|
||||||
|
if (!body) {
|
||||||
|
this._bodyText = '';
|
||||||
|
} else if (typeof body === 'string') {
|
||||||
|
this._bodyText = body;
|
||||||
|
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.blob && Blob.prototype.isPrototypeOf(body)) {
|
||||||
|
this._bodyBlob = body;
|
||||||
|
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.formData && FormData.prototype.isPrototypeOf(body)) {
|
||||||
|
this._bodyFormData = body;
|
||||||
|
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
||||||
|
this._bodyText = body.toString();
|
||||||
|
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && \(JAVASCRIPT_UTIL_VAR_NAME).support.blob && \(JAVASCRIPT_UTIL_VAR_NAME).isDataView(body)) {
|
||||||
|
this._bodyArrayBuffer = bufferClone(body.buffer);
|
||||||
|
this._bodyInit = new Blob([this._bodyArrayBuffer]);
|
||||||
|
} else if (\(JAVASCRIPT_UTIL_VAR_NAME).support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
|
||||||
|
this._bodyArrayBuffer = bufferClone(body);
|
||||||
|
} else {
|
||||||
|
this._bodyText = body = Object.prototype.toString.call(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blob = function () {
|
||||||
|
if (bodyUsed) {
|
||||||
|
return Promise.reject(new TypeError('Already read'));
|
||||||
|
}
|
||||||
|
bodyUsed = true;
|
||||||
|
if (this._bodyBlob) {
|
||||||
|
return Promise.resolve(this._bodyBlob);
|
||||||
|
} else if (this._bodyArrayBuffer) {
|
||||||
|
return Promise.resolve(new Blob([this._bodyArrayBuffer]));
|
||||||
|
} else if (this._bodyFormData) {
|
||||||
|
throw new Error('could not read FormData body as blob');
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(new Blob([this._bodyText]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._bodyArrayBuffer) {
|
||||||
|
if (bodyUsed) {
|
||||||
|
return Promise.reject(new TypeError('Already read'));
|
||||||
|
}
|
||||||
|
bodyUsed = true;
|
||||||
|
if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
|
||||||
|
return Promise.resolve(
|
||||||
|
this._bodyArrayBuffer.buffer.slice(
|
||||||
|
this._bodyArrayBuffer.byteOffset,
|
||||||
|
this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(this._bodyArrayBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.blob().then(\(JAVASCRIPT_UTIL_VAR_NAME).readBlobAsArrayBuffer);
|
||||||
|
},
|
||||||
|
isString: function(variable) {
|
||||||
|
return typeof variable === 'string' || variable instanceof String;
|
||||||
|
},
|
||||||
|
convertBodyRequest: function(body) {
|
||||||
|
if (body == null) {
|
||||||
|
return new Promise((resolve, reject) => resolve(null));
|
||||||
|
}
|
||||||
|
if (\(JAVASCRIPT_UTIL_VAR_NAME).isString(body) || (\(JAVASCRIPT_UTIL_VAR_NAME).support.searchParams && body instanceof URLSearchParams)) {
|
||||||
|
return new Promise((resolve, reject) => resolve(body.toString()));
|
||||||
|
}
|
||||||
|
if (window.Response != null) {
|
||||||
|
return new Response(body).arrayBuffer().then(function(arrayBuffer) {
|
||||||
|
return Array.from(new Uint8Array(arrayBuffer));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return \(JAVASCRIPT_UTIL_VAR_NAME).convertBodyToArrayBuffer(body).then(function(arrayBuffer) {
|
||||||
|
return Array.from(new Uint8Array(arrayBuffer));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
arrayBufferToString: function(arrayBuffer) {
|
||||||
|
var uint8Array = new Uint8Array(arrayBuffer);
|
||||||
|
return uint8Array.reduce(function(acc, i) { return acc += String.fromCharCode.apply(null, [i]); }, '');
|
||||||
|
},
|
||||||
|
isBodyFormData: function(bodyString) {
|
||||||
|
return bodyString.indexOf('------WebKitFormBoundary') >= 0;
|
||||||
|
},
|
||||||
|
getFormDataContentType: function(bodyString) {
|
||||||
|
var boundary = bodyString.substr(2, 40);
|
||||||
|
return 'multipart/form-data; boundary=' + boundary;
|
||||||
|
},
|
||||||
|
convertHeadersToJson: function(headers) {
|
||||||
|
var headersObj = {};
|
||||||
|
for (var header of headers.keys()) {
|
||||||
|
var value = headers.get(header);
|
||||||
|
headersObj[header] = value;
|
||||||
|
}
|
||||||
|
return headersObj;
|
||||||
|
},
|
||||||
|
convertJsonToHeaders: function(headersJson) {
|
||||||
|
return new Headers(headersJson);
|
||||||
|
},
|
||||||
|
convertCredentialsToJson: function(credentials) {
|
||||||
|
var credentialsObj = {};
|
||||||
|
if (window.FederatedCredential != null && credentials instanceof FederatedCredential) {
|
||||||
|
credentialsObj.type = credentials.type;
|
||||||
|
credentialsObj.id = credentials.id;
|
||||||
|
credentialsObj.name = credentials.name;
|
||||||
|
credentialsObj.protocol = credentials.protocol;
|
||||||
|
credentialsObj.provider = credentials.provider;
|
||||||
|
credentialsObj.iconURL = credentials.iconURL;
|
||||||
|
} else if (window.PasswordCredential != null && credentials instanceof PasswordCredential) {
|
||||||
|
credentialsObj.type = credentials.type;
|
||||||
|
credentialsObj.id = credentials.id;
|
||||||
|
credentialsObj.name = credentials.name;
|
||||||
|
credentialsObj.password = credentials.password;
|
||||||
|
credentialsObj.iconURL = credentials.iconURL;
|
||||||
|
} else {
|
||||||
|
credentialsObj.type = 'default';
|
||||||
|
credentialsObj.value = credentials;
|
||||||
|
}
|
||||||
|
return credentialsObj;
|
||||||
|
},
|
||||||
|
convertJsonToCredential: function(credentialsJson) {
|
||||||
|
var credentials;
|
||||||
|
if (window.FederatedCredential != null && credentialsJson.type === 'federated') {
|
||||||
|
credentials = new FederatedCredential({
|
||||||
|
id: credentialsJson.id,
|
||||||
|
name: credentialsJson.name,
|
||||||
|
protocol: credentialsJson.protocol,
|
||||||
|
provider: credentialsJson.provider,
|
||||||
|
iconURL: credentialsJson.iconURL
|
||||||
|
});
|
||||||
|
} else if (window.PasswordCredential != null && credentialsJson.type === 'password') {
|
||||||
|
credentials = new PasswordCredential({
|
||||||
|
id: credentialsJson.id,
|
||||||
|
name: credentialsJson.name,
|
||||||
|
password: credentialsJson.password,
|
||||||
|
iconURL: credentialsJson.iconURL
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
credentials = credentialsJson.value == null ? undefined : credentialsJson.value;
|
||||||
|
}
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
"""
|
|
@ -0,0 +1,62 @@
|
||||||
|
//
|
||||||
|
// LastTouchedAnchorOrImageJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let LAST_TOUCHED_ANCHOR_OR_IMAGE_JS_SOURCE = """
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = null;
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = null;
|
||||||
|
(function() {
|
||||||
|
document.addEventListener('touchstart', function(event) {
|
||||||
|
var target = event.target;
|
||||||
|
while (target) {
|
||||||
|
if (target.tagName === 'IMG') {
|
||||||
|
var img = target;
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = {
|
||||||
|
url: img.src
|
||||||
|
};
|
||||||
|
var parent = img.parentNode;
|
||||||
|
while (parent) {
|
||||||
|
if (parent.tagName === 'A') {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = {
|
||||||
|
title: parent.textContent,
|
||||||
|
url: parent.href,
|
||||||
|
src: img.src
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parent = parent.parentNode;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (target.tagName === 'A') {
|
||||||
|
var link = target;
|
||||||
|
var images = link.getElementsByTagName('img');
|
||||||
|
var img = (images.length > 0) ? images[0] : null;
|
||||||
|
var imgSrc = (img != null) ? img.src : null;
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched = (img != null) ? {url: imgSrc} : window.\(JAVASCRIPT_BRIDGE_NAME)._lastImageTouched;
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._lastAnchorOrImageTouched = {
|
||||||
|
title: link.textContent,
|
||||||
|
url: link.href,
|
||||||
|
src: imgSrc
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target = target.parentNode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
"""
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// resourceObserverJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT"
|
||||||
|
let FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE = "window.\(JAVASCRIPT_BRIDGE_NAME)._useOnLoadResource"
|
||||||
|
|
||||||
|
let ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: ON_LOAD_RESOURCE_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: ON_LOAD_RESOURCE_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let ON_LOAD_RESOURCE_JS_SOURCE = """
|
||||||
|
\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) = true;
|
||||||
|
(function() {
|
||||||
|
var observer = new PerformanceObserver(function(list) {
|
||||||
|
list.getEntries().forEach(function(entry) {
|
||||||
|
if (\(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == null || \(FLAG_VARIABLE_FOR_ON_LOAD_RESOURCE_JS_SOURCE) == true) {
|
||||||
|
var resource = {
|
||||||
|
"url": entry.name,
|
||||||
|
"initiatorType": entry.initiatorType,
|
||||||
|
"startTime": entry.startTime,
|
||||||
|
"duration": entry.duration
|
||||||
|
};
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onLoadResource", resource);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
observer.observe({entryTypes: ['resource']});
|
||||||
|
})();
|
||||||
|
"""
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// OnScrollEvent.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/10/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: ON_SCROLL_CHANGED_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: ON_SCROLL_CHANGED_EVENT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: ["onScrollChanged"])
|
||||||
|
|
||||||
|
let ON_SCROLL_CHANGED_EVENT_JS_SOURCE = """
|
||||||
|
(function(){
|
||||||
|
document.addEventListener('scroll', function(e) {
|
||||||
|
var _windowId = \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
window.webkit.messageHandlers["onScrollChanged"].postMessage(
|
||||||
|
{
|
||||||
|
x: window.scrollX,
|
||||||
|
y: window.scrollY,
|
||||||
|
_windowId: _windowId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
"""
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// OnWindowBlurEventJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: ON_WINDOW_BLUR_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: ON_WINDOW_BLUR_EVENT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let ON_WINDOW_BLUR_EVENT_JS_SOURCE = """
|
||||||
|
(function(){
|
||||||
|
window.addEventListener('blur', function(e) {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowBlur');
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
"""
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// OnWindowFocusEventJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let ON_WINDOW_FOCUS_EVENT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
|
||||||
|
source: ON_WINDOW_FOCUS_EVENT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let ON_WINDOW_FOCUS_EVENT_JS_SOURCE = """
|
||||||
|
(function(){
|
||||||
|
window.addEventListener('focus', function(e) {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler('onWindowFocus');
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
"""
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// OriginalViewPortMetaTagContentJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentEnd,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let ORIGINAL_VIEWPORT_METATAG_CONTENT_JS_SOURCE = """
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = "";
|
||||||
|
(function() {
|
||||||
|
var metaTagNodes = document.head.getElementsByTagName('meta');
|
||||||
|
for (var i = 0; i < metaTagNodes.length; i++) {
|
||||||
|
var metaTagNode = metaTagNodes[i];
|
||||||
|
if (metaTagNode.name === "viewport") {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent = metaTagNode.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
"""
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// PluginScripts.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class PluginScriptsUtil {
|
||||||
|
public static let VAR_PLACEHOLDER_VALUE = "$IN_APP_WEBVIEW_PLACEHOLDER_VALUE"
|
||||||
|
public static let VAR_FUNCTION_ARGUMENT_NAMES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_NAMES"
|
||||||
|
public static let VAR_FUNCTION_ARGUMENT_VALUES = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENT_VALUES"
|
||||||
|
public static let VAR_FUNCTION_ARGUMENTS_OBJ = "$IN_APP_WEBVIEW_FUNCTION_ARGUMENTS_OBJ"
|
||||||
|
public static let VAR_FUNCTION_BODY = "$IN_APP_WEBVIEW_FUNCTION_BODY"
|
||||||
|
public static let VAR_RESULT_UUID = "$IN_APP_WEBVIEW_RESULT_UUID"
|
||||||
|
|
||||||
|
public static let GET_SELECTED_TEXT_JS_SOURCE = """
|
||||||
|
(function(){
|
||||||
|
var txt;
|
||||||
|
if (window.getSelection) {
|
||||||
|
txt = window.getSelection().toString();
|
||||||
|
} else if (window.document.getSelection) {
|
||||||
|
txt = window.document.getSelection().toString();
|
||||||
|
} else if (window.document.selection) {
|
||||||
|
txt = window.document.selection.createRange().text;
|
||||||
|
}
|
||||||
|
return txt;
|
||||||
|
})();
|
||||||
|
"""
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// PrintJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_PRINT_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let PRINT_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: PRINT_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: PRINT_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentStart,
|
||||||
|
forMainFrameOnly: false,
|
||||||
|
requiredInAllContentWorlds: true,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let PRINT_JS_SOURCE = """
|
||||||
|
window.print = function() {
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME).callHandler("onPrintRequest", window.location.href);
|
||||||
|
}
|
||||||
|
"""
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// SupportZoomJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT = PluginScript(
|
||||||
|
groupName: NOT_SUPPORT_ZOOM_JS_PLUGIN_SCRIPT_GROUP_NAME,
|
||||||
|
source: NOT_SUPPORT_ZOOM_JS_SOURCE,
|
||||||
|
injectionTime: .atDocumentEnd,
|
||||||
|
forMainFrameOnly: true,
|
||||||
|
requiredInAllContentWorlds: false,
|
||||||
|
messageHandlerNames: [])
|
||||||
|
|
||||||
|
let NOT_SUPPORT_ZOOM_JS_SOURCE = """
|
||||||
|
(function() {
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.setAttribute('name', 'viewport');
|
||||||
|
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
})()
|
||||||
|
"""
|
||||||
|
|
||||||
|
let SUPPORT_ZOOM_JS_SOURCE = """
|
||||||
|
(function() {
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.setAttribute('name', 'viewport');
|
||||||
|
meta.setAttribute('content', window.\(JAVASCRIPT_BRIDGE_NAME)._originalViewPortMetaTagContent);
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
})()
|
||||||
|
"""
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
//
|
||||||
|
// WebMessageChannelJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/03/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let WEB_MESSAGE_CHANNELS_VARIABLE_NAME = "window.\(JAVASCRIPT_BRIDGE_NAME)._webMessageChannels"
|
|
@ -0,0 +1,112 @@
|
||||||
|
//
|
||||||
|
// WebMessageListenerJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/03/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let WEB_MESSAGE_LISTENER_JS_SOURCE = """
|
||||||
|
function FlutterInAppWebViewWebMessageListener(jsObjectName) {
|
||||||
|
this.jsObjectName = jsObjectName;
|
||||||
|
this.listeners = [];
|
||||||
|
this.onmessage = null;
|
||||||
|
}
|
||||||
|
FlutterInAppWebViewWebMessageListener.prototype.postMessage = function(message) {
|
||||||
|
window.webkit.messageHandlers['onWebMessageListenerPostMessageReceived'].postMessage({jsObjectName: this.jsObjectName, message: message});
|
||||||
|
};
|
||||||
|
FlutterInAppWebViewWebMessageListener.prototype.addEventListener = function(type, listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.listeners.push(listener);
|
||||||
|
};
|
||||||
|
FlutterInAppWebViewWebMessageListener.prototype.removeEventListener = function(type, listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var index = this.listeners.indexOf(listener);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6 = function(ip_string) {
|
||||||
|
// replace ipv4 address if any
|
||||||
|
var ipv4 = ip_string.match(/(.*:)([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)/);
|
||||||
|
if (ipv4) {
|
||||||
|
ip_string = ipv4[1];
|
||||||
|
ipv4 = ipv4[2].match(/[0-9]+/g);
|
||||||
|
for (var i = 0;i < 4;i ++) {
|
||||||
|
var byte = parseInt(ipv4[i],10);
|
||||||
|
ipv4[i] = ("0" + byte.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
ip_string += ipv4[0] + ipv4[1] + ':' + ipv4[2] + ipv4[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
// take care of leading and trailing ::
|
||||||
|
ip_string = ip_string.replace(/^:|:$/g, '');
|
||||||
|
|
||||||
|
var ipv6 = ip_string.split(':');
|
||||||
|
|
||||||
|
for (var i = 0; i < ipv6.length; i ++) {
|
||||||
|
var hex = ipv6[i];
|
||||||
|
if (hex != "") {
|
||||||
|
// normalize leading zeros
|
||||||
|
ipv6[i] = ("0000" + hex).substr(-4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// normalize grouped zeros ::
|
||||||
|
hex = [];
|
||||||
|
for (var j = ipv6.length; j <= 8; j ++) {
|
||||||
|
hex.push('0000');
|
||||||
|
}
|
||||||
|
ipv6[i] = hex.join(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipv6.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
window.\(JAVASCRIPT_BRIDGE_NAME)._isOriginAllowed = function(allowedOriginRules, scheme, host, port) {
|
||||||
|
for (var rule of allowedOriginRules) {
|
||||||
|
if (rule === "*") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (scheme == null || scheme === "") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((scheme == null || scheme === "") && (host == null || host === "") && (port === 0 || port === "" || port == null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var rulePort = rule.port == null || rule.port === 0 ? (rule.scheme == "https" ? 443 : 80) : rule.port;
|
||||||
|
var currentPort = port === 0 || port === "" || port == null ? (scheme == "https" ? 443 : 80) : port;
|
||||||
|
var IPv6 = null;
|
||||||
|
if (rule.host != null && rule.host[0] === "[") {
|
||||||
|
try {
|
||||||
|
IPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(rule.host.substring(1, rule.host.length - 1));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
var hostIPv6 = null;
|
||||||
|
try {
|
||||||
|
hostIPv6 = window.\(JAVASCRIPT_BRIDGE_NAME)._normalizeIPv6(host);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
var schemeAllowed = scheme == rule.scheme;
|
||||||
|
|
||||||
|
var hostAllowed = rule.host == null ||
|
||||||
|
rule.host === "" ||
|
||||||
|
host === rule.host ||
|
||||||
|
(rule.host[0] === "*" && host != null && host.indexOf(rule.host.split("*")[1]) >= 0) ||
|
||||||
|
(hostIPv6 != null && IPv6 != null && hostIPv6 === IPv6);
|
||||||
|
|
||||||
|
var portAllowed = rulePort === currentPort;
|
||||||
|
|
||||||
|
if (schemeAllowed && hostAllowed && portAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
"""
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// WindowIdJS.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 16/02/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let WINDOW_ID_JS_PLUGIN_SCRIPT_GROUP_NAME = "IN_APP_WEBVIEW_WINDOW_ID_JS_PLUGIN_SCRIPT"
|
||||||
|
|
||||||
|
let WINDOW_ID_VARIABLE_JS_SOURCE = "window._\(JAVASCRIPT_BRIDGE_NAME)_windowId"
|
||||||
|
|
||||||
|
let WINDOW_ID_INITIALIZE_JS_SOURCE = """
|
||||||
|
(function() {
|
||||||
|
\(WINDOW_ID_VARIABLE_JS_SOURCE) = \(PluginScriptsUtil.VAR_PLACEHOLDER_VALUE);
|
||||||
|
return \(WINDOW_ID_VARIABLE_JS_SOURCE);
|
||||||
|
})()
|
||||||
|
"""
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// CustomUIPrintPageRenderer.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
//
|
||||||
|
//public class CustomUIPrintPageRenderer : UIPrintPageRenderer {
|
||||||
|
// private var _numberOfPages: Int?
|
||||||
|
// private var forceRenderingQuality: Int?
|
||||||
|
//
|
||||||
|
// public init(numberOfPage: Int? = nil, forceRenderingQuality: Int? = nil) {
|
||||||
|
// super.init()
|
||||||
|
// self._numberOfPages = numberOfPage
|
||||||
|
// self.forceRenderingQuality = forceRenderingQuality
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// open override var numberOfPages: Int {
|
||||||
|
// get {
|
||||||
|
// return _numberOfPages ?? super.numberOfPages
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @available(iOS 14.5, *)
|
||||||
|
// open override func currentRenderingQuality(forRequested requestedRenderingQuality: UIPrintRenderingQuality) -> UIPrintRenderingQuality {
|
||||||
|
// if let forceRenderingQuality = forceRenderingQuality,
|
||||||
|
// let quality = UIPrintRenderingQuality.init(rawValue: forceRenderingQuality) {
|
||||||
|
// return quality
|
||||||
|
// }
|
||||||
|
// return super.currentRenderingQuality(forRequested: requestedRenderingQuality)
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// PrintAttributes.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class PrintAttributes : NSObject {
|
||||||
|
var orientation: NSPrintInfo.PaperOrientation?
|
||||||
|
var margins: NSEdgeInsets?
|
||||||
|
var paperRect: CGRect?
|
||||||
|
var colorMode: String?
|
||||||
|
var duplex: Int?
|
||||||
|
|
||||||
|
public init(fromPrintJobController: PrintJobController) {
|
||||||
|
super.init()
|
||||||
|
if let job = fromPrintJobController.job {
|
||||||
|
let printInfo = job.printInfo
|
||||||
|
orientation = printInfo.orientation
|
||||||
|
margins = NSEdgeInsets(top: printInfo.topMargin,
|
||||||
|
left: printInfo.leftMargin,
|
||||||
|
bottom: printInfo.bottomMargin,
|
||||||
|
right: printInfo.rightMargin)
|
||||||
|
paperRect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: printInfo.paperSize)
|
||||||
|
colorMode = printInfo.printSettings["ColorModel"] as? String
|
||||||
|
duplex = printInfo.printSettings["com_apple_print_PrintSettings_PMDuplexing"] as? Int
|
||||||
|
print(printInfo.printSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toMap () -> [String:Any?] {
|
||||||
|
return [
|
||||||
|
"paperRect": paperRect?.toMap(),
|
||||||
|
"margins": margins?.toMap(),
|
||||||
|
"orientation": orientation?.rawValue,
|
||||||
|
"colorMode": colorMode,
|
||||||
|
"duplex": duplex
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// PrintJobChannelDelegate.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 09/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public class PrintJobChannelDelegate : ChannelDelegate {
|
||||||
|
private weak var printJobController: PrintJobController?
|
||||||
|
|
||||||
|
public init(printJobController: PrintJobController, channel: FlutterMethodChannel) {
|
||||||
|
super.init(channel: channel)
|
||||||
|
self.printJobController = printJobController
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
|
let arguments = call.arguments as? NSDictionary
|
||||||
|
|
||||||
|
switch call.method {
|
||||||
|
case "getInfo":
|
||||||
|
if let printJobController = printJobController {
|
||||||
|
result(printJobController.getInfo()?.toMap())
|
||||||
|
} else {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "dispose":
|
||||||
|
if let printJobController = printJobController {
|
||||||
|
printJobController.dispose()
|
||||||
|
result(true)
|
||||||
|
} else {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func onComplete(completed: Bool, error: Error?) {
|
||||||
|
let arguments: [String: Any?] = [
|
||||||
|
"completed": completed,
|
||||||
|
"error": error?.localizedDescription
|
||||||
|
]
|
||||||
|
channel?.invokeMethod("onComplete", arguments: arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func dispose() {
|
||||||
|
super.dispose()
|
||||||
|
printJobController = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
//
|
||||||
|
// PrintJob.swift
|
||||||
|
// flutter_inappwebview
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 09/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import FlutterMacOS
|
||||||
|
|
||||||
|
public enum PrintJobState: Int {
|
||||||
|
case created = 1
|
||||||
|
case started = 3
|
||||||
|
case completed = 5
|
||||||
|
case failed = 6
|
||||||
|
case canceled = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PrintJobController : NSObject, Disposable {
|
||||||
|
static let METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_printjobcontroller_"
|
||||||
|
var id: String
|
||||||
|
var job: NSPrintOperation?
|
||||||
|
var settings: PrintJobSettings?
|
||||||
|
var channelDelegate: PrintJobChannelDelegate?
|
||||||
|
var state = PrintJobState.created
|
||||||
|
var creationTime = Int64(Date().timeIntervalSince1970 * 1000)
|
||||||
|
private var completionHandler: PrintJobController.CompletionHandler?
|
||||||
|
|
||||||
|
public typealias CompletionHandler = (_ printOperation: NSPrintOperation,
|
||||||
|
_ success: Bool,
|
||||||
|
_ contextInfo: UnsafeMutableRawPointer?) -> Void
|
||||||
|
|
||||||
|
public init(id: String, job: NSPrintOperation? = nil, settings: PrintJobSettings? = nil) {
|
||||||
|
self.id = id
|
||||||
|
super.init()
|
||||||
|
self.job = job
|
||||||
|
self.settings = settings
|
||||||
|
let channel = FlutterMethodChannel(name: PrintJobController.METHOD_CHANNEL_NAME_PREFIX + id,
|
||||||
|
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger)
|
||||||
|
self.channelDelegate = PrintJobChannelDelegate(printJobController: self, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func present(parentWindow: NSWindow? = nil, completionHandler: PrintJobController.CompletionHandler? = nil) {
|
||||||
|
guard let job = job else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state = .started
|
||||||
|
self.completionHandler = completionHandler
|
||||||
|
if let mainWindow = parentWindow ?? NSApplication.shared.mainWindow {
|
||||||
|
job.runModal(for: mainWindow, delegate: self, didRun: #selector(printOperationDidRun), contextInfo: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func printOperationDidRun(printOperation: NSPrintOperation,
|
||||||
|
success: Bool,
|
||||||
|
contextInfo: UnsafeMutableRawPointer?) {
|
||||||
|
state = success ? .completed : .canceled
|
||||||
|
channelDelegate?.onComplete(completed: success, error: nil)
|
||||||
|
if let completionHandler = completionHandler {
|
||||||
|
completionHandler(printOperation, success, contextInfo)
|
||||||
|
self.completionHandler = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getInfo() -> PrintJobInfo? {
|
||||||
|
guard let _ = job else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return PrintJobInfo.init(fromPrintJobController: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func disposeNoDismiss() {
|
||||||
|
channelDelegate?.dispose()
|
||||||
|
channelDelegate = nil
|
||||||
|
completionHandler = nil
|
||||||
|
job = nil
|
||||||
|
PrintJobManager.jobs[id] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispose() {
|
||||||
|
channelDelegate?.dispose()
|
||||||
|
channelDelegate = nil
|
||||||
|
completionHandler = nil
|
||||||
|
job = nil
|
||||||
|
PrintJobManager.jobs[id] = nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// PrintJobInfo.swift
|
||||||
|
// flutter_downloader
|
||||||
|
//
|
||||||
|
// Created by Lorenzo Pichilli on 10/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class PrintJobInfo : NSObject {
|
||||||
|
var state: PrintJobState
|
||||||
|
var attributes: PrintAttributes
|
||||||
|
var creationTime: Int64
|
||||||
|
var numberOfPages: Int?
|
||||||
|
var copies: Int?
|
||||||
|
var label: String?
|
||||||
|
var printerName: String?
|
||||||
|
var printerType: String?
|
||||||
|
|
||||||
|
public init(fromPrintJobController: PrintJobController) {
|
||||||
|
state = fromPrintJobController.state
|
||||||
|
creationTime = fromPrintJobController.creationTime
|
||||||
|
attributes = PrintAttributes.init(fromPrintJobController: fromPrintJobController)
|
||||||
|
if let job = fromPrintJobController.job {
|
||||||
|
let printInfo = job.printInfo
|
||||||
|
printerName = printInfo.printer.name
|
||||||
|
printerType = printInfo.printer.type.rawValue
|
||||||
|
copies = printInfo.printSettings["com_apple_print_PrintSettings_PMCopies"] as? Int
|
||||||
|
}
|
||||||
|
super.init()
|
||||||
|
if let job = fromPrintJobController.job {
|
||||||
|
let printInfo = job.printInfo
|
||||||
|
label = job.jobTitle
|
||||||
|
numberOfPages = printInfo.printSettings["com_apple_print_PrintSettings_PMLastPage"] as? Int
|
||||||
|
if numberOfPages == nil || numberOfPages! > job.pageRange.length {
|
||||||
|
numberOfPages = job.pageRange.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toMap () -> [String:Any?] {
|
||||||
|
return [
|
||||||
|
"state": state.rawValue,
|
||||||
|
"attributes": attributes.toMap(),
|
||||||
|
"numberOfPages": numberOfPages,
|
||||||
|
"copies": copies,
|
||||||
|
"creationTime": creationTime,
|
||||||
|
"label": label,
|
||||||
|
"printerName": printerName,
|
||||||
|
"printerType": printerType
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|