android users now can directly install new updates

This commit is contained in:
Schnitzel5 2025-03-28 23:54:33 +01:00
parent 175ea414b8
commit 4791acc425
7 changed files with 80 additions and 47 deletions

View file

@ -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}"

View file

@ -29,7 +29,7 @@ Future<void> checkForUpdate(
log(info.data.toString());
}
final updateAvailable = await _checkUpdate();
if (compareVersions(info.version, updateAvailable.$1) < 0 || true) {
if (compareVersions(info.version, updateAvailable.$1) < 0) {
if (manualUpdate) {
BotToast.showText(text: l10n.new_update_available);
await Future.delayed(const Duration(seconds: 1));
@ -49,7 +49,7 @@ Future<void> checkForUpdate(
}
}
Future<(String, String, String, List<String>)> _checkUpdate() async {
Future<(String, String, String, List<dynamic>)> _checkUpdate() async {
final http = MClient.init(reqcopyWith: {'useDartHttpClient': true});
try {
final res = await http.get(
@ -67,8 +67,7 @@ Future<(String, String, String, List<String>)> _checkUpdate() async {
resListJson.first["html_url"].toString(),
(resListJson.first["assets"] as List)
.map((asset) => asset["browser_download_url"])
.toList()
as List<String>,
.toList(),
);
} catch (e) {
rethrow;

View file

@ -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 {

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
@ -8,10 +9,11 @@ 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<String>) updateAvailable;
final (String, String, String, List<dynamic>) updateAvailable;
const DownloadFileScreen({required this.updateAvailable, super.key});
@override
@ -23,6 +25,7 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
int _received = 0;
late http.StreamedResponse _response;
final List<int> _bytes = [];
late StreamSubscription<List<int>>? _subscription;
@override
Widget build(BuildContext context) {
@ -30,26 +33,42 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
final updateAvailable = widget.updateAvailable;
return AlertDialog(
title: Text(l10n.new_update_available),
content: Text(
"${l10n.app_version(updateAvailable.$1)}\n\n${updateAvailable.$2}",
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: [
_total > 0
? Row(
children: [
LinearProgressIndicator(
value: _received > 0 ? _total / _received : 0,
),
Text('${_received ~/ 1024}/${_total ~/ 1024} KB'),
],
)
: SizedBox.shrink(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
onPressed: () async {
await _subscription?.cancel();
if (context.mounted) {
Navigator.pop(context);
}
},
child: Text(l10n.cancel),
),
@ -62,7 +81,7 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
String apkUrl = "";
for (String abi in androidInfo.supportedAbis) {
final url = updateAvailable.$4.firstWhereOrNull(
(apk) => apk.contains(abi),
(apk) => (apk as String).contains(abi),
);
if (url != null) {
apkUrl = url;
@ -70,10 +89,6 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
}
}
await _downloadApk(apkUrl);
print("DEBUG");
print(
androidInfo.supportedAbis.join(", "),
); // x86_64, arm64-v8a, armeabi-v7a
} else {
_launchInBrowser(Uri.parse(updateAvailable.$3));
}
@ -87,28 +102,46 @@ class _DownloadFileScreenState extends ConsumerState<DownloadFileScreen> {
}
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);
}
});
}
_response.stream
.listen((value) {
setState(() {
_bytes.addAll(value);
_received += value.length;
});
})
.onDone(() async {
final file = File(
'${(await getApplicationDocumentsDirectory()).path}/${url.split("/").lastOrNull ?? "Mangayomi.apk"}',
);
await file.writeAsBytes(_bytes);
final FlutterAppInstaller appInstaller = FlutterAppInstaller();
await appInstaller.installApk(filePath: file.path);
await file.delete();
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 {

View file

@ -6,7 +6,7 @@ part of 'statistics_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$statisticsStateHash() => r'6c94816bb70881890bc883480677e38885fa6ab2';
String _$statisticsStateHash() => r'81e1957e0e39a9863a8e7d0e1dc565c4eb0e6f9a';
/// Copied from Dart SDK
class _SystemHash {

View file

@ -6,7 +6,7 @@ part of 'aniskip.dart';
// RiverpodGenerator
// **************************************************************************
String _$aniSkipHash() => r'887869b54e2e151633efd46da83bde845e14f421';
String _$aniSkipHash() => r'2e5d19b025a2207ff64da7bf7908450ea9e5ff8c';
/// See also [AniSkip].
@ProviderFor(AniSkip)

View file

@ -6,7 +6,7 @@ part of 'anilist.dart';
// RiverpodGenerator
// **************************************************************************
String _$anilistHash() => r'70e8cd537270a9054a1ef72de117fc7ad5545218';
String _$anilistHash() => r'ddd07acc8d28d2aa95c942566109e9393ca9e5ed';
/// Copied from Dart SDK
class _SystemHash {