diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index c1b19fd22d3e..cbf1f04a6181 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.0 + +* Implemented new `loadFile` and `loadHtmlString` methods from the platform interface. + ## 2.3.0 * Implemented new `loadRequest` method from platform interface. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj index 2be87fbf81be..e292b1bd52fa 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -273,7 +273,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1030; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 68BDCAE823C3F7CB00D9C032 = { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index d7453a8ce862..cb713d767632 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ '''; +const String kLocalFileExamplePage = ''' + + + +Load file or HTML string example + + + +

Local demo page

+

+ This is an example page used to demonstrate how to load a local file or HTML + string using the Flutter + webview plugin. +

+ + + +'''; + class _WebViewExample extends StatefulWidget { const _WebViewExample({Key? key}) : super(key: key); @@ -121,6 +143,8 @@ enum _MenuOptions { listCache, clearCache, navigationDelegate, + loadLocalFile, + loadHtmlString, doPostRequest, } @@ -159,6 +183,12 @@ class _SampleMenu extends StatelessWidget { case _MenuOptions.navigationDelegate: _onNavigationDelegateExample(controller.data!, context); break; + case _MenuOptions.loadLocalFile: + _onLoadLocalFileExample(controller.data!, context); + break; + case _MenuOptions.loadHtmlString: + _onLoadHtmlStringExample(controller.data!, context); + break; case _MenuOptions.doPostRequest: _onDoPostRequest(controller.data!, context); break; @@ -194,6 +224,14 @@ class _SampleMenu extends StatelessWidget { value: _MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.loadHtmlString, + child: Text('Load HTML string'), + ), + const PopupMenuItem<_MenuOptions>( + value: _MenuOptions.loadLocalFile, + child: Text('Load local file'), + ), const PopupMenuItem<_MenuOptions>( value: _MenuOptions.doPostRequest, child: Text('Post Request'), @@ -268,6 +306,18 @@ class _SampleMenu extends StatelessWidget { await controller.loadUrl('data:text/html;base64,$contentBase64'); } + void _onLoadLocalFileExample( + WebViewController controller, BuildContext context) async { + String pathToIndex = await _prepareLocalFile(); + + await controller.loadFile(pathToIndex); + } + + void _onLoadHtmlStringExample( + WebViewController controller, BuildContext context) async { + await controller.loadHtmlString(kLocalFileExamplePage); + } + void _onDoPostRequest( WebViewController controller, BuildContext context) async { WebViewRequest request = WebViewRequest( @@ -292,6 +342,16 @@ class _SampleMenu extends StatelessWidget { children: cookieWidgets.toList(), ); } + + static Future _prepareLocalFile() async { + final String tmpDir = (await getTemporaryDirectory()).path; + File indexFile = File('$tmpDir/www/index.html'); + + await Directory('$tmpDir/www').create(recursive: true); + await indexFile.writeAsString(kLocalFileExamplePage); + + return indexFile.path; + } } class _NavigationControls extends StatelessWidget { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart index b2555cd831c6..ab4b77336765 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/web_view.dart @@ -305,6 +305,35 @@ class WebViewController { WebView _widget; + /// Loads the file located on the specified [absoluteFilePath]. + /// + /// The [absoluteFilePath] parameter should contain the absolute path to the + /// file as it is stored on the device. For example: + /// `/Users/username/Documents/www/index.html`. + /// + /// Throws an ArgumentError if the [absoluteFilePath] does not exist. + Future loadFile( + String absoluteFilePath, + ) { + assert(absoluteFilePath.isNotEmpty); + return _webViewPlatformController.loadFile(absoluteFilePath); + } + + /// Loads the supplied HTML string. + /// + /// The [baseUrl] parameter is used when resolving relative URLs within the + /// HTML string. + Future loadHtmlString( + String html, { + String? baseUrl, + }) { + assert(html.isNotEmpty); + return _webViewPlatformController.loadHtmlString( + html, + baseUrl: baseUrl, + ); + } + /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index 229da5e337a5..c8001c8ffde4 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -8,6 +8,9 @@ environment: dependencies: flutter: sdk: flutter + + path_provider: ^2.0.6 + webview_flutter_wkwebview: # When depending on this package from a real application you should use: # webview_flutter: ^x.y.z @@ -31,3 +34,4 @@ flutter: assets: - assets/sample_audio.ogg - assets/sample_video.mp4 + \ No newline at end of file diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m index b8355ad18183..351d1ae58760 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.m @@ -140,6 +140,10 @@ - (UIView*)view { - (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([[call method] isEqualToString:@"updateSettings"]) { [self onUpdateSettings:call result:result]; + } else if ([[call method] isEqualToString:@"loadFile"]) { + [self onLoadFile:call result:result]; + } else if ([[call method] isEqualToString:@"loadHtmlString"]) { + [self onLoadHtmlString:call result:result]; } else if ([[call method] isEqualToString:@"loadUrl"]) { [self onLoadUrl:call result:result]; } else if ([[call method] isEqualToString:@"loadRequest"]) { @@ -192,6 +196,60 @@ - (void)onUpdateSettings:(FlutterMethodCall*)call result:(FlutterResult)result { result([FlutterError errorWithCode:@"updateSettings_failed" message:error details:nil]); } +- (void)onLoadFile:(FlutterMethodCall*)call result:(FlutterResult)result { + NSString* error = nil; + if (![FLTWebViewController isValidStringArgument:[call arguments] withErrorMessage:&error]) { + result([FlutterError errorWithCode:@"loadFile_failed" + message:@"Failed parsing file path." + details:error]); + return; + } + + NSURL* url = [NSURL fileURLWithPath:[call arguments] isDirectory:NO]; + + if (!url) { + NSString* errorDetails = [NSString stringWithFormat:@"Initializing NSURL with the supplied " + @"'%@' path resulted in a nil value.", + [call arguments]]; + result([FlutterError errorWithCode:@"loadFile_failed" + message:@"Failed parsing file path." + details:errorDetails]); + return; + } + + NSURL* baseUrl = [url URLByDeletingLastPathComponent]; + + [_webView loadFileURL:url allowingReadAccessToURL:baseUrl]; + result(nil); +} + +- (void)onLoadHtmlString:(FlutterMethodCall*)call result:(FlutterResult)result { + NSDictionary* arguments = [call arguments]; + if (![arguments isKindOfClass:NSDictionary.class]) { + result([FlutterError + errorWithCode:@"loadHtmlString_failed" + message:@"Failed parsing arguments." + details:@"Arguments should be a dictionary containing at least a 'html' element and " + @"optionally a 'baseUrl' argument. For example: `@{ @\"html\": @\"some html " + @"code\", @\"baseUrl\": @\"https://flutter.dev\" }`"]); + return; + } + + NSString* htmlString = [call arguments][@"html"]; + NSString* baseUrl = + [call arguments][@"baseUrl"] == [NSNull null] ? nil : [call arguments][@"baseUrl"]; + NSString* error = nil; + if (![FLTWebViewController isValidStringArgument:htmlString withErrorMessage:&error]) { + result([FlutterError errorWithCode:@"loadHtmlString_failed" + message:@"Failed parsing HTML string argument." + details:error]); + return; + } + + [_webView loadHTMLString:htmlString baseURL:[NSURL URLWithString:baseUrl]]; + result(nil); +} + - (void)onLoadUrl:(FlutterMethodCall*)call result:(FlutterResult)result { NSMutableDictionary* requestData = [[NSMutableDictionary alloc] init]; if (call.arguments[@"url"]) { @@ -556,6 +614,37 @@ - (void)updateUserAgent:(NSString*)userAgent { } } +/** + * Validates if the given `argument` is a non-null, non-empty string. + * + * @param argument The argument that should be validated. + * @param errorDetails An optional NSString variable which will contain a detailed error message in + * case the supplied argument is not valid. + * @return `YES` if the given `argument` is a valid non-null, non-empty string; otherwise `NO`. + */ ++ (BOOL)isValidStringArgument:(id)argument withErrorMessage:(NSString**)errorDetails { + if (!argument) { + if (errorDetails) { + *errorDetails = @"Argument is nil."; + } + return NO; + } + if (![argument isKindOfClass:NSString.class]) { + if (errorDetails) { + *errorDetails = @"Argument is not of type NSString."; + } + return NO; + } + if (![argument length]) { + if (errorDetails) { + *errorDetails = @"Argument contains an empty string."; + } + return NO; + } + + return YES; +} + #pragma mark WKUIDelegate - (WKWebView*)webView:(WKWebView*)webView diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index ff2b69fa8d20..466c1a2a4fcd 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 2.3.0 +version: 2.4.0 environment: sdk: ">=2.14.0 <3.0.0"