diff --git a/lib/main.dart b/lib/main.dart index 0ebbb7f..e40f87b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:bot_toast/bot_toast.dart'; +import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -30,6 +31,11 @@ late Isar isar; WebViewEnvironment? webViewEnvironment; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); + if (Platform.isLinux) { + if (runWebViewTitleBarWidget(args)) { + return; + } + } MediaKit.ensureInitialized(); await RustLib.init(); if (!(Platform.isAndroid || Platform.isIOS)) { diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 0c865a3..50dd47b 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -48,8 +48,6 @@ import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:share_plus/share_plus.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; -import 'package:url_launcher/url_launcher.dart'; - import '../../../utils/constant.dart'; class MangaDetailView extends ConsumerStatefulWidget { @@ -1559,22 +1557,7 @@ class _MangaDetailViewState extends ConsumerState 'sourceId': source.id.toString(), 'title': manga.name! }; - if (Platform.isLinux) { - final urll = Uri.parse(url); - if (!await launchUrl( - urll, - mode: LaunchMode.inAppBrowserView, - )) { - if (!await launchUrl( - urll, - mode: LaunchMode.externalApplication, - )) { - throw 'Could not launch $url'; - } - } - } else { - context.push("/mangawebview", extra: data); - } + context.push("/mangawebview", extra: data); }, child: Column( children: [ diff --git a/lib/modules/manga/home/manga_home_screen.dart b/lib/modules/manga/home/manga_home_screen.dart index c63d6ce..dd6c2d7 100644 --- a/lib/modules/manga/home/manga_home_screen.dart +++ b/lib/modules/manga/home/manga_home_screen.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -25,7 +24,6 @@ import 'package:mangayomi/modules/manga/home/widget/mangas_card_selector.dart'; import 'package:mangayomi/modules/widgets/gridview_widget.dart'; import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart'; import 'package:mangayomi/utils/global_style.dart'; -import 'package:url_launcher/url_launcher.dart'; class MangaHomeScreen extends ConsumerStatefulWidget { final Source source; @@ -267,22 +265,7 @@ class _MangaHomeScreenState extends ConsumerState { 'sourceId': source.id.toString(), 'title': '' }; - if (Platform.isLinux) { - final url = Uri.parse(baseUrl); - if (!await launchUrl( - url, - mode: LaunchMode.inAppBrowserView, - )) { - if (!await launchUrl( - url, - mode: LaunchMode.externalApplication, - )) { - throw 'Could not launch $url'; - } - } - } else { - context.push("/mangawebview", extra: data); - } + context.push("/mangawebview", extra: data); } else { final res = await context.push('/extension_detail', extra: source); @@ -623,22 +606,7 @@ class _MangaHomeScreenState extends ConsumerState { "hasCloudFlare": source.hasCloudflare ?? false }; - if (Platform.isLinux) { - final url = Uri.parse(baseUrl); - if (!await launchUrl( - url, - mode: LaunchMode.inAppBrowserView, - )) { - if (!await launchUrl( - url, - mode: LaunchMode.externalApplication, - )) { - throw 'Could not launch $url'; - } - } - } else { - context.push("/mangawebview", extra: data); - } + context.push("/mangawebview", extra: data); }, icon: Icon( Icons.public, diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index f726e04..84cdefc 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -41,7 +41,6 @@ import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:window_manager/window_manager.dart'; typedef DoubleClickAnimationListener = void Function(); @@ -1344,22 +1343,7 @@ class _MangaChapterPageGalleryState 'sourceId': source.id.toString(), 'title': chapter.name! }; - if (Platform.isLinux) { - final urll = Uri.parse(url); - if (!await launchUrl( - urll, - mode: LaunchMode.inAppBrowserView, - )) { - if (!await launchUrl( - urll, - mode: LaunchMode.externalApplication, - )) { - throw 'Could not launch $url'; - } - } - } else { - context.push("/mangawebview", extra: data); - } + context.push("/mangawebview", extra: data); }, icon: const Icon(Icons.public)), ], diff --git a/lib/modules/webview/webview.dart b/lib/modules/webview/webview.dart index 39e8ee5..1f3ce43 100644 --- a/lib/modules/webview/webview.dart +++ b/lib/modules/webview/webview.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -20,6 +23,41 @@ class MangaWebView extends ConsumerStatefulWidget { class _MangaWebViewState extends ConsumerState { double _progress = 0; + bool isNotDesktop = false; + @override + void initState() { + if (Platform.isLinux) { + _runWebViewDesktop(); + } else { + setState(() { + isNotDesktop = true; + }); + } + super.initState(); + } + + Webview? _desktopWebview; + _runWebViewDesktop() async { + _desktopWebview = await WebviewWindow.create(); + + final timer = Timer.periodic(const Duration(seconds: 1), (timer) async { + try { + final cookieList = await _desktopWebview!.getAllCookies(); + final ua = + await _desktopWebview!.evaluateJavaScript("navigator.userAgent") ?? + ""; + final cookie = cookieList.map((e) => "${e.name}=${e.value}").join(";"); + await MClient.setCookie(_url, ua, null, cookie: cookie); + } catch (_) {} + }); + _desktopWebview! + ..setBrightness(Brightness.dark) + ..launch(widget.url) + ..onClose.whenComplete(() { + timer.cancel(); + Navigator.pop(context); + }); + } InAppWebViewController? _webViewController; late String _url = widget.url; @@ -29,170 +67,191 @@ class _MangaWebViewState extends ConsumerState { @override Widget build(BuildContext context) { final l10n = l10nLocalizations(context); - return Material( - child: SafeArea( - child: WillPopScope( - onWillPop: () async { - final canGoback = await _webViewController?.canGoBack(); - if (canGoback ?? false) { - _webViewController?.goBack(); - } else if (context.mounted) { - context.pop(); - } - return false; - }, - child: Column( - children: [ - SizedBox( - height: AppBar().preferredSize.height, - child: Row( + return !isNotDesktop + ? Scaffold( + appBar: AppBar( + title: Text( + _title, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.bold), + ), + leading: IconButton( + onPressed: () { + _desktopWebview!.close(); + Navigator.pop(context); + }, + icon: const Icon(Icons.close)), + ), + ) + : Material( + child: SafeArea( + child: WillPopScope( + onWillPop: () async { + final canGoback = await _webViewController?.canGoBack(); + if (canGoback ?? false) { + _webViewController?.goBack(); + } else if (context.mounted) { + context.pop(); + } + return false; + }, + child: Column( children: [ - Expanded( - child: ListTile( - dense: true, - subtitle: Text( - _url, - style: const TextStyle( - fontSize: 10, overflow: TextOverflow.ellipsis), - ), - title: Text( - _title, - style: const TextStyle( - overflow: TextOverflow.ellipsis, - fontWeight: FontWeight.bold), - ), - leading: IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: const Icon(Icons.close)), + SizedBox( + height: AppBar().preferredSize.height, + child: Row( + children: [ + Expanded( + child: ListTile( + dense: true, + subtitle: Text( + _url, + style: const TextStyle( + fontSize: 10, + overflow: TextOverflow.ellipsis), + ), + title: Text( + _title, + style: const TextStyle( + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.bold), + ), + leading: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon(Icons.close)), + ), + ), + IconButton( + icon: Icon(Icons.arrow_back, + color: _canGoback ? null : Colors.grey), + onPressed: _canGoback + ? () { + _webViewController?.goBack(); + } + : null, + ), + IconButton( + icon: Icon(Icons.arrow_forward, + color: _canGoForward ? null : Colors.grey), + onPressed: _canGoForward + ? () { + _webViewController?.goForward(); + } + : null, + ), + PopupMenuButton( + popUpAnimationStyle: popupAnimationStyle, + itemBuilder: (context) { + return [ + PopupMenuItem( + value: 0, child: Text(l10n!.refresh)), + PopupMenuItem( + value: 1, child: Text(l10n.share)), + PopupMenuItem( + value: 2, + child: Text(l10n.open_in_browser)), + PopupMenuItem( + value: 3, child: Text(l10n.clear_cookie)), + ]; + }, + onSelected: (value) async { + if (value == 0) { + _webViewController?.reload(); + } else if (value == 1) { + Share.share(_url); + } else if (value == 2) { + await InAppBrowser.openWithSystemBrowser( + url: WebUri(_url)); + } else if (value == 3) { + CookieManager.instance().deleteAllCookies(); + MClient.deleteAllCookies(_url); + } + }), + ], ), ), - IconButton( - icon: Icon(Icons.arrow_back, - color: _canGoback ? null : Colors.grey), - onPressed: _canGoback - ? () { - _webViewController?.goBack(); - } - : null, - ), - IconButton( - icon: Icon(Icons.arrow_forward, - color: _canGoForward ? null : Colors.grey), - onPressed: _canGoForward - ? () { - _webViewController?.goForward(); - } - : null, - ), - PopupMenuButton( - popUpAnimationStyle: popupAnimationStyle, - itemBuilder: (context) { - return [ - PopupMenuItem( - value: 0, child: Text(l10n!.refresh)), - PopupMenuItem( - value: 1, child: Text(l10n.share)), - PopupMenuItem( - value: 2, child: Text(l10n.open_in_browser)), - PopupMenuItem( - value: 3, child: Text(l10n.clear_cookie)), - ]; + _progress < 1.0 + ? LinearProgressIndicator(value: _progress) + : Container(), + Expanded( + child: InAppWebView( + webViewEnvironment: webViewEnvironment, + onWebViewCreated: (controller) async { + _webViewController = controller; }, - onSelected: (value) async { - if (value == 0) { - _webViewController?.reload(); - } else if (value == 1) { - Share.share(_url); - } else if (value == 2) { - await InAppBrowser.openWithSystemBrowser( - url: WebUri(_url)); - } else if (value == 3) { - CookieManager.instance().deleteAllCookies(); - MClient.deleteAllCookies(_url); + onLoadStart: (controller, url) async { + setState(() { + _url = url.toString(); + }); + }, + shouldOverrideUrlLoading: + (controller, navigationAction) async { + var uri = navigationAction.request.url!; + + if (![ + "http", + "https", + "file", + "chrome", + "data", + "javascript", + "about" + ].contains(uri.scheme)) { + if (await canLaunchUrl(uri)) { + // Launch the App + await launchUrl( + uri, + ); + // and cancel the request + return NavigationActionPolicy.CANCEL; + } } - }), + + return NavigationActionPolicy.ALLOW; + }, + onLoadStop: (controller, url) async { + if (mounted) { + setState(() { + _url = url.toString(); + }); + } + }, + onProgressChanged: (controller, progress) async { + if (mounted) { + setState(() { + _progress = progress / 100; + }); + } + }, + onUpdateVisitedHistory: + (controller, url, isReload) async { + final ua = await controller.evaluateJavascript( + source: "navigator.userAgent") ?? + ""; + await MClient.setCookie( + url.toString(), ua, controller); + final canGoback = await controller.canGoBack(); + final canGoForward = await controller.canGoForward(); + final title = await controller.getTitle(); + if (mounted) { + setState(() { + _url = url.toString(); + _title = title!; + _canGoback = canGoback; + _canGoForward = canGoForward; + }); + } + }, + initialUrlRequest: URLRequest(url: WebUri(widget.url)), + ), + ), ], ), ), - _progress < 1.0 - ? LinearProgressIndicator(value: _progress) - : Container(), - Expanded( - child: InAppWebView( - webViewEnvironment: webViewEnvironment, - onWebViewCreated: (controller) async { - _webViewController = controller; - }, - onLoadStart: (controller, url) async { - setState(() { - _url = url.toString(); - }); - }, - shouldOverrideUrlLoading: - (controller, navigationAction) async { - var uri = navigationAction.request.url!; - - if (![ - "http", - "https", - "file", - "chrome", - "data", - "javascript", - "about" - ].contains(uri.scheme)) { - if (await canLaunchUrl(uri)) { - // Launch the App - await launchUrl( - uri, - ); - // and cancel the request - return NavigationActionPolicy.CANCEL; - } - } - - return NavigationActionPolicy.ALLOW; - }, - onLoadStop: (controller, url) async { - if (mounted) { - setState(() { - _url = url.toString(); - }); - } - }, - onProgressChanged: (controller, progress) async { - if (mounted) { - setState(() { - _progress = progress / 100; - }); - } - }, - onUpdateVisitedHistory: (controller, url, isReload) async { - final ua = await controller.evaluateJavascript( - source: "navigator.userAgent") ?? - ""; - await MClient.setCookie(url.toString(), ua, controller); - final canGoback = await controller.canGoBack(); - final canGoForward = await controller.canGoForward(); - final title = await controller.getTitle(); - if (mounted) { - setState(() { - _url = url.toString(); - _title = title!; - _canGoback = canGoback; - _canGoForward = canGoForward; - }); - } - }, - initialUrlRequest: URLRequest(url: WebUri(widget.url)), - ), - ), - ], - ), - ), - ), - ); + ), + ); } } diff --git a/lib/services/http/m_client.dart b/lib/services/http/m_client.dart index 10207bb..fb70f3d 100644 --- a/lib/services/http/m_client.dart +++ b/lib/services/http/m_client.dart @@ -67,16 +67,24 @@ class MClient { } static Future setCookie(String url, String ua, - flutter_inappwebview.InAppWebViewController webViewController, + flutter_inappwebview.InAppWebViewController? webViewController, {String? cookie}) async { List cookies = []; - cookies = (await flutter_inappwebview.CookieManager.instance( - webViewEnvironment: webViewEnvironment) - .getCookies( - url: flutter_inappwebview.WebUri(url), - webViewController: webViewController)) - .map((e) => "${e.name}=${e.value}") - .toList(); + if (Platform.isLinux) { + cookies = cookie + ?.split(RegExp('(?<=)(,)(?=[^;]+?=)')) + .where((cookie) => cookie.isNotEmpty) + .toList() ?? + []; + } else { + cookies = (await flutter_inappwebview.CookieManager.instance( + webViewEnvironment: webViewEnvironment) + .getCookies( + url: flutter_inappwebview.WebUri(url), + webViewController: webViewController)) + .map((e) => "${e.name}=${e.value}") + .toList(); + } if (cookies.isNotEmpty) { final host = Uri.parse(url).host; final newCookie = cookies.join("; "); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 64758a6..60476fb 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -16,6 +17,9 @@ #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin"); + desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar); g_autoptr(FlPluginRegistrar) flutter_qjs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterQjsPlugin"); flutter_qjs_plugin_register_with_registrar(flutter_qjs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e6da95..a6727cd 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_webview_window flutter_qjs isar_flutter_libs media_kit_libs_linux diff --git a/pubspec.lock b/pubspec.lock index 74d4c2b..b205dce 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -318,6 +318,15 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + desktop_webview_window: + dependency: "direct main" + description: + path: "packages/desktop_webview_window" + ref: main + resolved-ref: "2aa8d449881974182d033df9635cf7c198d2553a" + url: "https://github.com/kodjodevf/desktop_webview_window.git" + source: git + version: "0.2.4" directed_graph: dependency: transitive description: @@ -671,10 +680,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: ce89c5a993ca5eea74535f798478502c30a625ecb10a1de4d7fef5cd1bcac2a4 + sha256: "8ae664a70174163b9f65ea68dd8673e29db8f9095de7b5cd00e167c621f4fef5" url: "https://pub.dev" source: hosted - version: "14.4.1" + version: "14.6.0" google_fonts: dependency: "direct main" description: @@ -1614,10 +1623,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8af9566..a7c6ea1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,11 @@ dependencies: pseudom: ^1.0.1 path: ^1.9.0 freezed_annotation: ^2.0.0 + desktop_webview_window: + git: + url: https://github.com/kodjodevf/desktop_webview_window.git + path: packages/desktop_webview_window + ref: main dependency_overrides: http: ^1.2.1