From 2d04958574ad7744c6b34e6c8fa7aca93d8d955c Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Wed, 19 Jun 2024 21:34:06 +0400 Subject: [PATCH 01/33] wip: Rewriting with webview_flutter package --- .../ada_chat_flutter/analysis_options.yaml | 1 - .../example/lib/screens/ada_chat_screen.dart | 2 +- .../lib/src/ada_controller.dart | 50 ++- .../lib/src/ada_controller_init.dart | 6 +- .../lib/src/ada_web_view.dart | 308 ++++++++++++------ .../lib/src/browser_controller.dart | 6 +- .../lib/src/customized_web_view.dart | 2 +- packages/ada_chat_flutter/pubspec.yaml | 10 +- 8 files changed, 237 insertions(+), 148 deletions(-) diff --git a/packages/ada_chat_flutter/analysis_options.yaml b/packages/ada_chat_flutter/analysis_options.yaml index 6336f36df..6744da4d2 100644 --- a/packages/ada_chat_flutter/analysis_options.yaml +++ b/packages/ada_chat_flutter/analysis_options.yaml @@ -5,7 +5,6 @@ linter: use_key_in_widget_constructors: false no_adjacent_strings_in_list: false literal_only_boolean_expressions: false - prefer_single_quotes: false slash_for_doc_comments: false curly_braces_in_flow_control_structures: false omit_local_variable_types: false diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index f3e15daa9..4bf01cb14 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -34,7 +34,7 @@ class _AdaChatScreenState extends State { body: Stack( children: [ AdaWebView( - handle: 'example-handle', + handle: 'headspace-sandbox', name: 'User 1', email: 'qqq@google.com', phone: '+5342342131324', diff --git a/packages/ada_chat_flutter/lib/src/ada_controller.dart b/packages/ada_chat_flutter/lib/src/ada_controller.dart index 1fef625ea..ab13f137a 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:ada_chat_flutter/src/ada_controller_init.dart'; -import 'package:ada_chat_flutter/src/ada_exception.dart'; typedef FlatObject = Map; @@ -9,8 +8,9 @@ typedef FlatObject = Map; /// https://developers.ada.cx/reference/embed2-reference class AdaController extends AdaControllerInit { @override - Future start() => webViewController.evaluateJavascript( - source: ''' + Future start() => webViewController.runJavaScript( + // todo Check if function(){}() wrapper is needed + ''' (function() { adaEmbed.start({ handle: "$handle", @@ -20,42 +20,32 @@ class AdaController extends AdaControllerInit { ''', ); - Future deleteHistory() => webViewController.evaluateJavascript( - source: ''' + Future deleteHistory() => webViewController.runJavaScript( + ''' (function() { adaEmbed.deleteHistory(); })(); ''', ); - Future> getInfo() async { - final result = await webViewController.callAsyncJavaScript( - functionBody: ''' + Future getInfo() async { + return await webViewController.runJavaScriptReturningResult( + ''' return await adaEmbed.getInfo(); ''', ); - - if (result == null) { - throw const AdaNullResultException(); - } else if (result.error != null) { - throw AdaExecException(result.error!); - } - - return (result.value as Map) - .map((key, value) => MapEntry(key as String, value as bool)); } - Future reset() => webViewController.evaluateJavascript( - source: ''' + Future reset() => webViewController.runJavaScript( + ''' (function() { adaEmbed.reset(); })(); ''', ); - Future setLanguage(String language) => - webViewController.evaluateJavascript( - source: ''' + Future setLanguage(String language) => webViewController.runJavaScript( + ''' (function() { adaEmbed.setLanguage("$language"); })(); @@ -65,8 +55,8 @@ return await adaEmbed.getInfo(); Future setMetaFields(FlatObject meta) async { final metaJson = jsonEncode(meta); - await webViewController.evaluateJavascript( - source: ''' + await webViewController.runJavaScript( + ''' (function() { adaEmbed.setMetaFields($metaJson); })(); @@ -77,8 +67,8 @@ return await adaEmbed.getInfo(); Future setSensitiveMetaFields(FlatObject meta) async { final metaJson = jsonEncode(meta); - await webViewController.evaluateJavascript( - source: ''' + await webViewController.runJavaScript( + ''' (function() { adaEmbed.setSensitiveMetaFields($metaJson); })(); @@ -86,8 +76,8 @@ return await adaEmbed.getInfo(); ); } - Future stop() => webViewController.evaluateJavascript( - source: ''' + Future stop() => webViewController.runJavaScript( + ''' (function() { adaEmbed.stop(); })(); @@ -96,8 +86,8 @@ return await adaEmbed.getInfo(); @override Future triggerAnswer(String answerId) => - webViewController.evaluateJavascript( - source: ''' + webViewController.runJavaScript( + ''' (function() { adaEmbed.triggerAnswer("$answerId"); })(); diff --git a/packages/ada_chat_flutter/lib/src/ada_controller_init.dart b/packages/ada_chat_flutter/lib/src/ada_controller_init.dart index bae914ef8..3498b9d18 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller_init.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller_init.dart @@ -1,18 +1,18 @@ import 'dart:async'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:meta/meta.dart'; +import 'package:webview_flutter/webview_flutter.dart'; abstract class AdaControllerInit { @visibleForTesting @protected - late InAppWebViewController webViewController; + late WebViewController webViewController; @visibleForTesting @protected late String handle; FutureOr init({ - required InAppWebViewController webViewController, + required WebViewController webViewController, required String handle, }) { this.webViewController = webViewController; diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index f2f2a35a2..0ae873d71 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -4,11 +4,14 @@ import 'dart:convert'; import 'package:ada_chat_flutter/src/ada_controller.dart'; import 'package:ada_chat_flutter/src/ada_controller_init.dart'; import 'package:ada_chat_flutter/src/browser_settings.dart'; -import 'package:ada_chat_flutter/src/customized_web_view.dart'; import 'package:ada_chat_flutter/src/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +// Import for Android features. +import 'package:webview_flutter_android/webview_flutter_android.dart'; +// Import for iOS features. +import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; /// Ada chat WebView widget. /// @@ -59,7 +62,7 @@ class AdaWebView extends StatefulWidget { /// Url to your own page with assets/embed.html file. /// The domain must be added to approved domains. (Doc)[https://docs.ada.cx/docs/scripted/use-ada-with-your-website/deploy-ada-on-your-website-or-app/restrict-your-bot-to-a-list-of-approved-domains/]. - final URLRequest? urlRequest; + final Uri? urlRequest; // todo Rename to embedUri /// Controller for Ada chat. The default implementation is [AdaController]. final AdaControllerInit? controller; @@ -86,11 +89,11 @@ class AdaWebView extends StatefulWidget { final BrowserSettings? browserSettings; - final void Function(dynamic data)? onLoaded; - final void Function(dynamic isRolledOut)? onAdaReady; - final void Function(dynamic event)? onEvent; - final void Function(dynamic event)? onConversationEnd; - final void Function(bool isDrawerOpen)? onDrawerToggle; + final void Function(String data)? onLoaded; + final void Function(String isRolledOut)? onAdaReady; + final void Function(String event)? onEvent; + final void Function(String event)? onConversationEnd; + final void Function(String isDrawerOpen)? onDrawerToggle; final void Function(String level, String message)? onConsoleMessage; final void Function(String request, String error)? onLoadingError; @@ -104,7 +107,7 @@ class AdaWebView extends StatefulWidget { properties.add(StringProperty('name', name)); properties.add(StringProperty('email', email)); properties.add(StringProperty('phone', phone)); - properties.add(DiagnosticsProperty('urlRequest', urlRequest)); + properties.add(DiagnosticsProperty('urlRequest', urlRequest)); properties.add(StringProperty('language', language)); properties.add(StringProperty('cluster', cluster)); properties.add(StringProperty('domain', domain)); @@ -133,16 +136,114 @@ class AdaWebView extends StatefulWidget { } class _AdaWebViewState extends State { - late final _settings = InAppWebViewSettings( - supportZoom: false, - horizontalScrollBarEnabled: false, - verticalScrollBarEnabled: false, - useWideViewPort: false, - disableDefaultErrorPage: true, - allowFileAccessFromFileURLs: _allowFileAccessFromFileURLs, - allowsBackForwardNavigationGestures: false, - disableContextMenu: true, - ); + late final WebViewController _controller; + + // late final _settings = InAppWebViewSettings( + // supportZoom: false, + // horizontalScrollBarEnabled: false, + // verticalScrollBarEnabled: false, + // useWideViewPort: false, + // disableDefaultErrorPage: true, + // allowFileAccessFromFileURLs: _allowFileAccessFromFileURLs, + // allowsBackForwardNavigationGestures: false, + // disableContextMenu: true, + // ); + + @override + void initState() { + super.initState(); + + late final PlatformWebViewControllerCreationParams params; + + if (WebViewPlatform.instance is WebKitWebViewPlatform) { + params = WebKitWebViewControllerCreationParams( + allowsInlineMediaPlayback: true, + mediaTypesRequiringUserAction: const {}, + ); + } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { + params = AndroidWebViewControllerCreationParams(); + } else { + params = const PlatformWebViewControllerCreationParams(); + } + + _controller = WebViewController.fromPlatformCreationParams(params); + + // final platform = _controller.platform; + // if (platform is AndroidWebViewController) { + // AndroidWebViewController.enableDebugging(true); + // platform.setMediaPlaybackRequiresUserGesture(false); + // platform.setMediaPlaybackRequiresUserGesture(false); + // } + + _controller + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(Colors.white) + ..setOnConsoleMessage(_onConsoleMessage) + ..enableZoom(false) + ..setNavigationDelegate( + NavigationDelegate( + onHttpAuthRequest: _onHttpAuthRequest, + onUrlChange: _onUrlChange, + onProgress: _onProgress, + onPageStarted: _onPageStarted, + onPageFinished: _onPageFinished, + onHttpError: _onHttpError, + onWebResourceError: _onWebResourceError, + onNavigationRequest: _onNavigationRequest, + ), + ); + + if (widget.urlRequest == null) { + _controller + .loadFlutterAsset('packages/ada_chat_flutter/assets/embed.html'); + } else { + _controller.loadRequest(widget.urlRequest!); + } + } + + void _onConsoleMessage(message) { + print('AdaWebView:onConsoleMessage: ' + 'level=${message.level}, ' + 'message=${message.message}'); + } + + FutureOr _onNavigationRequest(NavigationRequest request) { + print('AdaWebView:onNavigationRequest: ' + 'url=${request.url}'); + // if (request.url.startsWith('https://www.youtube.com/')) { + // return NavigationDecision.prevent; + // } + return NavigationDecision.navigate; + } + + void _onWebResourceError(WebResourceError error) { + print('AdaWebView:onWebResourceError: ' + 'errorCode=${error.errorCode}, ' + 'description=${error.description}'); + } + + void _onHttpError(HttpResponseError error) { + print('AdaWebView:onHttpError: request=${error.request}, ' + 'response=${error.response}'); + } + + void _onPageFinished(String url) { + print('AdaWebView:onPageFinished: url=$url'); + } + + void _onPageStarted(String url) { + print('AdaWebView:onPageStarted: url=$url'); + } + + void _onProgress(int progress) => widget.onProgressChanged?.call(progress); + + void _onUrlChange(change) { + print('AdaWebView:onUrlChange: url=${change.url}'); + } + + void _onHttpAuthRequest(request) { + print('AdaWebView:onHttpAuthRequest: host=${request.host}'); + } /// Unsafe feature. Needed if the embed.html file is not hosted anywhere, then /// the file from the assets will be used. @@ -150,31 +251,32 @@ class _AdaWebViewState extends State { late final bool _allowFileAccessFromFileURLs = widget.urlRequest == null; @override - Widget build(BuildContext context) => InAppWebView( - initialUrlRequest: widget.urlRequest, - initialFile: _getInitialFile, - initialSettings: _settings, - shouldOverrideUrlLoading: _shouldOverrideUrlLoading, - onProgressChanged: (_, progress) => - widget.onProgressChanged?.call(progress), - onReceivedError: (controller, request, error) => - widget.onLoadingError?.call( - request.toString(), - error.toString(), - ), - onReceivedHttpError: (controller, request, errorResponse) => - widget.onLoadingError?.call( - request.toString(), - errorResponse.toString(), - ), - onWebViewCreated: _init, - onLoadStop: _start, - onConsoleMessage: (controller, consoleMessage) => - widget.onConsoleMessage?.call( - consoleMessage.messageLevel.toString(), - consoleMessage.message, - ), - ); + Widget build(BuildContext context) => WebViewWidget(controller: _controller); + // InAppWebView( + // initialUrlRequest: widget.urlRequest, + // initialFile: _getInitialFile, + // initialSettings: _settings, + // shouldOverrideUrlLoading: _shouldOverrideUrlLoading, + // onProgressChanged: (_, progress) => + // widget.onProgressChanged?.call(progress), + // onReceivedError: (controller, request, error) => + // widget.onLoadingError?.call( + // request.toString(), + // error.toString(), + // ), + // onReceivedHttpError: (controller, request, errorResponse) => + // widget.onLoadingError?.call( + // request.toString(), + // errorResponse.toString(), + // ), + // onWebViewCreated: _init, + // onLoadStop: _start, + // onConsoleMessage: (controller, consoleMessage) => + // widget.onConsoleMessage?.call( + // consoleMessage.messageLevel.toString(), + // consoleMessage.message, + // ), + // ); String? get _getInitialFile { return widget.urlRequest != null @@ -182,7 +284,7 @@ class _AdaWebViewState extends State { : 'packages/ada_chat_flutter/assets/embed.html'; } - Future _start(InAppWebViewController controller, WebUri? url) async { + Future _start() async { final metaFields = { ...widget.metaFields, 'sdkType': getOsName, @@ -215,8 +317,8 @@ class _AdaWebViewState extends State { final settingsJson = jsonEncode(settings); - await controller.evaluateJavascript( - source: ''' + await _controller.runJavaScript( + ''' window.adaSettings = { ...$settingsJson, lazy: true, @@ -259,66 +361,66 @@ console.log("adaSettings: " + JSON.stringify(window.adaSettings)); ); } - Future _init(InAppWebViewController controller) async { + Future _init() async { widget.controller?.init( - webViewController: controller, + webViewController: _controller, handle: widget.handle, ); - controller.addJavaScriptHandler( - handlerName: 'onLoaded', - callback: (data) => widget.onLoaded?.call(data), - ); - - controller.addJavaScriptHandler( - handlerName: 'onConversationEnd', - callback: (event) => widget.onConversationEnd?.call(event), - ); - - controller.addJavaScriptHandler( - handlerName: 'onDrawerToggle', - callback: (isDrawerOpen) => - widget.onDrawerToggle?.call(isDrawerOpen as bool), - ); - - controller.addJavaScriptHandler( - handlerName: 'onAdaReady', - callback: (isRolledOut) => widget.onAdaReady?.call(isRolledOut), - ); - - controller.addJavaScriptHandler( - handlerName: 'onEvent', - callback: (event) => widget.onEvent?.call(event), - ); + // _controller.addJavaScriptHandler( + // handlerName: 'onLoaded', + // callback: (data) => widget.onLoaded?.call(data), + // ); + // + // controller.addJavaScriptHandler( + // handlerName: 'onConversationEnd', + // callback: (event) => widget.onConversationEnd?.call(event), + // ); + // + // controller.addJavaScriptHandler( + // handlerName: 'onDrawerToggle', + // callback: (isDrawerOpen) => + // widget.onDrawerToggle?.call(isDrawerOpen as bool), + // ); + // + // controller.addJavaScriptHandler( + // handlerName: 'onAdaReady', + // callback: (isRolledOut) => widget.onAdaReady?.call(isRolledOut), + // ); + // + // controller.addJavaScriptHandler( + // handlerName: 'onEvent', + // callback: (event) => widget.onEvent?.call(event), + // ); } - Future _shouldOverrideUrlLoading( - InAppWebViewController controller, - NavigationAction navigationAction, - ) async { - final url = navigationAction.request.url; - debugPrint('_shouldOverrideUrlLoading: $url, host=${url?.host}'); - - if (url == null || - url.toString() == 'about:blank' || - url.toString() == widget.urlRequest?.url.toString() || - url.toString().endsWith('/ada_chat_flutter/assets/embed.html') || - url.host == '${widget.handle}.ada.support') { - return NavigationActionPolicy.ALLOW; - } - - unawaited( - showAdaptiveDialog( - context: context, - barrierColor: Colors.transparent, - builder: (context) => CustomizedWebView( - url: url, - browserSettings: widget.browserSettings, - ), - useRootNavigator: false, - ), - ); - - return NavigationActionPolicy.CANCEL; - } + // Future _shouldOverrideUrlLoading( + // InAppWebViewController controller, + // NavigationAction navigationAction, + // ) async { + // final url = navigationAction.request.url; + // debugPrint('_shouldOverrideUrlLoading: $url, host=${url?.host}'); + // + // if (url == null || + // url.toString() == 'about:blank' || + // url.toString() == widget.urlRequest?.url.toString() || + // url.toString().endsWith('/ada_chat_flutter/assets/embed.html') || + // url.host == '${widget.handle}.ada.support') { + // return NavigationActionPolicy.ALLOW; + // } + // + // unawaited( + // showAdaptiveDialog( + // context: context, + // barrierColor: Colors.transparent, + // builder: (context) => CustomizedWebView( + // url: url, + // browserSettings: widget.browserSettings, + // ), + // useRootNavigator: false, + // ), + // ); + // + // return NavigationActionPolicy.CANCEL; + // } } diff --git a/packages/ada_chat_flutter/lib/src/browser_controller.dart b/packages/ada_chat_flutter/lib/src/browser_controller.dart index f5f49485e..f1c22e751 100644 --- a/packages/ada_chat_flutter/lib/src/browser_controller.dart +++ b/packages/ada_chat_flutter/lib/src/browser_controller.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:webview_flutter/webview_flutter.dart'; class BrowserController extends ChangeNotifier { - InAppWebViewController? _controller; + WebViewController? _controller; String _title = ''; String _host = ''; bool _isHttps = true; bool _backIsAvailable = false; bool _forwardIsAvailable = false; - void init(InAppWebViewController controller) => _controller = controller; + void init(WebViewController controller) => _controller = controller; Future goBack() async => _controller?.goBack(); diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index d1fffc3fd..ccc0efd35 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -9,7 +9,7 @@ class CustomizedWebView extends StatefulWidget { this.browserSettings, }); - final WebUri url; + final Uri url; final BrowserSettings? browserSettings; @override diff --git a/packages/ada_chat_flutter/pubspec.yaml b/packages/ada_chat_flutter/pubspec.yaml index 1ceed81aa..ea30fa619 100644 --- a/packages/ada_chat_flutter/pubspec.yaml +++ b/packages/ada_chat_flutter/pubspec.yaml @@ -1,24 +1,22 @@ name: ada_chat_flutter description: Flutter implementation of Ada chat -version: 0.0.4 +version: 0.1.0 environment: sdk: '>=3.0.0 <4.0.0' flutter: ">=3.19.0" dependencies: - fk_user_agent: ^2.1.0 # Used by MockWebViewDependencies() tests helper flutter: sdk: flutter - flutter_inappwebview: ^6.0.0 meta: ^1.8.0 - mocktail: ^1.0.3 # Used by MockWebViewDependencies() tests helper - plugin_platform_interface: ^2.1.8 # Used by MockWebViewDependencies() tests helper + webview_flutter: ^4.8.0 dev_dependencies: - flutter_lints: ^2.0.0 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter + mocktail: ^1.0.3 # Used by MockWebViewDependencies() tests helper # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From fd76457797228f5223ff14de977f29885dcabf29 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Thu, 20 Jun 2024 19:55:29 +0400 Subject: [PATCH 02/33] wip: Fix compile errors in lib folder --- .../ada_chat_flutter/analysis_options.yaml | 18 +-- .../lib/ada_chat_flutter.dart | 3 - .../lib/src/ada_web_view.dart | 55 +-------- .../lib/src/customized_web_view.dart | 70 ++++++----- .../src/test/mock_web_view_dependencies.dart | 115 ------------------ 5 files changed, 53 insertions(+), 208 deletions(-) delete mode 100644 packages/ada_chat_flutter/lib/src/test/mock_web_view_dependencies.dart diff --git a/packages/ada_chat_flutter/analysis_options.yaml b/packages/ada_chat_flutter/analysis_options.yaml index 6744da4d2..6ba64e864 100644 --- a/packages/ada_chat_flutter/analysis_options.yaml +++ b/packages/ada_chat_flutter/analysis_options.yaml @@ -2,18 +2,20 @@ include: package:flutter_lints/flutter.yaml linter: rules: - use_key_in_widget_constructors: false - no_adjacent_strings_in_list: false - literal_only_boolean_expressions: false - slash_for_doc_comments: false + avoid_void_async: true + constant_identifier_names: false curly_braces_in_flow_control_structures: false + literal_only_boolean_expressions: false + no_adjacent_strings_in_list: false + non_constant_identifier_names: false omit_local_variable_types: false - use_function_type_syntax_for_parameters: false prefer_const_constructors: false - non_constant_identifier_names: false - constant_identifier_names: false - avoid_void_async: true require_trailing_commas: true + slash_for_doc_comments: false + unawaited_futures: true + use_function_type_syntax_for_parameters: false + use_key_in_widget_constructors: false + analyzer: exclude: diff --git a/packages/ada_chat_flutter/lib/ada_chat_flutter.dart b/packages/ada_chat_flutter/lib/ada_chat_flutter.dart index 61950931b..374ab4e77 100644 --- a/packages/ada_chat_flutter/lib/ada_chat_flutter.dart +++ b/packages/ada_chat_flutter/lib/ada_chat_flutter.dart @@ -1,10 +1,7 @@ library ada_chat_flutter; -export 'package:flutter_inappwebview/flutter_inappwebview.dart'; - export 'src/ada_controller.dart'; export 'src/ada_exception.dart'; export 'src/ada_web_view.dart'; export 'src/browser_controller.dart'; export 'src/browser_settings.dart'; -export 'src/test/mock_web_view_dependencies.dart'; diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 0ae873d71..6fbe22da1 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -138,17 +138,6 @@ class AdaWebView extends StatefulWidget { class _AdaWebViewState extends State { late final WebViewController _controller; - // late final _settings = InAppWebViewSettings( - // supportZoom: false, - // horizontalScrollBarEnabled: false, - // verticalScrollBarEnabled: false, - // useWideViewPort: false, - // disableDefaultErrorPage: true, - // allowFileAccessFromFileURLs: _allowFileAccessFromFileURLs, - // allowsBackForwardNavigationGestures: false, - // disableContextMenu: true, - // ); - @override void initState() { super.initState(); @@ -170,9 +159,9 @@ class _AdaWebViewState extends State { // final platform = _controller.platform; // if (platform is AndroidWebViewController) { - // AndroidWebViewController.enableDebugging(true); - // platform.setMediaPlaybackRequiresUserGesture(false); - // platform.setMediaPlaybackRequiresUserGesture(false); + // AndroidWebViewController.enableDebugging(true); + // platform.setMediaPlaybackRequiresUserGesture(false); + // platform.setMediaPlaybackRequiresUserGesture(false); // } _controller @@ -229,10 +218,12 @@ class _AdaWebViewState extends State { void _onPageFinished(String url) { print('AdaWebView:onPageFinished: url=$url'); + _start(); } void _onPageStarted(String url) { print('AdaWebView:onPageStarted: url=$url'); + _init(); } void _onProgress(int progress) => widget.onProgressChanged?.call(progress); @@ -245,44 +236,8 @@ class _AdaWebViewState extends State { print('AdaWebView:onHttpAuthRequest: host=${request.host}'); } - /// Unsafe feature. Needed if the embed.html file is not hosted anywhere, then - /// the file from the assets will be used. - /// More about it (here)[https://inappwebview.dev/docs/webview/in-app-webview#antipatterns]. - late final bool _allowFileAccessFromFileURLs = widget.urlRequest == null; - @override Widget build(BuildContext context) => WebViewWidget(controller: _controller); - // InAppWebView( - // initialUrlRequest: widget.urlRequest, - // initialFile: _getInitialFile, - // initialSettings: _settings, - // shouldOverrideUrlLoading: _shouldOverrideUrlLoading, - // onProgressChanged: (_, progress) => - // widget.onProgressChanged?.call(progress), - // onReceivedError: (controller, request, error) => - // widget.onLoadingError?.call( - // request.toString(), - // error.toString(), - // ), - // onReceivedHttpError: (controller, request, errorResponse) => - // widget.onLoadingError?.call( - // request.toString(), - // errorResponse.toString(), - // ), - // onWebViewCreated: _init, - // onLoadStop: _start, - // onConsoleMessage: (controller, consoleMessage) => - // widget.onConsoleMessage?.call( - // consoleMessage.messageLevel.toString(), - // consoleMessage.message, - // ), - // ); - - String? get _getInitialFile { - return widget.urlRequest != null - ? null - : 'packages/ada_chat_flutter/assets/embed.html'; - } Future _start() async { final metaFields = { diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index ccc0efd35..840378c3e 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -1,6 +1,6 @@ import 'package:ada_chat_flutter/src/browser_settings.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:webview_flutter/webview_flutter.dart'; class CustomizedWebView extends StatefulWidget { const CustomizedWebView({ @@ -17,14 +17,47 @@ class CustomizedWebView extends StatefulWidget { } class _CustomizedWebViewState extends State { - final _settings = InAppWebViewSettings( - useWideViewPort: false, - ); + late final WebViewController _controller = WebViewController(); @override void initState() { super.initState(); widget.browserSettings?.init(); + + late final PlatformWebViewControllerCreationParams params; + + _controller + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onUrlChange: _onUrlChange, + onPageStarted: _onPageStarted, + onPageFinished: _onPageFinished, + onNavigationRequest: _onNavigationRequest, + ), + ); + } + + void _onUrlChange(change) { + print('AdaWebView:onUrlChange: url=${change.url}'); + } + + void _onPageFinished(String url) { + print('AdaWebView:onPageFinished: url=$url'); + } + + void _onPageStarted(String url) { + print('AdaWebView:onPageStarted: url=$url'); + } + + Future _onNavigationRequest( + NavigationRequest request) async { + print('AdaWebView:onNavigationRequest: ' + 'url=${request.url}'); + // if (request.url.startsWith('https://www.youtube.com/')) { + // return NavigationDecision.prevent; + // } + return NavigationDecision.navigate; } @override @@ -35,34 +68,7 @@ class _CustomizedWebViewState extends State { @override Widget build(BuildContext context) { - final child = InAppWebView( - initialUrlRequest: URLRequest(url: widget.url), - initialSettings: _settings, - onLoadStop: ( - InAppWebViewController webViewController, - WebUri? url, - ) async { - if (widget.browserSettings == null) { - return; - } - - widget.browserSettings?.control?.init(webViewController); - - final title = await webViewController.getTitle(); - widget.browserSettings?.control?.setTitle(title ?? ''); - - final webUri = await webViewController.getUrl(); - widget.browserSettings?.control?.setHost(webUri?.host ?? ''); - widget.browserSettings?.control - ?.setIsHttps(webUri?.isScheme('https') ?? false); - - final canGoBack = await webViewController.canGoBack(); - widget.browserSettings?.control?.setBackIsAvailable(canGoBack); - - final canGoForward = await webViewController.canGoForward(); - widget.browserSettings?.control?.setForwardIsAvailable(canGoForward); - }, - ); + final child = WebViewWidget(controller: _controller); final pageBuilder = widget.browserSettings?.pageBuilder; final browserController = widget.browserSettings?.control; diff --git a/packages/ada_chat_flutter/lib/src/test/mock_web_view_dependencies.dart b/packages/ada_chat_flutter/lib/src/test/mock_web_view_dependencies.dart deleted file mode 100644 index 9173b456f..000000000 --- a/packages/ada_chat_flutter/lib/src/test/mock_web_view_dependencies.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:fk_user_agent/fk_user_agent.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -// ignore: depend_on_referenced_packages -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -/// When you try to run test with widgets containing inappwebview -/// you will see following error: -/// A platform implementation for `flutter_inappwebview` has not been set. Please ensure that an -/// implementation of `InAppWebViewPlatform` has been set to `InAppWebViewPlatform.instance` before use. -/// For unit testing, `InAppWebViewPlatform.instance` can be set with your own test implementation. -/// -/// The solution can be found here: -/// https://github.com/pichillilorenzo/flutter_inappwebview/issues/2019#issuecomment-1959601042 -/// -/// Usage of the solution in tests: -/// ```dart -/// void main() { -// final mockWebViewDependencies = MockWebViewDependencies(); -// -// setUp(() { -// mockWebViewDependencies.init(); -// }); -// -// tearDown(() { -// mockWebViewDependencies.tearDown(); -// }); -/// ``` -class MockWebViewPlatform extends Mock - with MockPlatformInterfaceMixin - implements InAppWebViewPlatform {} - -class MockPlatformCookieManager extends Mock - with MockPlatformInterfaceMixin - implements PlatformCookieManager {} - -class MockWebViewWidget extends Mock - with MockPlatformInterfaceMixin - implements PlatformInAppWebViewWidget {} - -class FakeCookieParams extends Fake - implements PlatformCookieManagerCreationParams {} - -class FakeWebUri extends Fake implements WebUri {} - -class FakeWidgetParams extends Fake - implements PlatformInAppWebViewWidgetCreationParams {} - -class MockWebViewDependencies { - static const MethodChannel channel = MethodChannel('fk_user_agent'); - - Future init() async { - registerFallbackValue(FakeCookieParams()); - registerFallbackValue(FakeWebUri()); - registerFallbackValue(FakeWidgetParams()); - - // Mock webview widget - final mockWidget = MockWebViewWidget(); - when(() => mockWidget.build(any())).thenReturn(const SizedBox.shrink()); - - // Mock cookie manager - final mockCookieManager = MockPlatformCookieManager(); - when(() => mockCookieManager.deleteAllCookies()) - .thenAnswer((_) => Future.value(true)); - when( - () => mockCookieManager.setCookie( - url: any(named: 'url'), - name: any(named: 'name'), - value: any(named: 'value'), - path: any(named: 'path'), - domain: any(named: 'domain'), - expiresDate: any(named: 'expiresDate'), - maxAge: any(named: 'maxAge'), - isSecure: any(named: 'isSecure'), - isHttpOnly: any(named: 'isHttpOnly'), - sameSite: any(named: 'sameSite'), - // ignore: deprecated_member_use - iosBelow11WebViewController: any(named: 'iosBelow11WebViewController'), - webViewController: any(named: 'webViewController'), - ), - ).thenAnswer((_) => Future.value(true)); - - // Mock webview platform - final mockPlatform = MockWebViewPlatform(); - when(() => mockPlatform.createPlatformInAppWebViewWidget(any())) - .thenReturn(mockWidget); - when(() => mockPlatform.createPlatformCookieManager(any())) - .thenReturn(mockCookieManager); - - // Use mock - InAppWebViewPlatform.instance = mockPlatform; - - // Mock user agent in setUp or setUpAll - // ignore: deprecated_member_use - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return {'webViewUserAgent': 'userAgent'}; - }); - FkUserAgent.init(); - } - - void tearDown() { - // ignore: deprecated_member_use - channel.setMockMethodCallHandler(null); - } - - /// This double pump is needed for triggering the build of the webview - /// otherwise it will fail - Future doublePump(WidgetTester tester) async { - await tester.pump(); - await tester.pump(); - } -} From d436aa1eabe27261ca8d1215f4f91825259a5514 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Thu, 20 Jun 2024 19:56:17 +0400 Subject: [PATCH 03/33] fix: Fix handle --- .../ada_chat_flutter/example/lib/screens/ada_chat_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index 4bf01cb14..f3e15daa9 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -34,7 +34,7 @@ class _AdaChatScreenState extends State { body: Stack( children: [ AdaWebView( - handle: 'headspace-sandbox', + handle: 'example-handle', name: 'User 1', email: 'qqq@google.com', phone: '+5342342131324', From 076bbf215e776c6006795995c8e4c024a6f3cb3e Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Thu, 20 Jun 2024 20:21:59 +0400 Subject: [PATCH 04/33] fix: Fix JS in AdaController --- .../lib/src/ada_controller.dart | 57 ++++--------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_controller.dart b/packages/ada_chat_flutter/lib/src/ada_controller.dart index ab13f137a..dbb75b061 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller.dart @@ -9,58 +9,37 @@ typedef FlatObject = Map; class AdaController extends AdaControllerInit { @override Future start() => webViewController.runJavaScript( - // todo Check if function(){}() wrapper is needed ''' -(function() { - adaEmbed.start({ - handle: "$handle", - parentElement: "content_frame" - }); -})(); +adaEmbed.start({ + handle: "$handle", + parentElement: "content_frame" +}); ''', ); Future deleteHistory() => webViewController.runJavaScript( - ''' -(function() { - adaEmbed.deleteHistory(); -})(); -''', + 'adaEmbed.deleteHistory();', ); Future getInfo() async { return await webViewController.runJavaScriptReturningResult( - ''' -return await adaEmbed.getInfo(); -''', + 'return await adaEmbed.getInfo();', ); } Future reset() => webViewController.runJavaScript( - ''' -(function() { - adaEmbed.reset(); -})(); -''', + 'adaEmbed.reset();', ); Future setLanguage(String language) => webViewController.runJavaScript( - ''' -(function() { - adaEmbed.setLanguage("$language"); -})(); -''', + 'adaEmbed.setLanguage("$language");', ); Future setMetaFields(FlatObject meta) async { final metaJson = jsonEncode(meta); await webViewController.runJavaScript( - ''' -(function() { - adaEmbed.setMetaFields($metaJson); -})(); -''', + 'adaEmbed.setMetaFields($metaJson);', ); } @@ -68,29 +47,17 @@ return await adaEmbed.getInfo(); final metaJson = jsonEncode(meta); await webViewController.runJavaScript( - ''' -(function() { - adaEmbed.setSensitiveMetaFields($metaJson); -})(); -''', + 'adaEmbed.setSensitiveMetaFields($metaJson);', ); } Future stop() => webViewController.runJavaScript( - ''' -(function() { - adaEmbed.stop(); -})(); -''', + 'adaEmbed.stop();', ); @override Future triggerAnswer(String answerId) => webViewController.runJavaScript( - ''' -(function() { - adaEmbed.triggerAnswer("$answerId"); -})(); -''', + 'adaEmbed.triggerAnswer("$answerId");', ); } From d70112b20d97e682fa5cb079ef4597ea1b868f8e Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Thu, 20 Jun 2024 20:22:28 +0400 Subject: [PATCH 05/33] fix: Add init and start to _onPageFinished() --- .../lib/src/ada_web_view.dart | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 6fbe22da1..5f316aaf5 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -148,6 +148,7 @@ class _AdaWebViewState extends State { params = WebKitWebViewControllerCreationParams( allowsInlineMediaPlayback: true, mediaTypesRequiringUserAction: const {}, + limitsNavigationsToAppBoundDomains: false, // todo Remove? ); } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { params = AndroidWebViewControllerCreationParams(); @@ -157,12 +158,11 @@ class _AdaWebViewState extends State { _controller = WebViewController.fromPlatformCreationParams(params); - // final platform = _controller.platform; - // if (platform is AndroidWebViewController) { - // AndroidWebViewController.enableDebugging(true); - // platform.setMediaPlaybackRequiresUserGesture(false); - // platform.setMediaPlaybackRequiresUserGesture(false); - // } + final platform = _controller.platform; + if (platform is AndroidWebViewController) { + AndroidWebViewController.enableDebugging(true); + } + // else if (platform is WebKitWebViewController) {} _controller ..setJavaScriptMode(JavaScriptMode.unrestricted) @@ -218,12 +218,15 @@ class _AdaWebViewState extends State { void _onPageFinished(String url) { print('AdaWebView:onPageFinished: url=$url'); - _start(); + + Future.delayed(Duration.zero, () async { + await _init(); + await _start(); + }); } void _onPageStarted(String url) { print('AdaWebView:onPageStarted: url=$url'); - _init(); } void _onProgress(int progress) => widget.onProgressChanged?.call(progress); From 1bc5de4d3524ff5aa349382eda97aa513ad3bfa2 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Thu, 20 Jun 2024 23:22:57 +0400 Subject: [PATCH 06/33] wip: Can't load ada --- .../example/lib/screens/ada_chat_screen.dart | 14 +- .../lib/src/ada_web_view.dart | 131 ++++++++++-------- packages/ada_chat_flutter/pubspec.yaml | 2 +- .../test/src/ada_controller_init_test.dart | 4 - 4 files changed, 83 insertions(+), 68 deletions(-) diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index f3e15daa9..2c6ac92e9 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -56,11 +56,6 @@ class _AdaChatScreenState extends State { onProgressChanged: (progress) => setState(() { _progress = progress / 100; }), - onAdaReady: (isRolledOut) { - debugPrint( - 'AdaChatScreen:onAdaReady: isRolledOut=$isRolledOut'); - setState(() => _progress = 0); - }, browserSettings: BrowserSettings( pageBuilder: (context, browser, controller) => Scaffold( body: SafeArea( @@ -73,6 +68,11 @@ class _AdaChatScreenState extends State { ), onLoaded: (data) => debugPrint('AdaChatScreen:onLoaded: data=$data'), + onAdaReady: (isRolledOut) { + debugPrint( + 'AdaChatScreen:onAdaReady: isRolledOut=$isRolledOut'); + setState(() => _progress = 0); + }, onEvent: (event) => debugPrint('AdaChatScreen:onEvent: event=$event'), onConsoleMessage: (level, message) => @@ -82,9 +82,9 @@ class _AdaChatScreenState extends State { debugPrint('AdaChatScreen:onConversationEnd: event=$event'), onDrawerToggle: (isDrawerOpen) => debugPrint( 'AdaChatScreen:onConversationEnd: isDrawerOpen=$isDrawerOpen'), - onLoadingError: (request, error) => + onLoadingError: (request, response) => debugPrint('AdaChatScreen:onLoadingError: ' - 'request=$request, error=$error'), + 'request=$request, response=$response'), ), AnimatedPositioned( duration: const Duration(milliseconds: 500), diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 5f316aaf5..adeaee1fd 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -8,9 +8,9 @@ import 'package:ada_chat_flutter/src/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; -// Import for Android features. +// ignore: depend_on_referenced_packages import 'package:webview_flutter_android/webview_flutter_android.dart'; -// Import for iOS features. +// ignore: depend_on_referenced_packages import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; /// Ada chat WebView widget. @@ -40,8 +40,8 @@ class AdaWebView extends StatefulWidget { this.testMode = false, this.onProgressChanged, this.browserSettings, - this.onLoaded, this.onAdaReady, + this.onLoaded, this.onEvent, this.onConversationEnd, this.onDrawerToggle, @@ -89,13 +89,13 @@ class AdaWebView extends StatefulWidget { final BrowserSettings? browserSettings; - final void Function(String data)? onLoaded; - final void Function(String isRolledOut)? onAdaReady; - final void Function(String event)? onEvent; - final void Function(String event)? onConversationEnd; - final void Function(String isDrawerOpen)? onDrawerToggle; + final void Function(dynamic data)? onLoaded; + final void Function(dynamic isRolledOut)? onAdaReady; + final void Function(dynamic event)? onEvent; + final void Function(dynamic event)? onConversationEnd; + final void Function(dynamic isDrawerOpen)? onDrawerToggle; final void Function(String level, String message)? onConsoleMessage; - final void Function(String request, String error)? onLoadingError; + final void Function(String request, String response)? onLoadingError; @override State createState() => _AdaWebViewState(); @@ -190,11 +190,8 @@ class _AdaWebViewState extends State { } } - void _onConsoleMessage(message) { - print('AdaWebView:onConsoleMessage: ' - 'level=${message.level}, ' - 'message=${message.message}'); - } + void _onConsoleMessage(JavaScriptConsoleMessage message) => + widget.onConsoleMessage?.call(message.level.toString(), message.message); FutureOr _onNavigationRequest(NavigationRequest request) { print('AdaWebView:onNavigationRequest: ' @@ -211,31 +208,28 @@ class _AdaWebViewState extends State { 'description=${error.description}'); } - void _onHttpError(HttpResponseError error) { - print('AdaWebView:onHttpError: request=${error.request}, ' - 'response=${error.response}'); - } + void _onHttpError(HttpResponseError error) => widget.onLoadingError + ?.call(error.request.toString(), error.response.toString()); void _onPageFinished(String url) { print('AdaWebView:onPageFinished: url=$url'); - Future.delayed(Duration.zero, () async { - await _init(); - await _start(); - }); + _start(); } void _onPageStarted(String url) { print('AdaWebView:onPageStarted: url=$url'); + + _init(); } void _onProgress(int progress) => widget.onProgressChanged?.call(progress); - void _onUrlChange(change) { + void _onUrlChange(UrlChange change) { print('AdaWebView:onUrlChange: url=${change.url}'); } - void _onHttpAuthRequest(request) { + void _onHttpAuthRequest(HttpAuthRequest request) { print('AdaWebView:onHttpAuthRequest: host=${request.host}'); } @@ -281,23 +275,26 @@ window.adaSettings = { ...$settingsJson, lazy: true, parentElement: "content_frame", + adaReadyCallback: function(isRolledOut) { + onAdaReady.postMessage(JSON.stringify(isRolledOut)); + }, onAdaEmbedLoaded: () => { adaEmbed.subscribeEvent("ada:chat_frame_timeout", (data, context) => { - window.flutter_inappwebview.callHandler("onLoaded", data); + onLoaded.postMessage(data === undefined ? "" : JSON.stringify(data)); }); }, conversationEndCallback: function(event) { - window.flutter_inappwebview.callHandler("onConversationEnd", event); - }, - adaReadyCallback: function(isRolledOut) { - window.flutter_inappwebview.callHandler("onAdaReady", isRolledOut); + console.log("onConversationEnd: " + JSON.stringify(event)); + onConversationEnd.postMessage(JSON.stringify(event)); }, toggleCallback: function(isDrawerOpen) { - window.flutter_inappwebview.callHandler("onDrawerToggle", isDrawerOpen); + console.log("onDrawerToggle: " + JSON.stringify(isDrawerOpen)); + onDrawerToggle.postMessage(JSON.stringify(isDrawerOpen)); }, eventCallbacks: { "*": function(event) { - window.flutter_inappwebview.callHandler("onEvent", JSON.stringify(event)); + console.log("onEvent: " + JSON.stringify(event)); + onEvent.postMessage(JSON.stringify(event)); } } }; @@ -325,31 +322,53 @@ console.log("adaSettings: " + JSON.stringify(window.adaSettings)); handle: widget.handle, ); - // _controller.addJavaScriptHandler( - // handlerName: 'onLoaded', - // callback: (data) => widget.onLoaded?.call(data), - // ); - // - // controller.addJavaScriptHandler( - // handlerName: 'onConversationEnd', - // callback: (event) => widget.onConversationEnd?.call(event), - // ); - // - // controller.addJavaScriptHandler( - // handlerName: 'onDrawerToggle', - // callback: (isDrawerOpen) => - // widget.onDrawerToggle?.call(isDrawerOpen as bool), - // ); - // - // controller.addJavaScriptHandler( - // handlerName: 'onAdaReady', - // callback: (isRolledOut) => widget.onAdaReady?.call(isRolledOut), - // ); - // - // controller.addJavaScriptHandler( - // handlerName: 'onEvent', - // callback: (event) => widget.onEvent?.call(event), - // ); + await _controller.addJavaScriptChannel( + 'onLoaded', + onMessageReceived: (JavaScriptMessage message) { + final json = jsonStrToMap(message.message); + widget.onLoaded?.call(json); + }, + ); + + await _controller.addJavaScriptChannel( + 'onConversationEnd', + onMessageReceived: (JavaScriptMessage message) { + final json = jsonStrToMap(message.message); + widget.onConversationEnd?.call(json); + }, + ); + + await _controller.addJavaScriptChannel( + 'onDrawerToggle', + onMessageReceived: (JavaScriptMessage message) { + final json = jsonStrToMap(message.message); + widget.onDrawerToggle?.call(json); + }, + ); + + await _controller.addJavaScriptChannel( + 'onAdaReady', + onMessageReceived: (JavaScriptMessage message) { + final json = jsonStrToMap(message.message); + widget.onAdaReady?.call(json); + }, + ); + + await _controller.addJavaScriptChannel( + 'onEvent', + onMessageReceived: (JavaScriptMessage message) { + final json = jsonStrToMap(message.message); + widget.onEvent?.call(json); + }, + ); + } + + dynamic jsonStrToMap(String message) { + if (message.isEmpty) { + return {}; + } + + return jsonDecode(message); } // Future _shouldOverrideUrlLoading( diff --git a/packages/ada_chat_flutter/pubspec.yaml b/packages/ada_chat_flutter/pubspec.yaml index ea30fa619..4cc9f4928 100644 --- a/packages/ada_chat_flutter/pubspec.yaml +++ b/packages/ada_chat_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: ada_chat_flutter description: Flutter implementation of Ada chat -version: 0.1.0 +version: 1.0.0 environment: sdk: '>=3.0.0 <4.0.0' diff --git a/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart b/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart index 02260914e..4ac9e8cf5 100644 --- a/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart +++ b/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart @@ -1,11 +1,7 @@ import 'package:ada_chat_flutter/src/ada_controller_init.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -class MockInAppWebViewController extends Mock - implements InAppWebViewController {} - class AdaControllerInitImpl extends AdaControllerInit { @override void start() {} From 93b6f2e6a8762e84a25083653eadbb1e36063cb9 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 18:14:43 +0400 Subject: [PATCH 07/33] wip: Refactor --- .../ada_chat_flutter/analysis_options.yaml | 1 - .../lib/src/ada_controller.dart | 42 +++++++------------ .../lib/src/ada_web_view.dart | 2 +- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/packages/ada_chat_flutter/analysis_options.yaml b/packages/ada_chat_flutter/analysis_options.yaml index 6ba64e864..9f692afeb 100644 --- a/packages/ada_chat_flutter/analysis_options.yaml +++ b/packages/ada_chat_flutter/analysis_options.yaml @@ -16,7 +16,6 @@ linter: use_function_type_syntax_for_parameters: false use_key_in_widget_constructors: false - analyzer: exclude: - 'lib/generated/**' diff --git a/packages/ada_chat_flutter/lib/src/ada_controller.dart b/packages/ada_chat_flutter/lib/src/ada_controller.dart index dbb75b061..665335d6f 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller.dart @@ -8,56 +8,42 @@ typedef FlatObject = Map; /// https://developers.ada.cx/reference/embed2-reference class AdaController extends AdaControllerInit { @override - Future start() => webViewController.runJavaScript( - ''' + Future start() => webViewController.runJavaScript(''' adaEmbed.start({ handle: "$handle", parentElement: "content_frame" }); -''', - ); +'''); - Future deleteHistory() => webViewController.runJavaScript( - 'adaEmbed.deleteHistory();', - ); + Future deleteHistory() => + webViewController.runJavaScript('adaEmbed.deleteHistory();'); Future getInfo() async { - return await webViewController.runJavaScriptReturningResult( - 'return await adaEmbed.getInfo();', - ); + return await webViewController + .runJavaScriptReturningResult('return await adaEmbed.getInfo();'); } - Future reset() => webViewController.runJavaScript( - 'adaEmbed.reset();', - ); + Future reset() => webViewController.runJavaScript('adaEmbed.reset();'); - Future setLanguage(String language) => webViewController.runJavaScript( - 'adaEmbed.setLanguage("$language");', - ); + Future setLanguage(String language) => + webViewController.runJavaScript('adaEmbed.setLanguage("$language");'); Future setMetaFields(FlatObject meta) async { final metaJson = jsonEncode(meta); - await webViewController.runJavaScript( - 'adaEmbed.setMetaFields($metaJson);', - ); + await webViewController.runJavaScript('adaEmbed.setMetaFields($metaJson);'); } Future setSensitiveMetaFields(FlatObject meta) async { final metaJson = jsonEncode(meta); - await webViewController.runJavaScript( - 'adaEmbed.setSensitiveMetaFields($metaJson);', - ); + await webViewController + .runJavaScript('adaEmbed.setSensitiveMetaFields($metaJson);'); } - Future stop() => webViewController.runJavaScript( - 'adaEmbed.stop();', - ); + Future stop() => webViewController.runJavaScript('adaEmbed.stop();'); @override Future triggerAnswer(String answerId) => - webViewController.runJavaScript( - 'adaEmbed.triggerAnswer("$answerId");', - ); + webViewController.runJavaScript('adaEmbed.triggerAnswer("$answerId");'); } diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index adeaee1fd..66fa431eb 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -280,7 +280,7 @@ window.adaSettings = { }, onAdaEmbedLoaded: () => { adaEmbed.subscribeEvent("ada:chat_frame_timeout", (data, context) => { - onLoaded.postMessage(data === undefined ? "" : JSON.stringify(data)); + onLoaded.postMessage(typeof data === "undefined" ? "" : JSON.stringify(data)); }); }, conversationEndCallback: function(event) { From 9cd940e471bbe94e3e0b7be24cda764fb98e27ee Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 19:50:30 +0400 Subject: [PATCH 08/33] feat: github.io page is working! Small fixes --- .../lib/src/ada_web_view.dart | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 66fa431eb..fb0b85392 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -19,11 +19,11 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; class AdaWebView extends StatefulWidget { const AdaWebView({ super.key, + required this.urlRequest, required this.handle, this.name, this.email, this.phone, - this.urlRequest, this.controller, this.language = 'en', this.cluster, @@ -60,9 +60,9 @@ class AdaWebView extends StatefulWidget { /// User phone, an additional field to add to the metadata final String? phone; - /// Url to your own page with assets/embed.html file. + /// Url to your own page with embed.html file. /// The domain must be added to approved domains. (Doc)[https://docs.ada.cx/docs/scripted/use-ada-with-your-website/deploy-ada-on-your-website-or-app/restrict-your-bot-to-a-list-of-approved-domains/]. - final Uri? urlRequest; // todo Rename to embedUri + final Uri urlRequest; // todo Rename to embedUri /// Controller for Ada chat. The default implementation is [AdaController]. final AdaControllerInit? controller; @@ -182,12 +182,7 @@ class _AdaWebViewState extends State { ), ); - if (widget.urlRequest == null) { - _controller - .loadFlutterAsset('packages/ada_chat_flutter/assets/embed.html'); - } else { - _controller.loadRequest(widget.urlRequest!); - } + _controller.loadRequest(widget.urlRequest); } void _onConsoleMessage(JavaScriptConsoleMessage message) => @@ -208,19 +203,24 @@ class _AdaWebViewState extends State { 'description=${error.description}'); } - void _onHttpError(HttpResponseError error) => widget.onLoadingError - ?.call(error.request.toString(), error.response.toString()); + void _onHttpError(HttpResponseError error) => widget.onLoadingError?.call( + error.request?.uri.toString() ?? '', + 'uri=${error.response?.uri}, ' + 'statusCode=${error.response?.statusCode}, ' + 'headers=${error.response?.headers}, ', + ); void _onPageFinished(String url) { print('AdaWebView:onPageFinished: url=$url'); - _start(); + Future.delayed(Duration.zero, () async { + await _init(); + await _start(); + }); } void _onPageStarted(String url) { print('AdaWebView:onPageStarted: url=$url'); - - _init(); } void _onProgress(int progress) => widget.onProgressChanged?.call(progress); @@ -303,7 +303,7 @@ console.log("adaSettings: " + JSON.stringify(window.adaSettings)); ); if (widget.autostart) { - widget.controller?.start(); + await widget.controller?.start(); } Future.delayed( From 6fae5637dd8b2aa5d5e46ff037cd622e1a192e63 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 19:51:13 +0400 Subject: [PATCH 09/33] fix: embed.html uri of example --- .../ada_chat_flutter/example/lib/screens/ada_chat_screen.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index 2c6ac92e9..a9ea0178f 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -34,6 +34,9 @@ class _AdaChatScreenState extends State { body: Stack( children: [ AdaWebView( + urlRequest: Uri.parse( + 'https://your.domain.com/embed.html', + ), handle: 'example-handle', name: 'User 1', email: 'qqq@google.com', From 71034e882c2ce99d7900dab684e84ed71b80c1fe Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 20:08:59 +0400 Subject: [PATCH 10/33] wip: getInfo() is not working --- packages/ada_chat_flutter/lib/src/ada_controller.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_controller.dart b/packages/ada_chat_flutter/lib/src/ada_controller.dart index 665335d6f..c0cb10879 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller.dart @@ -19,8 +19,11 @@ adaEmbed.start({ webViewController.runJavaScript('adaEmbed.deleteHistory();'); Future getInfo() async { - return await webViewController - .runJavaScriptReturningResult('return await adaEmbed.getInfo();'); + return await webViewController.runJavaScriptReturningResult(''' +let info = adaEmbed.getInfo(); +await info; +return info; +'''); } Future reset() => webViewController.runJavaScript('adaEmbed.reset();'); From 785dfaf27e3253489fdfa16d9742dd9028c2cc71 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 22:55:13 +0400 Subject: [PATCH 11/33] wip: getInfo() is not working --- packages/ada_chat_flutter/lib/src/ada_controller.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_controller.dart b/packages/ada_chat_flutter/lib/src/ada_controller.dart index c0cb10879..7689a6e54 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller.dart @@ -18,11 +18,10 @@ adaEmbed.start({ Future deleteHistory() => webViewController.runJavaScript('adaEmbed.deleteHistory();'); + // todo Fix getInfo() Future getInfo() async { return await webViewController.runJavaScriptReturningResult(''' -let info = adaEmbed.getInfo(); -await info; -return info; +return await adaEmbed.getInfo(); '''); } From ee30d1e87d8ac81ad68c5fc5086e08828375a2f4 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 22:55:45 +0400 Subject: [PATCH 12/33] wip: Headers are not helping to fix zoom on android --- .../lib/src/ada_web_view.dart | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index fb0b85392..385b19a4a 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -19,7 +19,7 @@ import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; class AdaWebView extends StatefulWidget { const AdaWebView({ super.key, - required this.urlRequest, + required this.embedUri, required this.handle, this.name, this.email, @@ -62,7 +62,7 @@ class AdaWebView extends StatefulWidget { /// Url to your own page with embed.html file. /// The domain must be added to approved domains. (Doc)[https://docs.ada.cx/docs/scripted/use-ada-with-your-website/deploy-ada-on-your-website-or-app/restrict-your-bot-to-a-list-of-approved-domains/]. - final Uri urlRequest; // todo Rename to embedUri + final Uri embedUri; /// Controller for Ada chat. The default implementation is [AdaController]. final AdaControllerInit? controller; @@ -107,7 +107,7 @@ class AdaWebView extends StatefulWidget { properties.add(StringProperty('name', name)); properties.add(StringProperty('email', email)); properties.add(StringProperty('phone', phone)); - properties.add(DiagnosticsProperty('urlRequest', urlRequest)); + properties.add(DiagnosticsProperty('urlRequest', embedUri)); properties.add(StringProperty('language', language)); properties.add(StringProperty('cluster', cluster)); properties.add(StringProperty('domain', domain)); @@ -182,7 +182,7 @@ class _AdaWebViewState extends State { ), ); - _controller.loadRequest(widget.urlRequest); + _controller.loadRequest(widget.embedUri); } void _onConsoleMessage(JavaScriptConsoleMessage message) => @@ -190,10 +190,40 @@ class _AdaWebViewState extends State { FutureOr _onNavigationRequest(NavigationRequest request) { print('AdaWebView:onNavigationRequest: ' - 'url=${request.url}'); - // if (request.url.startsWith('https://www.youtube.com/')) { - // return NavigationDecision.prevent; - // } + 'url=${request.url}, isMainFrame=${request.isMainFrame}'); + + if (request.url == widget.embedUri.toString()) { + final requestUri = Uri.parse(request.url); + + final uri = Uri( + scheme: requestUri.scheme, + userInfo: requestUri.userInfo, + host: requestUri.host, + port: requestUri.port, + path: requestUri.path, + queryParameters: { + ...requestUri.queryParameters, + ...{'dummy': 'dummy'}, + }, + fragment: requestUri.fragment, + ); + + final headers = { + "user-agent": + "Mozilla/5.0 (Linux; Android 13; sdk_gphone64_arm64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36", + "connection": "keep-alive", + "accept": + "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", + // "accept-language": "en-US,en;q=0.9", + "accept-encoding": "gzip, deflate", + // "host": "192.168.50.114:8000", + // "referer": "http://192.168.50.114:8000/" + }; + + _controller.loadRequest(uri, headers: headers); + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; } From 7aba591505c7e2cb705ce48a5a9c357e6420fffd Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Fri, 21 Jun 2024 23:02:30 +0400 Subject: [PATCH 13/33] wip: Even iPhone headers are not helping --- .../lib/src/ada_web_view.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 385b19a4a..1c65a9e17 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -210,15 +210,22 @@ class _AdaWebViewState extends State { final headers = { "user-agent": - "Mozilla/5.0 (Linux; Android 13; sdk_gphone64_arm64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36", + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1", "connection": "keep-alive", - "accept": - "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", - // "accept-language": "en-US,en;q=0.9", + "accept": "*/*", "accept-encoding": "gzip, deflate", - // "host": "192.168.50.114:8000", - // "referer": "http://192.168.50.114:8000/" }; + // { + // "user-agent": + // "Mozilla/5.0 (Linux; Android 13; sdk_gphone64_arm64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36", + // "connection": "keep-alive", + // "accept": + // "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", + // // "accept-language": "en-US,en;q=0.9", + // "accept-encoding": "gzip, deflate", + // // "host": "192.168.50.114:8000", + // // "referer": "http://192.168.50.114:8000/" + // }; _controller.loadRequest(uri, headers: headers); return NavigationDecision.prevent; From a72c0f9dd41b7260be5a153bbac36a228cbc7d53 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Mon, 24 Jun 2024 18:26:52 +0400 Subject: [PATCH 14/33] wip: Open link outside of the chat --- .../example/analysis_options.yaml | 30 +----- .../lib/src/ada_web_view.dart | 92 +++++-------------- .../lib/src/customized_web_view.dart | 12 +-- 3 files changed, 29 insertions(+), 105 deletions(-) diff --git a/packages/ada_chat_flutter/example/analysis_options.yaml b/packages/ada_chat_flutter/example/analysis_options.yaml index 61b6c4de1..5e2133eb6 100644 --- a/packages/ada_chat_flutter/example/analysis_options.yaml +++ b/packages/ada_chat_flutter/example/analysis_options.yaml @@ -1,29 +1 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +include: ../analysis_options.yaml diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 1c65a9e17..0206471a4 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:ada_chat_flutter/src/ada_controller.dart'; import 'package:ada_chat_flutter/src/ada_controller_init.dart'; import 'package:ada_chat_flutter/src/browser_settings.dart'; +import 'package:ada_chat_flutter/src/customized_web_view.dart'; import 'package:ada_chat_flutter/src/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -148,7 +149,6 @@ class _AdaWebViewState extends State { params = WebKitWebViewControllerCreationParams( allowsInlineMediaPlayback: true, mediaTypesRequiringUserAction: const {}, - limitsNavigationsToAppBoundDomains: false, // todo Remove? ); } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { params = AndroidWebViewControllerCreationParams(); @@ -189,49 +189,31 @@ class _AdaWebViewState extends State { widget.onConsoleMessage?.call(message.level.toString(), message.message); FutureOr _onNavigationRequest(NavigationRequest request) { + final uri = Uri.parse(request.url); print('AdaWebView:onNavigationRequest: ' - 'url=${request.url}, isMainFrame=${request.isMainFrame}'); - - if (request.url == widget.embedUri.toString()) { - final requestUri = Uri.parse(request.url); - - final uri = Uri( - scheme: requestUri.scheme, - userInfo: requestUri.userInfo, - host: requestUri.host, - port: requestUri.port, - path: requestUri.path, - queryParameters: { - ...requestUri.queryParameters, - ...{'dummy': 'dummy'}, - }, - fragment: requestUri.fragment, - ); + 'url=${uri.toString()}, isMainFrame=${request.isMainFrame}'); + + if (request.isMainFrame || + uri.host.endsWith('${widget.handle}.ada.support') || + uri.toString() == 'about:blank') { + // final requestUri = Uri.parse(request.url); - final headers = { - "user-agent": - "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1", - "connection": "keep-alive", - "accept": "*/*", - "accept-encoding": "gzip, deflate", - }; - // { - // "user-agent": - // "Mozilla/5.0 (Linux; Android 13; sdk_gphone64_arm64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36", - // "connection": "keep-alive", - // "accept": - // "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", - // // "accept-language": "en-US,en;q=0.9", - // "accept-encoding": "gzip, deflate", - // // "host": "192.168.50.114:8000", - // // "referer": "http://192.168.50.114:8000/" - // }; - - _controller.loadRequest(uri, headers: headers); - return NavigationDecision.prevent; + return NavigationDecision.navigate; } - return NavigationDecision.navigate; + unawaited( + showAdaptiveDialog( + context: context, + barrierColor: Colors.transparent, + builder: (context) => CustomizedWebView( + url: uri, + browserSettings: widget.browserSettings, + ), + useRootNavigator: false, + ), + ); + + return NavigationDecision.prevent; } void _onWebResourceError(WebResourceError error) { @@ -407,34 +389,4 @@ console.log("adaSettings: " + JSON.stringify(window.adaSettings)); return jsonDecode(message); } - - // Future _shouldOverrideUrlLoading( - // InAppWebViewController controller, - // NavigationAction navigationAction, - // ) async { - // final url = navigationAction.request.url; - // debugPrint('_shouldOverrideUrlLoading: $url, host=${url?.host}'); - // - // if (url == null || - // url.toString() == 'about:blank' || - // url.toString() == widget.urlRequest?.url.toString() || - // url.toString().endsWith('/ada_chat_flutter/assets/embed.html') || - // url.host == '${widget.handle}.ada.support') { - // return NavigationActionPolicy.ALLOW; - // } - // - // unawaited( - // showAdaptiveDialog( - // context: context, - // barrierColor: Colors.transparent, - // builder: (context) => CustomizedWebView( - // url: url, - // browserSettings: widget.browserSettings, - // ), - // useRootNavigator: false, - // ), - // ); - // - // return NavigationActionPolicy.CANCEL; - // } } diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index 840378c3e..69cf28a82 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -24,8 +24,6 @@ class _CustomizedWebViewState extends State { super.initState(); widget.browserSettings?.init(); - late final PlatformWebViewControllerCreationParams params; - _controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( @@ -36,23 +34,25 @@ class _CustomizedWebViewState extends State { onNavigationRequest: _onNavigationRequest, ), ); + + _controller.loadRequest(widget.url); } void _onUrlChange(change) { - print('AdaWebView:onUrlChange: url=${change.url}'); + print('CustomizedWebView:onUrlChange: url=${change.url}'); } void _onPageFinished(String url) { - print('AdaWebView:onPageFinished: url=$url'); + print('CustomizedWebView:onPageFinished: url=$url'); } void _onPageStarted(String url) { - print('AdaWebView:onPageStarted: url=$url'); + print('CustomizedWebView:onPageStarted: url=$url'); } Future _onNavigationRequest( NavigationRequest request) async { - print('AdaWebView:onNavigationRequest: ' + print('CustomizedWebView:onNavigationRequest: ' 'url=${request.url}'); // if (request.url.startsWith('https://www.youtube.com/')) { // return NavigationDecision.prevent; From b53f2b85dd757af1fcb35dd9cecc8541c213135c Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Mon, 24 Jun 2024 19:04:47 +0400 Subject: [PATCH 15/33] feat: Update values like page Title and backIsAvailable from the controller. --- .../lib/src/customized_web_view.dart | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index 69cf28a82..21899afbb 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -42,8 +42,31 @@ class _CustomizedWebViewState extends State { print('CustomizedWebView:onUrlChange: url=${change.url}'); } - void _onPageFinished(String url) { + void _onPageFinished(String url) async { print('CustomizedWebView:onPageFinished: url=$url'); + + if (widget.browserSettings == null) { + return; + } + + widget.browserSettings?.control?.init(_controller); + + final title = await _controller.getTitle(); + widget.browserSettings?.control?.setTitle(title ?? ''); + + final currentUrl = await _controller.currentUrl(); + if (currentUrl != null) { + final currentUri = Uri.parse(currentUrl); + widget.browserSettings?.control?.setHost(currentUri.host ?? ''); + widget.browserSettings?.control + ?.setIsHttps(currentUri.isScheme('https') ?? false); + } + + final canGoBack = await _controller.canGoBack(); + widget.browserSettings?.control?.setBackIsAvailable(canGoBack); + + final canGoForward = await _controller.canGoForward(); + widget.browserSettings?.control?.setForwardIsAvailable(canGoForward); } void _onPageStarted(String url) { From e6af8781b971eccd24139ed7def4806bf898a70c Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Mon, 24 Jun 2024 19:27:02 +0400 Subject: [PATCH 16/33] fix: Small fixes --- .../example/lib/screens/ada_chat_screen.dart | 22 ++++---- .../example/lib/widgets/commands_menu.dart | 4 +- .../lib/src/ada_web_view.dart | 13 ++--- .../lib/src/customized_web_view.dart | 51 +++++++++---------- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index a9ea0178f..f3e2d5e78 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:ada_chat_flutter/ada_chat_flutter.dart'; import 'package:example/webview_controls/page_with_controls.dart'; import 'package:example/widgets/commands_menu.dart'; @@ -69,24 +71,22 @@ class _AdaChatScreenState extends State { ), ), ), - onLoaded: (data) => - debugPrint('AdaChatScreen:onLoaded: data=$data'), + onLoaded: (data) => log('AdaChatScreen:onLoaded: data=$data'), onAdaReady: (isRolledOut) { - debugPrint( - 'AdaChatScreen:onAdaReady: isRolledOut=$isRolledOut'); + log('AdaChatScreen:onAdaReady: isRolledOut=$isRolledOut'); setState(() => _progress = 0); }, - onEvent: (event) => - debugPrint('AdaChatScreen:onEvent: event=$event'), + onEvent: (event) => log('AdaChatScreen:onEvent: event=$event'), onConsoleMessage: (level, message) => - debugPrint('AdaChatScreen:onConsoleMessage: ' + log('AdaChatScreen:onConsoleMessage: ' 'level=$level, message=$message'), onConversationEnd: (event) => - debugPrint('AdaChatScreen:onConversationEnd: event=$event'), - onDrawerToggle: (isDrawerOpen) => debugPrint( - 'AdaChatScreen:onConversationEnd: isDrawerOpen=$isDrawerOpen'), + log('AdaChatScreen:onConversationEnd: event=$event'), + onDrawerToggle: (isDrawerOpen) => + log('AdaChatScreen:onConversationEnd: ' + 'isDrawerOpen=$isDrawerOpen'), onLoadingError: (request, response) => - debugPrint('AdaChatScreen:onLoadingError: ' + log('AdaChatScreen:onLoadingError: ' 'request=$request, response=$response'), ), AnimatedPositioned( diff --git a/packages/ada_chat_flutter/example/lib/widgets/commands_menu.dart b/packages/ada_chat_flutter/example/lib/widgets/commands_menu.dart index 5d79f92ee..b11c8a94c 100644 --- a/packages/ada_chat_flutter/example/lib/widgets/commands_menu.dart +++ b/packages/ada_chat_flutter/example/lib/widgets/commands_menu.dart @@ -211,7 +211,7 @@ class CommandsMenu extends StatelessWidget { ); if (result is Map) { - adaController.setSensitiveMetaFields(result); + await adaController.setSensitiveMetaFields(result); } } @@ -224,7 +224,7 @@ class CommandsMenu extends StatelessWidget { ); if (result is Map) { - adaController.setMetaFields(result); + await adaController.setMetaFields(result); } } diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 0206471a4..17e710131 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; import 'package:ada_chat_flutter/src/ada_controller.dart'; import 'package:ada_chat_flutter/src/ada_controller_init.dart'; @@ -190,7 +191,7 @@ class _AdaWebViewState extends State { FutureOr _onNavigationRequest(NavigationRequest request) { final uri = Uri.parse(request.url); - print('AdaWebView:onNavigationRequest: ' + log('AdaWebView:onNavigationRequest: ' 'url=${uri.toString()}, isMainFrame=${request.isMainFrame}'); if (request.isMainFrame || @@ -217,7 +218,7 @@ class _AdaWebViewState extends State { } void _onWebResourceError(WebResourceError error) { - print('AdaWebView:onWebResourceError: ' + log('AdaWebView:onWebResourceError: ' 'errorCode=${error.errorCode}, ' 'description=${error.description}'); } @@ -230,7 +231,7 @@ class _AdaWebViewState extends State { ); void _onPageFinished(String url) { - print('AdaWebView:onPageFinished: url=$url'); + log('AdaWebView:onPageFinished: url=$url'); Future.delayed(Duration.zero, () async { await _init(); @@ -239,17 +240,17 @@ class _AdaWebViewState extends State { } void _onPageStarted(String url) { - print('AdaWebView:onPageStarted: url=$url'); + log('AdaWebView:onPageStarted: url=$url'); } void _onProgress(int progress) => widget.onProgressChanged?.call(progress); void _onUrlChange(UrlChange change) { - print('AdaWebView:onUrlChange: url=${change.url}'); + log('AdaWebView:onUrlChange: url=${change.url}'); } void _onHttpAuthRequest(HttpAuthRequest request) { - print('AdaWebView:onHttpAuthRequest: host=${request.host}'); + log('AdaWebView:onHttpAuthRequest: host=${request.host}'); } @override diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index 21899afbb..3cea8a85b 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:ada_chat_flutter/src/browser_settings.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -17,14 +19,14 @@ class CustomizedWebView extends StatefulWidget { } class _CustomizedWebViewState extends State { - late final WebViewController _controller = WebViewController(); + late final WebViewController _webViewController = WebViewController(); @override void initState() { super.initState(); widget.browserSettings?.init(); - _controller + _webViewController ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate( @@ -35,51 +37,48 @@ class _CustomizedWebViewState extends State { ), ); - _controller.loadRequest(widget.url); + _webViewController.loadRequest(widget.url); } void _onUrlChange(change) { - print('CustomizedWebView:onUrlChange: url=${change.url}'); + log('CustomizedWebView:onUrlChange: url=${change.url}'); } - void _onPageFinished(String url) async { - print('CustomizedWebView:onPageFinished: url=$url'); + Future _onPageFinished(String url) async { + log('CustomizedWebView:onPageFinished: url=$url'); - if (widget.browserSettings == null) { + final pageController = widget.browserSettings?.control; + if (pageController == null) { return; } - widget.browserSettings?.control?.init(_controller); + pageController.init(_webViewController); - final title = await _controller.getTitle(); - widget.browserSettings?.control?.setTitle(title ?? ''); + final title = await _webViewController.getTitle(); + pageController.setTitle(title ?? ''); - final currentUrl = await _controller.currentUrl(); + final currentUrl = await _webViewController.currentUrl(); if (currentUrl != null) { final currentUri = Uri.parse(currentUrl); - widget.browserSettings?.control?.setHost(currentUri.host ?? ''); - widget.browserSettings?.control - ?.setIsHttps(currentUri.isScheme('https') ?? false); + pageController.setHost(currentUri.host); + pageController.setIsHttps(currentUri.isScheme('https')); } - final canGoBack = await _controller.canGoBack(); - widget.browserSettings?.control?.setBackIsAvailable(canGoBack); + final canGoBack = await _webViewController.canGoBack(); + pageController.setBackIsAvailable(canGoBack); - final canGoForward = await _controller.canGoForward(); - widget.browserSettings?.control?.setForwardIsAvailable(canGoForward); + final canGoForward = await _webViewController.canGoForward(); + pageController.setForwardIsAvailable(canGoForward); } void _onPageStarted(String url) { - print('CustomizedWebView:onPageStarted: url=$url'); + log('CustomizedWebView:onPageStarted: url=$url'); } Future _onNavigationRequest( - NavigationRequest request) async { - print('CustomizedWebView:onNavigationRequest: ' - 'url=${request.url}'); - // if (request.url.startsWith('https://www.youtube.com/')) { - // return NavigationDecision.prevent; - // } + NavigationRequest request, + ) async { + log('CustomizedWebView:onNavigationRequest: url=${request.url}'); return NavigationDecision.navigate; } @@ -91,7 +90,7 @@ class _CustomizedWebViewState extends State { @override Widget build(BuildContext context) { - final child = WebViewWidget(controller: _controller); + final child = WebViewWidget(controller: _webViewController); final pageBuilder = widget.browserSettings?.pageBuilder; final browserController = widget.browserSettings?.control; From be28da3d9b5b5016b9c7f634d8a79f05fa51dafa Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Mon, 24 Jun 2024 19:58:46 +0400 Subject: [PATCH 17/33] wip: POC that Ada button can be disabled --- .../lib/src/customized_web_view.dart | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index 3cea8a85b..4e54610ec 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -35,11 +35,17 @@ class _CustomizedWebViewState extends State { onPageFinished: _onPageFinished, onNavigationRequest: _onNavigationRequest, ), - ); + ) + ..setOnConsoleMessage(_onConsoleMessage); _webViewController.loadRequest(widget.url); } + void _onConsoleMessage(JavaScriptConsoleMessage message) => + log('CustomizedWebView:onConsoleMessage: ' + 'level=${message.level.toString()}, ' + 'message=${message.message}'); + void _onUrlChange(change) { log('CustomizedWebView:onUrlChange: url=${change.url}'); } @@ -69,16 +75,35 @@ class _CustomizedWebViewState extends State { final canGoForward = await _webViewController.canGoForward(); pageController.setForwardIsAvailable(canGoForward); + + await _hideAdaButton(); + } + + /// Explanation: https://developers.ada.cx/reference/customize-chat#hide-the-default-chat-button + Future _hideAdaButton() { + return _webViewController.runJavaScript(''' +var parent = document.getElementsByTagName('body').item(0); +var style = document.createElement('style'); +style.type = 'text/css'; +style.innerHTML = "#ada-button-frame{ display: none; }"; +parent.appendChild(style); +'''); } - void _onPageStarted(String url) { + Future _onPageStarted(String url) async { log('CustomizedWebView:onPageStarted: url=$url'); } Future _onNavigationRequest( NavigationRequest request, ) async { - log('CustomizedWebView:onNavigationRequest: url=${request.url}'); + final uri = Uri.parse(request.url); + log('CustomizedWebView:onNavigationRequest: url=$uri'); + + if (uri.toString().startsWith('https://headspace.ada.support/embed/')) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; } From a84ce5d24c3b911dd528563d83f30cb93d62b0eb Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Mon, 24 Jun 2024 20:04:49 +0400 Subject: [PATCH 18/33] wip: Comment out POC for future development --- .../lib/src/customized_web_view.dart | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index 4e54610ec..c259bc3a5 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -76,19 +76,20 @@ class _CustomizedWebViewState extends State { final canGoForward = await _webViewController.canGoForward(); pageController.setForwardIsAvailable(canGoForward); - await _hideAdaButton(); + // todo Refactor POC, move outside of the library + // await _hideAdaButton(); } /// Explanation: https://developers.ada.cx/reference/customize-chat#hide-the-default-chat-button - Future _hideAdaButton() { - return _webViewController.runJavaScript(''' -var parent = document.getElementsByTagName('body').item(0); -var style = document.createElement('style'); -style.type = 'text/css'; -style.innerHTML = "#ada-button-frame{ display: none; }"; -parent.appendChild(style); -'''); - } +// Future _hideAdaButton() { +// return _webViewController.runJavaScript(''' +// var parent = document.getElementsByTagName('body').item(0); +// var style = document.createElement('style'); +// style.type = 'text/css'; +// style.innerHTML = "#ada-button-frame{ display: none; }"; +// parent.appendChild(style); +// '''); +// } Future _onPageStarted(String url) async { log('CustomizedWebView:onPageStarted: url=$url'); @@ -100,9 +101,12 @@ parent.appendChild(style); final uri = Uri.parse(request.url); log('CustomizedWebView:onNavigationRequest: url=$uri'); - if (uri.toString().startsWith('https://headspace.ada.support/embed/')) { - return NavigationDecision.prevent; - } + // if (uri + // .toString() + // .contains(RegExp(r'^https://headspace.ada.support/embed/'))) { + // log('CustomizedWebView:onNavigationRequest: skip url=$uri'); + // return NavigationDecision.prevent; + // } return NavigationDecision.navigate; } From 186fdfc18b7d70a24753438e5baf0632c364dc89 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Mon, 24 Jun 2024 22:48:41 +0400 Subject: [PATCH 19/33] fix: Fix in app browser on android --- packages/ada_chat_flutter/lib/src/ada_web_view.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 17e710131..23674350d 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -194,8 +194,7 @@ class _AdaWebViewState extends State { log('AdaWebView:onNavigationRequest: ' 'url=${uri.toString()}, isMainFrame=${request.isMainFrame}'); - if (request.isMainFrame || - uri.host.endsWith('${widget.handle}.ada.support') || + if (uri.host == '${widget.handle}.ada.support' || uri.toString() == 'about:blank') { // final requestUri = Uri.parse(request.url); From 3a3772528c5262c68aa9ed547411948ea250aa44 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 17:17:37 +0400 Subject: [PATCH 20/33] fix: Fix getInfo() --- packages/ada_chat_flutter/lib/src/ada_controller.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_controller.dart b/packages/ada_chat_flutter/lib/src/ada_controller.dart index 7689a6e54..9df009696 100644 --- a/packages/ada_chat_flutter/lib/src/ada_controller.dart +++ b/packages/ada_chat_flutter/lib/src/ada_controller.dart @@ -18,12 +18,8 @@ adaEmbed.start({ Future deleteHistory() => webViewController.runJavaScript('adaEmbed.deleteHistory();'); - // todo Fix getInfo() - Future getInfo() async { - return await webViewController.runJavaScriptReturningResult(''' -return await adaEmbed.getInfo(); -'''); - } + Future getInfo() async => await webViewController + .runJavaScriptReturningResult('JSON.stringify(adaEmbed.getInfo());'); Future reset() => webViewController.runJavaScript('adaEmbed.reset();'); From 3cd9c7e3abe85c95e182ec6432c5d163fdc0f360 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 17:18:23 +0400 Subject: [PATCH 21/33] fix: Fix ada chat on iOS --- .../ada_chat_flutter/lib/src/ada_web_view.dart | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index 23674350d..c9d3f71dd 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -161,7 +161,10 @@ class _AdaWebViewState extends State { final platform = _controller.platform; if (platform is AndroidWebViewController) { - AndroidWebViewController.enableDebugging(true); + // AndroidWebViewController.enableDebugging(true); + // platform.setTextZoom(200); + // platform.setUserAgent( + // "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36"); } // else if (platform is WebKitWebViewController) {} @@ -194,10 +197,7 @@ class _AdaWebViewState extends State { log('AdaWebView:onNavigationRequest: ' 'url=${uri.toString()}, isMainFrame=${request.isMainFrame}'); - if (uri.host == '${widget.handle}.ada.support' || - uri.toString() == 'about:blank') { - // final requestUri = Uri.parse(request.url); - + if (_isAdaChatLink(uri) || _isAdaSupportLink(uri) || _isBlankPage(uri)) { return NavigationDecision.navigate; } @@ -216,6 +216,12 @@ class _AdaWebViewState extends State { return NavigationDecision.prevent; } + bool _isBlankPage(Uri uri) => uri.toString() == 'about:blank'; + + bool _isAdaSupportLink(Uri uri) => uri.host == '${widget.handle}.ada.support'; + + bool _isAdaChatLink(Uri uri) => uri == widget.embedUri; + void _onWebResourceError(WebResourceError error) { log('AdaWebView:onWebResourceError: ' 'errorCode=${error.errorCode}, ' From 9be39c2e0e6d9b28ef77ac05c7114dca2e89f7ec Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 17:59:04 +0400 Subject: [PATCH 22/33] fix: Add progress. Refactor with ListenableBuilder. --- .../lib/webview_controls/page_controls.dart | 174 +++++++++++------- .../webview_controls/page_with_controls.dart | 5 + .../example/lib/widgets/progress_bar.dart | 30 +++ .../lib/src/browser_controller.dart | 25 ++- .../lib/src/customized_web_view.dart | 12 ++ 5 files changed, 176 insertions(+), 70 deletions(-) create mode 100644 packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart diff --git a/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart b/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart index 5021db680..7deb700a7 100644 --- a/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart +++ b/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:ada_chat_flutter/ada_chat_flutter.dart'; import 'package:flutter/material.dart'; -class PageControls extends StatefulWidget { +class PageControls extends StatelessWidget { const PageControls({ super.key, required this.controller, @@ -12,84 +12,128 @@ class PageControls extends StatefulWidget { final BrowserController controller; @override - State createState() => _PageControlsState(); + Widget build(BuildContext context) => ListenableBuilder( + listenable: controller, + builder: (context, _) => PageControlsInner(controller: controller), + ); } -class _PageControlsState extends State { - @override - void initState() { - super.initState(); - widget.controller.addListener(_rebuild); - } - - @override - void dispose() { - widget.controller.removeListener(_rebuild); - super.dispose(); - } +class PageControlsInner extends StatelessWidget { + const PageControlsInner({ + super.key, + required this.controller, + }); - void _rebuild() => setState(() {}); + final BrowserController controller; @override - Widget build(BuildContext context) { - return Align( - alignment: Alignment.topCenter, - child: ClipRect( + Widget build(BuildContext context) => ClipRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), child: Row( children: [ - IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: widget.controller.backIsAvailable - ? widget.controller.goBack - : null, - ), - IconButton( - icon: const Icon(Icons.arrow_forward_ios), - onPressed: widget.controller.forwardIsAvailable - ? widget.controller.goForward - : null, + ArrowBackButton(controller: controller), + ArrowForwardButton(controller: controller), + RefreshButton(controller: controller), + Expanded( + child: PageDescription(controller: controller), ), - IconButton( - icon: const Icon(Icons.refresh), - onPressed: widget.controller.reload, + CloseButton(), + ], + ), + ), + ); +} + +class PageDescription extends StatelessWidget { + const PageDescription({ + super.key, + required this.controller, + }); + + final BrowserController controller; + + @override + Widget build(BuildContext context) => Column( + children: [ + Text( + controller.title, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall, + ), + Row( + children: [ + Icon( + controller.isHttps ? Icons.lock : Icons.lock_open, + size: 10, ), + const SizedBox(width: 8), Expanded( - child: Column( - children: [ - Text( - widget.controller.title, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ), - Row( - children: [ - Icon( - widget.controller.isHttps - ? Icons.lock - : Icons.lock_open, - size: 10, - ), - const SizedBox(width: 8), - Text( - widget.controller.host, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - ], + child: Text( + controller.host, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium, ), ), - IconButton( - icon: const Icon(Icons.close), - onPressed: Navigator.of(context).pop, - ), ], ), - ), - ), - ); - } + ], + ); +} + +class CloseButton extends StatelessWidget { + const CloseButton({ + super.key, + }); + + @override + Widget build(BuildContext context) => IconButton( + icon: const Icon(Icons.close), + onPressed: Navigator.of(context).pop, + ); +} + +class RefreshButton extends StatelessWidget { + const RefreshButton({ + super.key, + required this.controller, + }); + + final BrowserController controller; + + @override + Widget build(BuildContext context) => IconButton( + icon: const Icon(Icons.refresh), + onPressed: controller.reload, + ); +} + +class ArrowForwardButton extends StatelessWidget { + const ArrowForwardButton({ + super.key, + required this.controller, + }); + + final BrowserController controller; + + @override + Widget build(BuildContext context) => IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: controller.forwardIsAvailable ? controller.goForward : null, + ); +} + +class ArrowBackButton extends StatelessWidget { + const ArrowBackButton({ + super.key, + required this.controller, + }); + + final BrowserController controller; + + @override + Widget build(BuildContext context) => IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: controller.backIsAvailable ? controller.goBack : null, + ); } diff --git a/packages/ada_chat_flutter/example/lib/webview_controls/page_with_controls.dart b/packages/ada_chat_flutter/example/lib/webview_controls/page_with_controls.dart index 039236025..ab67b7419 100644 --- a/packages/ada_chat_flutter/example/lib/webview_controls/page_with_controls.dart +++ b/packages/ada_chat_flutter/example/lib/webview_controls/page_with_controls.dart @@ -1,6 +1,7 @@ import 'package:ada_chat_flutter/ada_chat_flutter.dart'; import 'package:example/webview_controls/page_controls.dart'; import 'package:example/widgets/horizontal_line.dart'; +import 'package:example/widgets/progress_bar.dart'; import 'package:flutter/material.dart'; class PageWithControls extends StatelessWidget { @@ -25,6 +26,10 @@ class PageWithControls extends StatelessWidget { const HorizontalLine(), ], ), + ListenableBuilder( + listenable: controller, + builder: (context, _) => ProgressBar(progress: controller.progress), + ), ], ); } diff --git a/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart b/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart new file mode 100644 index 000000000..52609a5e7 --- /dev/null +++ b/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart @@ -0,0 +1,30 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; + +class ProgressBar extends StatelessWidget { + const ProgressBar({ + super.key, + required this.progress, + }); + + final int progress; + + @override + Widget build(BuildContext context) { + log('@@@ progress=$progress'); + + return AnimatedPositioned( + duration: const Duration(milliseconds: 500), + top: _isNotLoading ? -5 : 0, + left: 0, + right: 0, + child: LinearProgressIndicator( + value: progress / 100, + minHeight: 5, + ), + ); + } + + bool get _isNotLoading => progress == 0 || progress == 100; +} diff --git a/packages/ada_chat_flutter/lib/src/browser_controller.dart b/packages/ada_chat_flutter/lib/src/browser_controller.dart index f1c22e751..fd0a5a0f2 100644 --- a/packages/ada_chat_flutter/lib/src/browser_controller.dart +++ b/packages/ada_chat_flutter/lib/src/browser_controller.dart @@ -3,11 +3,12 @@ import 'package:webview_flutter/webview_flutter.dart'; class BrowserController extends ChangeNotifier { WebViewController? _controller; - String _title = ''; - String _host = ''; - bool _isHttps = true; - bool _backIsAvailable = false; - bool _forwardIsAvailable = false; + var _title = ''; + var _host = ''; + var _isHttps = true; + var _backIsAvailable = false; + var _forwardIsAvailable = false; + var _progress = 0; void init(WebViewController controller) => _controller = controller; @@ -51,4 +52,18 @@ class BrowserController extends ChangeNotifier { _forwardIsAvailable = isAvailable; notifyListeners(); } + + int get progress => _progress; + + void setProgress(int progress) { + _progress = progress; + notifyListeners(); + } + + @override + String toString() { + return 'BrowserController{title: $_title, host: $_host, ' + 'isHttps: $_isHttps, backIsAvailable: $_backIsAvailable, ' + 'forwardIsAvailable: $_forwardIsAvailable, progress: $_progress}'; + } } diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index c259bc3a5..10a00d087 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -31,6 +31,7 @@ class _CustomizedWebViewState extends State { ..setNavigationDelegate( NavigationDelegate( onUrlChange: _onUrlChange, + onProgress: _onProgress, onPageStarted: _onPageStarted, onPageFinished: _onPageFinished, onNavigationRequest: _onNavigationRequest, @@ -91,6 +92,17 @@ class _CustomizedWebViewState extends State { // '''); // } + void _onProgress(int progress) { + log('CustomizedWebView:onProgress: progress=$progress'); + + final pageController = widget.browserSettings?.control; + if (pageController == null) { + return; + } + + pageController.setProgress(progress); + } + Future _onPageStarted(String url) async { log('CustomizedWebView:onPageStarted: url=$url'); } From b880f1fef2cbcf3bac0708edf55d6f098c374da4 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 18:04:46 +0400 Subject: [PATCH 23/33] fix: Simplify ProgressBar --- .../example/lib/widgets/progress_bar.dart | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart b/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart index 52609a5e7..c0b19723e 100644 --- a/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart +++ b/packages/ada_chat_flutter/example/lib/widgets/progress_bar.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; class ProgressBar extends StatelessWidget { @@ -11,20 +9,16 @@ class ProgressBar extends StatelessWidget { final int progress; @override - Widget build(BuildContext context) { - log('@@@ progress=$progress'); - - return AnimatedPositioned( - duration: const Duration(milliseconds: 500), - top: _isNotLoading ? -5 : 0, - left: 0, - right: 0, - child: LinearProgressIndicator( - value: progress / 100, - minHeight: 5, - ), - ); - } + Widget build(BuildContext context) => AnimatedPositioned( + duration: const Duration(milliseconds: 500), + top: _isLoading ? 0 : -5, + left: 0, + right: 0, + child: LinearProgressIndicator( + value: progress / 100, + minHeight: 5, + ), + ); - bool get _isNotLoading => progress == 0 || progress == 100; + bool get _isLoading => progress > 0 && progress < 100; } From dabd951b1586d0a99d30a7ca3e2a986d81b03de4 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 18:07:01 +0400 Subject: [PATCH 24/33] refactor: Rename some in PageControls --- .../lib/webview_controls/page_controls.dart | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart b/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart index 7deb700a7..ce42468d2 100644 --- a/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart +++ b/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart @@ -32,8 +32,8 @@ class PageControlsInner extends StatelessWidget { filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0), child: Row( children: [ - ArrowBackButton(controller: controller), - ArrowForwardButton(controller: controller), + BackArrowButton(controller: controller), + ForwardArrowButton(controller: controller), RefreshButton(controller: controller), Expanded( child: PageDescription(controller: controller), @@ -82,9 +82,7 @@ class PageDescription extends StatelessWidget { } class CloseButton extends StatelessWidget { - const CloseButton({ - super.key, - }); + const CloseButton({super.key}); @override Widget build(BuildContext context) => IconButton( @@ -108,8 +106,8 @@ class RefreshButton extends StatelessWidget { ); } -class ArrowForwardButton extends StatelessWidget { - const ArrowForwardButton({ +class ForwardArrowButton extends StatelessWidget { + const ForwardArrowButton({ super.key, required this.controller, }); @@ -123,8 +121,8 @@ class ArrowForwardButton extends StatelessWidget { ); } -class ArrowBackButton extends StatelessWidget { - const ArrowBackButton({ +class BackArrowButton extends StatelessWidget { + const BackArrowButton({ super.key, required this.controller, }); From 473cf559db7ada4b1d38245ee08677ab7775a501 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 18:40:38 +0400 Subject: [PATCH 25/33] chore: Add todo --- packages/ada_chat_flutter/lib/src/customized_web_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ada_chat_flutter/lib/src/customized_web_view.dart b/packages/ada_chat_flutter/lib/src/customized_web_view.dart index 10a00d087..36e117cee 100644 --- a/packages/ada_chat_flutter/lib/src/customized_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/customized_web_view.dart @@ -77,10 +77,11 @@ class _CustomizedWebViewState extends State { final canGoForward = await _webViewController.canGoForward(); pageController.setForwardIsAvailable(canGoForward); - // todo Refactor POC, move outside of the library + // todo POC for MSE-187, move outside of the library // await _hideAdaButton(); } + // todo POC for MSE-187, move outside of the library /// Explanation: https://developers.ada.cx/reference/customize-chat#hide-the-default-chat-button // Future _hideAdaButton() { // return _webViewController.runJavaScript(''' @@ -113,6 +114,7 @@ class _CustomizedWebViewState extends State { final uri = Uri.parse(request.url); log('CustomizedWebView:onNavigationRequest: url=$uri'); + // todo POC for MSE-187, move outside of the library // if (uri // .toString() // .contains(RegExp(r'^https://headspace.ada.support/embed/'))) { From b60392e1aa29cd5addd5331e9dae68cbb9740136 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 18:51:25 +0400 Subject: [PATCH 26/33] doc: Update README.md --- packages/ada_chat_flutter/README.md | 47 ++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/ada_chat_flutter/README.md b/packages/ada_chat_flutter/README.md index 0e15ac691..fef6ffbd0 100644 --- a/packages/ada_chat_flutter/README.md +++ b/packages/ada_chat_flutter/README.md @@ -1,13 +1,52 @@ # ada_chat_flutter Package This package provides a widget for integrating Ada chat into your Flutter application. -It uses the `flutter_inappwebview` package to display the chat interface. +It uses the `webview_flutter` package to display the chat interface. The same package is used to +show internal browser if user taps on link in the chat. ## Features -- Customizable chat settings -- Event callbacks +- Customizable chat settings. +- Customizable in app web browser look and controls. +- Various events callbacks. ## Usage -Check the example folder. \ No newline at end of file +Check the example folder. + +## AdaWebView parameters + +### embedUri parameter + +File with following content should be placed somewhere on your web server. Link to that html file +must be passed as `embedUri` parameter to `AdaWebView` widget. + +```html + + + + + + + + + + + + + +
+ + + +``` + +### handle parameter + +Another required parameter is handle. This is your bot handle from Ada dashboard. + From dcf8af5214bc3f15932c47f65354466277204f37 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 19:35:28 +0400 Subject: [PATCH 27/33] doc: Fix README.md --- packages/ada_chat_flutter/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ada_chat_flutter/README.md b/packages/ada_chat_flutter/README.md index fef6ffbd0..a5efeb952 100644 --- a/packages/ada_chat_flutter/README.md +++ b/packages/ada_chat_flutter/README.md @@ -18,8 +18,7 @@ Check the example folder. ### embedUri parameter -File with following content should be placed somewhere on your web server. Link to that html file -must be passed as `embedUri` parameter to `AdaWebView` widget. +File with following content should be placed somewhere on your web server. ```html @@ -46,6 +45,10 @@ must be passed as `embedUri` parameter to `AdaWebView` widget. ``` +Link to that html file must be passed as `embedUri` parameter to `AdaWebView` widget. + +Don't forged to add yor domain to allow list on Ada dashboard. + ### handle parameter Another required parameter is handle. This is your bot handle from Ada dashboard. From e7fdc6636151fc31eb103bd81caae023292580ca Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 21:40:08 +0400 Subject: [PATCH 28/33] feat: Remove exceptions. Fix README.md --- packages/ada_chat_flutter/README.md | 2 +- .../lib/ada_chat_flutter.dart | 1 - .../lib/src/ada_exception.dart | 22 -------------- .../test/src/ada_exception_test.dart | 30 ------------------- 4 files changed, 1 insertion(+), 54 deletions(-) delete mode 100644 packages/ada_chat_flutter/lib/src/ada_exception.dart delete mode 100644 packages/ada_chat_flutter/test/src/ada_exception_test.dart diff --git a/packages/ada_chat_flutter/README.md b/packages/ada_chat_flutter/README.md index a5efeb952..689bf1255 100644 --- a/packages/ada_chat_flutter/README.md +++ b/packages/ada_chat_flutter/README.md @@ -47,7 +47,7 @@ File with following content should be placed somewhere on your web server. Link to that html file must be passed as `embedUri` parameter to `AdaWebView` widget. -Don't forged to add yor domain to allow list on Ada dashboard. +Don't forget to add yor domain to the allow list on Ada dashboard. ### handle parameter diff --git a/packages/ada_chat_flutter/lib/ada_chat_flutter.dart b/packages/ada_chat_flutter/lib/ada_chat_flutter.dart index 374ab4e77..6bee497cd 100644 --- a/packages/ada_chat_flutter/lib/ada_chat_flutter.dart +++ b/packages/ada_chat_flutter/lib/ada_chat_flutter.dart @@ -1,7 +1,6 @@ library ada_chat_flutter; export 'src/ada_controller.dart'; -export 'src/ada_exception.dart'; export 'src/ada_web_view.dart'; export 'src/browser_controller.dart'; export 'src/browser_settings.dart'; diff --git a/packages/ada_chat_flutter/lib/src/ada_exception.dart b/packages/ada_chat_flutter/lib/src/ada_exception.dart deleted file mode 100644 index 4e6f555f4..000000000 --- a/packages/ada_chat_flutter/lib/src/ada_exception.dart +++ /dev/null @@ -1,22 +0,0 @@ -class AdaException implements Exception { - const AdaException(this.error); - - final String error; - - @override - String toString() => 'AdaException{error: $error}'; -} - -class AdaExecException extends AdaException { - const AdaExecException(super.error); - - @override - String toString() => 'AdaExecException{error: $error}'; -} - -class AdaNullResultException extends AdaException { - const AdaNullResultException() : super('Result is null'); - - @override - String toString() => 'AdaNullResultException{error: $error}'; -} diff --git a/packages/ada_chat_flutter/test/src/ada_exception_test.dart b/packages/ada_chat_flutter/test/src/ada_exception_test.dart deleted file mode 100644 index e17a70888..000000000 --- a/packages/ada_chat_flutter/test/src/ada_exception_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:ada_chat_flutter/src/ada_exception.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('AdaException', () { - test('toString returns correct format', () { - const error = 'Some error message'; - final exception = AdaException(error); - expect(exception.toString(), 'AdaException{error: $error}'); - }); - }); - - group('AdaExecException', () { - test('toString returns correct format', () { - const error = 'Execution error'; - final exception = AdaExecException(error); - expect(exception.toString(), 'AdaExecException{error: $error}'); - }); - }); - - group('AdaNullResultException', () { - test('toString returns correct format', () { - final exception = AdaNullResultException(); - expect( - exception.toString(), - 'AdaNullResultException{error: Result is null}', - ); - }); - }); -} From 95dd9320490be30b6a008219f0ac3674cdaf0217 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 21:40:37 +0400 Subject: [PATCH 29/33] fix: Fix ada_controller_init_test.dart and ada_controller_test.dart --- .../test/src/ada_controller_init_test.dart | 27 ++-- .../test/src/ada_controller_test.dart | 121 ++++++------------ 2 files changed, 48 insertions(+), 100 deletions(-) diff --git a/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart b/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart index 4ac9e8cf5..bfa5f3936 100644 --- a/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart +++ b/packages/ada_chat_flutter/test/src/ada_controller_init_test.dart @@ -1,34 +1,31 @@ +import 'package:ada_chat_flutter/src/ada_controller.dart'; import 'package:ada_chat_flutter/src/ada_controller_init.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:webview_flutter/webview_flutter.dart'; -class AdaControllerInitImpl extends AdaControllerInit { - @override - void start() {} - - @override - void triggerAnswer(String answerId) {} -} +class MockWebViewController extends Mock implements WebViewController {} void main() { group('AdaControllerInit tests - ', () { - late AdaControllerInitImpl adaControllerInit; - late MockInAppWebViewController mockInAppWebViewController; + late AdaControllerInit adaController; + late MockWebViewController mockWebViewController; setUp(() { - mockInAppWebViewController = MockInAppWebViewController(); - adaControllerInit = AdaControllerInitImpl(); + adaController = AdaController(); + mockWebViewController = MockWebViewController(); }); test( 'WHEN init called ' 'THEN should initialize inner state', () { - adaControllerInit.init( - webViewController: mockInAppWebViewController, + adaController.init( + webViewController: mockWebViewController, handle: 'testHandle', ); - expect(adaControllerInit.webViewController, mockInAppWebViewController); - expect(adaControllerInit.handle, 'testHandle'); + + expect(adaController.webViewController, mockWebViewController); + expect(adaController.handle, 'testHandle'); }); }); } diff --git a/packages/ada_chat_flutter/test/src/ada_controller_test.dart b/packages/ada_chat_flutter/test/src/ada_controller_test.dart index 02e3f1bd4..ebf9c8aba 100644 --- a/packages/ada_chat_flutter/test/src/ada_controller_test.dart +++ b/packages/ada_chat_flutter/test/src/ada_controller_test.dart @@ -1,12 +1,11 @@ import 'dart:convert'; import 'package:ada_chat_flutter/src/ada_controller.dart'; -import 'package:ada_chat_flutter/src/ada_exception.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:webview_flutter/webview_flutter.dart'; -class MockWebViewController extends Mock implements InAppWebViewController {} +class MockWebViewController extends Mock implements WebViewController {} const _handle = '_testHandle'; @@ -25,10 +24,8 @@ void main() { ); when( - () => mockWebViewController.evaluateJavascript( - source: any(named: 'source'), - ), - ).thenAnswer((_) async => null); + () => mockWebViewController.runJavaScript(any()), + ).thenAnswer((_) async {}); }); test( @@ -36,9 +33,8 @@ void main() { 'THEN should call evaluateJavascript with correct args', () { adaController.start(); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && @@ -56,9 +52,8 @@ void main() { 'THEN should call evaluateJavascript with correct args', () { adaController.deleteHistory(); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && arg.contains('adaEmbed.deleteHistory()'), @@ -68,62 +63,24 @@ void main() { ).called(1); }); - group('getInfo', () { - test( - 'WHEN getInfo is called ' - 'THEN should call callAsyncJavaScript with correct args', () async { - when( - () => mockWebViewController.callAsyncJavaScript( - functionBody: any(named: 'functionBody'), - ), - ).thenAnswer( - (_) async => CallAsyncJavaScriptResult( - value: { - 'info': true, - }, - ), - ); - final result = await adaController.getInfo(); - expect(result, {'info': true}); - verify( - () => mockWebViewController.callAsyncJavaScript( - functionBody: any( - named: 'functionBody', - that: predicate( - (arg) => arg is String && arg.contains('adaEmbed.getInfo()'), - ), + test( + 'WHEN getInfo is called ' + 'THEN should call callAsyncJavaScript with correct args', () async { + when( + () => mockWebViewController.runJavaScriptReturningResult(any()), + ).thenAnswer((_) async => '{"info": true}'); + + final result = await adaController.getInfo(); + expect(result, '{"info": true}'); + verify( + () => mockWebViewController.runJavaScriptReturningResult( + any( + that: predicate( + (arg) => arg is String && arg.contains('adaEmbed.getInfo()'), ), ), - ).called(1); - }); - - test( - 'WHEN getInfo is called and it throws an AdaNullResultException' - 'THEN should throw AdaNullResultException', () async { - when( - () => mockWebViewController.callAsyncJavaScript( - functionBody: any(named: 'functionBody'), - ), - ).thenAnswer((_) async => null); - expect( - () async => await adaController.getInfo(), - throwsA(isA()), - ); - }); - - test( - 'WHEN getInfo is called and it completes with an error' - 'THEN should throw AdaExecException', () async { - when( - () => mockWebViewController.callAsyncJavaScript( - functionBody: any(named: 'functionBody'), - ), - ).thenAnswer((_) async => CallAsyncJavaScriptResult(error: 'Error')); - expect( - () async => await adaController.getInfo(), - throwsA(isA()), - ); - }); + ), + ).called(1); }); test( @@ -131,9 +88,8 @@ void main() { 'THEN should call evaluateJavascript with correct args', () { adaController.reset(); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && arg.contains('adaEmbed.reset()'), ), @@ -147,9 +103,8 @@ void main() { 'THEN should call evaluateJavascript with correct args', () { adaController.setLanguage('en'); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && arg.contains('adaEmbed.setLanguage("en")'), @@ -166,9 +121,8 @@ void main() { await adaController.setMetaFields(fields); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && @@ -187,9 +141,8 @@ void main() { await adaController.setSensitiveMetaFields(fields); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && @@ -206,9 +159,8 @@ void main() { 'THEN should call evaluateJavascript with correct args', () { adaController.stop(); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && arg.contains('adaEmbed.stop()'), ), @@ -222,9 +174,8 @@ void main() { 'THEN should call evaluateJavascript with correct args', () { adaController.triggerAnswer('answerId'); verify( - () => mockWebViewController.evaluateJavascript( - source: any( - named: 'source', + () => mockWebViewController.runJavaScript( + any( that: predicate( (arg) => arg is String && From 43dd76058f0953812b0611db79c9bd2ceb044e63 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 21:41:28 +0400 Subject: [PATCH 30/33] refactor: Refactor progress calculation --- .../example/lib/screens/ada_chat_screen.dart | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index f3e2d5e78..9832e02e9 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:ada_chat_flutter/ada_chat_flutter.dart'; import 'package:example/webview_controls/page_with_controls.dart'; import 'package:example/widgets/commands_menu.dart'; +import 'package:example/widgets/progress_bar.dart'; import 'package:flutter/material.dart'; class AdaChatScreen extends StatefulWidget { @@ -19,7 +20,7 @@ class AdaChatScreen extends StatefulWidget { class _AdaChatScreenState extends State { final _adaController = AdaController(); - var _progress = 0.0; + var _progress = 0; @override Widget build(BuildContext context) => SafeArea( @@ -59,7 +60,7 @@ class _AdaChatScreenState extends State { 'keySens': 'valueSens', }, onProgressChanged: (progress) => setState(() { - _progress = progress / 100; + _progress = progress; }), browserSettings: BrowserSettings( pageBuilder: (context, browser, controller) => Scaffold( @@ -89,17 +90,9 @@ class _AdaChatScreenState extends State { log('AdaChatScreen:onLoadingError: ' 'request=$request, response=$response'), ), - AnimatedPositioned( - duration: const Duration(milliseconds: 500), - top: _isNotLoading ? -5 : 0, - left: 0, - right: 0, - child: LinearProgressIndicator(value: _progress, minHeight: 5), - ), + ProgressBar(progress: _progress), ], ), ), ); - - bool get _isNotLoading => _progress == 0 || _progress == 1; } From fa719b3760ebe3aed7d534ac9e77b22ff0e3b0df Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 22:30:56 +0400 Subject: [PATCH 31/33] test: Fix tests --- .../lib/src/ada_web_view.dart | 6 +- .../lib/src/browser_controller.dart | 7 -- .../test/src/ada_web_view_test.dart | 36 ------- .../test/src/browser_controller_test.dart | 17 ++-- .../test/src/customized_web_view_test.dart | 96 ------------------- 5 files changed, 12 insertions(+), 150 deletions(-) delete mode 100644 packages/ada_chat_flutter/test/src/ada_web_view_test.dart delete mode 100644 packages/ada_chat_flutter/test/src/customized_web_view_test.dart diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index c9d3f71dd..b368fc1a2 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -161,12 +161,8 @@ class _AdaWebViewState extends State { final platform = _controller.platform; if (platform is AndroidWebViewController) { - // AndroidWebViewController.enableDebugging(true); - // platform.setTextZoom(200); - // platform.setUserAgent( - // "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36"); + AndroidWebViewController.enableDebugging(kDebugMode); } - // else if (platform is WebKitWebViewController) {} _controller ..setJavaScriptMode(JavaScriptMode.unrestricted) diff --git a/packages/ada_chat_flutter/lib/src/browser_controller.dart b/packages/ada_chat_flutter/lib/src/browser_controller.dart index fd0a5a0f2..1b24c3426 100644 --- a/packages/ada_chat_flutter/lib/src/browser_controller.dart +++ b/packages/ada_chat_flutter/lib/src/browser_controller.dart @@ -59,11 +59,4 @@ class BrowserController extends ChangeNotifier { _progress = progress; notifyListeners(); } - - @override - String toString() { - return 'BrowserController{title: $_title, host: $_host, ' - 'isHttps: $_isHttps, backIsAvailable: $_backIsAvailable, ' - 'forwardIsAvailable: $_forwardIsAvailable, progress: $_progress}'; - } } diff --git a/packages/ada_chat_flutter/test/src/ada_web_view_test.dart b/packages/ada_chat_flutter/test/src/ada_web_view_test.dart deleted file mode 100644 index a8ef6501b..000000000 --- a/packages/ada_chat_flutter/test/src/ada_web_view_test.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:ada_chat_flutter/src/ada_web_view.dart'; -import 'package:ada_chat_flutter/src/test/mock_web_view_dependencies.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -class BuildContextMock extends Mock implements BuildContext {} - -void main() { - final mockWebViewDependencies = MockWebViewDependencies(); - - setUpAll(() { - registerFallbackValue(BuildContextMock()); - }); - - setUp(() async { - await mockWebViewDependencies.init(); - }); - - tearDown(() async { - mockWebViewDependencies.tearDown(); - }); - - testWidgets( - 'WHEN AdaWebView is shown ' - 'THEN should InAppWebView should be visible', - (tester) async { - await tester.pumpWidget( - AdaWebView(handle: 'test_handle'), - ); - - expect(find.byType(InAppWebView), findsOne); - }, - ); -} diff --git a/packages/ada_chat_flutter/test/src/browser_controller_test.dart b/packages/ada_chat_flutter/test/src/browser_controller_test.dart index 19f630341..02edfd3a1 100644 --- a/packages/ada_chat_flutter/test/src/browser_controller_test.dart +++ b/packages/ada_chat_flutter/test/src/browser_controller_test.dart @@ -1,17 +1,16 @@ import 'package:ada_chat_flutter/src/browser_controller.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:webview_flutter/webview_flutter.dart'; -class MockInAppWebViewController extends Mock - implements InAppWebViewController {} +class MockWebViewController extends Mock implements WebViewController {} void main() { late BrowserController browserController; - late MockInAppWebViewController mockController; + late MockWebViewController mockController; setUp(() { - mockController = MockInAppWebViewController(); + mockController = MockWebViewController(); browserController = BrowserController(); browserController.init(mockController); @@ -20,7 +19,7 @@ void main() { when(() => mockController.reload()).thenAnswer((_) async {}); }); - group('BrowserController', () { + group('BrowserController - ', () { test('goBack calls controller.goBack', () async { await browserController.goBack(); verify(() => mockController.goBack()).called(1); @@ -42,6 +41,12 @@ void main() { expect(browserController.title, newTitle); }); + test('progress getter returns correct value', () { + const progress = 42; + browserController.setProgress(progress); + expect(browserController.progress, progress); + }); + test('host getter returns correct value', () { const newHost = 'New Host'; browserController.setHost(newHost); diff --git a/packages/ada_chat_flutter/test/src/customized_web_view_test.dart b/packages/ada_chat_flutter/test/src/customized_web_view_test.dart deleted file mode 100644 index ec75f70c9..000000000 --- a/packages/ada_chat_flutter/test/src/customized_web_view_test.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:ada_chat_flutter/src/browser_controller.dart'; -import 'package:ada_chat_flutter/src/browser_settings.dart'; -import 'package:ada_chat_flutter/src/customized_web_view.dart'; -import 'package:ada_chat_flutter/src/test/mock_web_view_dependencies.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockInAppWebViewController extends Mock - implements InAppWebViewController {} - -class MockBrowserSettings extends Mock implements BrowserSettings {} - -class MockBrowserController extends Mock implements BrowserController {} - -class BuildContextMock extends Mock implements BuildContext {} - -void main() { - late MockInAppWebViewController mockWebViewController; - late MockBrowserSettings mockBrowserSettings; - late MockBrowserController mockBrowserController; - - final mockWebViewDependencies = MockWebViewDependencies(); - - setUpAll(() { - registerFallbackValue(BuildContextMock()); - }); - - tearDown(() async { - mockWebViewDependencies.tearDown(); - }); - - setUp(() async { - await mockWebViewDependencies.init(); - - mockWebViewController = MockInAppWebViewController(); - mockBrowserSettings = MockBrowserSettings(); - mockBrowserController = MockBrowserController(); - - when(() => mockBrowserSettings.control).thenReturn(mockBrowserController); - when(() => mockBrowserSettings.pageBuilder).thenReturn( - (context, browser, controller) => Container(child: browser), - ); - when(() => mockWebViewController.getTitle()) - .thenAnswer((_) async => 'Test Title'); - when(() => mockWebViewController.canGoBack()).thenAnswer((_) async => true); - when(() => mockWebViewController.canGoForward()) - .thenAnswer((_) async => false); - }); - - group('CustomizedWebView', () { - testWidgets('initializes BrowserSettings', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: CustomizedWebView( - url: WebUri('https://www.example.com'), - browserSettings: mockBrowserSettings, - ), - ), - ); - - verify(() => mockBrowserSettings.init()).called(1); - }); - - testWidgets('disposes BrowserSettings', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: CustomizedWebView( - url: WebUri('https://www.example.com'), - browserSettings: mockBrowserSettings, - ), - ), - ); - await tester.pumpWidget(Container()); // Trigger dispose - - verify(() => mockBrowserSettings.dispose()).called(1); - }); - - testWidgets('uses pageBuilder if provided', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: CustomizedWebView( - url: WebUri('https://www.example.com'), - browserSettings: mockBrowserSettings, - ), - ), - ); - - expect( - find.byType(Container), - findsOneWidget, - ); // Assuming pageBuilder returns a Container - }); - }); -} From d80b407a6e21e52f758ceacaa91b23dc81dea523 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 22:50:57 +0400 Subject: [PATCH 32/33] fix: Small fixes --- .../example/lib/screens/ada_chat_screen.dart | 2 +- .../lib/webview_controls/page_controls.dart | 20 +++++++++---------- .../lib/src/ada_web_view.dart | 3 --- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart index 9832e02e9..2acc754b2 100644 --- a/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart +++ b/packages/ada_chat_flutter/example/lib/screens/ada_chat_screen.dart @@ -84,7 +84,7 @@ class _AdaChatScreenState extends State { onConversationEnd: (event) => log('AdaChatScreen:onConversationEnd: event=$event'), onDrawerToggle: (isDrawerOpen) => - log('AdaChatScreen:onConversationEnd: ' + log('AdaChatScreen:onDrawerToggle: ' 'isDrawerOpen=$isDrawerOpen'), onLoadingError: (request, response) => log('AdaChatScreen:onLoadingError: ' diff --git a/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart b/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart index ce42468d2..43daa98fd 100644 --- a/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart +++ b/packages/ada_chat_flutter/example/lib/webview_controls/page_controls.dart @@ -62,18 +62,18 @@ class PageDescription extends StatelessWidget { style: Theme.of(context).textTheme.bodySmall, ), Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - controller.isHttps ? Icons.lock : Icons.lock_open, - size: 10, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - controller.host, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium, + if (controller.host.isNotEmpty) + Icon( + controller.isHttps ? Icons.lock : Icons.lock_open, + size: 10, ), + const SizedBox(width: 8), + Text( + controller.host, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium, ), ], ), diff --git a/packages/ada_chat_flutter/lib/src/ada_web_view.dart b/packages/ada_chat_flutter/lib/src/ada_web_view.dart index b368fc1a2..e770b00be 100644 --- a/packages/ada_chat_flutter/lib/src/ada_web_view.dart +++ b/packages/ada_chat_flutter/lib/src/ada_web_view.dart @@ -305,16 +305,13 @@ window.adaSettings = { }); }, conversationEndCallback: function(event) { - console.log("onConversationEnd: " + JSON.stringify(event)); onConversationEnd.postMessage(JSON.stringify(event)); }, toggleCallback: function(isDrawerOpen) { - console.log("onDrawerToggle: " + JSON.stringify(isDrawerOpen)); onDrawerToggle.postMessage(JSON.stringify(isDrawerOpen)); }, eventCallbacks: { "*": function(event) { - console.log("onEvent: " + JSON.stringify(event)); onEvent.postMessage(JSON.stringify(event)); } } From fa577ebec9ec4906a2012d1e388da1b9a495d2e9 Mon Sep 17 00:00:00 2001 From: slava-r-epam Date: Tue, 25 Jun 2024 22:58:46 +0400 Subject: [PATCH 33/33] fix: pubspec.yaml small fixes --- packages/ada_chat_flutter/pubspec.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/ada_chat_flutter/pubspec.yaml b/packages/ada_chat_flutter/pubspec.yaml index 4cc9f4928..5635ea9cc 100644 --- a/packages/ada_chat_flutter/pubspec.yaml +++ b/packages/ada_chat_flutter/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter implementation of Ada chat version: 1.0.0 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: '>=3.3.0 <4.0.0' flutter: ">=3.19.0" dependencies: @@ -16,7 +16,7 @@ dev_dependencies: flutter_lints: ^4.0.0 flutter_test: sdk: flutter - mocktail: ^1.0.3 # Used by MockWebViewDependencies() tests helper + mocktail: ^1.0.3 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -28,9 +28,6 @@ flutter: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - assets: - - assets/ - # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages