From 4c62b9dbbb2de41a6c61ff82e1cabaaaf7edba1f Mon Sep 17 00:00:00 2001 From: primozratej Date: Mon, 14 Oct 2024 17:17:39 +0200 Subject: [PATCH 1/2] Zoom only on image opened fullscreen --- lib/flavored/web_view.f.dart | 5 +- lib/pages/web_view.dart | 124 ++++++++++++++--------- lib/util/web_view_global_controller.dart | 81 +++++++++++++-- 3 files changed, 152 insertions(+), 58 deletions(-) diff --git a/lib/flavored/web_view.f.dart b/lib/flavored/web_view.f.dart index 9d38f3f..3a655b3 100644 --- a/lib/flavored/web_view.f.dart +++ b/lib/flavored/web_view.f.dart @@ -79,7 +79,7 @@ class FlavoredWebViewState extends ConsumerState { bottom: false, child: InAppWebView( initialUrlRequest: _initialRequest, - initialSettings: WebViewGlobalController.settings, + initialSettings: WebViewGlobalController.settings(), pullToRefreshController: pullToRefreshController, shouldOverrideUrlLoading: _shouldOverrideUrlLoading, shouldInterceptFetchRequest: _shouldInterceptFetchRequest, @@ -112,6 +112,7 @@ class FlavoredWebViewState extends ConsumerState { InAppWebViewController controller, NavigationAction action) async { // 1st check if url is not def. app url and open it in a browser or inApp. WebViewGlobalController.ajaxSetHeaders(headers: instance.customHeaders); + WebViewGlobalController.listenToImageOpen(); final url = action.request.url!.rawValue; /// First BLOCK everything that rules out as blocked. @@ -186,11 +187,13 @@ class FlavoredWebViewState extends ConsumerState { "document.querySelector('#account-login-form > div.form-group.field-login-rememberme').style.display='none';"); } WebViewGlobalController.ajaxSetHeaders(headers: instance.customHeaders); + WebViewGlobalController.listenToImageOpen(); LoadingProvider.of(ref).dismissAll(); } void _onLoadStart(InAppWebViewController controller, Uri? url) async { WebViewGlobalController.ajaxSetHeaders(headers: instance.customHeaders); + WebViewGlobalController.listenToImageOpen(); } void _onLoadError(InAppWebViewController controller, WebResourceRequest request, WebResourceError error) async { diff --git a/lib/pages/web_view.dart b/lib/pages/web_view.dart index 262a6bf..cace07f 100644 --- a/lib/pages/web_view.dart +++ b/lib/pages/web_view.dart @@ -83,27 +83,27 @@ class WebViewAppState extends ConsumerState { key: _scaffoldKey, backgroundColor: HexColor(_manifest.themeColor), body: SafeArea( - bottom: false, - // ignore: deprecated_member_use - child: WillPopScope( - onWillPop: () => exitApp(context, ref), - child: InAppWebView( - initialUrlRequest: _initialRequest, - initialSettings: WebViewGlobalController.settings, - pullToRefreshController: _pullToRefreshController, - shouldOverrideUrlLoading: _shouldOverrideUrlLoading, - onWebViewCreated: _onWebViewCreated, - shouldInterceptFetchRequest: _shouldInterceptFetchRequest, - onCreateWindow: _onCreateWindow, - onLoadStop: _onLoadStop, - onLoadStart: _onLoadStart, - onProgressChanged: _onProgressChanged, - onReceivedError: _onReceivedError, - onDownloadStartRequest: _onDownloadStartRequest, - onLongPressHitTestResult: WebViewGlobalController.onLongPressHitTestResult, - ), - ), - ), + bottom: false, + // ignore: deprecated_member_use + child: WillPopScope( + onWillPop: () => exitApp(context, ref), + child: InAppWebView( + initialUrlRequest: _initialRequest, + initialSettings: WebViewGlobalController.settings(), + pullToRefreshController: _pullToRefreshController, + shouldOverrideUrlLoading: _shouldOverrideUrlLoading, + onWebViewCreated: _onWebViewCreated, + shouldInterceptFetchRequest: _shouldInterceptFetchRequest, + onCreateWindow: _onCreateWindow, + onLoadStop: _onLoadStop, + onLoadStart: _onLoadStart, + onProgressChanged: _onProgressChanged, + onReceivedError: _onReceivedError, + onDownloadStartRequest: _onDownloadStartRequest, + onLongPressHitTestResult: + WebViewGlobalController.onLongPressHitTestResult, + ), + )), ); } @@ -129,12 +129,16 @@ class WebViewAppState extends ConsumerState { } String? payloadFromPush = InitFromPush.usePayload(); if (payloadFromPush != null) url = payloadFromPush; - return URLRequest(url: WebUri(url ?? _manifest.startUrl), headers: ref.read(humHubProvider).customHeaders); + return URLRequest( + url: WebUri(url ?? _manifest.startUrl), + headers: ref.read(humHubProvider).customHeaders); } Future _shouldOverrideUrlLoading( InAppWebViewController controller, NavigationAction action) async { - WebViewGlobalController.ajaxSetHeaders(headers: ref.read(humHubProvider).customHeaders); + WebViewGlobalController.ajaxSetHeaders( + headers: ref.read(humHubProvider).customHeaders); + WebViewGlobalController.listenToImageOpen(); final url = action.request.url!.rawValue; @@ -149,14 +153,18 @@ class WebViewAppState extends ConsumerState { } // For all other external links if (!url.startsWith(_manifest.baseUrl) && !action.isForMainFrame) { - await launchUrl(action.request.url!.uriValue, mode: LaunchMode.externalApplication); + await launchUrl(action.request.url!.uriValue, + mode: LaunchMode.externalApplication); return NavigationActionPolicy.CANCEL; } // 2nd Append customHeader if url is in app redirect and CANCEL the requests without custom headers if (Platform.isAndroid || action.navigationType == NavigationType.LINK_ACTIVATED || action.navigationType == NavigationType.FORM_SUBMITTED) { - Map mergedMap = {...?_initialRequest.headers, ...?action.request.headers}; + Map mergedMap = { + ...?_initialRequest.headers, + ...?action.request.headers + }; URLRequest newRequest = action.request.copyWith(headers: mergedMap); controller.loadUrl(urlRequest: newRequest); return NavigationActionPolicy.CANCEL; @@ -171,7 +179,8 @@ class WebViewAppState extends ConsumerState { await controller.addWebMessageListener( WebMessageListener( jsObjectName: "flutterChannel", - onPostMessage: (inMessage, sourceOrigin, isMainFrame, replyProxy) async { + onPostMessage: + (inMessage, sourceOrigin, isMainFrame, replyProxy) async { logInfo(inMessage); ChannelMessage message = ChannelMessage.fromJson(inMessage!.data); await _handleJSMessage(message, _headlessWebView!); @@ -182,13 +191,15 @@ class WebViewAppState extends ConsumerState { WebViewGlobalController.setValue(controller); } - Future _shouldInterceptFetchRequest(InAppWebViewController controller, FetchRequest request) async { + Future _shouldInterceptFetchRequest( + InAppWebViewController controller, FetchRequest request) async { logDebug("_shouldInterceptFetchRequest"); request.headers!.addAll(_initialRequest.headers!); return request; } - Future _onCreateWindow(InAppWebViewController controller, CreateWindowAction createWindowAction) async { + Future _onCreateWindow(InAppWebViewController controller, + CreateWindowAction createWindowAction) async { WebUri? urlToOpen = createWindowAction.request.url; if (urlToOpen == null) return Future.value(false); @@ -212,18 +223,22 @@ class WebViewAppState extends ConsumerState { _onLoadStop(InAppWebViewController controller, Uri? url) { // Disable remember me checkbox on login and set def. value to true: check if the page is actually login page, if it is inject JS that hides element if (url!.path.contains('/user/auth/login')) { - WebViewGlobalController.value! - .evaluateJavascript(source: "document.querySelector('#login-rememberme').checked=true"); + WebViewGlobalController.value!.evaluateJavascript( + source: "document.querySelector('#login-rememberme').checked=true"); WebViewGlobalController.value!.evaluateJavascript( source: "document.querySelector('#account-login-form > div.form-group.field-login-rememberme').style.display='none';"); } - WebViewGlobalController.ajaxSetHeaders(headers: ref.read(humHubProvider).customHeaders); + WebViewGlobalController.ajaxSetHeaders( + headers: ref.read(humHubProvider).customHeaders); + WebViewGlobalController.listenToImageOpen(); LoadingProvider.of(ref).dismissAll(); } void _onLoadStart(InAppWebViewController controller, Uri? url) async { - WebViewGlobalController.ajaxSetHeaders(headers: ref.read(humHubProvider).customHeaders); + WebViewGlobalController.ajaxSetHeaders( + headers: ref.read(humHubProvider).customHeaders); + WebViewGlobalController.listenToImageOpen(); } _onProgressChanged(InAppWebViewController controller, int progress) { @@ -232,8 +247,10 @@ class WebViewAppState extends ConsumerState { } } - void _onReceivedError(InAppWebViewController controller, WebResourceRequest request, WebResourceError error) { - if (error.description == 'net::ERR_INTERNET_DISCONNECTED') NoConnectionDialog.show(context); + void _onReceivedError(InAppWebViewController controller, + WebResourceRequest request, WebResourceError error) { + if (error.description == 'net::ERR_INTERNET_DISCONNECTED') + NoConnectionDialog.show(context); } _concludeAuth(URLRequest request) { @@ -241,20 +258,23 @@ class WebViewAppState extends ConsumerState { WebViewGlobalController.value!.loadUrl(urlRequest: request); } - Future _handleJSMessage(ChannelMessage message, HeadlessInAppWebView headlessWebView) async { + Future _handleJSMessage( + ChannelMessage message, HeadlessInAppWebView headlessWebView) async { switch (message.action) { case ChannelAction.showOpener: ref.read(humHubProvider).setIsHideOpener(false); ref.read(humHubProvider).clearSafeStorage(); FlutterAppBadger.updateBadgeCount(0); - Navigator.of(context).pushNamedAndRemoveUntil(Opener.path, (Route route) => false); + Navigator.of(context).pushNamedAndRemoveUntil( + Opener.path, (Route route) => false); break; case ChannelAction.hideOpener: ref.read(humHubProvider).setIsHideOpener(true); ref.read(humHubProvider).setHash(HumHub.generateHash(32)); break; case ChannelAction.registerFcmDevice: - String? token = ref.read(pushTokenProvider).value ?? await FirebaseMessaging.instance.getToken(); + String? token = ref.read(pushTokenProvider).value ?? + await FirebaseMessaging.instance.getToken(); if (token != null) { WebViewGlobalController.ajaxPost( url: message.url!, @@ -264,10 +284,12 @@ class WebViewAppState extends ConsumerState { } break; case ChannelAction.updateNotificationCount: - if (message.count != null) FlutterAppBadger.updateBadgeCount(message.count!); + if (message.count != null) + FlutterAppBadger.updateBadgeCount(message.count!); break; case ChannelAction.unregisterFcmDevice: - String? token = ref.read(pushTokenProvider).value ?? await FirebaseMessaging.instance.getToken(); + String? token = ref.read(pushTokenProvider).value ?? + await FirebaseMessaging.instance.getToken(); if (token != null) { WebViewGlobalController.ajaxPost( url: message.url!, @@ -290,9 +312,11 @@ class WebViewAppState extends ConsumerState { final exitConfirmed = await showDialog( context: context, builder: (context) => AlertDialog( - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10.0))), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0))), title: Text(AppLocalizations.of(context)!.web_view_exit_popup_title), - content: Text(AppLocalizations.of(context)!.web_view_exit_popup_content), + content: + Text(AppLocalizations.of(context)!.web_view_exit_popup_content), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), @@ -303,7 +327,8 @@ class WebViewAppState extends ConsumerState { var isHide = ref.read(humHubProvider).isHideDialog; isHide ? SystemNavigator.pop() - : Navigator.of(context).pushNamedAndRemoveUntil(Opener.path, (Route route) => false); + : Navigator.of(context).pushNamedAndRemoveUntil( + Opener.path, (Route route) => false); }, child: Text(AppLocalizations.of(context)!.yes), ), @@ -314,7 +339,8 @@ class WebViewAppState extends ConsumerState { } } - void _onDownloadStartRequest(InAppWebViewController controller, DownloadStartRequest downloadStartRequest) async { + void _onDownloadStartRequest(InAppWebViewController controller, + DownloadStartRequest downloadStartRequest) async { PersistentBottomSheetController? persistentController; //bool isBottomSheetVisible = false; @@ -334,7 +360,8 @@ class WebViewAppState extends ConsumerState { isDone = true; scaffoldMessengerStateKey.currentState?.showSnackBar( SnackBar( - content: Text('${AppLocalizations.of(context)!.file_download}: $filename'), + content: Text( + '${AppLocalizations.of(context)!.file_download}: $filename'), action: SnackBarAction( label: AppLocalizations.of(context)!.open, onPressed: () { @@ -351,19 +378,22 @@ class WebViewAppState extends ConsumerState { downloadTimer = Timer(const Duration(seconds: 1), () { // Show the persistent bottom sheet if not already shown if (!isDone) { - persistentController = _scaffoldKey.currentState!.showBottomSheet((context) { + persistentController = + _scaffoldKey.currentState!.showBottomSheet((context) { return Container( width: MediaQuery.of(context).size.width, height: 100, color: const Color(0xff313033), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "${AppLocalizations.of(context)!.downloading}...", - style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white), + style: const TextStyle( + fontWeight: FontWeight.bold, color: Colors.white), ), Stack( alignment: Alignment.center, diff --git a/lib/util/web_view_global_controller.dart b/lib/util/web_view_global_controller.dart index 084b453..7db2446 100644 --- a/lib/util/web_view_global_controller.dart +++ b/lib/util/web_view_global_controller.dart @@ -20,7 +20,8 @@ class WebViewGlobalController { /// [ref] is reference to the app state. /// [url] is the URL to evaluate. /// @return `true` if the URL should open in a new window, `false` otherwise. - static bool openCreateWindowInWebView({required String url, required Manifest manifest}) { + static bool openCreateWindowInWebView( + {required String url, required Manifest manifest}) { String? baseUrl = manifest.baseUrl; if (url.startsWith('$baseUrl/file/file/download')) return true; if (url.startsWith('$baseUrl/u')) return true; @@ -32,7 +33,10 @@ class WebViewGlobalController { _value = newValue; } - static void ajaxPost({required String url, required String data, Map? headers}) { + static void ajaxPost( + {required String url, + required String data, + Map? headers}) { String jsonHeaders = jsonEncode(headers); String jsCode4 = """ \$.ajax({ @@ -40,35 +44,92 @@ class WebViewGlobalController { type: 'POST', data: $data, headers: $jsonHeaders, - async: false, // IMPORTANT: it needs to be async + async: false, // IMPORTANT: it needs to be sync }); """; value?.evaluateJavascript(source: jsCode4); } static void ajaxSetHeaders({Map? headers}) { - String jsCode = "\$.ajaxSetup({headers: ${jsonEncode(headers).toString()}});"; + String jsCode = + "\$.ajaxSetup({headers: ${jsonEncode(headers).toString()}});"; value?.evaluateJavascript(source: jsCode); } - static void onLongPressHitTestResult(InAppWebViewController controller, InAppWebViewHitTestResult hitResult) async { + static void onLongPressHitTestResult(InAppWebViewController controller, + InAppWebViewHitTestResult hitResult) async { if (hitResult.extra != null && - ([InAppWebViewHitTestResultType.SRC_ANCHOR_TYPE, InAppWebViewHitTestResultType.EMAIL_TYPE] - .contains(hitResult.type))) { + ([ + InAppWebViewHitTestResultType.SRC_ANCHOR_TYPE, + InAppWebViewHitTestResultType.EMAIL_TYPE + ].contains(hitResult.type))) { Clipboard.setData( ClipboardData(text: hitResult.extra!), ); } } - static InAppWebViewSettings get settings => InAppWebViewSettings( + static Future listenToImageOpen() async { + // Inject JavaScript to monitor changes to the blueimp-gallery element + bool opened = false; + await _value?.evaluateJavascript(source: """ + // Create a MutationObserver to monitor changes in the #blueimp-gallery element's attributes + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + var galleryElement = document.getElementById('blueimp-gallery'); + + // Check if the gallery is opened (display: block) + if (galleryElement && galleryElement.style.display === 'block') { + // Send message to Flutter when the image gallery is opened + window.flutter_inappwebview.callHandler('onImageOpened'); + } + + // Check if the gallery is closed (display: none) + if (galleryElement && galleryElement.style.display === 'none') { + // Send message to Flutter when the image gallery is closed + window.flutter_inappwebview.callHandler('onImageClosed'); + } + }); + }); + + // Observe changes in the style attribute of the #blueimp-gallery element + var target = document.getElementById('blueimp-gallery'); + if (target) { + observer.observe(target, { attributes: true, attributeFilter: ['style'] }); + } + """); + + // Set up JavaScript handlers in Flutter to respond to image opening and closing events + _value?.addJavaScriptHandler( + handlerName: 'onImageOpened', + callback: (args) { + if (opened) return; + opened = true; + _value?.setSettings(settings: settings(zoom: opened)); + }, + ); + + _value?.addJavaScriptHandler( + handlerName: 'onImageClosed', + callback: (args) { + if (!opened) return; + opened = false; + _value?.setSettings(settings: settings(zoom: opened)); + }, + ); + } + + static InAppWebViewSettings settings({bool zoom = false}) { + return InAppWebViewSettings( useShouldOverrideUrlLoading: true, useShouldInterceptFetchRequest: true, javaScriptEnabled: true, - supportZoom: false, javaScriptCanOpenWindowsAutomatically: true, supportMultipleWindows: true, useHybridComposition: true, allowsInlineMediaPlayback: true, - mediaPlaybackRequiresUserGesture: false); + mediaPlaybackRequiresUserGesture: false, + supportZoom: zoom ? true : false, + ); + } } From d8cb0fde3eccbc74dc248784c478d2e9a43eed5c Mon Sep 17 00:00:00 2001 From: primozratej Date: Thu, 17 Oct 2024 09:56:36 +0200 Subject: [PATCH 2/2] Reformat --- lib/pages/web_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/web_view.dart b/lib/pages/web_view.dart index cace07f..a54e517 100644 --- a/lib/pages/web_view.dart +++ b/lib/pages/web_view.dart @@ -249,8 +249,9 @@ class WebViewAppState extends ConsumerState { void _onReceivedError(InAppWebViewController controller, WebResourceRequest request, WebResourceError error) { - if (error.description == 'net::ERR_INTERNET_DISCONNECTED') + if (error.description == 'net::ERR_INTERNET_DISCONNECTED') { NoConnectionDialog.show(context); + } } _concludeAuth(URLRequest request) { @@ -284,8 +285,9 @@ class WebViewAppState extends ConsumerState { } break; case ChannelAction.updateNotificationCount: - if (message.count != null) + if (message.count != null) { FlutterAppBadger.updateBadgeCount(message.count!); + } break; case ChannelAction.unregisterFcmDevice: String? token = ref.read(pushTokenProvider).value ??