fix: code refactoring (#5)

This commit is contained in:
Alberto Ponces 2022-08-18 15:33:33 +01:00 committed by GitHub
parent 6153e1f9e8
commit 389eae1447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 318 additions and 361 deletions

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdkVersion 33
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -45,7 +45,7 @@ android {
defaultConfig { defaultConfig {
applicationId "app.revanced.manager" applicationId "app.revanced.manager"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

View File

@ -9,7 +9,9 @@
"updatesSubtitle": "Updates", "updatesSubtitle": "Updates",
"patchedSubtitle": "Patched Applications", "patchedSubtitle": "Patched Applications",
"updatesAvailable": "Updates Available", "updatesAvailable": "Updates Available",
"installed": "Installed" "installed": "Installed",
"notificationTitle": "ReVanced Manager was updated!",
"notificationText": "Tap to open the app"
}, },
"applicationItem": { "applicationItem": {
"patchButton": "Patch", "patchButton": "Patch",
@ -50,7 +52,9 @@
"installerView": { "installerView": {
"widgetTitle": "Installer", "widgetTitle": "Installer",
"fabInstallButton": "Install", "fabInstallButton": "Install",
"fabOpenButton": "Open" "fabOpenButton": "Open",
"notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer"
}, },
"settingsView": { "settingsView": {
"widgetTitle": "Settings", "widgetTitle": "Settings",

View File

@ -1,16 +1,11 @@
import 'package:revanced_manager/main.dart'; import 'package:revanced_manager/main.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_view.dart'; import 'package:revanced_manager/ui/views/app_selector/app_selector_view.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/contributors/contributors_view.dart'; import 'package:revanced_manager/ui/views/contributors/contributors_view.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/views/installer/installer_view.dart'; import 'package:revanced_manager/ui/views/installer/installer_view.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_view.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_view.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/root_checker/root_checker_view.dart'; import 'package:revanced_manager/ui/views/root_checker/root_checker_view.dart';
import 'package:revanced_manager/ui/views/settings/settings_view.dart'; import 'package:revanced_manager/ui/views/settings/settings_view.dart';
import 'package:stacked/stacked_annotations.dart'; import 'package:stacked/stacked_annotations.dart';
@ -30,13 +25,8 @@ import 'package:stacked_themes/stacked_themes.dart';
dependencies: [ dependencies: [
LazySingleton(classType: NavigationService), LazySingleton(classType: NavigationService),
LazySingleton(classType: PatcherAPI), LazySingleton(classType: PatcherAPI),
LazySingleton(classType: ManagerAPI),
LazySingleton(classType: RootAPI),
LazySingleton(classType: HomeViewModel), LazySingleton(classType: HomeViewModel),
LazySingleton(classType: PatcherViewModel), LazySingleton(classType: PatcherViewModel),
LazySingleton(classType: AppSelectorViewModel),
LazySingleton(classType: PatchesSelectorViewModel),
LazySingleton(classType: InstallerViewModel),
LazySingleton( LazySingleton(
classType: ThemeService, classType: ThemeService,
resolveUsing: ThemeService.getInstance, resolveUsing: ThemeService.getInstance,

View File

@ -1,32 +1,36 @@
import 'dart:io';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:github/github.dart'; import 'package:github/github.dart';
import 'package:injectable/injectable.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
@lazySingleton
class GithubAPI { class GithubAPI {
var github = GitHub(); final GitHub _github = GitHub();
Future<String?> latestRelease(String org, repoName) async { Future<File?> latestRelease(String org, repoName) async {
try { try {
var latestRelease = await github.repositories.getLatestRelease( var latestRelease = await _github.repositories.getLatestRelease(
RepositorySlug(org, repoName), RepositorySlug(org, repoName),
); );
return latestRelease.assets String? url = latestRelease.assets
?.firstWhere((asset) => ?.firstWhere((asset) =>
asset.name != null && asset.name != null &&
(asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) && (asset.name!.endsWith('.dex') || asset.name!.endsWith('.apk')) &&
!asset.name!.contains('-sources') && !asset.name!.contains('-sources') &&
!asset.name!.contains('-javadoc')) !asset.name!.contains('-javadoc'))
.browserDownloadUrl; .browserDownloadUrl;
if (url != null) {
return await DefaultCacheManager().getSingleFile(url);
}
} on Exception { } on Exception {
return ''; return null;
} }
return null;
} }
Future<String> latestCommitTime(String org, repoName) async { Future<String> latestCommitTime(String org, repoName) async {
try { try {
var repo = await github.repositories.getRepository( var repo = await _github.repositories.getRepository(
RepositorySlug(org, repoName), RepositorySlug(org, repoName),
); );
return repo.pushedAt != null return repo.pushedAt != null
@ -39,7 +43,7 @@ class GithubAPI {
Future<List<Contributor>> getContributors(String org, repoName) async { Future<List<Contributor>> getContributors(String org, repoName) async {
try { try {
var contributors = github.repositories.listContributors( var contributors = _github.repositories.listContributors(
RepositorySlug(org, repoName), RepositorySlug(org, repoName),
); );
return contributors.toList(); return contributors.toList();

View File

@ -1,52 +1,19 @@
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart' as p;
import 'package:revanced_manager/constants.dart'; import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/github_api.dart';
@lazySingleton
class ManagerAPI { class ManagerAPI {
final Dio dio = Dio(); final GithubAPI _githubAPI = GithubAPI();
final GithubAPI githubAPI = GithubAPI();
Future<String?> getPath() async { Future<File?> downloadPatches() async {
final path = await p.getApplicationSupportDirectory(); return await _githubAPI.latestRelease(ghOrg, patchesRepo);
Directory('${path.path}/revanced').createSync();
final workDirPath = '${path.path}/revanced';
return workDirPath;
} }
Future<File?> downloadAssets(String repo) async { Future<File?> downloadIntegrations() async {
try { return await _githubAPI.latestRelease(ghOrg, integrationsRepo);
final workDir = await getPath();
final dlUrl = await githubAPI.latestRelease(ghOrg, repo);
final name = dlUrl
?.split('/')
.lastWhere((element) => element.contains('revanced'));
final assetFile = File('$workDir/$name');
final response = await dio.get(
dlUrl!,
options: Options(
responseType: ResponseType.bytes,
followRedirects: true,
receiveTimeout: 0,
),
);
final raf = assetFile.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
raf.closeSync();
return assetFile;
} catch (e) {
return null;
}
} }
Future<void> downloadPatches() async { Future<File?> downloadManager() async {
await downloadAssets(patchesRepo); return await _githubAPI.latestRelease(ghOrg, managerRepo);
}
Future<void> downloadIntegrations() async {
await downloadAssets(integrationsRepo);
} }
} }

View File

@ -2,15 +2,12 @@ import 'dart:io';
import 'package:app_installer/app_installer.dart'; import 'package:app_installer/app_installer.dart';
import 'package:device_apps/device_apps.dart'; import 'package:device_apps/device_apps.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/utils/string.dart'; import 'package:revanced_manager/utils/string.dart';
import 'package:share_extend/share_extend.dart'; import 'package:share_extend/share_extend.dart';
@ -19,11 +16,8 @@ class PatcherAPI {
static const patcherChannel = MethodChannel( static const patcherChannel = MethodChannel(
'app.revanced.manager/patcher', 'app.revanced.manager/patcher',
); );
static const installerChannel = MethodChannel( final ManagerAPI _managerAPI = ManagerAPI();
'app.revanced.manager/installer', final RootAPI _rootAPI = RootAPI();
);
final GithubAPI githubAPI = GithubAPI();
final RootAPI rootAPI = RootAPI();
Directory? _tmpDir; Directory? _tmpDir;
Directory? _workDir; Directory? _workDir;
Directory? _cacheDir; Directory? _cacheDir;
@ -33,45 +27,18 @@ class PatcherAPI {
File? _patchedFile; File? _patchedFile;
File? _outFile; File? _outFile;
Future<dynamic> handlePlatformChannelMethods() async { Future<void> loadPatches() async {
installerChannel.setMethodCallHandler((call) async {
switch (call.method) {
case 'updateProgress':
if (call.arguments != null) {
locator<InstallerViewModel>().updateProgress(call.arguments);
}
break;
case 'updateLog':
if (call.arguments != null) {
locator<InstallerViewModel>().updateLog(call.arguments);
}
break;
}
});
}
Future<bool?> loadPatches() async {
if (_patchBundleFile == null) { if (_patchBundleFile == null) {
String? dexFileUrl = _patchBundleFile = await _managerAPI.downloadPatches();
await githubAPI.latestRelease('revanced', 'revanced-patches'); if (_patchBundleFile != null) {
if (dexFileUrl != null && dexFileUrl.isNotEmpty) { await patcherChannel.invokeMethod<bool>(
try { 'loadPatches',
_patchBundleFile = {
await DefaultCacheManager().getSingleFile(dexFileUrl); 'pathBundlesPaths': <String>[_patchBundleFile!.absolute.path],
return await patcherChannel.invokeMethod<bool>( },
'loadPatches', );
{
'pathBundlesPaths': <String>[_patchBundleFile!.absolute.path],
},
);
} on Exception {
_patchBundleFile = null;
return false;
}
} }
return false;
} }
return true;
} }
Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async { Future<List<ApplicationWithIcon>> getFilteredInstalledApps() async {
@ -181,20 +148,9 @@ class PatcherAPI {
return appliedPatches; return appliedPatches;
} }
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<void> initPatcher(bool mergeIntegrations) async { Future<void> initPatcher(bool mergeIntegrations) async {
if (mergeIntegrations) { if (mergeIntegrations) {
_integrations = await downloadIntegrations(); _integrations = await _managerAPI.downloadIntegrations();
} else {
_integrations = File('');
} }
_tmpDir = await getTemporaryDirectory(); _tmpDir = await getTemporaryDirectory();
_workDir = _tmpDir!.createTempSync('tmp-'); _workDir = _tmpDir!.createTempSync('tmp-');
@ -218,7 +174,7 @@ class PatcherAPI {
'inputFilePath': _inputFile!.path, 'inputFilePath': _inputFile!.path,
'patchedFilePath': _patchedFile!.path, 'patchedFilePath': _patchedFile!.path,
'outFilePath': _outFile!.path, 'outFilePath': _outFile!.path,
'integrationsPath': _integrations!.path, 'integrationsPath': _integrations != null ? _integrations!.path : '',
'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'cacheDirPath': _cacheDir!.path, 'cacheDirPath': _cacheDir!.path,
'mergeIntegrations': mergeIntegrations, 'mergeIntegrations': mergeIntegrations,
@ -231,7 +187,7 @@ class PatcherAPI {
if (_outFile != null) { if (_outFile != null) {
try { try {
if (patchedApp.isRooted && !patchedApp.isFromStorage) { if (patchedApp.isRooted && !patchedApp.isFromStorage) {
return rootAPI.installApp( return _rootAPI.installApp(
patchedApp.packageName, patchedApp.packageName,
patchedApp.apkFilePath, patchedApp.apkFilePath,
_outFile!.path, _outFile!.path,
@ -268,14 +224,14 @@ class PatcherAPI {
Future<bool> checkOldPatch(PatchedApplication patchedApp) async { Future<bool> checkOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) { if (patchedApp.isRooted) {
return await rootAPI.checkApp(patchedApp.packageName); return await _rootAPI.checkApp(patchedApp.packageName);
} }
return false; return false;
} }
Future<void> deleteOldPatch(PatchedApplication patchedApp) async { Future<void> deleteOldPatch(PatchedApplication patchedApp) async {
if (patchedApp.isRooted) { if (patchedApp.isRooted) {
await rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath); await _rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
} }
} }
} }

View File

@ -1,16 +1,14 @@
import 'package:injectable/injectable.dart';
import 'package:root/root.dart'; import 'package:root/root.dart';
@lazySingleton
class RootAPI { class RootAPI {
final String managerDirPath = "/data/adb/revanced_manager"; final String _managerDirPath = "/data/adb/revanced_manager";
final String postFsDataDirPath = "/data/adb/post-fs-data.d"; final String _postFsDataDirPath = "/data/adb/post-fs-data.d";
final String serviceDDirPath = "/data/adb/service.d"; final String _serviceDDirPath = "/data/adb/service.d";
Future<bool> checkApp(String packageName) async { Future<bool> checkApp(String packageName) async {
try { try {
String? res = await Root.exec( String? res = await Root.exec(
cmd: 'ls -la "$managerDirPath/$packageName"', cmd: 'ls -la "$_managerDirPath/$packageName"',
); );
return res != null && res.isNotEmpty; return res != null && res.isNotEmpty;
} on Exception { } on Exception {
@ -26,13 +24,13 @@ class RootAPI {
cmd: 'su -mm -c "umount -l $originalFilePath"', cmd: 'su -mm -c "umount -l $originalFilePath"',
); );
await Root.exec( await Root.exec(
cmd: 'rm -rf "$managerDirPath/$packageName"', cmd: 'rm -rf "$_managerDirPath/$packageName"',
); );
await Root.exec( await Root.exec(
cmd: 'rm -rf "$serviceDDirPath/$packageName.sh"', cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"',
); );
await Root.exec( await Root.exec(
cmd: 'rm -rf "$postFsDataDirPath/$packageName.sh"', cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"',
); );
} }
@ -43,7 +41,7 @@ class RootAPI {
) async { ) async {
try { try {
await Root.exec( await Root.exec(
cmd: 'mkdir -p "$managerDirPath/$packageName"', cmd: 'mkdir -p "$_managerDirPath/$packageName"',
); );
installServiceDScript(packageName); installServiceDScript(packageName);
installPostFsDataScript(packageName); installPostFsDataScript(packageName);
@ -58,10 +56,10 @@ class RootAPI {
Future<void> installServiceDScript(String packageName) async { Future<void> installServiceDScript(String packageName) async {
String content = '#!/system/bin/sh\n' String content = '#!/system/bin/sh\n'
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 1; done\n' 'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 1; done\n'
'base_path=$managerDirPath/$packageName/base.apk\n' 'base_path=$_managerDirPath/$packageName/base.apk\n'
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' 'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
'[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path'; '[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path';
String scriptFilePath = '$serviceDDirPath/$packageName.sh'; String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
await Root.exec( await Root.exec(
cmd: 'echo \'$content\' > "$scriptFilePath"', cmd: 'echo \'$content\' > "$scriptFilePath"',
); );
@ -74,7 +72,7 @@ class RootAPI {
String content = '#!/system/bin/sh\n' String content = '#!/system/bin/sh\n'
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n' 'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
'[ ! -z \$stock_path ] && umount -l \$stock_path'; '[ ! -z \$stock_path ] && umount -l \$stock_path';
String scriptFilePath = '$postFsDataDirPath/$packageName.sh'; String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
await Root.exec( await Root.exec(
cmd: 'echo \'$content\' > "$scriptFilePath"', cmd: 'echo \'$content\' > "$scriptFilePath"',
); );
@ -84,7 +82,7 @@ class RootAPI {
} }
Future<void> installApk(String packageName, String patchedFilePath) async { Future<void> installApk(String packageName, String patchedFilePath) async {
String newPatchedFilePath = '$managerDirPath/$packageName/base.apk'; String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
await Root.exec( await Root.exec(
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"', cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
); );
@ -104,7 +102,7 @@ class RootAPI {
String originalFilePath, String originalFilePath,
String patchedFilePath, String patchedFilePath,
) async { ) async {
String newPatchedFilePath = '$managerDirPath/$packageName/base.apk'; String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
await Root.exec( await Root.exec(
cmd: 'am force-stop "$packageName"', cmd: 'am force-stop "$packageName"',
); );

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/theme.dart'; import 'package:revanced_manager/theme.dart';
import 'package:revanced_manager/ui/widgets/installed_app_item.dart'; import 'package:revanced_manager/ui/widgets/installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/search_bar.dart'; import 'package:revanced_manager/ui/widgets/search_bar.dart';
@ -15,14 +14,13 @@ class AppSelectorView extends StatefulWidget {
} }
class _AppSelectorViewState extends State<AppSelectorView> { class _AppSelectorViewState extends State<AppSelectorView> {
String query = ''; String _query = '';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<AppSelectorViewModel>.reactive( return ViewModelBuilder<AppSelectorViewModel>.reactive(
disposeViewModel: false,
onModelReady: (model) => model.initialize(), onModelReady: (model) => model.initialize(),
viewModelBuilder: () => locator<AppSelectorViewModel>(), viewModelBuilder: () => AppSelectorViewModel(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
@ -51,17 +49,17 @@ class _AppSelectorViewState extends State<AppSelectorView> {
hintTextColor: Theme.of(context).colorScheme.tertiary, hintTextColor: Theme.of(context).colorScheme.tertiary,
onQueryChanged: (searchQuery) { onQueryChanged: (searchQuery) {
setState(() { setState(() {
query = searchQuery; _query = searchQuery;
}); });
}, },
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
query.isEmpty || query.length < 2 _query.isEmpty || _query.length < 2
? _getAllResults(model) ? _getAllResults(model)
: _getFilteredResults(model) : _getFilteredResults(model)
], ],
) )
: query.isEmpty || query.length < 2 : _query.isEmpty || _query.length < 2
? Center( ? Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
@ -105,7 +103,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
model.apps.sort((a, b) => a.appName.compareTo(b.appName)); model.apps.sort((a, b) => a.appName.compareTo(b.appName));
if (model.apps[index].appName.toLowerCase().contains( if (model.apps[index].appName.toLowerCase().contains(
query.toLowerCase(), _query.toLowerCase(),
)) { )) {
return InkWell( return InkWell(
onTap: () { onTap: () {

View File

@ -8,30 +8,22 @@ import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class AppSelectorViewModel extends BaseViewModel { class AppSelectorViewModel extends BaseViewModel {
final PatcherAPI patcherAPI = locator<PatcherAPI>(); final PatcherAPI _patcherAPI = locator<PatcherAPI>();
bool isRooted = false; final List<ApplicationWithIcon> apps = [];
bool isFromStorage = false; bool _isRooted = false;
List<ApplicationWithIcon> apps = [];
PatchedApplication? selectedApp;
Future<void> initialize() async { Future<void> initialize() async {
await getApps(); apps.addAll(await _patcherAPI.getFilteredInstalledApps());
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
isRooted = prefs.getBool('isRooted') ?? false; _isRooted = prefs.getBool('isRooted') ?? false;
notifyListeners(); notifyListeners();
} }
Future<void> getApps() async {
apps = await patcherAPI.getFilteredInstalledApps();
}
void selectApp(ApplicationWithIcon application) async { void selectApp(ApplicationWithIcon application) async {
isFromStorage = false;
PatchedApplication app = PatchedApplication( PatchedApplication app = PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
@ -39,18 +31,16 @@ class AppSelectorViewModel extends BaseViewModel {
apkFilePath: application.apkFilePath, apkFilePath: application.apkFilePath,
icon: application.icon, icon: application.icon,
patchDate: DateTime.now(), patchDate: DateTime.now(),
isRooted: isRooted, isRooted: _isRooted,
isFromStorage: isFromStorage, isFromStorage: false,
appliedPatches: [], appliedPatches: [],
); );
locator<AppSelectorViewModel>().selectedApp = app; locator<PatcherViewModel>().selectedApp = app;
locator<PatchesSelectorViewModel>().selectedPatches.clear(); locator<PatcherViewModel>().selectedPatches.clear();
locator<PatchesSelectorViewModel>().notifyListeners();
locator<PatcherViewModel>().notifyListeners(); locator<PatcherViewModel>().notifyListeners();
} }
Future<void> selectAppFromStorage(BuildContext context) async { Future<void> selectAppFromStorage(BuildContext context) async {
isFromStorage = true;
try { try {
FilePickerResult? result = await FilePicker.platform.pickFiles( FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom, type: FileType.custom,
@ -69,13 +59,12 @@ class AppSelectorViewModel extends BaseViewModel {
apkFilePath: result.files.single.path!, apkFilePath: result.files.single.path!,
icon: application.icon, icon: application.icon,
patchDate: DateTime.now(), patchDate: DateTime.now(),
isRooted: isRooted, isRooted: _isRooted,
isFromStorage: isFromStorage, isFromStorage: true,
appliedPatches: [], appliedPatches: [],
); );
locator<AppSelectorViewModel>().selectedApp = app; locator<PatcherViewModel>().selectedApp = app;
locator<PatchesSelectorViewModel>().selectedPatches.clear(); locator<PatcherViewModel>().selectedPatches.clear();
locator<PatchesSelectorViewModel>().notifyListeners();
locator<PatcherViewModel>().notifyListeners(); locator<PatcherViewModel>().notifyListeners();
} }
} }

View File

@ -9,7 +9,6 @@ class ContributorsView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<ContributorsViewModel>.reactive( return ViewModelBuilder<ContributorsViewModel>.reactive(
disposeViewModel: false,
viewModelBuilder: () => ContributorsViewModel(), viewModelBuilder: () => ContributorsViewModel(),
onModelReady: (model) => model.getContributors(), onModelReady: (model) => model.getContributors(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(

View File

@ -1,27 +1,36 @@
import 'package:github/github.dart'; import 'package:github/github.dart';
import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/github_api.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class ContributorsViewModel extends BaseViewModel { class ContributorsViewModel extends BaseViewModel {
final GithubAPI githubAPI = GithubAPI(); final GithubAPI _githubAPI = GithubAPI();
List<Contributor> patchesContributors = []; List<Contributor> patchesContributors = [];
List<Contributor> integrationsContributors = []; List<Contributor> integrationsContributors = [];
List<Contributor> patcherContributors = []; List<Contributor> patcherContributors = [];
List<Contributor> cliContributors = []; List<Contributor> cliContributors = [];
List<Contributor> managerContributors = []; List<Contributor> managerContributors = [];
Future<List<Contributor>> getContributors() async { Future<void> getContributors() async {
patchesContributors = patchesContributors = await _githubAPI.getContributors(
await githubAPI.getContributors('revanced', 'revanced-patches'); ghOrg,
integrationsContributors = patchesRepo,
await githubAPI.getContributors('revanced', 'revanced-integrations'); );
patcherContributors = integrationsContributors = await _githubAPI.getContributors(
await githubAPI.getContributors('revanced', 'revanced-patcher'); ghOrg,
cliContributors = integrationsRepo,
await githubAPI.getContributors('revanced', 'revanced-cli'); );
managerContributors = patcherContributors = await _githubAPI.getContributors(
await githubAPI.getContributors('revanced', 'revanced-manager'); ghOrg,
patcherRepo,
return []; );
cliContributors = await _githubAPI.getContributors(
ghOrg,
cliRepo,
);
managerContributors = await _githubAPI.getContributors(
ghOrg,
managerRepo,
);
} }
} }

View File

@ -55,6 +55,7 @@ class HomeView extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
LatestCommitCard( LatestCommitCard(
onPressed: () => model.updateManager(context),
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
const SizedBox(height: 23), const SizedBox(height: 23),
@ -77,7 +78,7 @@ class HomeView extends StatelessWidget {
label: "homeView.updatesAvailable", label: "homeView.updatesAvailable",
isSelected: model.showUpdatableApps, isSelected: model.showUpdatableApps,
onSelected: (value) { onSelected: (value) {
model.toggleUpdatableApps(value); model.toggleUpdatableApps(true);
}, },
), ),
const SizedBox(width: 10), const SizedBox(width: 10),

View File

@ -1,26 +1,41 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:app_installer/app_installer.dart';
import 'package:device_apps/device_apps.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart'; import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@lazySingleton @lazySingleton
class HomeViewModel extends BaseViewModel { class HomeViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>(); final NavigationService _navigationService = locator<NavigationService>();
final GithubAPI githubAPI = GithubAPI(); final ManagerAPI _managerAPI = ManagerAPI();
final PatcherAPI patcherAPI = locator<PatcherAPI>(); final GithubAPI _githubAPI = GithubAPI();
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
bool showUpdatableApps = true; bool showUpdatableApps = true;
Future<void> initialize() async { Future<void> initialize() async {
await patcherAPI.loadPatches(); await _patcherAPI.loadPatches();
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('ic_notification'),
),
onSelectNotification: (p) => DeviceApps.openApp('app.revanced.manager'),
);
} }
void toggleUpdatableApps(bool value) { void toggleUpdatableApps(bool value) {
@ -28,13 +43,10 @@ class HomeViewModel extends BaseViewModel {
notifyListeners(); notifyListeners();
} }
Future downloadPatches() => locator<ManagerAPI>().downloadPatches();
Future downloadIntegrations() => locator<ManagerAPI>().downloadIntegrations();
void navigateToInstaller(PatchedApplication app) async { void navigateToInstaller(PatchedApplication app) async {
locator<AppSelectorViewModel>().selectedApp = app; locator<PatcherViewModel>().selectedApp = app;
locator<PatchesSelectorViewModel>().selectedPatches = locator<PatcherViewModel>().selectedPatches =
await patcherAPI.getAppliedPatches(app); await _patcherAPI.getAppliedPatches(app);
_navigationService.navigateTo(Routes.installerView); _navigationService.navigateTo(Routes.installerView);
} }
@ -44,15 +56,37 @@ class HomeViewModel extends BaseViewModel {
List<String> patchedApps = prefs.getStringList('patchedApps') ?? []; List<String> patchedApps = prefs.getStringList('patchedApps') ?? [];
for (String str in patchedApps) { for (String str in patchedApps) {
PatchedApplication app = PatchedApplication.fromJson(json.decode(str)); PatchedApplication app = PatchedApplication.fromJson(json.decode(str));
bool hasUpdates = await githubAPI.hasUpdates( bool hasUpdates = await _githubAPI.hasUpdates(app, ghOrg, patchesRepo);
app,
'revanced',
'revanced-patches',
);
if (hasUpdates == isUpdatable) { if (hasUpdates == isUpdatable) {
list.add(app); list.add(app);
} }
} }
return list; return list;
} }
void updateManager(BuildContext context) async {
File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) {
flutterLocalNotificationsPlugin.show(
0,
// ignore: use_build_context_synchronously
FlutterI18n.translate(
context,
'homeView.notificationTitle',
),
// ignore: use_build_context_synchronously
FlutterI18n.translate(
context,
'homeView.notificationText',
),
const NotificationDetails(
android: AndroidNotificationDetails(
'revanced_manager_channel',
'ReVanced Manager Channel',
),
),
);
AppInstaller.installApk(managerApk.path);
}
}
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart'; import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -15,9 +14,8 @@ class InstallerView extends StatelessWidget {
(_) => _controller.jumpTo(_controller.position.maxScrollExtent), (_) => _controller.jumpTo(_controller.position.maxScrollExtent),
); );
return ViewModelBuilder<InstallerViewModel>.reactive( return ViewModelBuilder<InstallerViewModel>.reactive(
disposeViewModel: false, onModelReady: (model) => model.initialize(context),
onModelReady: (model) => model.initialize(), viewModelBuilder: () => InstallerViewModel(),
viewModelBuilder: () => locator<InstallerViewModel>(),
builder: (context, model, child) => WillPopScope( builder: (context, model, child) => WillPopScope(
child: Scaffold( child: Scaffold(
floatingActionButton: Visibility( floatingActionButton: Visibility(

View File

@ -1,30 +1,43 @@
import 'dart:convert'; import 'dart:convert';
import 'package:device_apps/device_apps.dart'; 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_background/flutter_background.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class InstallerViewModel extends BaseViewModel { class InstallerViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final PatchedApplication? _app = locator<PatcherViewModel>().selectedApp;
final List<Patch> _patches = locator<PatcherViewModel>().selectedPatches;
static const _installerChannel = MethodChannel(
'app.revanced.manager/installer',
);
double? progress = 0.0; double? progress = 0.0;
String logs = ''; String logs = '';
bool isPatching = false; bool isPatching = false;
bool isInstalled = false; bool isInstalled = false;
Future<void> initialize() async { Future<void> initialize(BuildContext context) async {
try { try {
await FlutterBackground.initialize( await FlutterBackground.initialize(
androidConfig: const FlutterBackgroundAndroidConfig( androidConfig: FlutterBackgroundAndroidConfig(
notificationTitle: 'Patching', notificationTitle: FlutterI18n.translate(
notificationText: 'ReVanced Manager is patching', context,
'installerView.notificationTitle',
),
notificationText: FlutterI18n.translate(
context,
'installerView.notificationText',
),
notificationImportance: AndroidNotificationImportance.Default, notificationImportance: AndroidNotificationImportance.Default,
notificationIcon: AndroidResource( notificationIcon: const AndroidResource(
name: 'ic_notification', name: 'ic_notification',
defType: 'drawable', defType: 'drawable',
), ),
@ -32,11 +45,28 @@ class InstallerViewModel extends BaseViewModel {
); );
await FlutterBackground.enableBackgroundExecution(); await FlutterBackground.enableBackgroundExecution();
} finally { } finally {
await locator<PatcherAPI>().handlePlatformChannelMethods(); await handlePlatformChannelMethods();
await runPatcher(); await runPatcher();
} }
} }
Future<dynamic> 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) { void updateProgress(double value) {
progress = value; progress = value;
isInstalled = false; isInstalled = false;
@ -59,37 +89,32 @@ class InstallerViewModel extends BaseViewModel {
Future<void> runPatcher() async { Future<void> runPatcher() async {
updateProgress(0.0); updateProgress(0.0);
PatchedApplication? selectedApp = if (_app != null && _patches.isNotEmpty) {
locator<AppSelectorViewModel>().selectedApp; String apkFilePath = _app!.apkFilePath;
List<Patch> selectedPatches =
locator<PatchesSelectorViewModel>().selectedPatches;
if (selectedApp != null && selectedPatches.isNotEmpty) {
String apkFilePath = selectedApp.apkFilePath;
try { try {
updateLog('Initializing installer'); updateLog('Initializing installer');
if (selectedApp.isRooted && !selectedApp.isFromStorage) { if (_app!.isRooted && !_app!.isFromStorage) {
updateLog('Checking if an old patched version exists'); updateLog('Checking if an old patched version exists');
bool oldExists = bool oldExists = await _patcherAPI.checkOldPatch(_app!);
await locator<PatcherAPI>().checkOldPatch(selectedApp);
if (oldExists) { if (oldExists) {
updateLog('Deleting old patched version'); updateLog('Deleting old patched version');
await locator<PatcherAPI>().deleteOldPatch(selectedApp); await _patcherAPI.deleteOldPatch(_app!);
} }
} }
updateLog('Creating working directory'); updateLog('Creating working directory');
bool mergeIntegrations = false; bool mergeIntegrations = false;
bool resourcePatching = false; bool resourcePatching = false;
if (selectedApp.packageName == 'com.google.android.youtube') { if (_app!.packageName == 'com.google.android.youtube') {
mergeIntegrations = true; mergeIntegrations = true;
resourcePatching = true; resourcePatching = true;
} else if (selectedApp.packageName == } else if (_app!.packageName ==
'com.google.android.apps.youtube.music') { 'com.google.android.apps.youtube.music') {
resourcePatching = true; resourcePatching = true;
} }
await locator<PatcherAPI>().initPatcher(mergeIntegrations); await _patcherAPI.initPatcher(mergeIntegrations);
await locator<PatcherAPI>().runPatcher( await _patcherAPI.runPatcher(
apkFilePath, apkFilePath,
selectedPatches, _patches,
mergeIntegrations, mergeIntegrations,
resourcePatching, resourcePatching,
); );
@ -107,21 +132,16 @@ class InstallerViewModel extends BaseViewModel {
} }
void installResult() async { void installResult() async {
PatchedApplication? selectedApp = if (_app != null) {
locator<AppSelectorViewModel>().selectedApp; updateLog(_app!.isRooted
List<Patch> selectedPatches =
locator<PatchesSelectorViewModel>().selectedPatches;
if (selectedApp != null) {
updateLog(selectedApp.isRooted
? 'Installing patched file using root method' ? 'Installing patched file using root method'
: 'Installing patched file using nonroot method'); : 'Installing patched file using nonroot method');
isInstalled = await locator<PatcherAPI>().installPatchedFile(selectedApp); isInstalled = await _patcherAPI.installPatchedFile(_app!);
if (isInstalled) { if (isInstalled) {
updateLog('Done'); updateLog('Done');
selectedApp.patchDate = DateTime.now(); _app!.patchDate = DateTime.now();
selectedApp.appliedPatches _app!.appliedPatches.addAll(_patches.map((p) => p.name).toList());
.addAll(selectedPatches.map((p) => p.name).toList()); await saveApp();
await saveApp(selectedApp);
} else { } else {
updateLog('An error occurred! Aborting'); updateLog('An error occurred! Aborting');
} }
@ -129,39 +149,33 @@ class InstallerViewModel extends BaseViewModel {
} }
void shareResult() { void shareResult() {
PatchedApplication? selectedApp = if (_app != null) {
locator<AppSelectorViewModel>().selectedApp; _patcherAPI.sharePatchedFile(_app!.name, _app!.version);
if (selectedApp != null) {
locator<PatcherAPI>().sharePatchedFile(
selectedApp.name,
selectedApp.version,
);
} }
} }
Future<void> cleanWorkplace() async { Future<void> cleanWorkplace() async {
locator<PatcherAPI>().cleanPatcher(); _patcherAPI.cleanPatcher();
locator<AppSelectorViewModel>().selectedApp = null; locator<PatcherViewModel>().selectedApp = null;
locator<PatchesSelectorViewModel>().selectedPatches.clear(); locator<PatcherViewModel>().selectedPatches.clear();
locator<PatchesSelectorViewModel>().notifyListeners();
locator<PatcherViewModel>().notifyListeners(); locator<PatcherViewModel>().notifyListeners();
} }
void openApp() { void openApp() {
PatchedApplication? selectedApp = if (_app != null) {
locator<AppSelectorViewModel>().selectedApp; DeviceApps.openApp(_app!.packageName);
if (selectedApp != null) {
DeviceApps.openApp(selectedApp.packageName);
} }
} }
Future<void> saveApp(PatchedApplication selectedApp) async { Future<void> saveApp() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); if (_app != null) {
List<String> patchedApps = prefs.getStringList('patchedApps') ?? []; SharedPreferences prefs = await SharedPreferences.getInstance();
String app = json.encode(selectedApp.toJson()); List<String> patchedApps = prefs.getStringList('patchedApps') ?? [];
patchedApps.removeWhere( String appStr = json.encode(_app!.toJson());
(a) => json.decode(a)['packageName'] == selectedApp.packageName); patchedApps.removeWhere(
patchedApps.add(app); (a) => json.decode(a)['packageName'] == _app!.packageName);
prefs.setStringList('patchedApps', patchedApps); patchedApps.add(appStr);
prefs.setStringList('patchedApps', patchedApps);
}
} }
} }

View File

@ -1,12 +1,17 @@
import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart'; import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
@lazySingleton
class PatcherViewModel extends BaseViewModel { class PatcherViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>(); PatchedApplication? selectedApp;
List<Patch> selectedPatches = [];
final NavigationService _navigationService = locator<NavigationService>();
void navigateToAppSelector() { void navigateToAppSelector() {
_navigationService.navigateTo(Routes.appSelectorView); _navigationService.navigateTo(Routes.appSelectorView);
@ -21,10 +26,10 @@ class PatcherViewModel extends BaseViewModel {
} }
bool showFabButton() { bool showFabButton() {
return locator<PatchesSelectorViewModel>().selectedPatches.isNotEmpty; return selectedPatches.isNotEmpty;
} }
bool dimPatchesCard() { bool dimPatchesCard() {
return locator<AppSelectorViewModel>().selectedApp == null; return selectedApp == null;
} }
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/theme.dart'; import 'package:revanced_manager/theme.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patch_item.dart'; import 'package:revanced_manager/ui/widgets/patch_item.dart';
@ -15,16 +14,14 @@ class PatchesSelectorView extends StatefulWidget {
} }
class _PatchesSelectorViewState extends State<PatchesSelectorView> { class _PatchesSelectorViewState extends State<PatchesSelectorView> {
final List<PatchItem> patches = []; final List<PatchItem> _items = [];
String query = ''; String query = '';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<PatchesSelectorViewModel>.reactive( return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
disposeViewModel: false,
fireOnModelReadyOnce: true,
onModelReady: (model) => model.initialize(), onModelReady: (model) => model.initialize(),
viewModelBuilder: () => locator<PatchesSelectorViewModel>(), viewModelBuilder: () => PatchesSelectorViewModel(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
@ -62,7 +59,10 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
onPressed: () => Navigator.of(context).pop(), onPressed: () {
model.selectPatches();
Navigator.of(context).pop();
},
child: I18nText('patchesSelectorView.fabButton'), child: I18nText('patchesSelectorView.fabButton'),
), ),
], ],
@ -79,7 +79,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
} }
Widget _getAllResults(PatchesSelectorViewModel model) { Widget _getAllResults(PatchesSelectorViewModel model) {
patches.clear(); _items.clear();
return Expanded( return Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: model.patches.length, itemCount: model.patches.length,
@ -93,8 +93,10 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
isSelected: model.selectedPatches.any( isSelected: model.selectedPatches.any(
(element) => element.name == model.patches[index].name, (element) => element.name == model.patches[index].name,
), ),
onChanged: (value) =>
model.selectPatch(model.patches[index].name, value),
); );
patches.add(item); _items.add(item);
return item; return item;
}, },
), ),
@ -102,7 +104,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
} }
Widget _getFilteredResults(PatchesSelectorViewModel model) { Widget _getFilteredResults(PatchesSelectorViewModel model) {
patches.clear(); _items.clear();
return Expanded( return Expanded(
child: ListView.builder( child: ListView.builder(
itemCount: model.patches.length, itemCount: model.patches.length,
@ -119,8 +121,10 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
isSelected: model.selectedPatches.any( isSelected: model.selectedPatches.any(
(element) => element.name == model.patches[index].name, (element) => element.name == model.patches[index].name,
), ),
onChanged: (value) =>
model.selectPatch(model.patches[index].name, value),
); );
patches.add(item); _items.add(item);
return item; return item;
} else { } else {
return const SizedBox(); return const SizedBox();

View File

@ -1,38 +1,34 @@
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.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/services/patcher_api.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patch_item.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatchesSelectorViewModel extends BaseViewModel { class PatchesSelectorViewModel extends BaseViewModel {
final PatcherAPI patcherAPI = locator<PatcherAPI>(); final PatcherAPI _patcherAPI = locator<PatcherAPI>();
List<Patch> patches = []; final List<Patch> patches = [];
List<Patch> selectedPatches = []; final List<Patch> selectedPatches =
locator<PatcherViewModel>().selectedPatches;
Future<void> initialize() async { Future<void> initialize() async {
await getPatches(); patches.addAll(await _patcherAPI.getFilteredPatches(
locator<PatcherViewModel>().selectedApp,
));
notifyListeners(); notifyListeners();
} }
Future<void> getPatches() async { void selectPatch(String name, bool isSelected) {
PatchedApplication? app = locator<AppSelectorViewModel>().selectedApp; Patch patch = patches.firstWhere((p) => p.name == name);
patches = await patcherAPI.getFilteredPatches(app); if (isSelected && !selectedPatches.contains(patch)) {
selectedPatches.add(patch);
} else {
selectedPatches.remove(patch);
}
notifyListeners();
} }
void selectPatch(PatchItem item) { void selectPatches() {
Patch patch = locator<PatchesSelectorViewModel>() locator<PatcherViewModel>().selectedPatches = selectedPatches;
.patches
.firstWhere((p) => p.name == item.name);
if (item.isSelected &&
!locator<PatchesSelectorViewModel>().selectedPatches.contains(patch)) {
locator<PatchesSelectorViewModel>().selectedPatches.add(patch);
} else {
locator<PatchesSelectorViewModel>().selectedPatches.remove(patch);
}
locator<PatchesSelectorViewModel>().notifyListeners();
locator<PatcherViewModel>().notifyListeners(); locator<PatcherViewModel>().notifyListeners();
} }
} }

View File

@ -11,7 +11,6 @@ class RootCheckerView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ViewModelBuilder<RootCheckerViewModel>.reactive( return ViewModelBuilder<RootCheckerViewModel>.reactive(
disposeViewModel: false,
viewModelBuilder: () => RootCheckerViewModel(), viewModelBuilder: () => RootCheckerViewModel(),
builder: (context, model, child) => Scaffold( builder: (context, model, child) => Scaffold(
floatingActionButton: Column( floatingActionButton: Column(

View File

@ -6,12 +6,13 @@ import 'package:root/root.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
class RootCheckerViewModel extends BaseViewModel { class RootCheckerViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>(); final NavigationService _navigationService = locator<NavigationService>();
bool? isRooted = false; bool isRooted = false;
Future<void> checkRoot() async { Future<void> checkRoot() async {
isRooted = await Root.isRooted(); bool? res = await Root.isRooted();
if (isRooted == true) { isRooted = res != null && res == true;
if (isRooted) {
navigateToHome(); navigateToHome();
} }
notifyListeners(); notifyListeners();
@ -19,7 +20,7 @@ class RootCheckerViewModel extends BaseViewModel {
Future<void> navigateToHome() async { Future<void> navigateToHome() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
prefs.setBool('isRooted', isRooted!); prefs.setBool('isRooted', isRooted);
_navigationService.navigateTo(Routes.navigation); _navigationService.navigateTo(Routes.navigation);
notifyListeners(); notifyListeners();
} }

View File

@ -4,7 +4,7 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
class SettingsViewModel extends BaseViewModel { class SettingsViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>(); final NavigationService _navigationService = locator<NavigationService>();
void setLanguage(String language) { void setLanguage(String language) {
notifyListeners(); notifyListeners();

View File

@ -1,25 +1,21 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/constants.dart'; import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
class AppSelectorCard extends StatelessWidget { class AppSelectorCard extends StatelessWidget {
final Function()? onPressed; final Function() onPressed;
final Color? color; final Color? color;
AppSelectorCard({ const AppSelectorCard({
Key? key, Key? key,
this.onPressed, required this.onPressed,
this.color = const Color(0xff1B222B), this.color = const Color(0xff1B222B),
}) : super(key: key); }) : super(key: key);
final PatcherAPI patcherAPI = locator<PatcherAPI>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
@ -36,7 +32,7 @@ class AppSelectorCard extends StatelessWidget {
children: [ children: [
const SizedBox(height: 10), const SizedBox(height: 10),
I18nText( I18nText(
locator<AppSelectorViewModel>().selectedApp == null locator<PatcherViewModel>().selectedApp == null
? 'appSelectorCard.widgetTitle' ? 'appSelectorCard.widgetTitle'
: 'appSelectorCard.widgetTitleSelected', : 'appSelectorCard.widgetTitleSelected',
child: Text( child: Text(
@ -48,7 +44,7 @@ class AppSelectorCard extends StatelessWidget {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
locator<AppSelectorViewModel>().selectedApp == null locator<PatcherViewModel>().selectedApp == null
? I18nText( ? I18nText(
'appSelectorCard.widgetSubtitle', 'appSelectorCard.widgetSubtitle',
child: Text( child: Text(
@ -62,11 +58,9 @@ class AppSelectorCard extends StatelessWidget {
height: 16.0, height: 16.0,
child: ClipOval( child: ClipOval(
child: Image.memory( child: Image.memory(
locator<AppSelectorViewModel>().selectedApp == null locator<PatcherViewModel>().selectedApp == null
? Uint8List(0) ? Uint8List(0)
: locator<AppSelectorViewModel>() : locator<PatcherViewModel>().selectedApp!.icon,
.selectedApp!
.icon,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
@ -85,8 +79,8 @@ class AppSelectorCard extends StatelessWidget {
} }
String _getAppSelection() { String _getAppSelection() {
String name = locator<AppSelectorViewModel>().selectedApp!.name; String name = locator<PatcherViewModel>().selectedApp!.name;
String version = locator<AppSelectorViewModel>().selectedApp!.version; String version = locator<PatcherViewModel>().selectedApp!.version;
return '$name (v$version)'; return '$name (v$version)';
} }
} }

View File

@ -14,7 +14,7 @@ class ApplicationItem extends StatelessWidget {
final DateTime patchDate; final DateTime patchDate;
final String changelog; final String changelog;
final bool isUpdatableApp; final bool isUpdatableApp;
final Function()? onPressed; final Function() onPressed;
const ApplicationItem({ const ApplicationItem({
Key? key, Key? key,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
@ -10,7 +11,7 @@ class AvailableUpdatesCard extends StatelessWidget {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
final GithubAPI githubAPI = GithubAPI(); final GithubAPI _githubAPI = GithubAPI();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,10 +28,10 @@ class AvailableUpdatesCard extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
itemBuilder: (context, index) => FutureBuilder<String>( itemBuilder: (context, index) => FutureBuilder<String>(
future: githubAPI.getChangelog( future: _githubAPI.getChangelog(
snapshot.data![index], snapshot.data![index],
'revanced', ghOrg,
'revanced-patches', patchesRepo,
), ),
initialData: '', initialData: '',
builder: (context, snapshot2) => ApplicationItem( builder: (context, snapshot2) => ApplicationItem(

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart'; import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart'; import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
@ -10,7 +11,7 @@ class InstalledAppsCard extends StatelessWidget {
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
final GithubAPI githubAPI = GithubAPI(); final GithubAPI _githubAPI = GithubAPI();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,10 +28,10 @@ class InstalledAppsCard extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
itemBuilder: (context, index) => FutureBuilder<String>( itemBuilder: (context, index) => FutureBuilder<String>(
future: githubAPI.getChangelog( future: _githubAPI.getChangelog(
snapshot.data![index], snapshot.data![index],
'revanced', ghOrg,
'revanced-patches', patchesRepo,
), ),
initialData: '', initialData: '',
builder: (context, snapshot2) => ApplicationItem( builder: (context, snapshot2) => ApplicationItem(

View File

@ -6,9 +6,11 @@ import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/ui/widgets/patch_text_button.dart'; import 'package:revanced_manager/ui/widgets/patch_text_button.dart';
class LatestCommitCard extends StatefulWidget { class LatestCommitCard extends StatefulWidget {
final Function() onPressed;
final Color? color; final Color? color;
const LatestCommitCard({ const LatestCommitCard({
Key? key, Key? key,
required this.onPressed,
this.color = const Color(0xff1B222B), this.color = const Color(0xff1B222B),
}) : super(key: key); }) : super(key: key);
@ -17,7 +19,7 @@ class LatestCommitCard extends StatefulWidget {
} }
class _LatestCommitCardState extends State<LatestCommitCard> { class _LatestCommitCardState extends State<LatestCommitCard> {
final GithubAPI githubAPI = GithubAPI(); final GithubAPI _githubAPI = GithubAPI();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -45,10 +47,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
), ),
), ),
FutureBuilder<String>( FutureBuilder<String>(
future: githubAPI.latestCommitTime( future: _githubAPI.latestCommitTime(ghOrg, patcherRepo),
'revanced',
'revanced-patcher',
),
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(
snapshot.hasData && snapshot.data!.isNotEmpty snapshot.hasData && snapshot.data!.isNotEmpty
? FlutterI18n.translate( ? FlutterI18n.translate(
@ -78,10 +77,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
), ),
), ),
FutureBuilder<String>( FutureBuilder<String>(
future: githubAPI.latestCommitTime( future: _githubAPI.latestCommitTime(ghOrg, managerRepo),
'revanced',
'revanced-manager',
),
builder: (context, snapshot) => Text( builder: (context, snapshot) => Text(
snapshot.hasData && snapshot.data!.isNotEmpty snapshot.hasData && snapshot.data!.isNotEmpty
? FlutterI18n.translate( ? FlutterI18n.translate(
@ -105,7 +101,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
context, context,
'latestCommitCard.updateButton', 'latestCommitCard.updateButton',
), ),
onPressed: () => {}, onPressed: widget.onPressed,
backgroundColor: Theme.of(context).colorScheme.secondary, backgroundColor: Theme.of(context).colorScheme.secondary,
borderColor: Theme.of(context).colorScheme.secondary, borderColor: Theme.of(context).colorScheme.secondary,
), ),

View File

@ -5,10 +5,10 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/theme.dart'; import 'package:revanced_manager/theme.dart';
class MagiskButton extends StatelessWidget { class MagiskButton extends StatelessWidget {
final Function()? onPressed; final Function() onPressed;
const MagiskButton({ const MagiskButton({
Key? key, Key? key,
this.onPressed, required this.onPressed,
}) : super(key: key); }) : super(key: key);
@override @override

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class PatchItem extends StatefulWidget { class PatchItem extends StatefulWidget {
@ -10,6 +8,7 @@ class PatchItem extends StatefulWidget {
final String description; final String description;
final String version; final String version;
bool isSelected; bool isSelected;
final Function(bool) onChanged;
PatchItem({ PatchItem({
Key? key, Key? key,
@ -18,6 +17,7 @@ class PatchItem extends StatefulWidget {
required this.description, required this.description,
required this.version, required this.version,
required this.isSelected, required this.isSelected,
required this.onChanged,
}) : super(key: key); }) : super(key: key);
@override @override
@ -30,7 +30,7 @@ class _PatchItemState extends State<PatchItem> {
return InkWell( return InkWell(
onTap: () { onTap: () {
setState(() => widget.isSelected = !widget.isSelected); setState(() => widget.isSelected = !widget.isSelected);
locator<PatchesSelectorViewModel>().selectPatch(widget); widget.onChanged(widget.isSelected);
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -82,7 +82,7 @@ class _PatchItemState extends State<PatchItem> {
activeColor: Colors.blueGrey[500], activeColor: Colors.blueGrey[500],
onChanged: (newValue) { onChanged: (newValue) {
setState(() => widget.isSelected = newValue!); setState(() => widget.isSelected = newValue!);
locator<PatchesSelectorViewModel>().selectPatch(widget); widget.onChanged(widget.isSelected);
}, },
), ),
) )

View File

@ -4,16 +4,15 @@ import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/constants.dart'; import 'package:revanced_manager/constants.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
class PatchSelectorCard extends StatelessWidget { class PatchSelectorCard extends StatelessWidget {
final Function()? onPressed; final Function() onPressed;
final Color? color; final Color? color;
const PatchSelectorCard({ const PatchSelectorCard({
Key? key, Key? key,
this.onPressed, required this.onPressed,
this.color = const Color(0xff1B222B), this.color = const Color(0xff1B222B),
}) : super(key: key); }) : super(key: key);
@ -32,7 +31,7 @@ class PatchSelectorCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
I18nText( I18nText(
locator<PatchesSelectorViewModel>().selectedPatches.isEmpty locator<PatcherViewModel>().selectedPatches.isEmpty
? 'patchSelectorCard.widgetTitle' ? 'patchSelectorCard.widgetTitle'
: 'patchSelectorCard.widgetTitleSelected', : 'patchSelectorCard.widgetTitleSelected',
child: Text( child: Text(
@ -44,7 +43,7 @@ class PatchSelectorCard extends StatelessWidget {
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
locator<AppSelectorViewModel>().selectedApp == null locator<PatcherViewModel>().selectedApp == null
? I18nText( ? I18nText(
'patchSelectorCard.widgetSubtitle', 'patchSelectorCard.widgetSubtitle',
child: Text( child: Text(
@ -52,7 +51,7 @@ class PatchSelectorCard extends StatelessWidget {
style: robotoTextStyle, style: robotoTextStyle,
), ),
) )
: locator<PatchesSelectorViewModel>().selectedPatches.isEmpty : locator<PatcherViewModel>().selectedPatches.isEmpty
? I18nText( ? I18nText(
'patchSelectorCard.widgetEmptySubtitle', 'patchSelectorCard.widgetEmptySubtitle',
child: Text( child: Text(
@ -72,7 +71,7 @@ class PatchSelectorCard extends StatelessWidget {
String _getPatchesSelection() { String _getPatchesSelection() {
String text = ''; String text = '';
for (Patch p in locator<PatchesSelectorViewModel>().selectedPatches) { for (Patch p in locator<PatcherViewModel>().selectedPatches) {
text += '${p.simpleName} (v${p.version})\n'; text += '${p.simpleName} (v${p.version})\n';
} }
return text.substring(0, text.length - 1); return text.substring(0, text.length - 1);

View File

@ -5,13 +5,13 @@ import 'package:revanced_manager/theme.dart';
class PatchTextButton extends StatelessWidget { class PatchTextButton extends StatelessWidget {
final String text; final String text;
final Function()? onPressed; final Function() onPressed;
final Color borderColor; final Color borderColor;
final Color backgroundColor; final Color backgroundColor;
const PatchTextButton({ const PatchTextButton({
Key? key, Key? key,
required this.text, required this.text,
this.onPressed, required this.onPressed,
this.borderColor = const Color(0xff7792BA), this.borderColor = const Color(0xff7792BA),
this.backgroundColor = Colors.transparent, this.backgroundColor = Colors.transparent,
}) : super(key: key); }) : super(key: key);

View File

@ -4,7 +4,7 @@ homepage: https://github.com/Aunali321/revanced-manager
publish_to: 'none' publish_to: 'none'
version: 1.0.0+1 version: 0.0.1+1
environment: environment:
sdk: ">=2.17.5 <3.0.0" sdk: ">=2.17.5 <3.0.0"
@ -16,7 +16,6 @@ dependencies:
git: git:
url: https://github.com/ponces/flutter_plugin_device_apps url: https://github.com/ponces/flutter_plugin_device_apps
ref: appinfo-from-storage ref: appinfo-from-storage
dio: ^4.0.6
expandable: ^5.0.1 expandable: ^5.0.1
file_picker: ^5.0.1 file_picker: ^5.0.1
flutter: flutter:
@ -24,6 +23,7 @@ dependencies:
flutter_background: ^1.1.0 flutter_background: ^1.1.0
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0
flutter_i18n: ^0.32.4 flutter_i18n: ^0.32.4
flutter_local_notifications: ^9.8.0+1
flutter_svg: ^1.1.1+1 flutter_svg: ^1.1.1+1
fluttertoast: ^8.0.9 fluttertoast: ^8.0.9
get_it: ^7.2.0 get_it: ^7.2.0