Limit patch history to 1 app
This commit is contained in:
parent
f375fa9b35
commit
8ac25bfb40
|
@ -26,14 +26,14 @@
|
|||
"widgetTitle": "Dashboard",
|
||||
|
||||
"updatesSubtitle": "Updates",
|
||||
"patchHistorySubtitle": "Patch History",
|
||||
"patchHistorySubtitle": "Last Patched App",
|
||||
"patchedSubtitle": "Installed apps",
|
||||
|
||||
"noUpdates": "No updates available",
|
||||
|
||||
"WIP": "Work in progress...",
|
||||
|
||||
"noHistory": "No patch history available",
|
||||
"noHistory": "No app found",
|
||||
"noInstallations": "No patched apps installed",
|
||||
"installUpdate": "Continue to install the update?",
|
||||
|
||||
|
@ -307,17 +307,22 @@
|
|||
"uninstallButton": "Uninstall",
|
||||
"unpatchButton": "Unpatch",
|
||||
"exportButton": "Export",
|
||||
"deleteButton": "Delete",
|
||||
"rootDialogTitle": "Error",
|
||||
|
||||
"unpatchDialogText": "Are you sure you want to unpatch this app?",
|
||||
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
|
||||
|
||||
"removeAppDialogTitle": "Remove app?",
|
||||
"removeAppDialogText": "Are you sure you want to remove this app?",
|
||||
|
||||
"packageNameLabel": "Package name",
|
||||
"installTypeLabel": "Installation type",
|
||||
"rootTypeLabel": "Root",
|
||||
"nonRootTypeLabel": "Non-root",
|
||||
"patchedDateLabel": "Patched date",
|
||||
"appliedPatchesLabel": "Applied patches",
|
||||
"sizeLabel": "File size",
|
||||
|
||||
"patchedDateHint": "{date} at {time}",
|
||||
"appliedPatchesHint": "{quantity} applied patches",
|
||||
|
|
|
@ -16,7 +16,8 @@ class PatchedApplication {
|
|||
this.isRooted = false,
|
||||
this.isFromStorage = false,
|
||||
this.appliedPatches = const [],
|
||||
this.outFilePath = '',
|
||||
this.patchedFilePath = '',
|
||||
this.fileSize = 0,
|
||||
});
|
||||
|
||||
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
||||
|
@ -34,7 +35,8 @@ class PatchedApplication {
|
|||
bool isRooted;
|
||||
bool isFromStorage;
|
||||
List<String> appliedPatches;
|
||||
String outFilePath;
|
||||
String patchedFilePath;
|
||||
int fileSize;
|
||||
|
||||
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
||||
|
||||
|
|
|
@ -297,21 +297,23 @@ class ManagerAPI {
|
|||
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 {
|
||||
final Directory appCache = await getTemporaryDirectory();
|
||||
app.outFilePath = outFile.copySync('${appCache.path}/${app.packageName}.apk').path;
|
||||
final ApplicationWithIcon? installed = await DeviceApps.getApp(
|
||||
app.packageName,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
if (installed != null) {
|
||||
app.name = installed.appName;
|
||||
app.version = installed.versionName!;
|
||||
app.icon = installed.icon;
|
||||
}
|
||||
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()),
|
||||
|
@ -676,6 +678,16 @@ class ManagerAPI {
|
|||
patchedApps.addAll(mountedApps);
|
||||
|
||||
await setPatchedApps(patchedApps);
|
||||
|
||||
// Removed saved app history 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 {
|
||||
|
|
|
@ -207,7 +207,7 @@ Future<void> stopPatcher() async {
|
|||
}
|
||||
|
||||
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
||||
if (patchedApp.outFilePath != null) {
|
||||
if (patchedApp.patchedFilePath != '') {
|
||||
try {
|
||||
if (patchedApp.isRooted) {
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
|
@ -215,11 +215,11 @@ Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
|||
return _rootAPI.installApp(
|
||||
patchedApp.packageName,
|
||||
patchedApp.apkFilePath,
|
||||
patchedApp.outFilePath!,
|
||||
patchedApp.patchedFilePath,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final install = await InstallPlugin.installApk(patchedApp.outFilePath!);
|
||||
final install = await InstallPlugin.installApk(patchedApp.patchedFilePath);
|
||||
return install['isSuccess'];
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
|
@ -234,11 +234,11 @@ Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
|||
|
||||
void exportPatchedFile(PatchedApplication app) {
|
||||
try {
|
||||
if (app.outFilePath != '') {
|
||||
if (app.patchedFilePath != '') {
|
||||
final String newName = _getFileName(app.name, app.version);
|
||||
FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile!.path,
|
||||
sourceFilePath: app.patchedFilePath,
|
||||
fileName: newName,
|
||||
),
|
||||
);
|
||||
|
@ -252,12 +252,12 @@ void exportPatchedFile(PatchedApplication app) {
|
|||
|
||||
void sharePatchedFile(PatchedApplication app) {
|
||||
try {
|
||||
if (app.outFilePath != '') {
|
||||
if (app.patchedFilePath != '') {
|
||||
final String newName = _getFileName(app.name, app.version);
|
||||
final int lastSeparator = app.outFilePath.lastIndexOf('/');
|
||||
final int lastSeparator = app.patchedFilePath.lastIndexOf('/');
|
||||
final String newPath =
|
||||
app.outFilePath.substring(0, lastSeparator + 1) + newName;
|
||||
final File shareFile = File(app.outFilePath).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) {
|
||||
|
|
|
@ -86,10 +86,10 @@ class HomeViewModel extends BaseViewModel {
|
|||
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
||||
}
|
||||
|
||||
void navigateToAppInfo(PatchedApplication app, bool isInstalled) {
|
||||
void navigateToAppInfo(PatchedApplication app, bool isHistory) {
|
||||
_navigationService.navigateTo(
|
||||
Routes.appInfoView,
|
||||
arguments: AppInfoViewArguments(app: app, isInstalled: isInstalled),
|
||||
arguments: AppInfoViewArguments(app: app, isHistory: isHistory),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@ class AppInfoView extends StatelessWidget {
|
|||
const AppInfoView({
|
||||
super.key,
|
||||
required this.app,
|
||||
required this.isHistory,
|
||||
});
|
||||
final PatchedApplication app;
|
||||
final bool isInstalled;
|
||||
final bool isHistory;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -75,26 +76,26 @@ class AppInfoView extends StatelessWidget {
|
|||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
onTap: () => isInstalled
|
||||
? model.openApp(app)
|
||||
: model.installApp(context, app),
|
||||
onTap: () => isHistory
|
||||
? model.installApp(context, app)
|
||||
: model.openApp(app),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
isInstalled
|
||||
? Icons.open_in_new_outlined
|
||||
: Icons.download_outlined,
|
||||
isHistory
|
||||
? Icons.download_outlined
|
||||
: Icons.open_in_new_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
I18nText(
|
||||
isInstalled
|
||||
? 'appInfoView.openButton'
|
||||
: 'appInfoView.installButton',
|
||||
isHistory
|
||||
? 'appInfoView.installButton'
|
||||
: 'appInfoView.openButton',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
|
@ -121,30 +122,30 @@ class AppInfoView extends StatelessWidget {
|
|||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
onTap: () => isInstalled
|
||||
? model.showUninstallDialog(
|
||||
onTap: () => isHistory
|
||||
? model.exportApp(app)
|
||||
: model.showUninstallDialog(
|
||||
context,
|
||||
app,
|
||||
false,
|
||||
)
|
||||
: model.exportApp(app),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
isInstalled
|
||||
? Icons.delete_outline
|
||||
: Icons.save,
|
||||
isHistory
|
||||
? Icons.save
|
||||
: Icons.delete_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
I18nText(
|
||||
isInstalled
|
||||
? 'appInfoView.uninstallButton'
|
||||
: 'appInfoView.exportButton',
|
||||
isHistory
|
||||
? 'appInfoView.exportButton'
|
||||
: 'appInfoView.uninstallButton',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
|
@ -166,14 +167,60 @@ class AppInfoView extends StatelessWidget {
|
|||
endIndent: 12.0,
|
||||
width: 1.0,
|
||||
),
|
||||
if (app.isRooted)
|
||||
if (isHistory)
|
||||
VerticalDivider(
|
||||
color: Theme.of(context).canvasColor,
|
||||
indent: 12.0,
|
||||
endIndent: 12.0,
|
||||
width: 1.0,
|
||||
),
|
||||
if (app.isRooted)
|
||||
if (isHistory)
|
||||
Expanded(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
onTap: () => model.showDeleteDialog(
|
||||
context,
|
||||
app,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons
|
||||
.delete_forever_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
I18nText(
|
||||
'appInfoView.deleteButton',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isHistory && app.isRooted)
|
||||
VerticalDivider(
|
||||
color: Theme.of(context).canvasColor,
|
||||
indent: 12.0,
|
||||
endIndent: 12.0,
|
||||
width: 1.0,
|
||||
),
|
||||
if (!isHistory && app.isRooted)
|
||||
Expanded(
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
|
@ -239,7 +286,9 @@ class AppInfoView extends StatelessWidget {
|
|||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'appInfoView.installTypeLabel',
|
||||
isHistory
|
||||
? 'appInfoView.sizeLabel'
|
||||
: 'appInfoView.versionLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
|
@ -248,9 +297,11 @@ class AppInfoView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
subtitle: app.isRooted
|
||||
? I18nText('appInfoView.rootTypeLabel')
|
||||
: I18nText('appInfoView.nonRootTypeLabel'),
|
||||
subtitle: Text(
|
||||
isHistory
|
||||
? model.getFileSizeString(app.fileSize)
|
||||
: app.version,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
ListTile(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
import 'dart:math';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
|
@ -130,6 +131,35 @@ class AppInfoViewModel extends BaseViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> showDeleteDialog(
|
||||
BuildContext context,
|
||||
PatchedApplication app,
|
||||
) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('appInfoView.removeAppDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('appInfoView.removeAppDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('cancelButton'),
|
||||
isFilled: false,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () => {
|
||||
_managerAPI.deleteLastPatchedApp(),
|
||||
Navigator.of(context)..pop()..pop(),
|
||||
locator<HomeViewModel>().initialize(context),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String getPrettyDate(BuildContext context, DateTime dateTime) {
|
||||
return DateFormat.yMMMMd(Localizations.localeOf(context).languageCode)
|
||||
.format(dateTime);
|
||||
|
@ -140,6 +170,12 @@ class AppInfoViewModel extends BaseViewModel {
|
|||
.format(dateTime);
|
||||
}
|
||||
|
||||
String getFileSizeString(int bytes) {
|
||||
const suffixes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
final i = (log(bytes) / log(1024)).floor();
|
||||
return '${(bytes / pow(1024, i)).toStringAsFixed(2)} ${suffixes[i]}';
|
||||
}
|
||||
|
||||
Future<void> showAppliedPatchesDialog(
|
||||
BuildContext context,
|
||||
PatchedApplication app,
|
||||
|
|
|
@ -80,7 +80,7 @@ class InstalledAppsCard extends StatelessWidget {
|
|||
name: app.name,
|
||||
patchDate: app.patchDate,
|
||||
onPressed: () =>
|
||||
locator<HomeViewModel>().navigateToAppInfo(app, true),
|
||||
locator<HomeViewModel>().navigateToAppInfo(app, false),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
|
@ -10,8 +10,7 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||
|
||||
//ignore: must_be_immutable
|
||||
class PatchHistoryCard extends StatelessWidget {
|
||||
PatchHistoryCard({Key? key}) : super(key: key);
|
||||
|
||||
PatchHistoryCard({super.key});
|
||||
PatchedApplication? app = locator<HomeViewModel>().lastPatchedApp;
|
||||
|
||||
@override
|
||||
|
@ -50,7 +49,7 @@ class PatchHistoryCard extends StatelessWidget {
|
|||
name: app!.name,
|
||||
patchDate: app!.patchDate,
|
||||
onPressed: () =>
|
||||
locator<HomeViewModel>().navigateToAppInfo(app!, false),
|
||||
locator<HomeViewModel>().navigateToAppInfo(app!, true),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue