init static method
✅ Initialize WebView with full auto-size and click-channel support
Implementation
static WebViewController init({required bool isTour}) {
final c = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..enableZoom(false);
c.setNavigationDelegate(
NavigationDelegate(
onProgress: (_) {},
onPageStarted: (_) {},
onPageFinished: (String url) async {
try {
print("✅ onPageFinished: $url");
await Future.delayed(const Duration(milliseconds: 400));
// Inject button click listeners
await c.runJavaScript('''
document.querySelectorAll('button[data-action]').forEach(btn => {
btn.addEventListener('click', (event) => {
const action = btn.getAttribute('data-action');
const anchor = btn.closest('a');
let payload = { action };
if (action === 'link' && anchor) {
event.preventDefault();
payload.url = anchor.href;
}
FlutterChannel.postMessage(JSON.stringify(payload));
});
});
''');
// --- Safe JSON parser (no double decode) ---
dynamic safeJsonParse(dynamic jsResult) {
if (jsResult == null) return {};
dynamic str = jsResult.toString().trim();
// Unwrap quoted JSON like "\"{...}\""
if (str.startsWith('"') && str.endsWith('"')) {
str = str.substring(1, str.length - 1);
}
// Replace escaped quotes
str = str.replaceAll(r'\"', '"');
try {
final json = jsonDecode(str);
if (json is Map) return json;
} catch (_) {}
return {};
}
Map<String, double> newSize = {"height": 400, "width": 400};
for (int attempt = 0; attempt < 3; attempt++) {
final jsResult = await c
.runJavaScriptReturningResult(calculateHtmlDocDimensions);
if (jsResult.toString().contains('-1')) {
await Future.delayed(const Duration(milliseconds: 300));
continue;
}
final decoded = safeJsonParse(jsResult);
final heightVal =
double.tryParse(decoded['height']?.toString() ?? '0') ?? 0;
final widthVal =
double.tryParse(decoded['width']?.toString() ?? '0') ?? 0;
// Pixel ratio
final pixelRatioJs = await c
.runJavaScriptReturningResult('window.devicePixelRatio');
final pixelRatio =
double.tryParse(pixelRatioJs.toString()) ?? 1.0;
final adjustedHeight = (heightVal / pixelRatio);
final adjustedWidth = (widthVal / pixelRatio);
// Sanity cap
if (adjustedHeight > 3000 || adjustedHeight < 100) {
print("⚠️ Height suspicious ($adjustedHeight), fallback used");
newSize = {"height": 400, "width": 400};
} else {
newSize = {"height": adjustedHeight, "width": adjustedWidth};
}
break;
}
if (newSize["height"]! > 5000) newSize["height"] = 500;
sizeNotifier.value = newSize;
print('📏 Final WebView size (adjusted): $newSize');
} catch (e, st) {
print('❌ Error calculating WebView size: $e\n$st');
sizeNotifier.value = {"height": 400, "width": 400};
}
},
onHttpError: (error) => print("HTTP Error: $error"),
onWebResourceError: (error) => print("Web Resource Error: $error"),
onNavigationRequest: (NavigationRequest request) {
return NavigationDecision.navigate;
},
),
);
if (!isTour) controller = c;
return c;
}