Skip to content

Commit

Permalink
fix: 🐛 Force dapp webview to keep focus.
Browse files Browse the repository at this point in the history
This ensures deeplink calls work.
  • Loading branch information
Chralu committed Nov 7, 2024
1 parent 20f2450 commit e125a45
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 147 deletions.
157 changes: 94 additions & 63 deletions lib/infrastructure/rpc/awc_webview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class _AWCWebviewState extends State<AWCWebview> with WidgetsBindingObserver {
InAppWebViewController? _controller;
WebviewMessagePortStreamChannel? _channel;
bool _loaded = false;
late final FocusNode _focusNode;

@override
void initState() {
Expand All @@ -97,6 +98,7 @@ class _AWCWebviewState extends State<AWCWebview> with WidgetsBindingObserver {
defaultTargetPlatform == TargetPlatform.android) {
InAppWebViewController.setWebContentsDebuggingEnabled(true);
}
_focusNode = FocusNode();
WidgetsBinding.instance.addObserver(this);

super.initState();
Expand All @@ -105,6 +107,7 @@ class _AWCWebviewState extends State<AWCWebview> with WidgetsBindingObserver {
@override
void dispose() {
_peerServer?.close();
_focusNode.dispose();
WidgetsBinding.instance.removeObserver(this);
AWCWebview._logger.info('AWC webview disposed.');

Expand All @@ -115,87 +118,99 @@ class _AWCWebviewState extends State<AWCWebview> with WidgetsBindingObserver {
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
unawaited(_restoreMessageChannelRPC(_controller!));
_maintainWebviewFocus(_controller!);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
}
super.didChangeAppLifecycleState(state);
}

void _requestFocus() {
if (!_focusNode.hasFocus) _focusNode.requestFocus();
_controller?.requestFocus();
}

@override
Widget build(BuildContext context) => Stack(
children: [
Opacity(
opacity: _loaded ? 1 : 0,
child: ColoredBox(
color: Colors.black,
child: InAppWebView(
initialSettings: InAppWebViewSettings(
isInspectable: kDebugMode,
transparentBackground: true,
),
onLoadStop: (controller, url) async {
_controller = controller;
setState(() {
_loaded = true;
});
await _initMessageChannelRPC(controller);
},
onWebViewCreated: (controller) async {
await controller.loadUrl(
urlRequest: URLRequest(url: WebUri.uri(widget.uri)),
);
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
final uri = navigationAction.request.url;
if (uri == null) {
return NavigationActionPolicy.ALLOW;
}

final matchingEvmWallet = EVMWallet.fromScheme(
uri.scheme,
);

if (matchingEvmWallet == null) {
return NavigationActionPolicy.ALLOW;
}

if (!await canLaunchUrl(uri.uriValue)) {
child: Focus(
autofocus: true,
focusNode: _focusNode,
child: InAppWebView(
initialSettings: InAppWebViewSettings(
isInspectable: kDebugMode,
transparentBackground: true,
),
onLoadStop: (controller, url) async {
_controller = controller;
setState(() {
_loaded = true;
});
await _initMessageChannelRPC(controller);
_maintainWebviewFocus(controller);
},
onWebViewCreated: (controller) async {
await controller.loadUrl(
urlRequest: URLRequest(url: WebUri.uri(widget.uri)),
);
},
shouldOverrideUrlLoading:
(controller, navigationAction) async {
final uri = navigationAction.request.url;
if (uri == null) {
return NavigationActionPolicy.ALLOW;
}

final matchingEvmWallet = EVMWallet.fromScheme(
uri.scheme,
);

if (matchingEvmWallet == null) {
return NavigationActionPolicy.ALLOW;
}

if (!await canLaunchUrl(uri.uriValue)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Wallet ${matchingEvmWallet.name} not installed', // TODO(Chralu): internationalize this
),
),
);
return NavigationActionPolicy.CANCEL;
}

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Wallet ${matchingEvmWallet.name} not installed', // TODO(Chralu): internationalize this
),
'Opening wallet ${matchingEvmWallet.name}',
), // TODO(Chralu): internationalize this
duration: const Duration(days: 1),
),
);

unawaited(
launchUrl(uri, mode: LaunchMode.externalApplication),
);

return NavigationActionPolicy.CANCEL;
}

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Opening wallet ${matchingEvmWallet.name}',
), // TODO(Chralu): internationalize this
duration: const Duration(days: 1),
),
);

unawaited(
launchUrl(uri, mode: LaunchMode.externalApplication),
);

return NavigationActionPolicy.CANCEL;
},
onReceivedHttpError: (controller, request, errorResponse) {
AWCWebview._logger.warning(
'HTTP error: ${errorResponse.statusCode} ${request.url}',
);
},
onReceivedServerTrustAuthRequest:
(controller, challenge) async {
// TODO(reddwarf03): WARNING: Accepting all certificates is dangerous and should only be used during development.
return ServerTrustAuthResponse(
action: ServerTrustAuthResponseAction.PROCEED,
);
},
},
onReceivedHttpError: (controller, request, errorResponse) {
AWCWebview._logger.warning(
'HTTP error: ${errorResponse.statusCode} ${request.url}',
);
},
onReceivedServerTrustAuthRequest:
(controller, challenge) async {
// TODO(reddwarf03): WARNING: Accepting all certificates is dangerous and should only be used during development.
return ServerTrustAuthResponse(
action: ServerTrustAuthResponseAction.PROCEED,
);
},
),
),
),
),
Expand Down Expand Up @@ -225,6 +240,22 @@ class _AWCWebviewState extends State<AWCWebview> with WidgetsBindingObserver {
_channel?.port = port1;
}

void _maintainWebviewFocus(
InAppWebViewController controller,
) {
controller
..addJavaScriptHandler(
handlerName: 'focusLost',
callback: (event) {
_requestFocus();
},
)
..evaluateJavascript(
source:
"onblur = (event) => { window.flutter_inappwebview.callHandler('focusLost'); }",
);
}

Future<WebMessagePort> _restoreMessageChannelPorts(
InAppWebViewController controller,
) async {
Expand Down
Loading

0 comments on commit e125a45

Please sign in to comment.