mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-26 12:31:02 +00:00
feat: add installer and enable app selection from storage (#2)
This commit is contained in:
@@ -1,22 +1,45 @@
|
||||
import 'dart:io';
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:installed_apps/app_info.dart';
|
||||
import 'package:installed_apps/installed_apps.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/application_info.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
||||
import 'package:revanced_manager/utils/string.dart';
|
||||
import 'package:share_extend/share_extend.dart';
|
||||
|
||||
@lazySingleton
|
||||
class PatcherAPI {
|
||||
static const platform = MethodChannel('app.revanced.manager/patcher');
|
||||
final GithubAPI githubAPI = GithubAPI();
|
||||
final List<AppInfo> _filteredPackages = [];
|
||||
final List<ApplicationWithIcon> _filteredPackages = [];
|
||||
final Map<String, List<Patch>> _filteredPatches = <String, List<Patch>>{};
|
||||
bool isRoot = false;
|
||||
Directory? _workDir;
|
||||
Directory? _cacheDir;
|
||||
File? _patchBundleFile;
|
||||
static const platform = MethodChannel('app.revanced/patcher');
|
||||
File? _integrations;
|
||||
File? _inputFile;
|
||||
File? _patchedFile;
|
||||
File? _outFile;
|
||||
|
||||
Future<void> loadPatches() async {
|
||||
Future<dynamic> handlePlatformChannelMethods() async {
|
||||
platform.setMethodCallHandler((call) async {
|
||||
switch (call.method) {
|
||||
case 'updateInstallerLog':
|
||||
var message = call.arguments<String>('message');
|
||||
locator<InstallerViewModel>().addLog(message);
|
||||
return 'OK';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool?> loadPatches() async {
|
||||
if (_patchBundleFile == null) {
|
||||
String? dexFileUrl =
|
||||
await githubAPI.latestRelease('revanced', 'revanced-patches');
|
||||
@@ -24,7 +47,7 @@ class PatcherAPI {
|
||||
_patchBundleFile =
|
||||
await DefaultCacheManager().getSingleFile(dexFileUrl);
|
||||
try {
|
||||
await platform.invokeMethod(
|
||||
return await platform.invokeMethod<bool>(
|
||||
'loadPatches',
|
||||
{
|
||||
'pathBundlesPaths': <String>[_patchBundleFile!.absolute.path],
|
||||
@@ -32,12 +55,15 @@ class PatcherAPI {
|
||||
);
|
||||
} on PlatformException {
|
||||
_patchBundleFile = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<List<AppInfo>> getFilteredInstalledApps() async {
|
||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
|
||||
if (_patchBundleFile != null && _filteredPackages.isEmpty) {
|
||||
try {
|
||||
List<String>? patchesPackages =
|
||||
@@ -45,8 +71,11 @@ class PatcherAPI {
|
||||
if (patchesPackages != null) {
|
||||
for (String package in patchesPackages) {
|
||||
try {
|
||||
AppInfo app = await InstalledApps.getAppInfo(package);
|
||||
_filteredPackages.add(app);
|
||||
ApplicationWithIcon? app = await DeviceApps.getApp(package, true)
|
||||
as ApplicationWithIcon?;
|
||||
if (app != null) {
|
||||
_filteredPackages.add(app);
|
||||
}
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
@@ -60,25 +89,25 @@ class PatcherAPI {
|
||||
return _filteredPackages;
|
||||
}
|
||||
|
||||
Future<List<Patch>?> getFilteredPatches(AppInfo? targetApp) async {
|
||||
if (_patchBundleFile != null && targetApp != null) {
|
||||
if (_filteredPatches[targetApp.packageName] == null ||
|
||||
_filteredPatches[targetApp.packageName]!.isEmpty) {
|
||||
_filteredPatches[targetApp.packageName!] = [];
|
||||
Future<List<Patch>?> getFilteredPatches(ApplicationInfo? selectedApp) async {
|
||||
if (_patchBundleFile != null && selectedApp != null) {
|
||||
if (_filteredPatches[selectedApp.packageName] == null ||
|
||||
_filteredPatches[selectedApp.packageName]!.isEmpty) {
|
||||
_filteredPatches[selectedApp.packageName] = [];
|
||||
try {
|
||||
var patches = await platform.invokeListMethod<Map<dynamic, dynamic>>(
|
||||
'getFilteredPatches',
|
||||
{
|
||||
'targetPackage': targetApp.packageName,
|
||||
'targetVersion': targetApp.versionName,
|
||||
'targetPackage': selectedApp.packageName,
|
||||
'targetVersion': selectedApp.version,
|
||||
'ignoreVersion': true,
|
||||
},
|
||||
);
|
||||
if (patches != null) {
|
||||
for (var patch in patches) {
|
||||
if (!_filteredPatches[targetApp.packageName]!
|
||||
if (!_filteredPatches[selectedApp.packageName]!
|
||||
.any((element) => element.name == patch['name'])) {
|
||||
_filteredPatches[targetApp.packageName]!.add(
|
||||
_filteredPatches[selectedApp.packageName]!.add(
|
||||
Patch(
|
||||
name: patch['name'],
|
||||
simpleName: (patch['name'] as String)
|
||||
@@ -94,13 +123,168 @@ class PatcherAPI {
|
||||
}
|
||||
}
|
||||
} on PlatformException {
|
||||
_filteredPatches[targetApp.packageName]!.clear();
|
||||
_filteredPatches[selectedApp.packageName]!.clear();
|
||||
return List.empty();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return List.empty();
|
||||
}
|
||||
return _filteredPatches[targetApp.packageName];
|
||||
return _filteredPatches[selectedApp.packageName];
|
||||
}
|
||||
|
||||
Future<File?> downloadIntegrations() async {
|
||||
String? apkFileUrl =
|
||||
await githubAPI.latestRelease('revanced', 'revanced-integrations');
|
||||
if (apkFileUrl != null && apkFileUrl.isNotEmpty) {
|
||||
return await DefaultCacheManager().getSingleFile(apkFileUrl);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool?> initPatcher() async {
|
||||
try {
|
||||
_integrations = await downloadIntegrations();
|
||||
if (_integrations != null) {
|
||||
Directory tmpDir = await getTemporaryDirectory();
|
||||
_workDir = tmpDir.createTempSync('tmp-');
|
||||
_inputFile = File('${_workDir!.path}/base.apk');
|
||||
_patchedFile = File('${_workDir!.path}/patched.apk');
|
||||
_outFile = File('${_workDir!.path}/out.apk');
|
||||
_cacheDir = Directory('${_workDir!.path}/cache');
|
||||
_cacheDir!.createSync();
|
||||
return true;
|
||||
}
|
||||
} on Exception {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool?> copyInputFile(String originalFilePath) async {
|
||||
if (_inputFile != null) {
|
||||
try {
|
||||
return await platform.invokeMethod<bool>(
|
||||
'copyInputFile',
|
||||
{
|
||||
'originalFilePath': originalFilePath,
|
||||
'inputFilePath': _inputFile!.path,
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool?> createPatcher() async {
|
||||
if (_inputFile != null && _cacheDir != null) {
|
||||
try {
|
||||
return await platform.invokeMethod<bool>(
|
||||
'createPatcher',
|
||||
{
|
||||
'inputFilePath': _inputFile!.path,
|
||||
'cacheDirPath': _cacheDir!.path,
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool?> mergeIntegrations() async {
|
||||
try {
|
||||
return await platform.invokeMethod<bool>(
|
||||
'mergeIntegrations',
|
||||
{
|
||||
'integrationsPath': _integrations!.path,
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> applyPatches(List<Patch> selectedPatches) async {
|
||||
try {
|
||||
return await platform.invokeMethod<bool>(
|
||||
'applyPatches',
|
||||
{
|
||||
'selectedPatches': selectedPatches.map((e) => e.name).toList(),
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> repackPatchedFile() async {
|
||||
if (_inputFile != null && _patchedFile != null) {
|
||||
try {
|
||||
return await platform.invokeMethod<bool>(
|
||||
'repackPatchedFile',
|
||||
{
|
||||
'inputFilePath': _inputFile!.path,
|
||||
'patchedFilePath': _patchedFile!.path,
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool?> signPatchedFile() async {
|
||||
if (_patchedFile != null && _outFile != null) {
|
||||
try {
|
||||
return await platform.invokeMethod<bool>(
|
||||
'signPatchedFile',
|
||||
{
|
||||
'patchedFilePath': _patchedFile!.path,
|
||||
'outFilePath': _outFile!.path,
|
||||
},
|
||||
);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> installPatchedFile() async {
|
||||
if (_outFile != null) {
|
||||
try {
|
||||
if (isRoot) {
|
||||
// TBD
|
||||
} else {
|
||||
await AppInstaller.installApk(_outFile!.path);
|
||||
}
|
||||
return true;
|
||||
} on Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cleanPatcher() {
|
||||
if (_workDir != null) {
|
||||
_workDir!.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
bool sharePatchedFile(String packageName) {
|
||||
if (_outFile != null) {
|
||||
String sharePath = '${_outFile!.parent.path}/$packageName.revanced.apk';
|
||||
File share = _outFile!.copySync(sharePath);
|
||||
ShareExtend.share(share.path, "file");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user