💄 Optimize embed view renderer loading logic
This commit is contained in:
		| @@ -1,4 +1,5 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:flutter_inappwebview/flutter_inappwebview.dart'; | import 'package:flutter_inappwebview/flutter_inappwebview.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/models/post.dart'; | import 'package:island/models/post.dart'; | ||||||
| @@ -22,6 +23,10 @@ class EmbedViewRenderer extends HookConsumerWidget { | |||||||
|     final theme = Theme.of(context); |     final theme = Theme.of(context); | ||||||
|     final colorScheme = theme.colorScheme; |     final colorScheme = theme.colorScheme; | ||||||
|  |  | ||||||
|  |     // State management for lazy loading | ||||||
|  |     final shouldLoad = useState(false); | ||||||
|  |     final isLoading = useState(false); | ||||||
|  |  | ||||||
|     return Container( |     return Container( | ||||||
|       constraints: BoxConstraints(maxHeight: maxHeight ?? 400), |       constraints: BoxConstraints(maxHeight: maxHeight ?? 400), | ||||||
|       decoration: BoxDecoration( |       decoration: BoxDecoration( | ||||||
| @@ -93,125 +98,181 @@ class EmbedViewRenderer extends HookConsumerWidget { | |||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|  |  | ||||||
|             // WebView content |             // WebView content with lazy loading | ||||||
|             AspectRatio( |             AspectRatio( | ||||||
|               aspectRatio: embedView.aspectRatio ?? 1, |               aspectRatio: embedView.aspectRatio ?? 1, | ||||||
|               child: InAppWebView( |               child: | ||||||
|                 initialUrlRequest: URLRequest(url: WebUri(embedView.uri)), |                   shouldLoad.value | ||||||
|                 initialSettings: InAppWebViewSettings( |                       ? Stack( | ||||||
|                   javaScriptEnabled: true, |                         children: [ | ||||||
|                   mediaPlaybackRequiresUserGesture: false, |                           InAppWebView( | ||||||
|                   allowsInlineMediaPlayback: true, |                             initialUrlRequest: URLRequest( | ||||||
|                   useShouldOverrideUrlLoading: true, |                               url: WebUri(embedView.uri), | ||||||
|                   useOnLoadResource: true, |                             ), | ||||||
|                   supportZoom: false, |                             initialSettings: InAppWebViewSettings( | ||||||
|                   useWideViewPort: false, |                               javaScriptEnabled: true, | ||||||
|                   loadWithOverviewMode: true, |                               mediaPlaybackRequiresUserGesture: false, | ||||||
|                   builtInZoomControls: false, |                               allowsInlineMediaPlayback: true, | ||||||
|                   displayZoomControls: false, |                               useShouldOverrideUrlLoading: true, | ||||||
|                   minimumFontSize: 12, |                               useOnLoadResource: true, | ||||||
|                   preferredContentMode: UserPreferredContentMode.RECOMMENDED, |                               supportZoom: false, | ||||||
|                   allowsBackForwardNavigationGestures: false, |                               useWideViewPort: false, | ||||||
|                   allowsLinkPreview: false, |                               loadWithOverviewMode: true, | ||||||
|                   isInspectable: false, |                               builtInZoomControls: false, | ||||||
|                 ), |                               displayZoomControls: false, | ||||||
|                 onWebViewCreated: (controller) { |                               minimumFontSize: 12, | ||||||
|                   // Configure webview settings |                               preferredContentMode: | ||||||
|                   controller.addJavaScriptHandler( |                                   UserPreferredContentMode.RECOMMENDED, | ||||||
|                     handlerName: 'onHeightChanged', |                               allowsBackForwardNavigationGestures: false, | ||||||
|                     callback: (args) { |                               allowsLinkPreview: false, | ||||||
|                       // Handle dynamic height changes if needed |                               isInspectable: false, | ||||||
|                     }, |                             ), | ||||||
|                   ); |                             onWebViewCreated: (controller) { | ||||||
|                 }, |                               // Configure webview settings | ||||||
|                 onLoadStart: (controller, url) { |                               controller.addJavaScriptHandler( | ||||||
|                   // Handle load start |                                 handlerName: 'onHeightChanged', | ||||||
|                 }, |                                 callback: (args) { | ||||||
|                 onLoadStop: (controller, url) async { |                                   // Handle dynamic height changes if needed | ||||||
|                   // Inject CSS to improve mobile display and remove borders |                                 }, | ||||||
|                   await controller.evaluateJavascript( |                               ); | ||||||
|                     source: ''' |                             }, | ||||||
|                     // Remove unwanted elements |                             onLoadStart: (controller, url) { | ||||||
|                     var elements = document.querySelectorAll('nav, header, footer, .ads, .advertisement, .sidebar'); |                               isLoading.value = true; | ||||||
|                     for (var i = 0; i < elements.length; i++) { |                             }, | ||||||
|                       elements[i].style.display = 'none'; |                             onLoadStop: (controller, url) async { | ||||||
|                     } |                               isLoading.value = false; | ||||||
|  |                               // Inject CSS to improve mobile display and remove borders | ||||||
|  |                               await controller.evaluateJavascript( | ||||||
|  |                                 source: ''' | ||||||
|  |                             // Remove unwanted elements | ||||||
|  |                             var elements = document.querySelectorAll('nav, header, footer, .ads, .advertisement, .sidebar'); | ||||||
|  |                             for (var i = 0; i < elements.length; i++) { | ||||||
|  |                               elements[i].style.display = 'none'; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|                     // Remove borders from embedded content (YouTube, Vimeo, etc.) |                             // Remove borders from embedded content (YouTube, Vimeo, etc.) | ||||||
|                     var iframes = document.querySelectorAll('iframe'); |                             var iframes = document.querySelectorAll('iframe'); | ||||||
|                     for (var i = 0; i < iframes.length; i++) { |                             for (var i = 0; i < iframes.length; i++) { | ||||||
|                       iframes[i].style.border = 'none'; |                               iframes[i].style.border = 'none'; | ||||||
|                       iframes[i].style.borderRadius = '0'; |                               iframes[i].style.borderRadius = '0'; | ||||||
|                     } |                             } | ||||||
|  |  | ||||||
|                     // Remove borders from video elements |                             // Remove borders from video elements | ||||||
|                     var videos = document.querySelectorAll('video'); |                             var videos = document.querySelectorAll('video'); | ||||||
|                     for (var i = 0; i < videos.length; i++) { |                             for (var i = 0; i < videos.length; i++) { | ||||||
|                       videos[i].style.border = 'none'; |                               videos[i].style.border = 'none'; | ||||||
|                       videos[i].style.borderRadius = '0'; |                               videos[i].style.borderRadius = '0'; | ||||||
|                     } |                             } | ||||||
|  |  | ||||||
|                     // Remove borders from any element that might have them |                             // Remove borders from any element that might have them | ||||||
|                     var allElements = document.querySelectorAll('*'); |                             var allElements = document.querySelectorAll('*'); | ||||||
|                     for (var i = 0; i < allElements.length; i++) { |                             for (var i = 0; i < allElements.length; i++) { | ||||||
|                       if (allElements[i].style.border) { |                               if (allElements[i].style.border) { | ||||||
|                         allElements[i].style.border = 'none'; |                                 allElements[i].style.border = 'none'; | ||||||
|                       } |                               } | ||||||
|                     } |                             } | ||||||
|  |  | ||||||
|                     // Improve readability |                             // Improve readability | ||||||
|                     var body = document.body; |                             var body = document.body; | ||||||
|                     body.style.fontSize = '14px'; |                             body.style.fontSize = '14px'; | ||||||
|                     body.style.lineHeight = '1.4'; |                             body.style.lineHeight = '1.4'; | ||||||
|                     body.style.margin = '0'; |                             body.style.margin = '0'; | ||||||
|                     body.style.padding = '0'; |                             body.style.padding = '0'; | ||||||
|  |  | ||||||
|                     // Handle dynamic content |                             // Handle dynamic content | ||||||
|                     var observer = new MutationObserver(function(mutations) { |                             var observer = new MutationObserver(function(mutations) { | ||||||
|                       // Remove borders from newly added elements |                               // Remove borders from newly added elements | ||||||
|                       var newIframes = document.querySelectorAll('iframe'); |                               var newIframes = document.querySelectorAll('iframe'); | ||||||
|                       for (var i = 0; i < newIframes.length; i++) { |                               for (var i = 0; i < newIframes.length; i++) { | ||||||
|                         if (!newIframes[i].style.border || newIframes[i].style.border !== 'none') { |                                 if (!newIframes[i].style.border || newIframes[i].style.border !== 'none') { | ||||||
|                           newIframes[i].style.border = 'none'; |                                   newIframes[i].style.border = 'none'; | ||||||
|                           newIframes[i].style.borderRadius = '0'; |                                   newIframes[i].style.borderRadius = '0'; | ||||||
|                         } |                                 } | ||||||
|                       } |                               } | ||||||
|                       var newVideos = document.querySelectorAll('video'); |                               var newVideos = document.querySelectorAll('video'); | ||||||
|                       for (var i = 0; i < newVideos.length; i++) { |                               for (var i = 0; i < newVideos.length; i++) { | ||||||
|                         if (!newVideos[i].style.border || newVideos[i].style.border !== 'none') { |                                 if (!newVideos[i].style.border || newVideos[i].style.border !== 'none') { | ||||||
|                           newVideos[i].style.border = 'none'; |                                   newVideos[i].style.border = 'none'; | ||||||
|                           newVideos[i].style.borderRadius = '0'; |                                   newVideos[i].style.borderRadius = '0'; | ||||||
|                         } |                                 } | ||||||
|                       } |                               } | ||||||
|                       window.flutter_inappwebview.callHandler('onHeightChanged', document.body.scrollHeight); |                               window.flutter_inappwebview.callHandler('onHeightChanged', document.body.scrollHeight); | ||||||
|                     }); |                             }); | ||||||
|                     observer.observe(document.body, { childList: true, subtree: true }); |                             observer.observe(document.body, { childList: true, subtree: true }); | ||||||
|                   ''', |                           ''', | ||||||
|                   ); |                               ); | ||||||
|                 }, |                             }, | ||||||
|                 onLoadError: (controller, url, code, message) { |                             onLoadError: (controller, url, code, message) { | ||||||
|                   // Handle load errors |                               isLoading.value = false; | ||||||
|                 }, |                             }, | ||||||
|                 onLoadHttpError: (controller, url, statusCode, description) { |                             onLoadHttpError: ( | ||||||
|                   // Handle HTTP errors |                               controller, | ||||||
|                 }, |                               url, | ||||||
|                 shouldOverrideUrlLoading: (controller, navigationAction) async { |                               statusCode, | ||||||
|                   final uri = navigationAction.request.url; |                               description, | ||||||
|                   if (uri != null && uri.toString() != embedView.uri) { |                             ) { | ||||||
|                     // Open external links in browser |                               isLoading.value = false; | ||||||
|                     // You might want to use url_launcher here |                             }, | ||||||
|                     return NavigationActionPolicy.CANCEL; |                             shouldOverrideUrlLoading: ( | ||||||
|                   } |                               controller, | ||||||
|                   return NavigationActionPolicy.ALLOW; |                               navigationAction, | ||||||
|                 }, |                             ) async { | ||||||
|                 onProgressChanged: (controller, progress) { |                               final uri = navigationAction.request.url; | ||||||
|                   // Handle progress changes if needed |                               if (uri != null && | ||||||
|                 }, |                                   uri.toString() != embedView.uri) { | ||||||
|                 onConsoleMessage: (controller, consoleMessage) { |                                 // Open external links in browser | ||||||
|                   // Handle console messages for debugging |                                 // You might want to use url_launcher here | ||||||
|                   debugPrint('WebView Console: ${consoleMessage.message}'); |                                 return NavigationActionPolicy.CANCEL; | ||||||
|                 }, |                               } | ||||||
|               ), |                               return NavigationActionPolicy.ALLOW; | ||||||
|  |                             }, | ||||||
|  |                             onProgressChanged: (controller, progress) { | ||||||
|  |                               // Handle progress changes if needed | ||||||
|  |                             }, | ||||||
|  |                             onConsoleMessage: (controller, consoleMessage) { | ||||||
|  |                               // Handle console messages for debugging | ||||||
|  |                               debugPrint( | ||||||
|  |                                 'WebView Console: ${consoleMessage.message}', | ||||||
|  |                               ); | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|  |                           if (isLoading.value) | ||||||
|  |                             Container( | ||||||
|  |                               color: colorScheme.surfaceContainerLowest, | ||||||
|  |                               child: const Center( | ||||||
|  |                                 child: CircularProgressIndicator(), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                         ], | ||||||
|  |                       ) | ||||||
|  |                       : GestureDetector( | ||||||
|  |                         onTap: () { | ||||||
|  |                           shouldLoad.value = true; | ||||||
|  |                         }, | ||||||
|  |                         child: Container( | ||||||
|  |                           color: colorScheme.surfaceContainerLowest, | ||||||
|  |                           child: Column( | ||||||
|  |                             mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |                             children: [ | ||||||
|  |                               Icon( | ||||||
|  |                                 Symbols.play_arrow, | ||||||
|  |                                 size: 48, | ||||||
|  |                                 color: colorScheme.onSurfaceVariant.withOpacity( | ||||||
|  |                                   0.6, | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               const SizedBox(height: 8), | ||||||
|  |                               Text( | ||||||
|  |                                 'Tap to load content', | ||||||
|  |                                 style: theme.textTheme.bodyMedium?.copyWith( | ||||||
|  |                                   color: colorScheme.onSurfaceVariant | ||||||
|  |                                       .withOpacity(0.6), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ], | ||||||
|  |                           ), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|             ), |             ), | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user