feat: add App Info View

This commit is contained in:
Alberto Ponces 2022-09-05 13:43:13 +01:00
parent 5e8e090e34
commit 2944a2b788
13 changed files with 433 additions and 25 deletions

View File

@ -1,5 +1,8 @@
{
"okButton": "OK",
"cancelButton": "Cancel",
"enabledLabel": "Enabled",
"disabledLabel": "Disabled",
"main": {
"dashboardTab": "Dashboard",
"patcherTab": "Patcher",
@ -22,7 +25,7 @@
},
"applicationItem": {
"patchButton": "Patch",
"openButton": "Open",
"infoButton": "Info",
"changelogLabel": "Changelog"
},
"latestCommitCard": {
@ -107,5 +110,20 @@
"grantPermission": "Grant Root Permission",
"grantedPermission": "Magisk permission granted: {isRooted}",
"nonRootButton": "Nonroot"
},
"appInfoView": {
"widgetTitle": "App Info",
"openButton": "Open",
"uninstallButton": "Uninstall",
"patchButton": "Patch",
"alertDialogTitle": "Uninstall",
"alertDialogText": "Are you sure you want to uninstall this app?",
"errorDialogText": "App was installed with root mode enabled but currently root mode is disabled.\nPlease enable root mode first.",
"packageNameLabel": "Package Name",
"rootModeLabel": "Root Mode",
"patchedDateLabel": "Patched Date",
"patchedDateHint": "{date} at {time}",
"appliedPatchesLabel": "Applied Patches",
"appliedPatchesHint": "{quantity} applied patches"
}
}

View File

@ -17,7 +17,6 @@ class PatchedApplication {
Uint8List icon;
DateTime patchDate;
final bool isRooted;
final bool isFromStorage;
bool hasUpdates;
List<String> appliedPatches;
List<String> changelog;
@ -30,7 +29,6 @@ class PatchedApplication {
required this.icon,
required this.patchDate,
this.isRooted = false,
this.isFromStorage = false,
this.hasUpdates = false,
this.appliedPatches = const [],
this.changelog = const [],

View File

@ -102,12 +102,18 @@ class ManagerAPI {
await setPatchedApps(patchedApps);
}
Future<void> deletePatchedApp(PatchedApplication app) async {
List<PatchedApplication> patchedApps = getPatchedApps();
patchedApps.removeWhere((a) => a.packageName == app.packageName);
await setPatchedApps(patchedApps);
}
Future<void> reAssessSavedApps() async {
bool isRoot = isRooted() ?? false;
bool isRooted = this.isRooted() ?? false;
List<PatchedApplication> patchedApps = getPatchedApps();
List<PatchedApplication> toRemove = [];
for (PatchedApplication app in patchedApps) {
bool isRemove = await isAppUninstalled(app, isRoot);
bool isRemove = await isAppUninstalled(app, isRooted);
if (isRemove) {
toRemove.add(app);
} else {
@ -133,9 +139,9 @@ class ManagerAPI {
await setPatchedApps(patchedApps);
}
Future<bool> isAppUninstalled(PatchedApplication app, bool isRoot) async {
Future<bool> isAppUninstalled(PatchedApplication app, bool isRooted) async {
bool existsRoot = false;
if (isRoot) {
if (isRooted) {
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
}
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);

View File

@ -146,7 +146,7 @@ class PatcherAPI {
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
if (_outFile != null) {
try {
if (patchedApp.isRooted && !patchedApp.isFromStorage) {
if (patchedApp.isRooted) {
return _rootAPI.installApp(
patchedApp.packageName,
patchedApp.apkFilePath,

View File

@ -59,8 +59,7 @@ class AppSelectorViewModel extends BaseViewModel {
apkFilePath: result.files.single.path!,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: _isRooted,
isFromStorage: true,
isRooted: false,
);
locator<PatcherViewModel>().selectedPatches.clear();
locator<PatcherViewModel>().notifyListeners();

View File

@ -70,7 +70,6 @@ class InstallerView extends StatelessWidget {
label: model.isInstalled
? I18nText('installerView.openButton')
: I18nText('installerView.installButton'),
isFilled: true,
isExpanded: true,
onPressed: () {
if (model.isInstalled) {

View File

@ -99,7 +99,7 @@ class InstallerViewModel extends BaseViewModel {
if (_app != null && _patches.isNotEmpty) {
String apkFilePath = _app!.apkFilePath;
try {
if (_app!.isRooted && !_app!.isFromStorage) {
if (_app!.isRooted) {
update(0.0, '', 'Checking if an old patched version exists');
bool oldExists = await _patcherAPI.checkOldPatch(_app!);
if (oldExists) {

View File

@ -0,0 +1,247 @@
import 'package:device_apps/device_apps.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/ui/widgets/appInfoView/app_info_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:stacked/stacked.dart';
class AppInfoView extends StatelessWidget {
final PatchedApplication app;
const AppInfoView({
Key? key,
required this.app,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ViewModelBuilder<AppInfoViewModel>.reactive(
onModelReady: (model) => model.initialize(),
viewModelBuilder: () => AppInfoViewModel(),
builder: (context, model, child) => Scaffold(
body: CustomScrollView(
slivers: <Widget>[
CustomSliverAppBar(
title: I18nText(
'appInfoView.widgetTitle',
child: Text(
'',
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
),
SliverPadding(
padding: const EdgeInsets.all(20.0),
sliver: SliverList(
delegate: SliverChildListDelegate.fixed(
<Widget>[
SizedBox(
height: 64.0,
child: CircleAvatar(
child: Image.memory(
app.icon,
fit: BoxFit.cover,
),
),
),
const SizedBox(height: 20),
Text(
app.name,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(height: 4),
Text(
app.version,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.subtitle1,
),
const SizedBox(height: 20),
CustomCard(
child: IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
InkWell(
onTap: () => DeviceApps.openApp(
app.packageName,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.open_in_new_outlined,
color:
Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 10),
I18nText(
'appInfoView.openButton',
child: Text(
'',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
VerticalDivider(
color: Theme.of(context).canvasColor,
),
InkWell(
onTap: () =>
model.showUninstallAlertDialog(context, app),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.delete_outline,
color:
Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 10),
I18nText(
'appInfoView.uninstallButton',
child: Text(
'',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
VerticalDivider(
color: Theme.of(context).canvasColor,
),
InkWell(
onTap: () {
model.navigateToPatcher(app);
Navigator.of(context).pop();
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.build_outlined,
color:
Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 10),
I18nText(
'appInfoView.patchButton',
child: Text(
'',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
],
),
),
),
const SizedBox(height: 20),
ListTile(
contentPadding: EdgeInsets.zero,
title: I18nText(
'appInfoView.packageNameLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: Text(app.packageName),
),
const SizedBox(height: 4),
ListTile(
contentPadding: EdgeInsets.zero,
title: I18nText(
'appInfoView.rootModeLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: model.isRooted
? I18nText('enabledLabel')
: I18nText('disabledLabel'),
),
const SizedBox(height: 4),
ListTile(
contentPadding: EdgeInsets.zero,
title: I18nText(
'appInfoView.patchedDateLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: I18nText(
'appInfoView.patchedDateHint',
translationParams: {
'date': model.getPrettyDate(context, app.patchDate),
'time': model.getPrettyTime(context, app.patchDate),
},
),
),
const SizedBox(height: 4),
ListTile(
contentPadding: EdgeInsets.zero,
title: I18nText(
'appInfoView.appliedPatchesLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: I18nText(
'appInfoView.appliedPatchesHint',
translationParams: {
'quantity': app.appliedPatches.length.toString(),
},
),
onTap: () => model.showAppliedPatchesDialog(context, app),
),
],
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,132 @@
import 'package:device_apps/device_apps.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:intl/intl.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/installerView/custom_material_button.dart';
import 'package:revanced_manager/utils/string.dart';
import 'package:stacked/stacked.dart';
class AppInfoViewModel extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final RootAPI _rootAPI = RootAPI();
bool isRooted = false;
void initialize() {
isRooted = _managerAPI.isRooted() ?? false;
}
void uninstallApp(PatchedApplication app) {
if (app.isRooted) {
_rootAPI.deleteApp(app.packageName, app.apkFilePath);
_managerAPI.deletePatchedApp(app);
} else {
DeviceApps.uninstallApp(app.packageName);
_managerAPI.deletePatchedApp(app);
}
}
void navigateToPatcher(PatchedApplication app) async {
locator<PatcherViewModel>().selectedApp = app;
locator<PatcherViewModel>().selectedPatches =
await _patcherAPI.getAppliedPatches(app.appliedPatches);
locator<PatcherViewModel>().notifyListeners();
locator<NavigationViewModel>().setIndex(1);
}
Future<void> showUninstallAlertDialog(
BuildContext context,
PatchedApplication app,
) async {
if (app.isRooted && !isRooted) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('appInfoView.alertDialogTitle'),
content: I18nText('appInfoView.errorDialogText'),
actions: [
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
],
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
),
);
} else {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('appInfoView.alertDialogTitle'),
content: I18nText('appInfoView.alertDialogText'),
actions: [
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () {
uninstallApp(app);
locator<NavigationViewModel>().notifyListeners();
Navigator.of(context).pop();
Navigator.of(context).pop();
},
)
],
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
),
);
}
}
String getPrettyDate(BuildContext context, DateTime dateTime) {
return DateFormat.yMMMMd(Localizations.localeOf(context).languageCode)
.format(dateTime);
}
String getPrettyTime(BuildContext context, DateTime dateTime) {
return DateFormat.jm(Localizations.localeOf(context).languageCode)
.format(dateTime);
}
Future<void> showAppliedPatchesDialog(
BuildContext context,
PatchedApplication app,
) async {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('appInfoView.appliedPatchesLabel'),
content: Text(getAppliedPatchesString(app.appliedPatches)),
actions: [
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
],
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
),
);
}
String getAppliedPatchesString(List<String> appliedPatches) {
List<String> names = appliedPatches
.map((p) => p
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG'))
.toList();
return '\u2022 ${names.join('\n\u2022 ')}';
}
}

View File

@ -1,11 +1,12 @@
import 'package:device_apps/device_apps.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/appInfoView/app_info_view.dart';
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/open_container_wrapper.dart';
class InstalledAppsCard extends StatelessWidget {
InstalledAppsCard({Key? key}) : super(key: key);
@ -39,14 +40,19 @@ class InstalledAppsCard extends StatelessWidget {
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
children: apps
.map((app) => ApplicationItem(
.map(
(app) => OpenContainerWrapper(
openBuilder: (_, __) => AppInfoView(app: app),
closedBuilder: (_, openContainer) => ApplicationItem(
icon: app.icon,
name: app.name,
patchDate: app.patchDate,
changelog: app.changelog,
isUpdatableApp: false,
onPressed: () => DeviceApps.openApp(app.packageName),
))
onPressed: openContainer,
),
),
)
.toList(),
);
}

View File

@ -148,7 +148,6 @@ class _PatchItemState extends State<PatchItem> {
),
actions: [
CustomMaterialButton(
isFilled: true,
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)

View File

@ -53,14 +53,17 @@ class ApplicationItem extends StatelessWidget {
],
),
const Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: CustomMaterialButton(
label: isUpdatableApp
? I18nText('applicationItem.patchButton')
: I18nText('applicationItem.openButton'),
onPressed: onPressed,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
CustomMaterialButton(
label: isUpdatableApp
? I18nText('applicationItem.patchButton')
: I18nText('applicationItem.infoButton'),
onPressed: onPressed,
),
],
),
],
),

View File

@ -37,6 +37,7 @@ dependencies:
google_fonts: ^3.0.1
http: ^0.13.4
injectable: ^1.5.3
intl: ^0.17.0
json_annotation: ^4.6.0
package_info_plus: ^1.4.3+1
path_provider: ^2.0.11