import 'dart:convert'; import 'package:device_apps/device_apps.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_background/flutter_background.dart'; import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:stacked/stacked.dart'; class InstallerViewModel extends BaseViewModel { final PatcherAPI _patcherAPI = locator(); final PatchedApplication? _app = locator().selectedApp; final List _patches = locator().selectedPatches; static const _installerChannel = MethodChannel( 'app.revanced.manager/installer', ); double? progress = 0.0; String logs = ''; bool isPatching = false; bool isInstalled = false; Future initialize(BuildContext context) async { try { await FlutterBackground.initialize( androidConfig: FlutterBackgroundAndroidConfig( notificationTitle: FlutterI18n.translate( context, 'installerView.notificationTitle', ), notificationText: FlutterI18n.translate( context, 'installerView.notificationText', ), notificationImportance: AndroidNotificationImportance.Default, notificationIcon: const AndroidResource( name: 'ic_notification', defType: 'drawable', ), ), ); await FlutterBackground.enableBackgroundExecution(); } finally { await handlePlatformChannelMethods(); await runPatcher(); } } Future handlePlatformChannelMethods() async { _installerChannel.setMethodCallHandler((call) async { switch (call.method) { case 'updateProgress': if (call.arguments != null) { updateProgress(call.arguments); } break; case 'updateLog': if (call.arguments != null) { updateLog(call.arguments); } break; } }); } void updateProgress(double value) { progress = value; isInstalled = false; isPatching = progress == 1.0 ? false : true; if (progress == 0.0) { logs = ''; } notifyListeners(); } void updateLog(String message) { if (message.isNotEmpty && !message.startsWith('Merging L')) { if (logs.isNotEmpty) { logs += '\n'; } logs += message; notifyListeners(); } } Future runPatcher() async { updateProgress(0.0); if (_app != null && _patches.isNotEmpty) { String apkFilePath = _app!.apkFilePath; try { updateLog('Initializing installer'); if (_app!.isRooted && !_app!.isFromStorage) { updateLog('Checking if an old patched version exists'); bool oldExists = await _patcherAPI.checkOldPatch(_app!); if (oldExists) { updateLog('Deleting old patched version'); await _patcherAPI.deleteOldPatch(_app!); } } updateLog('Creating working directory'); bool mergeIntegrations = false; bool resourcePatching = false; if (_app!.packageName == 'com.google.android.youtube') { mergeIntegrations = true; resourcePatching = true; } else if (_app!.packageName == 'com.google.android.apps.youtube.music') { resourcePatching = true; } await _patcherAPI.initPatcher(mergeIntegrations); await _patcherAPI.runPatcher( apkFilePath, _patches, mergeIntegrations, resourcePatching, ); } on Exception { updateLog('An error occurred! Aborting'); } } else { updateLog('No app or patches selected! Aborting'); } try { await FlutterBackground.disableBackgroundExecution(); } finally { isPatching = false; } } void installResult() async { if (_app != null) { updateLog(_app!.isRooted ? 'Installing patched file using root method' : 'Installing patched file using nonroot method'); isInstalled = await _patcherAPI.installPatchedFile(_app!); if (isInstalled) { updateLog('Done'); _app!.patchDate = DateTime.now(); _app!.appliedPatches.addAll(_patches.map((p) => p.name).toList()); await saveApp(); } else { updateLog('An error occurred! Aborting'); } } } void shareResult() { if (_app != null) { _patcherAPI.sharePatchedFile(_app!.name, _app!.version); } } Future cleanWorkplace() async { _patcherAPI.cleanPatcher(); locator().selectedApp = null; locator().selectedPatches.clear(); locator().notifyListeners(); } void openApp() { if (_app != null) { DeviceApps.openApp(_app!.packageName); } } Future saveApp() async { if (_app != null) { SharedPreferences prefs = await SharedPreferences.getInstance(); List patchedApps = prefs.getStringList('patchedApps') ?? []; String appStr = json.encode(_app!.toJson()); patchedApps.removeWhere( (a) => json.decode(a)['packageName'] == _app!.packageName); patchedApps.add(appStr); prefs.setStringList('patchedApps', patchedApps); } } }