From bd45ed64ee7edba11c7fc1dfddafc80eb8a3d1ea Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Fri, 17 Jan 2025 14:50:18 +0100 Subject: [PATCH] remove old webview deps --- .../presentation/widgets/web_view.dart | 612 ------------------ .../web_view/utils/download_helper.dart | 39 -- 2 files changed, 651 deletions(-) delete mode 100644 app/lib/features/web_view/presentation/widgets/web_view.dart delete mode 100644 app/lib/features/web_view/utils/download_helper.dart diff --git a/app/lib/features/web_view/presentation/widgets/web_view.dart b/app/lib/features/web_view/presentation/widgets/web_view.dart deleted file mode 100644 index 9a332913..00000000 --- a/app/lib/features/web_view/presentation/widgets/web_view.dart +++ /dev/null @@ -1,612 +0,0 @@ -// import 'dart:async'; -// import 'dart:convert'; - -// import 'package:flutter/material.dart'; -// import 'package:flutter_hooks/flutter_hooks.dart'; - -// import 'package:hooks_riverpod/hooks_riverpod.dart'; -// import 'package:lensai/core/logger.dart'; -// import 'package:lensai/domain/services/generic_website.dart'; -// import 'package:lensai/features/bangs/domain/providers.dart'; -// import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart'; -// import 'package:lensai/features/chat_archive/domain/repositories/archive.dart'; -// import 'package:lensai/features/kagi/data/entities/modes.dart'; -// import 'package:lensai/features/search_browser/domain/entities/sheet.dart'; -// import 'package:lensai/features/search_browser/domain/providers.dart'; -// import 'package:lensai/features/settings/data/models/settings.dart'; -// import 'package:lensai/features/settings/data/repositories/settings_repository.dart'; -// import 'package:lensai/features/web_view/domain/providers.dart'; -// import 'package:lensai/features/web_view/domain/repositories/web_view.dart'; -// import 'package:lensai/features/web_view/presentation/controllers/readerability.dart'; -// import 'package:lensai/features/web_view/presentation/controllers/switch_new_tab.dart'; -// import 'package:lensai/features/web_view/presentation/widgets/web_page_dialog.dart'; -// import 'package:lensai/features/web_view/utils/download_helper.dart'; -// import 'package:lensai/utils/platform_util.dart' as platform_util; -// import 'package:lensai/utils/ui_helper.dart' as ui_helper; -// import 'package:url_launcher/url_launcher.dart'; - -// const _webViewSupportedSchemes = [ -// "http", -// "https", -// "file", -// "chrome", -// "data", -// "javascript", -// "about", -// ]; - -// class WebView extends StatefulHookConsumerWidget { -// final String tabId; -// final URLRequest? initialUrlRequest; - -// const WebView({ -// required this.tabId, -// required super.key, -// this.initialUrlRequest, -// }); - -// @override -// ConsumerState createState() => _WebViewState(); -// } - -// class _WebViewState extends ConsumerState { -// Timer? _onLoadStopDebounce; -// Timer? _periodicScreenshotUpdate; - -// Future _downloadChat( -// DownloadStartRequest downloadStartRequest, -// BuildContext context, -// ) async { -// final fileName = getDispositionFileName( -// downloadStartRequest.contentDisposition!, -// ); - -// if (fileName != null) { -// final entity = ChatEntity.fromFileName(fileName); -// if (entity.name != null) { -// final fileWrite = await ref -// .read(chatArchiveRepositoryProvider.notifier) -// .archiveChat(fileName, downloadStartRequest.url); - -// return fileWrite.fold( -// (_) { -// ScaffoldMessenger.of(context).showSnackBar( -// SnackBar( -// backgroundColor: Colors.green, -// content: Text( -// 'Conversation "$entity" saved successfully!', -// style: const TextStyle(color: Colors.white), -// ), -// ), -// ); - -// return true; -// }, -// onFailure: (errorMessage) { -// ui_helper.showErrorMessage( -// context, -// errorMessage.toString(), -// ); - -// return false; -// }, -// ); -// } -// } - -// return false; -// } - -// @override -// Future dispose() async { -// super.dispose(); - -// _onLoadStopDebounce?.cancel(); -// _periodicScreenshotUpdate?.cancel(); - -// logger.i('Disposed ${widget.key} ${widget.tabId}'); -// } - -// @override -// Widget build(BuildContext context) { -// final initialSettings = useMemoized( -// () => InAppWebViewSettings( -// // isInspectable: kDebugMode, -// useOnDownloadStart: true, -// allowsLinkPreview: false, -// disableLongPressContextMenuOnLinks: true, -// useShouldOverrideUrlLoading: true, -// javaScriptEnabled: (ref.read(settingsRepositoryProvider).valueOrNull ?? -// Settings.withDefaults()) -// .enableJavascript, -// saveFormData: false, -// disabledActionModeMenuItems: ActionModeMenuItem.MENU_ITEM_WEB_SEARCH, -// ), -// ); - -// final showEarlyAccessFeatures = ref.watch( -// settingsRepositoryProvider.select( -// (value) => value -// .showEarlyAccessFeatures, -// ), -// ); - -// // We keep listening to changes and cache them in this ref. -// // This is important, since our WebView will not have the main scope anymore -// // to react to updates from ref.watch -// // -// // With this solution we dont need to read on every interception request -// // and can just obtain the value from here -// final blockContentHosts = useRef?>(null); -// ref.listen( -// blockContentHostsProvider, -// (previous, next) { -// blockContentHosts.value = next.valueOrNull; -// }, -// ); - -// ref.listen( -// settingsRepositoryProvider.select( -// (value) => -// value.enableJavascript, -// ), -// (previous, next) async { -// final controller = ref.read(webViewControllerProvider(widget.tabId)); -// await controller?.setSettings( -// settings: initialSettings.copy()..javaScriptEnabled = next, -// ); -// }, -// ); - -// final webViewProgress = useValueNotifier(100); -// final inPageSearchResult = -// useValueNotifier<({int activeMatchOrdinal, int numberOfMatches})?>( -// null, -// ); - -// final findInteractionController = useMemoized( -// () => FindInteractionController( -// onFindResultReceived: ( -// controller, -// activeMatchOrdinal, -// numberOfMatches, -// isDoneCounting, -// ) { -// if (isDoneCounting) { -// inPageSearchResult.value = ( -// activeMatchOrdinal: activeMatchOrdinal, -// numberOfMatches: numberOfMatches, -// ); -// } -// }, -// ), -// ); - -// useOnAppLifecycleStateChange((previous, current) async { -// final controller = ref.read(webViewControllerProvider(widget.tabId)); - -// switch (current) { -// case AppLifecycleState.paused: -// if (platform_util.isAndroid()) { -// await controller?.pause(); -// } -// if (platform_util.isAndroid() || platform_util.isIOS()) { -// await controller?.pauseTimers(); -// } -// case AppLifecycleState.resumed: -// if (platform_util.isAndroid()) { -// await controller?.resume(); -// } -// if (platform_util.isAndroid() || platform_util.isIOS()) { -// await controller?.resumeTimers(); -// } -// default: -// } -// }); - -// return Stack( -// children: [ -// InAppWebView( -// initialUrlRequest: widget.initialUrlRequest, -// initialSettings: initialSettings, -// findInteractionController: findInteractionController, -// contextMenu: ContextMenu( -// menuItems: [ -// ContextMenuItem( -// id: 1, -// title: "Search", -// action: () async { -// final controller = -// ref.read(webViewControllerProvider(widget.tabId)); - -// final selectedText = await controller?.getSelectedText(); - -// if (selectedText != null && selectedText.isNotEmpty) { -// final searchBang = -// await ref.read(kagiSearchBangDataProvider.future); - -// await ref -// .read(switchNewTabControllerProvider.notifier) -// .add(searchBang!.getUrl(selectedText)); -// } -// }, -// ), -// if (showEarlyAccessFeatures) -// ContextMenuItem( -// id: 2, -// title: "Assistant", -// action: () async { -// final controller = -// ref.read(webViewControllerProvider(widget.tabId)); - -// final selectedText = await controller?.getSelectedText(); - -// if (selectedText != null && selectedText.isNotEmpty) { -// ref.read(bottomSheetProvider.notifier).show( -// CreateTabSheet( -// content: selectedText, -// preferredTool: KagiTool.assistant, -// ), -// ); -// } -// }, -// ), -// ], -// ), -// onWebViewCreated: (controller) async { -// if (platform_util.isAndroid()) { -// await controller.startSafeBrowsing(); -// } - -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .copyWith(controller: controller); -// }, -// onReceivedServerTrustAuthRequest: (controller, challenge) async { -// final sslError = challenge.protectionSpace.sslError; - -// if (sslError != null && sslError.code != null) { -// final url = await controller.getUrl(); -// if (challenge.protectionSpace.host == url?.host) { -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .copyWith(sslError: sslError); - -// if (context.mounted) { -// ui_helper.showErrorMessage( -// context, -// 'We detected an security issue and did not continue to ${url?.authority}: ${sslError.message}', -// ); -// } -// } - -// return ServerTrustAuthResponse( -// // ignore: avoid_redundant_argument_values -// action: ServerTrustAuthResponseAction.CANCEL, -// ); -// } - -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .copyWith(sslError: null); - -// return ServerTrustAuthResponse( -// action: ServerTrustAuthResponseAction.PROCEED, -// ); -// }, -// shouldInterceptRequest: (controller, request) async { -// if (blockContentHosts.value?.contains(request.url.host) == true) { -// return WebResourceResponse( -// contentType: "text/plain", -// data: utf8.encode("Blocked"), -// statusCode: 403, -// reasonPhrase: "Forbidden", -// ); -// } - -// //Check if main page aka main frame is upgradeable -// if (request.url.isScheme('http') && -// request.isForMainFrame == true) { -// final upgradedUri = await ref -// .read(genericWebsiteServiceProvider.notifier) -// .tryUpgradeToHttps(request.url); - -// if (upgradedUri != null) { -// unawaited( -// controller.loadUrl( -// urlRequest: URLRequest(url: WebUri.uri(upgradedUri)), -// ), -// ); - -// return WebResourceResponse( -// statusCode: 200, -// reasonPhrase: "Redirecting...", -// ); -// } -// } - -// if (request.url.isScheme('http')) { -// if ((ref.read(settingsRepositoryProvider).valueOrNull ?? -// Settings.withDefaults()) -// .blockHttpProtocol) { -// return WebResourceResponse( -// contentType: "text/plain", -// data: utf8.encode("HTTP protocol is blocked"), -// statusCode: 403, -// reasonPhrase: "Forbidden", -// ); -// } -// } - -// return null; -// }, -// onProgressChanged: (controller, progress) { -// webViewProgress.value = progress; -// }, -// onLoadStart: (controller, url) async { -// final readabilityNotifier = ref.read( -// readerabilityControllerProvider(widget.tabId).notifier, -// ); - -// if (url != null) { -// await ref.read(tabStateProvider(widget.tabId).notifier).copyWith( -// url: url, -// // ignore: avoid_redundant_argument_values -// sslError: null, -// ); -// } - -// readabilityNotifier.reset(); -// }, -// onLoadStop: (controller, url) async { -// if (url != null) { -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .copyWith(url: url); -// } - -// _onLoadStopDebounce?.cancel(); -// _onLoadStopDebounce = -// Timer(const Duration(milliseconds: 150), () async { -// final readabilityNotifier = ref.read( -// readerabilityControllerProvider(widget.tabId).notifier, -// ); - -// await readabilityNotifier.checkReaderable(); - -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .updateScreenshot() -// .whenComplete(() { -// _periodicScreenshotUpdate?.cancel(); -// _periodicScreenshotUpdate = -// Timer.periodic(const Duration(seconds: 5), (timer) async { -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .updateScreenshot() -// .onError((error, stackTrace) { -// logger.e(error, stackTrace: stackTrace); -// timer.cancel(); -// }); -// }); -// }); -// }); -// }, -// onUpdateVisitedHistory: (controller, url, isReload) async { -// if (isReload != true) { -// final history = ( -// canGoBack: await controller.canGoBack(), -// canGoForward: await controller.canGoForward() -// ); - -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .copyWith(pageHistory: history); -// } -// }, -// shouldOverrideUrlLoading: (controller, navigationAction) async { -// final url = navigationAction.request.url; -// if (url != null) { -// final launchExternal = ref.read( -// settingsRepositoryProvider.select( -// (value) => value -// .launchUrlExternal, -// ), -// ); - -// final unhandledScheme = -// !_webViewSupportedSchemes.contains(url.scheme); - -// if (unhandledScheme || -// (launchExternal && url.host != 'kagi.com')) { -// if (await canLaunchUrl(url)) { -// var success = false; -// if (unhandledScheme) { -// success = await launchUrl(url); -// } else if (launchExternal) { -// success = await launchUrl( -// url, -// mode: LaunchMode.externalApplication, -// ); -// } - -// if (!success) { -// if (context.mounted) { -// ui_helper.showErrorMessage( -// context, -// 'Could not launch URL ($url)', -// ); -// } -// } -// // and cancel the request -// return NavigationActionPolicy.CANCEL; -// } -// } -// } - -// return NavigationActionPolicy.ALLOW; -// }, -// onLongPressHitTestResult: (controller, hitTestResult) async { -// if (switch (hitTestResult.type) { -// InAppWebViewHitTestResultType.SRC_IMAGE_ANCHOR_TYPE || -// InAppWebViewHitTestResultType.SRC_ANCHOR_TYPE || -// InAppWebViewHitTestResultType.IMAGE_TYPE => -// true, -// _ => false, -// }) { -// final requestFocusNodeHrefResult = -// await controller.requestFocusNodeHref(); - -// final url = requestFocusNodeHrefResult?.url ?? -// Uri.tryParse(requestFocusNodeHrefResult?.src ?? ''); -// if (url?.hasScheme == true && url?.hasAuthority == true) { -// ref.read(overlayDialogProvider.notifier).show( -// WebPageDialog( -// url: url!, -// onDismiss: () { -// ref.read(overlayDialogProvider.notifier).dismiss(); -// }, -// ), -// ); -// } -// } -// }, -// onTitleChanged: (controller, title) async { -// await ref -// .read(tabStateProvider(widget.tabId).notifier) -// .copyWith(title: title); -// }, -// onDownloadStartRequest: (controller, downloadStartRequest) async { -// final handled = switch (downloadStartRequest.mimeType) { -// 'text/markdown' => -// await _downloadChat(downloadStartRequest, context), -// _ => false -// }; - -// if (!handled) { -// if (context.mounted) { -// await ui_helper.launchUrlFeedback( -// context, -// downloadStartRequest.url, -// mode: LaunchMode.platformDefault, -// ); -// } -// } - -// webViewProgress.value = 100; -// }, -// ), -// Positioned( -// bottom: 0, -// left: 0, -// right: 0, -// child: HookBuilder( -// builder: (context) { -// final value = useValueListenable(webViewProgress); - -// return Visibility( -// visible: value < 100, -// child: LinearProgressIndicator( -// value: value / 100, -// ), -// ); -// }, -// ), -// ), -// Positioned( -// bottom: 0, -// left: 0, -// right: 0, -// child: HookConsumer( -// builder: (context, ref, child) { -// final showFindInPage = ref.watch(showFindInPageProvider); - -// final textController = useTextEditingController(); -// final progress = useValueListenable(webViewProgress); - -// return Visibility( -// visible: showFindInPage, -// child: Padding( -// padding: -// EdgeInsets.only(bottom: (progress < 100) ? 4.0 : 0.0), -// child: Material( -// child: Row( -// children: [ -// const SizedBox(width: 8), -// Expanded( -// child: TextField( -// controller: textController, -// autofocus: true, -// autocorrect: false, -// decoration: const InputDecoration.collapsed( -// hintText: 'Find in page', -// ), -// keyboardType: TextInputType.text, -// onSubmitted: (value) async { -// if (value == '') { -// inPageSearchResult.value = null; -// await findInteractionController.clearMatches(); -// } else { -// await findInteractionController.findAll( -// find: value, -// ); -// } -// }, -// ), -// ), -// HookConsumer( -// builder: (context, ref, child) { -// final searchResult = -// useValueListenable(inPageSearchResult); - -// if (searchResult != null) { -// if (searchResult.numberOfMatches == 0) { -// return const Text('Not found'); -// } - -// return Text( -// '${searchResult.activeMatchOrdinal + 1} of ${searchResult.numberOfMatches}', -// ); -// } - -// return const SizedBox.shrink(); -// }, -// ), -// IconButton( -// icon: const Icon(Icons.arrow_upward), -// onPressed: () async { -// await findInteractionController.findNext( -// forward: false, -// ); -// }, -// ), -// IconButton( -// icon: const Icon(Icons.arrow_downward), -// onPressed: () async { -// await findInteractionController.findNext(); -// }, -// ), -// IconButton( -// icon: const Icon(Icons.clear), -// onPressed: () async { -// ref -// .read(showFindInPageProvider.notifier) -// .update(false); - -// inPageSearchResult.value = null; -// await findInteractionController.clearMatches(); -// textController.clear(); -// }, -// ), -// ], -// ), -// ), -// ), -// ); -// }, -// ), -// ), -// ], -// ); -// } -// } diff --git a/app/lib/features/web_view/utils/download_helper.dart b/app/lib/features/web_view/utils/download_helper.dart deleted file mode 100644 index b417cd30..00000000 --- a/app/lib/features/web_view/utils/download_helper.dart +++ /dev/null @@ -1,39 +0,0 @@ -//https://stackoverflow.com/a/67994693/20878798 - -import 'package:universal_io/io.dart'; - -final _utf8FilenameRegex = RegExp( - r"filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)", - caseSensitive: false, -); - -final _asciiFilenameRegex = RegExp( - r"""^filename=(["']?)(.*?[^\\])\1(?:; ?|$)""", - caseSensitive: false, -); - -String? getDispositionFileName(String contentDisposition) { - if (_utf8FilenameRegex.hasMatch(contentDisposition)) { - final file = Uri.decodeComponent( - _utf8FilenameRegex.firstMatch(contentDisposition)!.group(1)!, - ); - - return File(file).uri.pathSegments.last; - } else { - // Prevent ReDos attacks by anchoring the ascii regex to string start and - // slicing off everything before 'filename=' - final filenameStart = contentDisposition.toLowerCase().indexOf('filename='); - if (filenameStart >= 0) { - final partialDisposition = contentDisposition.substring(filenameStart); - final matches = _asciiFilenameRegex.firstMatch(partialDisposition); - if (matches != null && matches.group(2) != null) { - final file = matches.group(2); - if (file != null) { - return File(file).uri.pathSegments.last; - } - } - } - } - - return null; -}