💄 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
|
||||||
// Remove borders from embedded content (YouTube, Vimeo, etc.)
|
await controller.evaluateJavascript(
|
||||||
var iframes = document.querySelectorAll('iframe');
|
source: '''
|
||||||
for (var i = 0; i < iframes.length; i++) {
|
// Remove unwanted elements
|
||||||
iframes[i].style.border = 'none';
|
var elements = document.querySelectorAll('nav, header, footer, .ads, .advertisement, .sidebar');
|
||||||
iframes[i].style.borderRadius = '0';
|
for (var i = 0; i < elements.length; i++) {
|
||||||
}
|
elements[i].style.display = 'none';
|
||||||
|
}
|
||||||
// Remove borders from video elements
|
|
||||||
var videos = document.querySelectorAll('video');
|
// Remove borders from embedded content (YouTube, Vimeo, etc.)
|
||||||
for (var i = 0; i < videos.length; i++) {
|
var iframes = document.querySelectorAll('iframe');
|
||||||
videos[i].style.border = 'none';
|
for (var i = 0; i < iframes.length; i++) {
|
||||||
videos[i].style.borderRadius = '0';
|
iframes[i].style.border = 'none';
|
||||||
}
|
iframes[i].style.borderRadius = '0';
|
||||||
|
}
|
||||||
// Remove borders from any element that might have them
|
|
||||||
var allElements = document.querySelectorAll('*');
|
// Remove borders from video elements
|
||||||
for (var i = 0; i < allElements.length; i++) {
|
var videos = document.querySelectorAll('video');
|
||||||
if (allElements[i].style.border) {
|
for (var i = 0; i < videos.length; i++) {
|
||||||
allElements[i].style.border = 'none';
|
videos[i].style.border = 'none';
|
||||||
}
|
videos[i].style.borderRadius = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Improve readability
|
// Remove borders from any element that might have them
|
||||||
var body = document.body;
|
var allElements = document.querySelectorAll('*');
|
||||||
body.style.fontSize = '14px';
|
for (var i = 0; i < allElements.length; i++) {
|
||||||
body.style.lineHeight = '1.4';
|
if (allElements[i].style.border) {
|
||||||
body.style.margin = '0';
|
allElements[i].style.border = 'none';
|
||||||
body.style.padding = '0';
|
}
|
||||||
|
}
|
||||||
// Handle dynamic content
|
|
||||||
var observer = new MutationObserver(function(mutations) {
|
// Improve readability
|
||||||
// Remove borders from newly added elements
|
var body = document.body;
|
||||||
var newIframes = document.querySelectorAll('iframe');
|
body.style.fontSize = '14px';
|
||||||
for (var i = 0; i < newIframes.length; i++) {
|
body.style.lineHeight = '1.4';
|
||||||
if (!newIframes[i].style.border || newIframes[i].style.border !== 'none') {
|
body.style.margin = '0';
|
||||||
newIframes[i].style.border = 'none';
|
body.style.padding = '0';
|
||||||
newIframes[i].style.borderRadius = '0';
|
|
||||||
}
|
// Handle dynamic content
|
||||||
}
|
var observer = new MutationObserver(function(mutations) {
|
||||||
var newVideos = document.querySelectorAll('video');
|
// Remove borders from newly added elements
|
||||||
for (var i = 0; i < newVideos.length; i++) {
|
var newIframes = document.querySelectorAll('iframe');
|
||||||
if (!newVideos[i].style.border || newVideos[i].style.border !== 'none') {
|
for (var i = 0; i < newIframes.length; i++) {
|
||||||
newVideos[i].style.border = 'none';
|
if (!newIframes[i].style.border || newIframes[i].style.border !== 'none') {
|
||||||
newVideos[i].style.borderRadius = '0';
|
newIframes[i].style.border = 'none';
|
||||||
}
|
newIframes[i].style.borderRadius = '0';
|
||||||
}
|
}
|
||||||
window.flutter_inappwebview.callHandler('onHeightChanged', document.body.scrollHeight);
|
}
|
||||||
});
|
var newVideos = document.querySelectorAll('video');
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
for (var i = 0; i < newVideos.length; i++) {
|
||||||
''',
|
if (!newVideos[i].style.border || newVideos[i].style.border !== 'none') {
|
||||||
);
|
newVideos[i].style.border = 'none';
|
||||||
},
|
newVideos[i].style.borderRadius = '0';
|
||||||
onLoadError: (controller, url, code, message) {
|
}
|
||||||
// Handle load errors
|
}
|
||||||
},
|
window.flutter_inappwebview.callHandler('onHeightChanged', document.body.scrollHeight);
|
||||||
onLoadHttpError: (controller, url, statusCode, description) {
|
});
|
||||||
// Handle HTTP errors
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
},
|
''',
|
||||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
);
|
||||||
final uri = navigationAction.request.url;
|
},
|
||||||
if (uri != null && uri.toString() != embedView.uri) {
|
onLoadError: (controller, url, code, message) {
|
||||||
// Open external links in browser
|
isLoading.value = false;
|
||||||
// You might want to use url_launcher here
|
},
|
||||||
return NavigationActionPolicy.CANCEL;
|
onLoadHttpError: (
|
||||||
}
|
controller,
|
||||||
return NavigationActionPolicy.ALLOW;
|
url,
|
||||||
},
|
statusCode,
|
||||||
onProgressChanged: (controller, progress) {
|
description,
|
||||||
// Handle progress changes if needed
|
) {
|
||||||
},
|
isLoading.value = false;
|
||||||
onConsoleMessage: (controller, consoleMessage) {
|
},
|
||||||
// Handle console messages for debugging
|
shouldOverrideUrlLoading: (
|
||||||
debugPrint('WebView Console: ${consoleMessage.message}');
|
controller,
|
||||||
},
|
navigationAction,
|
||||||
),
|
) async {
|
||||||
|
final uri = navigationAction.request.url;
|
||||||
|
if (uri != null &&
|
||||||
|
uri.toString() != embedView.uri) {
|
||||||
|
// Open external links in browser
|
||||||
|
// You might want to use url_launcher here
|
||||||
|
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