mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-05-19 20:21:54 +00:00
Merge pull request #427 from Schnitzel5/feature/android-update
android users now can directly install new updates
This commit is contained in:
commit
a26a764753
11 changed files with 215 additions and 41 deletions
|
|
@ -4,6 +4,7 @@
|
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<application
|
||||
android:label="Mangayomi"
|
||||
android:name="${applicationName}"
|
||||
|
|
@ -46,6 +47,15 @@
|
|||
<data android:scheme="mangayomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
|
|
|
|||
5
android/app/src/main/res/xml/file_paths.xml
Normal file
5
android/app/src/main/res/xml/file_paths.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path path="Android/data/com.kodjodevf.mangayomi/" name="files_root" />
|
||||
<external-path path="." name="external_storage_root" />
|
||||
</paths>
|
||||
|
|
@ -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<void> 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<void> 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<void> checkForUpdate(
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _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<dynamic>)> _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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
152
lib/modules/more/about/providers/download_file_screen.dart
Normal file
152
lib/modules/more/about/providers/download_file_screen.dart
Normal file
|
|
@ -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<dynamic>) updateAvailable;
|
||||
const DownloadFileScreen({required this.updateAvailable, super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DownloadFileScreen> createState() => _DownloadFileScreenState();
|
||||
}
|
||||
|
||||
class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
|
||||
int _total = 0;
|
||||
int _received = 0;
|
||||
late http.StreamedResponse _response;
|
||||
final List<int> _bytes = [];
|
||||
late StreamSubscription<List<int>>? _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<void> _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<void> _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<void> _launchInBrowser(Uri url) async {
|
||||
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
|
||||
throw 'Could not launch $url';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ part of 'statistics_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$statisticsStateHash() => r'6c94816bb70881890bc883480677e38885fa6ab2';
|
||||
String _$statisticsStateHash() => r'81e1957e0e39a9863a8e7d0e1dc565c4eb0e6f9a';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'aniskip.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
|
||||
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
|
||||
|
||||
/// See also [AniSkip].
|
||||
@ProviderFor(AniSkip)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'anilist.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$anilistHash() => r'70e8cd537270a9054a1ef72de117fc7ad5545218';
|
||||
String _$anilistHash() => r'ddd07acc8d28d2aa95c942566109e9393ca9e5ed';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
36
pubspec.lock
36
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue