diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6de00b03..c8927d18 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/modules/more/about/providers/check_for_update.dart b/lib/modules/more/about/providers/check_for_update.dart index 847976fe..23c7b9e5 100644 --- a/lib/modules/more/about/providers/check_for_update.dart +++ b/lib/modules/more/about/providers/check_for_update.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:mangayomi/modules/more/about/providers/download_file_screen.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; import 'package:mangayomi/services/fetch_sources_list.dart'; import 'package:mangayomi/services/http/m_client.dart'; @@ -10,7 +11,6 @@ import 'package:mangayomi/utils/extensions/string_extensions.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:url_launcher/url_launcher.dart'; part 'check_for_update.g.dart'; @riverpod @@ -28,7 +28,6 @@ Future checkForUpdate( if (kDebugMode) { log(info.data.toString()); } - final updateAvailable = await _checkUpdate(); if (compareVersions(info.version, updateAvailable.$1) < 0) { if (manualUpdate) { @@ -39,32 +38,7 @@ Future checkForUpdate( showDialog( context: context, builder: (context) { - return AlertDialog( - title: Text(l10n.new_update_available), - content: Text( - "${l10n.app_version(updateAvailable.$1)}\n\n${updateAvailable.$2}", - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text(l10n.cancel), - ), - const SizedBox(width: 15), - ElevatedButton( - onPressed: () { - _launchInBrowser(Uri.parse(updateAvailable.$3)); - }, - child: Text(l10n.download), - ), - ], - ), - ], - ); + return DownloadFileScreen(updateAvailable: updateAvailable); }, ); } @@ -75,13 +49,7 @@ Future checkForUpdate( } } -Future _launchInBrowser(Uri url) async { - if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { - throw 'Could not launch $url'; - } -} - -Future<(String, String, String)> _checkUpdate() async { +Future<(String, String, String, List)> _checkUpdate() async { final http = MClient.init(reqcopyWith: {'useDartHttpClient': true}); try { final res = await http.get( @@ -97,6 +65,9 @@ Future<(String, String, String)> _checkUpdate() async { .substringBefore('-'), resListJson.first["body"].toString(), resListJson.first["html_url"].toString(), + (resListJson.first["assets"] as List) + .map((asset) => asset["browser_download_url"]) + .toList(), ); } catch (e) { rethrow; diff --git a/lib/modules/more/about/providers/check_for_update.g.dart b/lib/modules/more/about/providers/check_for_update.g.dart index 6f73b745..f6d859b5 100644 --- a/lib/modules/more/about/providers/check_for_update.g.dart +++ b/lib/modules/more/about/providers/check_for_update.g.dart @@ -6,7 +6,7 @@ part of 'check_for_update.dart'; // RiverpodGenerator // ************************************************************************** -String _$checkForUpdateHash() => r'ff9623d8b0b4a0485cde58c0a8b5447b64bb02ab'; +String _$checkForUpdateHash() => r'cc0baa7eda3bef643e56ea7eed3e2ded87730e3c'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/modules/more/about/providers/download_file_screen.dart b/lib/modules/more/about/providers/download_file_screen.dart new file mode 100644 index 00000000..96d02da0 --- /dev/null +++ b/lib/modules/more/about/providers/download_file_screen.dart @@ -0,0 +1,152 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_app_installer/flutter_app_installer.dart'; +import 'package:flutter_qjs/quickjs/ffi.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'package:mangayomi/providers/l10n_providers.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class DownloadFileScreen extends ConsumerStatefulWidget { + final (String, String, String, List) updateAvailable; + const DownloadFileScreen({required this.updateAvailable, super.key}); + + @override + ConsumerState createState() => _DownloadFileScreenState(); +} + +class _DownloadFileScreenState extends ConsumerState { + int _total = 0; + int _received = 0; + late http.StreamedResponse _response; + final List _bytes = []; + late StreamSubscription>? _subscription; + + @override + Widget build(BuildContext context) { + final l10n = l10nLocalizations(context)!; + final updateAvailable = widget.updateAvailable; + return AlertDialog( + title: Text(l10n.new_update_available), + content: SingleChildScrollView( + child: Column( + children: [ + Text( + "${l10n.app_version(updateAvailable.$1)}\n\n${updateAvailable.$2}", + ), + _total > 0 + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: LinearProgressIndicator( + value: _total > 0 ? (_received * 1.0) / _total : 0.0, + ), + ), + Flexible( + child: Text( + '${(_received / 1048576.0).toStringAsFixed(2)}/${(_total / 1048576.0).toStringAsFixed(2)} MB', + ), + ), + ], + ) + : SizedBox.shrink(), + ], + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () async { + await _subscription?.cancel(); + if (context.mounted) { + Navigator.pop(context); + } + }, + child: Text(l10n.cancel), + ), + const SizedBox(width: 15), + ElevatedButton( + onPressed: () async { + if (Platform.isAndroid) { + final deviceInfo = DeviceInfoPlugin(); + final androidInfo = await deviceInfo.androidInfo; + String apkUrl = ""; + for (String abi in androidInfo.supportedAbis) { + final url = updateAvailable.$4.firstWhereOrNull( + (apk) => (apk as String).contains(abi), + ); + if (url != null) { + apkUrl = url; + break; + } + } + await _downloadApk(apkUrl); + } else { + _launchInBrowser(Uri.parse(updateAvailable.$3)); + } + }, + child: Text(l10n.download), + ), + ], + ), + ], + ); + } + + Future _downloadApk(String url) async { + var status = await Permission.storage.status; + if (!status.isGranted) { + await Permission.storage.request(); + } + Directory? dir = Directory('/storage/emulated/0/Download'); + if (!await dir.exists()) dir = await getExternalStorageDirectory(); + final file = File( + '${dir!.path}/${url.split("/").lastOrNull ?? "Mangayomi.apk"}', + ); + if (await file.exists()) { + await _installApk(file); + if (context.mounted) { + Navigator.pop(context); + } + return; + } + _response = await http.Client().send(http.Request('GET', Uri.parse(url))); + _total = _response.contentLength ?? 0; + _subscription = _response.stream.listen((value) { + setState(() { + _bytes.addAll(value); + _received += value.length; + }); + }); + _subscription?.onDone(() async { + await file.writeAsBytes(_bytes); + await _installApk(file); + if (context.mounted) { + Navigator.pop(context); + } + }); + } + + Future _installApk(File file) async { + var status = await Permission.requestInstallPackages.status; + if (!status.isGranted) { + await Permission.requestInstallPackages.request(); + } + final FlutterAppInstaller appInstaller = FlutterAppInstaller(); + await appInstaller.installApk(filePath: file.path); + } + + Future _launchInBrowser(Uri url) async { + if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { + throw 'Could not launch $url'; + } + } +} diff --git a/lib/modules/more/statistics/statistics_provider.g.dart b/lib/modules/more/statistics/statistics_provider.g.dart index 9f7b7e10..178007ee 100644 --- a/lib/modules/more/statistics/statistics_provider.g.dart +++ b/lib/modules/more/statistics/statistics_provider.g.dart @@ -6,7 +6,7 @@ part of 'statistics_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$statisticsStateHash() => r'6c94816bb70881890bc883480677e38885fa6ab2'; +String _$statisticsStateHash() => r'81e1957e0e39a9863a8e7d0e1dc565c4eb0e6f9a'; /// Copied from Dart SDK class _SystemHash { diff --git a/lib/services/aniskip.g.dart b/lib/services/aniskip.g.dart index b2e1defb..c05db920 100644 --- a/lib/services/aniskip.g.dart +++ b/lib/services/aniskip.g.dart @@ -6,7 +6,7 @@ part of 'aniskip.dart'; // RiverpodGenerator // ************************************************************************** -String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421'; +String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c'; /// See also [AniSkip]. @ProviderFor(AniSkip) diff --git a/lib/services/trackers/anilist.g.dart b/lib/services/trackers/anilist.g.dart index 558da2c4..834afd64 100644 --- a/lib/services/trackers/anilist.g.dart +++ b/lib/services/trackers/anilist.g.dart @@ -6,7 +6,7 @@ part of 'anilist.dart'; // RiverpodGenerator // ************************************************************************** -String _$anilistHash() => r'70e8cd537270a9054a1ef72de117fc7ad5545218'; +String _$anilistHash() => r'ddd07acc8d28d2aa95c942566109e9393ca9e5ed'; /// Copied from Dart SDK class _SystemHash { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 6365741e..596d9721 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import app_links import audio_session import connectivity_plus +import device_info_plus import flutter_inappwebview_macos import flutter_qjs import flutter_web_auth_2 @@ -33,6 +34,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterQjsPlugin.register(with: registry.registrar(forPlugin: "FlutterQjsPlugin")) FlutterWebAuth2Plugin.register(with: registry.registrar(forPlugin: "FlutterWebAuth2Plugin")) diff --git a/pubspec.lock b/pubspec.lock index 2edbcdc1..739cd5a7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -399,6 +399,22 @@ packages: url: "https://github.com/kodjodevf/desktop_webview_window.git" source: git version: "0.2.4" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" + url: "https://pub.dev" + source: hosted + version: "11.3.3" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" directed_graph: dependency: transitive description: @@ -532,6 +548,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_app_installer: + dependency: "direct main" + description: + name: flutter_app_installer + sha256: b71f7c3f6c5712b6f9bdcde798bbb8a0c4047cab47c4364f7252de8c95d67358 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_cache_manager: dependency: transitive description: @@ -2128,10 +2152,18 @@ packages: dependency: "direct main" description: name: win32 - sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f url: "https://pub.dev" source: hosted - version: "5.10.1" + version: "5.12.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" window_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 99d670d8..d92f68ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,8 @@ dependencies: app_links: ^6.4.0 win32: ^5.10.1 protobuf: ^3.1.0 + device_info_plus: ^11.3.3 + flutter_app_installer: ^1.0.0 dependency_overrides: http: ^1.2.2