mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 01:01:56 +01:00
fix: root installation and foreground task and improve installer a bit
This commit is contained in:
parent
8fd942a808
commit
5c71930ec1
10 changed files with 231 additions and 207 deletions
|
@ -4,10 +4,11 @@
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<application
|
||||
android:label="ReVanced Manager"
|
||||
android:name="${applicationName}"
|
||||
|
@ -34,7 +35,6 @@
|
|||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<service android:name="com.pravera.flutter_foreground_task.service.ForegroundService" />
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileProvider"
|
||||
|
|
|
@ -38,9 +38,8 @@
|
|||
},
|
||||
"patchSelectorCard": {
|
||||
"widgetTitle": "Select patches",
|
||||
"widgetFirstSubtitle": "Select an application first.",
|
||||
"widgetSecondSubtitle": "No patches selected.",
|
||||
"widgetThirdSubtitle": "{selected} patch(es) selected."
|
||||
"widgetSubtitle": "Select an application first.",
|
||||
"widgetEmptySubtitle": "No patches selected."
|
||||
},
|
||||
"appSelectorView": {
|
||||
"searchBarHint": "Search applications",
|
||||
|
@ -53,8 +52,8 @@
|
|||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Installer",
|
||||
"installButton": "Install",
|
||||
"shareButton": "Share"
|
||||
"fabInstallButton": "Install",
|
||||
"fabOpenButton": "Open"
|
||||
},
|
||||
"settingsView": {
|
||||
"widgetTitle": "Settings",
|
||||
|
|
|
@ -40,13 +40,12 @@ class GithubAPI {
|
|||
|
||||
Future<List<Contributor>> getContributors(String org, repoName) async {
|
||||
try {
|
||||
var contributors = await github.repositories.listContributors(
|
||||
var contributors = github.repositories.listContributors(
|
||||
RepositorySlug(org, repoName),
|
||||
);
|
||||
return contributors.toList();
|
||||
} on Exception {
|
||||
print(Exception);
|
||||
return [];
|
||||
return List.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,11 +32,8 @@ class PatcherAPI {
|
|||
|
||||
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';
|
||||
if (call.method == 'updateInstallerLog' && call.arguments != null) {
|
||||
locator<InstallerViewModel>().addLog(call.arguments);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -298,4 +295,17 @@ class PatcherAPI {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkOldPatch(PatchedApplication patchedApp) async {
|
||||
if (patchedApp.isRooted) {
|
||||
return await rootAPI.checkApp(patchedApp.packageName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> deleteOldPatch(PatchedApplication patchedApp) async {
|
||||
if (patchedApp.isRooted) {
|
||||
await rootAPI.deleteApp(patchedApp.packageName, patchedApp.apkFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:root/root.dart';
|
||||
|
||||
|
@ -9,17 +7,35 @@ class RootAPI {
|
|||
final String postFsDataDirPath = "/data/adb/post-fs-data.d";
|
||||
final String serviceDDirPath = "/data/adb/service.d";
|
||||
|
||||
bool deleteApp(String packageName) {
|
||||
Future<bool> checkApp(String packageName) async {
|
||||
try {
|
||||
File('$managerDirPath/$packageName.apk').deleteSync();
|
||||
File('$serviceDDirPath/$packageName.sh').deleteSync();
|
||||
File('$postFsDataDirPath/$packageName.sh').deleteSync();
|
||||
return true;
|
||||
String? res = await Root.exec(
|
||||
cmd: 'ls -la "$managerDirPath/$packageName"',
|
||||
);
|
||||
return res != null && res.isNotEmpty;
|
||||
} on Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteApp(String packageName, String originalFilePath) async {
|
||||
await Root.exec(
|
||||
cmd: 'am force-stop "$packageName"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$managerDirPath/$packageName"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$serviceDDirPath/$packageName.sh"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'rm -rf "$postFsDataDirPath/$packageName.sh"',
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> installApp(
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
|
@ -27,66 +43,76 @@ class RootAPI {
|
|||
) async {
|
||||
try {
|
||||
await Root.exec(
|
||||
cmd: 'mkdir "$managerDirPath"',
|
||||
);
|
||||
String newPatchedFilePath = '$managerDirPath/$packageName.apk';
|
||||
installServiceDScript(
|
||||
packageName,
|
||||
originalFilePath,
|
||||
newPatchedFilePath,
|
||||
);
|
||||
installPostFsDataScript(
|
||||
packageName,
|
||||
originalFilePath,
|
||||
newPatchedFilePath,
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'cp $patchedFilePath $newPatchedFilePath',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chmod 644 "$newPatchedFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chown system:system "$newPatchedFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chcon u:object_r:apk_data_file:s0 "$newPatchedFilePath"',
|
||||
cmd: 'mkdir -p "$managerDirPath/$packageName"',
|
||||
);
|
||||
installServiceDScript(packageName);
|
||||
installPostFsDataScript(packageName);
|
||||
installApk(packageName, patchedFilePath);
|
||||
mountApk(packageName, originalFilePath, patchedFilePath);
|
||||
return true;
|
||||
} on Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> installServiceDScript(
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
String patchedFilePath,
|
||||
) async {
|
||||
Future<void> installServiceDScript(String packageName) async {
|
||||
String content = '#!/system/bin/sh\n'
|
||||
'while [ "\$(getprop sys.boot_completed | tr -d \'\r\')" != "1" ]; do sleep 1; done\n'
|
||||
'sleep 1\n'
|
||||
'chcon u:object_r:apk_data_file:s0 $patchedFilePath\n'
|
||||
'mount -o bind $patchedFilePath $originalFilePath';
|
||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 1; done\n'
|
||||
'base_path=$managerDirPath/$packageName/base.apk\n'
|
||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||
'[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path';
|
||||
String scriptFilePath = '$serviceDDirPath/$packageName.sh';
|
||||
await Root.exec(
|
||||
cmd: 'echo "$content" > "$scriptFilePath"',
|
||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chmod 744 "$scriptFilePath"',
|
||||
);
|
||||
await Root.exec(cmd: 'chmod 744 "$scriptFilePath"');
|
||||
}
|
||||
|
||||
Future<void> installPostFsDataScript(
|
||||
Future<void> installPostFsDataScript(String packageName) async {
|
||||
String content = '#!/system/bin/sh\n'
|
||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||
'[ ! -z \$stock_path ] && umount -l \$stock_path';
|
||||
String scriptFilePath = '$postFsDataDirPath/$packageName.sh';
|
||||
await Root.exec(
|
||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chmod 744 "$scriptFilePath"',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> installApk(String packageName, String patchedFilePath) async {
|
||||
String newPatchedFilePath = '$managerDirPath/$packageName/base.apk';
|
||||
await Root.exec(
|
||||
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chmod 644 "$newPatchedFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chown system:system "$newPatchedFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'chcon u:object_r:apk_data_file:s0 "$newPatchedFilePath"',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> mountApk(
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
String patchedFilePath,
|
||||
) async {
|
||||
String content = '#!/system/bin/sh\n'
|
||||
'while read line; do echo \$line | grep $originalFilePath | '
|
||||
'awk \'{print \$2}\' | xargs umount -l; done< /proc/mounts';
|
||||
String scriptFilePath = '$postFsDataDirPath/$packageName.sh';
|
||||
String newPatchedFilePath = '$managerDirPath/$packageName/base.apk';
|
||||
await Root.exec(
|
||||
cmd: 'echo "$content" > "$scriptFilePath"',
|
||||
cmd: 'am force-stop "$packageName"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
||||
);
|
||||
await Root.exec(
|
||||
cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"',
|
||||
);
|
||||
await Root.exec(cmd: 'chmod 744 $scriptFilePath');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
||||
|
@ -18,130 +17,89 @@ class InstallerView extends StatelessWidget {
|
|||
disposeViewModel: false,
|
||||
onModelReady: (model) => model.initialize(),
|
||||
viewModelBuilder: () => locator<InstallerViewModel>(),
|
||||
builder: (context, model, child) => WillStartForegroundTask(
|
||||
onWillStart: () async => model.isPatching,
|
||||
androidNotificationOptions: AndroidNotificationOptions(
|
||||
channelId: 'revanced-patcher-patching',
|
||||
channelName: 'Patching',
|
||||
channelDescription: 'This notification appears when the patching '
|
||||
'foreground service is running.',
|
||||
channelImportance: NotificationChannelImportance.LOW,
|
||||
priority: NotificationPriority.LOW,
|
||||
),
|
||||
notificationTitle: 'Patching',
|
||||
notificationText: 'ReVanced Manager is patching',
|
||||
callback: () => {},
|
||||
child: WillPopScope(
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
controller: _controller,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: constraints.maxWidth,
|
||||
minHeight: constraints.maxHeight,
|
||||
builder: (context, model, child) => WillPopScope(
|
||||
child: Scaffold(
|
||||
floatingActionButton: Visibility(
|
||||
visible: model.showButtons,
|
||||
child: FloatingActionButton.extended(
|
||||
onPressed: () =>
|
||||
model.isInstalled ? model.openApp() : model.installResult(),
|
||||
label: I18nText(model.isInstalled
|
||||
? 'installerView.fabOpenButton'
|
||||
: 'installerView.fabInstallButton'),
|
||||
icon: model.isInstalled
|
||||
? const Icon(Icons.open_in_new)
|
||||
: const Icon(Icons.install_mobile),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
controller: _controller,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
I18nText(
|
||||
'installerView.widgetTitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: model.showButtons,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.share),
|
||||
onPressed: () => model.shareResult(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
I18nText(
|
||||
'installerView.widgetTitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
horizontal: 4.0,
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: Colors.white,
|
||||
value: model.progress,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SelectableText(
|
||||
model.logs,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 15,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Visibility(
|
||||
visible: model.showButtons,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MaterialButton(
|
||||
textColor: Colors.white,
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 8,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
onPressed: () => model.installResult(),
|
||||
child: I18nText(
|
||||
'installerView.installButton',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MaterialButton(
|
||||
textColor: Colors.white,
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 8,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
onPressed: () => model.shareResult(),
|
||||
child: I18nText(
|
||||
'installerView.shareButton',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: LinearProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: Colors.white,
|
||||
value: model.progress,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SelectableText(
|
||||
model.logs,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 15,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onWillPop: () async {
|
||||
if (!model.isPatching) {
|
||||
model.cleanWorkplace();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
),
|
||||
onWillPop: () async {
|
||||
if (!model.isPatching) {
|
||||
model.cleanWorkplace();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter_background/flutter_background.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
|
@ -11,9 +13,22 @@ class InstallerViewModel extends BaseViewModel {
|
|||
double? progress = 0.2;
|
||||
String logs = '';
|
||||
bool isPatching = false;
|
||||
bool isInstalled = false;
|
||||
bool showButtons = false;
|
||||
|
||||
Future<void> initialize() async {
|
||||
await FlutterBackground.initialize(
|
||||
androidConfig: const FlutterBackgroundAndroidConfig(
|
||||
notificationTitle: 'Patching',
|
||||
notificationText: 'ReVanced Manager is patching',
|
||||
notificationImportance: AndroidNotificationImportance.Default,
|
||||
notificationIcon: AndroidResource(
|
||||
name: 'ic_launcher_foreground',
|
||||
defType: 'drawable',
|
||||
),
|
||||
),
|
||||
);
|
||||
await FlutterBackground.enableBackgroundExecution();
|
||||
await locator<PatcherAPI>().handlePlatformChannelMethods();
|
||||
runPatcher();
|
||||
}
|
||||
|
@ -28,6 +43,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||
|
||||
void updateProgress(double value) {
|
||||
progress = value;
|
||||
isInstalled = false;
|
||||
isPatching = progress == 1.0 ? false : true;
|
||||
showButtons = progress == 1.0 ? true : false;
|
||||
if (progress == 0.0) {
|
||||
|
@ -46,6 +62,18 @@ class InstallerViewModel extends BaseViewModel {
|
|||
locator<PatchesSelectorViewModel>().selectedPatches;
|
||||
if (selectedPatches.isNotEmpty) {
|
||||
addLog('Initializing installer...');
|
||||
if (selectedApp.isRooted) {
|
||||
addLog('Checking if an old patched version exists...');
|
||||
bool oldExists =
|
||||
await locator<PatcherAPI>().checkOldPatch(selectedApp);
|
||||
addLog('Done');
|
||||
if (oldExists) {
|
||||
addLog('Deleting old patched version...');
|
||||
await locator<PatcherAPI>().deleteOldPatch(selectedApp);
|
||||
addLog('Done');
|
||||
}
|
||||
}
|
||||
addLog('Creating working directory...');
|
||||
bool? isSuccess = await locator<PatcherAPI>().initPatcher();
|
||||
if (isSuccess != null && isSuccess) {
|
||||
addLog('Done');
|
||||
|
@ -108,6 +136,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||
} else {
|
||||
addLog('No app selected! Aborting...');
|
||||
}
|
||||
await FlutterBackground.disableBackgroundExecution();
|
||||
isPatching = false;
|
||||
}
|
||||
|
||||
|
@ -118,9 +147,8 @@ class InstallerViewModel extends BaseViewModel {
|
|||
addLog(selectedApp.isRooted
|
||||
? 'Installing patched file using root method...'
|
||||
: 'Installing patched file using nonroot method...');
|
||||
bool isSucess =
|
||||
await locator<PatcherAPI>().installPatchedFile(selectedApp);
|
||||
if (isSucess) {
|
||||
isInstalled = await locator<PatcherAPI>().installPatchedFile(selectedApp);
|
||||
if (isInstalled) {
|
||||
addLog('Done');
|
||||
} else {
|
||||
addLog('An error occurred! Aborting...');
|
||||
|
@ -139,10 +167,18 @@ class InstallerViewModel extends BaseViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
void cleanWorkplace() {
|
||||
Future<void> cleanWorkplace() async {
|
||||
locator<PatcherAPI>().cleanPatcher();
|
||||
locator<AppSelectorViewModel>().selectedApp = null;
|
||||
locator<PatchesSelectorViewModel>().selectedPatches.clear();
|
||||
locator<PatcherViewModel>().notifyListeners();
|
||||
}
|
||||
|
||||
void openApp() {
|
||||
PatchedApplication? selectedApp =
|
||||
locator<AppSelectorViewModel>().selectedApp;
|
||||
if (selectedApp != null) {
|
||||
DeviceApps.openApp(selectedApp.packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,13 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||
void selectPatches(List<PatchItem> patchItems) {
|
||||
selectedPatches.clear();
|
||||
if (patches != null) {
|
||||
for (PatchItem patch in patchItems) {
|
||||
if (patch.isSelected) {
|
||||
selectedPatches.add(
|
||||
patches!.firstWhere((element) => element.name == patch.name),
|
||||
);
|
||||
for (PatchItem item in patchItems) {
|
||||
if (item.isSelected) {
|
||||
Patch patch =
|
||||
patches!.firstWhere((element) => element.name == item.name);
|
||||
if (!selectedPatches.contains(patch)) {
|
||||
selectedPatches.add(patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class PatchSelectorCard extends StatelessWidget {
|
|||
const SizedBox(height: 10),
|
||||
locator<AppSelectorViewModel>().selectedApp == null
|
||||
? I18nText(
|
||||
'patchSelectorCard.widgetFirstSubtitle',
|
||||
'patchSelectorCard.widgetSubtitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: robotoTextStyle,
|
||||
|
@ -51,24 +51,18 @@ class PatchSelectorCard extends StatelessWidget {
|
|||
)
|
||||
: locator<PatchesSelectorViewModel>().selectedPatches.isEmpty
|
||||
? I18nText(
|
||||
'patchSelectorCard.widgetSecondSubtitle',
|
||||
'patchSelectorCard.widgetEmptySubtitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: robotoTextStyle,
|
||||
),
|
||||
)
|
||||
: I18nText(
|
||||
'patchSelectorCard.widgetThirdSubtitle',
|
||||
translationParams: {
|
||||
'selected': locator<PatchesSelectorViewModel>()
|
||||
.selectedPatches
|
||||
.length
|
||||
.toString()
|
||||
},
|
||||
child: Text(
|
||||
'',
|
||||
style: robotoTextStyle,
|
||||
),
|
||||
: Text(
|
||||
locator<PatchesSelectorViewModel>()
|
||||
.selectedPatches
|
||||
.map((e) => e.simpleName)
|
||||
.toList()
|
||||
.join('\n'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -17,8 +17,8 @@ dependencies:
|
|||
file_picker: ^5.0.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_background: ^1.1.0
|
||||
flutter_cache_manager: ^3.3.0
|
||||
flutter_foreground_task: ^3.8.1
|
||||
flutter_i18n: ^0.32.4
|
||||
flutter_svg: ^1.1.1+1
|
||||
fluttertoast: ^8.0.9
|
||||
|
|
Loading…
Reference in a new issue