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));
// 1️⃣ Remove scrollbars everywhere
await c.runJavaScript('''
document.documentElement.style.setProperty('margin','0','important');
document.documentElement.style.setProperty('padding','0','important');
document.documentElement.style.setProperty('overflow','hidden','important');
document.body.style.setProperty('margin','0','important');
document.body.style.setProperty('padding','0','important');
document.body.style.setProperty('overflow','hidden','important');
Array.from(document.querySelectorAll('*')).forEach(el=>{
el.style.setProperty('overflow','hidden','important');
});
''');
// 2️⃣ Flutter JS Channel Setup (Android + iOS)
await c.runJavaScript('''
if (typeof window.FlutterChannel === 'undefined') {
window.FlutterChannel = {
postMessage: function(message) {
try {
if (window.MyFlutterApp) {
window.MyFlutterApp.postMessage(message);
}
if (window.webkit?.messageHandlers?.FlutterChannel) {
window.webkit.messageHandlers.FlutterChannel.postMessage(message);
}
console.log("FlutterChannel message:", message);
} catch(e) {
console.error("[Channel error]", e);
}
}
};
}
''');
// 3️⃣ Universal Safe Button Handler (Android FIX)
await c.runJavaScript(Platform.isAndroid
? '''
(function() {
function send(action, url) {
const payload = JSON.stringify({ action: action, url: url || null });
// iOS
try {
if (window.webkit?.messageHandlers?.FlutterChannel) {
window.webkit.messageHandlers.FlutterChannel.postMessage(payload);
}
} catch(e){}
// Android (REQUIRES MyFlutterApp)
try {
if (window.MyFlutterApp) {
window.MyFlutterApp.postMessage(payload);
}
} catch(e){}
// Fallback
try {
if (window.FlutterChannel) {
window.FlutterChannel.postMessage(payload);
}
} catch(e){}
}
function attach() {
const buttons = document.querySelectorAll("button[data-action]");
buttons.forEach(btn => {
if (btn.__added) return;
btn.__added = true;
const handler = function(e) {
e.preventDefault();
e.stopImmediatePropagation();
const action = btn.getAttribute("data-action");
const anchor = btn.closest("a");
send(action, anchor?.href || null);
};
btn.addEventListener("click", handler, true);
btn.addEventListener("touchend", handler, true);
});
}
attach();
new MutationObserver(attach).observe(document.body, { childList: true, subtree: true });
})();
'''
: '''
(function() {
console.log("[JS] Attaching persistent tour button listeners");
function sendToFlutter(action, url) {
const payload = JSON.stringify({
action: action,
url: url || null
});
try {
if (window.FlutterChannel) window.FlutterChannel.postMessage(payload);
if (window.MyFlutterApp) window.MyFlutterApp.postMessage(payload);
if (window.webkit?.messageHandlers?.FlutterChannel) {
window.webkit.messageHandlers.FlutterChannel.postMessage(payload);
}
} catch(e) {
console.error("[JS] Send error:", e);
}
}
function attachListeners() {
const buttons = document.querySelectorAll("button[data-action]");
console.log("[JS] Found " + buttons.length + " buttons");
buttons.forEach(btn => {
if (btn._listenerAttached) return;
btn._listenerAttached = true;
const action = btn.getAttribute("data-action");
const handler = function(event) {
event.preventDefault();
event.stopImmediatePropagation();
const anchor = btn.closest("a");
sendToFlutter(action, anchor?.href || null);
};
btn.addEventListener("click", handler, true);
btn.addEventListener("touchend", handler, true);
btn.style.cursor = "pointer";
});
}
// Initial attach
attachListeners();
// Re-attach when DOM changes (SPA, animations)
new MutationObserver(attachListeners)
.observe(document.body, { childList: true, subtree: true });
// Android safety fallback
setInterval(attachListeners, 600);
})();
''');
// 4️⃣ (Optional) Debug verification
final result = await c.runJavaScriptReturningResult('''
(function() {
return JSON.stringify({
buttons: document.querySelectorAll("button[data-action]").length,
hasChannel: typeof window.FlutterChannel !== "undefined"
});
})();
''');
print("🔧 JS Setup Result: $result");
} catch (e, st) {
print("❌ Error in onPageFinished: $e\n$st");
}
},
onHttpError: (error) => print("HTTP Error: $error"),
onWebResourceError: (error) => print("Web Resource Error: $error"),
onNavigationRequest: (NavigationRequest request) {
return NavigationDecision.navigate;
},
),
);
c.addJavaScriptChannel(
'MyFlutterApp', // REQUIRED FOR ANDROID
onMessageReceived: (JavaScriptMessage message) {
handleJsMessage(message.message);
},
);
c.addJavaScriptChannel(
'FlutterChannel',
onMessageReceived: (JavaScriptMessage message) {
final data = jsonDecode(message.message);
switch (data['action']) {
case 'onNextStep':
print("NEXTTTT");
TourUtil.next();
break;
case 'onPrevStep':
TourUtil.previous();
break;
case 'openLink':
final url = data['url'] as String;
Util.launchInBrowser(url);
break;
case 'onCloseStep':
TourUtil.finish();
break;
}
},
);
if (!isTour) controller = c;
return c;
}