Skip to content

Commit

Permalink
feat: Save last patched app (#1414)
Browse files Browse the repository at this point in the history
Co-authored-by: aAbed <[email protected]>
Co-authored-by: Ushie <[email protected]>
Co-authored-by: oSumAtrIX <[email protected]>
Co-authored-by: Mr. X <[email protected]>
Co-authored-by: festry0 <[email protected]>
  • Loading branch information
6 people authored Jun 29, 2024
1 parent b26760b commit 7720408
Show file tree
Hide file tree
Showing 16 changed files with 448 additions and 46 deletions.
13 changes: 12 additions & 1 deletion assets/i18n/strings.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
"refreshSuccess": "Refreshed successfully",
"widgetTitle": "Dashboard",
"updatesSubtitle": "Updates",
"patchedSubtitle": "Patched apps",
"lastPatchedAppSubtitle": "Last patched app",
"patchedSubtitle": "Installed apps",
"changeLaterSubtitle": "You can change this in the settings at a later time.",
"noUpdates": "No updates available",
"WIP": "Work in progress...",
"noSavedAppFound": "No app found",
"noInstallations": "No patched apps installed",
"installUpdate": "Continue to install the update?",
"updateSheetTitle": "Update ReVanced Manager",
Expand Down Expand Up @@ -205,6 +207,8 @@
"showUpdateDialogHint": "Show a dialog when a new update is available",
"universalPatchesLabel": "Show universal patches",
"universalPatchesHint": "Display all apps and universal patches (may slow down the app list)",
"lastPatchedAppLabel": "Save patched app",
"lastPatchedAppHint": "Save the last patch to install or export later",
"versionCompatibilityCheckLabel": "Version compatibility check",
"versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version",
"requireSuggestedAppVersionLabel": "Require suggested app version",
Expand Down Expand Up @@ -256,18 +260,25 @@
"appInfoView": {
"widgetTitle": "App info",
"openButton": "Open",
"installButton": "Install",
"uninstallButton": "Uninstall",
"unmountButton": "Unmount",
"exportButton": "Export",
"deleteButton": "Delete",
"rootDialogTitle": "Error",
"lastPatchedAppDescription": "This is a backup of the app that was last patched.",
"unmountDialogText": "Are you sure you want to unmount this app?",
"uninstallDialogText": "Are you sure you want to uninstall this app?",
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
"removeAppDialogTitle": "Delete app?",
"removeAppDialogText": "Are you sure you want to delete this backup?",
"packageNameLabel": "Package name",
"installTypeLabel": "Installation type",
"mountTypeLabel": "Mount",
"regularTypeLabel": "Regular",
"patchedDateLabel": "Patched date",
"appliedPatchesLabel": "Applied patches",
"sizeLabel": "File size",
"patchedDateHint": "${date} at ${time}",
"appliedPatchesHint": "${quantity} applied patches",
"updateNotImplemented": "This feature has not been implemented yet"
Expand Down
4 changes: 4 additions & 0 deletions lib/models/patched_application.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class PatchedApplication {
this.isRooted = false,
this.isFromStorage = false,
this.appliedPatches = const [],
this.patchedFilePath = '',
this.fileSize = 0,
});

factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
Expand All @@ -33,6 +35,8 @@ class PatchedApplication {
bool isRooted;
bool isFromStorage;
List<String> appliedPatches;
String patchedFilePath;
int fileSize;

Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);

Expand Down
124 changes: 124 additions & 0 deletions lib/services/manager_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,14 @@ class ManagerAPI {
await _prefs.setBool('requireSuggestedAppVersionEnabled', value);
}

bool isLastPatchedAppEnabled() {
return _prefs.getBool('lastPatchedAppEnabled') ?? true;
}

Future<void> enableLastPatchedAppStatus(bool value) async {
await _prefs.setBool('lastPatchedAppEnabled', value);
}

Future<void> setKeystorePassword(String password) async {
await _prefs.setString('keystorePassword', password);
}
Expand Down Expand Up @@ -334,6 +342,34 @@ class ManagerAPI {
}
}

PatchedApplication? getLastPatchedApp() {
final String? app = _prefs.getString('lastPatchedApp');
return app != null ? PatchedApplication.fromJson(jsonDecode(app)) : null;
}

Future<void> deleteLastPatchedApp() async {
final PatchedApplication? app = getLastPatchedApp();
if (app != null) {
final File file = File(app.patchedFilePath);
await file.delete();
await _prefs.remove('lastPatchedApp');
}
}

Future<void> setLastPatchedApp(
PatchedApplication app,
File outFile,
) async {
deleteLastPatchedApp();
final Directory appCache = await getApplicationSupportDirectory();
app.patchedFilePath = outFile.copySync('${appCache.path}/lastPatchedApp.apk').path;
app.fileSize = outFile.lengthSync();
await _prefs.setString(
'lastPatchedApp',
json.encode(app.toJson()),
);
}

List<PatchedApplication> getPatchedApps() {
final List<String> apps = _prefs.getStringList('patchedApps') ?? [];
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
Expand Down Expand Up @@ -692,6 +728,16 @@ class ManagerAPI {
patchedApps.addAll(mountedApps);

await setPatchedApps(patchedApps);

// Delete the saved app if the file is not found.
final PatchedApplication? lastPatchedApp = getLastPatchedApp();
if (lastPatchedApp != null) {
final File file = File(lastPatchedApp.patchedFilePath);
if (!file.existsSync()) {
deleteLastPatchedApp();
_prefs.remove('lastPatchedApp');
}
}
}

Future<bool> isAppUninstalled(PatchedApplication app) async {
Expand Down Expand Up @@ -786,4 +832,82 @@ class ManagerAPI {
selectedPatchesFile.deleteSync();
}
}

Future<bool> installTypeDialog(BuildContext context) async {
final ValueNotifier<int> installType = ValueNotifier(0);
if (isRooted) {
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text(t.installerView.installType),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
icon: const Icon(Icons.file_download_outlined),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
content: SingleChildScrollView(
child: ValueListenableBuilder(
valueListenable: installType,
builder: (context, value, child) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
child: Text(
t.installerView.installTypeDescription,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.secondary,
),
),
),
RadioListTile(
title: Text(t.installerView.installNonRootType),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
value: 0,
groupValue: value,
onChanged: (selected) {
installType.value = selected!;
},
),
RadioListTile(
title: Text(t.installerView.installRootType),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16),
value: 1,
groupValue: value,
onChanged: (selected) {
installType.value = selected!;
},
),
],
);
},
),
),
actions: [
OutlinedButton(
child: Text(t.cancelButton),
onPressed: () {
Navigator.of(context).pop();
},
),
FilledButton(
child: Text(t.installerView.installButton),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
);
}
return false;
}
}
22 changes: 11 additions & 11 deletions lib/services/patcher_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class PatcherAPI {
BuildContext context,
PatchedApplication patchedApp,
) async {
if (outFile != null) {
if (patchedApp.patchedFilePath != '') {
_managerAPI.ctx = context;
try {
if (patchedApp.isRooted) {
Expand All @@ -232,7 +232,7 @@ class PatcherAPI {
return await _rootAPI.install(
patchedApp.packageName,
patchedApp.apkFilePath,
outFile!.path,
patchedApp.patchedFilePath,
)
? 0
: 1;
Expand All @@ -246,7 +246,7 @@ class PatcherAPI {
if (context.mounted) {
return await installApk(
context,
outFile!.path,
patchedApp.patchedFilePath,
);
}
}
Expand Down Expand Up @@ -368,13 +368,13 @@ class PatcherAPI {
return cleanInstall ? 10 : 1;
}

void exportPatchedFile(String appName, String version) {
void exportPatchedFile(PatchedApplication app) {
try {
if (outFile != null) {
final String newName = _getFileName(appName, version);
final String newName = _getFileName(app.name, app.version);
FlutterFileDialog.saveFile(
params: SaveFileDialogParams(
sourceFilePath: outFile!.path,
sourceFilePath: app.patchedFilePath,
fileName: newName,
mimeTypesFilter: ['application/vnd.android.package-archive'],
),
Expand All @@ -387,14 +387,14 @@ class PatcherAPI {
}
}

void sharePatchedFile(String appName, String version) {
void sharePatchedFile(PatchedApplication app) {
try {
if (outFile != null) {
final String newName = _getFileName(appName, version);
final int lastSeparator = outFile!.path.lastIndexOf('/');
final String newName = _getFileName(app.name, app.version);
final int lastSeparator = app.patchedFilePath.lastIndexOf('/');
final String newPath =
outFile!.path.substring(0, lastSeparator + 1) + newName;
final File shareFile = outFile!.copySync(newPath);
app.patchedFilePath.substring(0, lastSeparator + 1) + newName;
final File shareFile = File(app.patchedFilePath).copySync(newPath);
Share.shareXFiles([XFile(shareFile.path)]);
}
} on Exception catch (e) {
Expand Down
17 changes: 17 additions & 0 deletions lib/ui/views/home/home_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:revanced_manager/gen/strings.g.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/homeView/installed_apps_card.dart';
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
import 'package:revanced_manager/ui/widgets/homeView/last_patched_app_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:stacked/stacked.dart';

Expand Down Expand Up @@ -44,6 +45,22 @@ class HomeView extends StatelessWidget {
const SizedBox(height: 10),
LatestCommitCard(model: model, parentContext: context),
const SizedBox(height: 23),
Visibility(
visible: model.isLastPatchedAppEnabled(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t.homeView.lastPatchedAppSubtitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 10),
LastPatchedAppCard(),
const SizedBox(height: 10),

],
),
),
Text(
t.homeView.patchedSubtitle,
style: Theme.of(context).textTheme.titleLarge,
Expand Down
10 changes: 8 additions & 2 deletions lib/ui/views/home/home_viewmodel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class HomeViewModel extends BaseViewModel {
final Toast _toast = locator<Toast>();
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
bool showUpdatableApps = false;
PatchedApplication? lastPatchedApp;
bool releaseBuild = false;
List<PatchedApplication> patchedInstalledApps = [];
String _currentManagerVersion = '';
Expand Down Expand Up @@ -102,10 +103,10 @@ class HomeViewModel extends BaseViewModel {
}
}

void navigateToAppInfo(PatchedApplication app) {
void navigateToAppInfo(PatchedApplication app, bool isLastPatchedApp) {
_navigationService.navigateTo(
Routes.appInfoView,
arguments: AppInfoViewArguments(app: app),
arguments: AppInfoViewArguments(app: app, isLastPatchedApp: isLastPatchedApp),
);
}

Expand All @@ -123,10 +124,15 @@ class HomeViewModel extends BaseViewModel {
}

void getPatchedApps() {
lastPatchedApp = _managerAPI.getLastPatchedApp();
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
notifyListeners();
}

bool isLastPatchedAppEnabled() {
return _managerAPI.isLastPatchedAppEnabled();
}

Future<bool> hasManagerUpdates() async {
if (!_managerAPI.releaseBuild) {
return false;
Expand Down
Loading

0 comments on commit 7720408

Please sign in to comment.