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