diff --git a/CHANGELOG.md b/CHANGELOG.md index 813a388f..d13954cc 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Added `ProxyController` for Android - Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState` WebView controller methods - Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS`, `forceDarkStrategy` WebView settings +- Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events - Added support for `onPermissionRequest` event on iOS 15.0+ - Updated `getMetaThemeColor` on iOS 15.0+ - Deprecated `onLoadError` for `onReceivedError`. `onReceivedError` will be called also for subframes. diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index da4b6637..99fee0fa 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -177,6 +177,7 @@ CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = PFP8UV45Y6; LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; @@ -443,6 +444,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = PFP8UV45Y6; ENABLE_BITCODE = NO; @@ -456,8 +459,9 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-Example"; + PRODUCT_BUNDLE_IDENTIFIER = com.pichillilorenzo.flutterinappwebviewExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -471,6 +475,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = PFP8UV45Y6; ENABLE_BITCODE = NO; @@ -484,8 +490,9 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.pichillilorenzo.flutter-inappwebview-Example"; + PRODUCT_BUNDLE_IDENTIFIER = com.pichillilorenzo.flutterinappwebviewExample; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index 136d21e7..cfeb4956 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -309,6 +309,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, options: [.new, .old], context: nil) + if #available(iOS 15.0, *) { + addObserver(self, + forKeyPath: #keyPath(WKWebView.cameraCaptureState), + options: [.new, .old], + context: nil) + + addObserver(self, + forKeyPath: #keyPath(WKWebView.microphoneCaptureState), + options: [.new, .old], + context: nil) + } + NotificationCenter.default.addObserver( self, selector: #selector(onCreateContextMenu), @@ -628,18 +640,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, let progress = Int(estimatedProgress * 100) onProgressChanged(progress: progress) inAppBrowserDelegate?.didChangeProgress(progress: estimatedProgress) - } else if keyPath == #keyPath(WKWebView.url) && change?[NSKeyValueChangeKey.newKey] is URL { + } else if keyPath == #keyPath(WKWebView.url) && change?[.newKey] is URL { initializeWindowIdJS() let newUrl = change?[NSKeyValueChangeKey.newKey] as? URL onUpdateVisitedHistory(url: newUrl?.absoluteString) inAppBrowserDelegate?.didUpdateVisitedHistory(url: newUrl) - } else if keyPath == #keyPath(WKWebView.title) && change?[NSKeyValueChangeKey.newKey] is String { - let newTitle = change?[NSKeyValueChangeKey.newKey] as? String + } else if keyPath == #keyPath(WKWebView.title) && change?[.newKey] is String { + let newTitle = change?[.newKey] as? String onTitleChanged(title: newTitle) inAppBrowserDelegate?.didChangeTitle(title: newTitle) } else if keyPath == #keyPath(UIScrollView.contentOffset) { - let newContentOffset = change?[NSKeyValueChangeKey.newKey] as? CGPoint - let oldContentOffset = change?[NSKeyValueChangeKey.oldKey] as? CGPoint + let newContentOffset = change?[.newKey] as? CGPoint + let oldContentOffset = change?[.oldKey] as? CGPoint let startedByUser = scrollView.isDragging || scrollView.isDecelerating if newContentOffset != oldContentOffset { DispatchQueue.main.async { @@ -647,15 +659,30 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, } } } -// else if #available(iOS 15.0, *) { -// if keyPath == #keyPath(WKWebView.fullscreenState) { + else if #available(iOS 15.0, *) { + if keyPath == #keyPath(WKWebView.cameraCaptureState) || keyPath == #keyPath(WKWebView.microphoneCaptureState) { + var oldState: WKMediaCaptureState? = nil + if let oldValue = change?[.oldKey] as? Int { + oldState = WKMediaCaptureState.init(rawValue: oldValue) + } + var newState: WKMediaCaptureState? = nil + if let newValue = change?[.newKey] as? Int { + newState = WKMediaCaptureState.init(rawValue: newValue) + } + if keyPath == #keyPath(WKWebView.cameraCaptureState) { + onCameraCaptureStateChanged(oldState: oldState, newState: newState) + } else { + onMicrophoneCaptureStateChanged(oldState: oldState, newState: newState) + } + } +// else if keyPath == #keyPath(WKWebView.fullscreenState) { // if fullscreenState == .enteringFullscreen { // onEnterFullscreen() // } else if fullscreenState == .exitingFullscreen { // onExitFullscreen() // } // } -// } + } replaceGestureHandlerIfNeeded() } @@ -2756,6 +2783,18 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { channel?.invokeMethod("onExitFullscreen", arguments: []) } + @available(iOS 15.0, *) + public func onCameraCaptureStateChanged(oldState: WKMediaCaptureState?, newState: WKMediaCaptureState?) { + let arguments = ["oldState": oldState?.rawValue, "newState": newState?.rawValue] + channel?.invokeMethod("onCameraCaptureStateChanged", arguments: arguments) + } + + @available(iOS 15.0, *) + public func onMicrophoneCaptureStateChanged(oldState: WKMediaCaptureState?, newState: WKMediaCaptureState?) { + let arguments = ["oldState": oldState?.rawValue, "newState": newState?.rawValue] + channel?.invokeMethod("onMicrophoneCaptureStateChanged", arguments: arguments) + } + // public func onContextMenuConfigurationForElement(linkURL: String?, result: FlutterResult?) { // let arguments: [String: Any?] = ["linkURL": linkURL] // channel?.invokeMethod("onContextMenuConfigurationForElement", arguments: arguments, result: result) @@ -3130,9 +3169,11 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { removeObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress)) removeObserver(self, forKeyPath: #keyPath(WKWebView.url)) removeObserver(self, forKeyPath: #keyPath(WKWebView.title)) -// if #available(iOS 15.0, *) { + if #available(iOS 15.0, *) { + removeObserver(self, forKeyPath: #keyPath(WKWebView.cameraCaptureState)) + removeObserver(self, forKeyPath: #keyPath(WKWebView.microphoneCaptureState)) // removeObserver(self, forKeyPath: #keyPath(WKWebView.fullscreenState)) -// } + } scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale)) resumeTimers() diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart index b7f1fd88..e4bebeb1 100755 --- a/lib/src/in_app_browser/in_app_browser.dart +++ b/lib/src/in_app_browser/in_app_browser.dart @@ -1047,6 +1047,28 @@ class InAppBrowser { Future? shouldAllowDeprecatedTLS( URLAuthenticationChallenge challenge) {} + ///Event fired when a change in the camera capture state occurred. + /// + ///**NOTE**: available only on iOS 15.0+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + void onCameraCaptureStateChanged( + MediaCaptureState? oldState, + MediaCaptureState? newState, + ) {} + + ///Event fired when a change in the microphone capture state occurred. + /// + ///**NOTE**: available only on iOS 15.0+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + void onMicrophoneCaptureStateChanged( + MediaCaptureState? oldState, + MediaCaptureState? newState, + ) {} + void throwIfAlreadyOpened({String message = ''}) { if (this.isOpened()) { throw InAppBrowserAlreadyOpenedException([ diff --git a/lib/src/in_app_webview/headless_in_app_webview.dart b/lib/src/in_app_webview/headless_in_app_webview.dart index 64648abe..a05bb759 100644 --- a/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/lib/src/in_app_webview/headless_in_app_webview.dart @@ -147,6 +147,8 @@ class HeadlessInAppWebView implements WebView { @Deprecated('Use shouldAllowDeprecatedTLS instead') this.iosShouldAllowDeprecatedTLS, this.shouldAllowDeprecatedTLS, + this.onCameraCaptureStateChanged, + this.onMicrophoneCaptureStateChanged, }) { id = IdGenerator.generate(); webViewController = new InAppWebViewController(id, this); @@ -401,11 +403,10 @@ class HeadlessInAppWebView implements WebView { ///Use [onDownloadStartRequest] instead @Deprecated('Use onDownloadStartRequest instead') @override - final void Function(InAppWebViewController controller, Uri url)? - onDownloadStart; + void Function(InAppWebViewController controller, Uri url)? onDownloadStart; @override - final void Function(InAppWebViewController controller, + void Function(InAppWebViewController controller, DownloadStartRequest downloadStartRequest)? onDownloadStartRequest; @override @@ -430,12 +431,12 @@ class HeadlessInAppWebView implements WebView { ///Use [onReceivedError] instead. @Deprecated("Use onReceivedError instead") @override - final void Function(InAppWebViewController controller, Uri? url, int code, + void Function(InAppWebViewController controller, Uri? url, int code, String message)? onLoadError; @override - final void Function(InAppWebViewController controller, - WebResourceRequest request, WebResourceError error)? onReceivedError; + void Function(InAppWebViewController controller, WebResourceRequest request, + WebResourceError error)? onReceivedError; ///Use [onReceivedHttpError] instead. @Deprecated("Use onReceivedHttpError instead") @@ -443,9 +444,7 @@ class HeadlessInAppWebView implements WebView { void Function(InAppWebViewController controller, Uri? url, int statusCode, String description)? onLoadHttpError; - final void Function( - InAppWebViewController controller, - WebResourceRequest request, + void Function(InAppWebViewController controller, WebResourceRequest request, WebResourceResponse errorResponse)? onReceivedHttpError; @override @@ -662,4 +661,18 @@ class HeadlessInAppWebView implements WebView { Future Function( InAppWebViewController controller, WebResourceRequest request)? shouldInterceptRequest; + + @override + Future Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged; + + @override + Future Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged; } diff --git a/lib/src/in_app_webview/in_app_webview.dart b/lib/src/in_app_webview/in_app_webview.dart index 1c11c7de..16254a09 100755 --- a/lib/src/in_app_webview/in_app_webview.dart +++ b/lib/src/in_app_webview/in_app_webview.dart @@ -139,6 +139,8 @@ class InAppWebView extends StatefulWidget implements WebView { @Deprecated('Use shouldAllowDeprecatedTLS instead') this.iosShouldAllowDeprecatedTLS, this.shouldAllowDeprecatedTLS, + this.onCameraCaptureStateChanged, + this.onMicrophoneCaptureStateChanged, this.gestureRecognizers, }) : super(key: key); @@ -542,6 +544,20 @@ class InAppWebView extends StatefulWidget implements WebView { final Future Function( InAppWebViewController controller, WebResourceRequest request)? shouldInterceptRequest; + + @override + final Future Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged; + + @override + final Future Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged; } class _InAppWebViewState extends State { @@ -680,7 +696,9 @@ class _InAppWebViewState extends State { @override void dispose() { dynamic viewId = _controller?.getViewId(); - if (viewId != null && kIsWeb && WebPlatformManager.webViews.containsKey(viewId)) { + if (viewId != null && + kIsWeb && + WebPlatformManager.webViews.containsKey(viewId)) { WebPlatformManager.webViews.remove(viewId); } super.dispose(); diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index 7eed780a..49e8e00d 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1081,7 +1081,7 @@ class InAppWebViewController { onLoadCallback != null) { onLoadCallback(); } - return null; + break; case "onInjectedScriptError": String id = call.arguments[0]; var onErrorCallback = _injectedScriptsFromURL[id]?.onError; @@ -1089,7 +1089,31 @@ class InAppWebViewController { onErrorCallback != null) { onErrorCallback(); } - return null; + break; + case "onCameraCaptureStateChanged": + if ((_webview != null && _webview!.onCameraCaptureStateChanged != null) || + _inAppBrowser != null) { + var oldState = MediaCaptureState.fromValue(call.arguments["oldState"]); + var newState = MediaCaptureState.fromValue(call.arguments["newState"]); + + if (_webview != null && _webview!.onCameraCaptureStateChanged != null) + _webview!.onCameraCaptureStateChanged!(this, oldState, newState); + else + _inAppBrowser!.onCameraCaptureStateChanged(oldState, newState); + } + break; + case "onMicrophoneCaptureStateChanged": + if ((_webview != null && _webview!.onMicrophoneCaptureStateChanged != null) || + _inAppBrowser != null) { + var oldState = MediaCaptureState.fromValue(call.arguments["oldState"]); + var newState = MediaCaptureState.fromValue(call.arguments["newState"]); + + if (_webview != null && _webview!.onMicrophoneCaptureStateChanged != null) + _webview!.onMicrophoneCaptureStateChanged!(this, oldState, newState); + else + _inAppBrowser!.onMicrophoneCaptureStateChanged(oldState, newState); + } + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; // decode args to json diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index bd01bedc..b0573ef2 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -848,6 +848,30 @@ abstract class WebView { InAppWebViewController controller, URLAuthenticationChallenge challenge)? shouldAllowDeprecatedTLS; + ///Event fired when a change in the camera capture state occurred. + /// + ///**NOTE**: available only on iOS 15.0+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + final Future Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onCameraCaptureStateChanged; + + ///Event fired when a change in the microphone capture state occurred. + /// + ///**NOTE**: available only on iOS 15.0+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS + final Future Function( + InAppWebViewController controller, + MediaCaptureState? oldState, + MediaCaptureState? newState, + )? onMicrophoneCaptureStateChanged; + ///Initial url request that will be loaded. /// ///**NOTE for Android**: when loading an URL Request using "POST" method, headers are ignored. @@ -927,7 +951,8 @@ abstract class WebView { @Deprecated('Use onReceivedError instead') this.onLoadError, this.onReceivedError, - @Deprecated("Use onReceivedHttpError instead") this.onLoadHttpError, + @Deprecated("Use onReceivedHttpError instead") + this.onLoadHttpError, this.onReceivedHttpError, this.onProgressChanged, this.onConsoleMessage, @@ -1015,6 +1040,8 @@ abstract class WebView { @Deprecated('Use shouldAllowDeprecatedTLS instead') this.iosShouldAllowDeprecatedTLS, this.shouldAllowDeprecatedTLS, + this.onCameraCaptureStateChanged, + this.onMicrophoneCaptureStateChanged, this.initialUrlRequest, this.initialFile, this.initialData,