mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-19 01:03:56 +00:00
Compare commits
18 Commits
v1.21.0-de
...
v1.21.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65feb34242 | ||
|
|
23690a98df | ||
|
|
7449d4e318 | ||
|
|
c6f9e36f4b | ||
|
|
e9cee0abe2 | ||
|
|
9440f23b55 | ||
|
|
c67b4b438c | ||
|
|
1bdb820aed | ||
|
|
a28d77bc65 | ||
|
|
d60f9aa1d8 | ||
|
|
3209c0e430 | ||
|
|
7ef8f0454b | ||
|
|
232b702789 | ||
|
|
694f2a9fae | ||
|
|
77204087bb | ||
|
|
b26760b216 | ||
|
|
3c36950aeb | ||
|
|
bbeb836923 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
flutter-version: 3.22.x
|
flutter-version: 3.22.x
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Some of the features ReVanced Manager provides are:
|
|||||||
|
|
||||||
## 🔽 Download
|
## 🔽 Download
|
||||||
|
|
||||||
You can download the most recent version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases).
|
You can download the most recent version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases/latest).
|
||||||
Learn how to use ReVanced Manager by following the [documentation](/docs).
|
Learn how to use ReVanced Manager by following the [documentation](/docs).
|
||||||
|
|
||||||
## 📚 Everything else
|
## 📚 Everything else
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ if (flutterVersionName == null) {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
ndkVersion flutter.ndkVersion
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
coreLibraryDesugaringEnabled true
|
coreLibraryDesugaringEnabled true
|
||||||
|
|||||||
@@ -4,14 +4,10 @@
|
|||||||
"dismissButton": "Dismiss",
|
"dismissButton": "Dismiss",
|
||||||
"quitButton": "Quit",
|
"quitButton": "Quit",
|
||||||
"updateButton": "Update",
|
"updateButton": "Update",
|
||||||
"enabledLabel": "Enabled",
|
|
||||||
"disabledLabel": "Disabled",
|
|
||||||
"installed": "Installed: ${version}",
|
|
||||||
"suggested": "Suggested: ${version}",
|
"suggested": "Suggested: ${version}",
|
||||||
"yesButton": "Yes",
|
"yesButton": "Yes",
|
||||||
"noButton": "No",
|
"noButton": "No",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"options": "Options",
|
|
||||||
"notice": "Notice",
|
"notice": "Notice",
|
||||||
"noShowAgain": "Don't show this again",
|
"noShowAgain": "Don't show this again",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
@@ -27,10 +23,10 @@
|
|||||||
"refreshSuccess": "Refreshed successfully",
|
"refreshSuccess": "Refreshed successfully",
|
||||||
"widgetTitle": "Dashboard",
|
"widgetTitle": "Dashboard",
|
||||||
"updatesSubtitle": "Updates",
|
"updatesSubtitle": "Updates",
|
||||||
"patchedSubtitle": "Patched apps",
|
"lastPatchedAppSubtitle": "Last patched app",
|
||||||
|
"patchedSubtitle": "Installed apps",
|
||||||
"changeLaterSubtitle": "You can change this in the settings at a later time.",
|
"changeLaterSubtitle": "You can change this in the settings at a later time.",
|
||||||
"noUpdates": "No updates available",
|
"noSavedAppFound": "No app found",
|
||||||
"WIP": "Work in progress...",
|
|
||||||
"noInstallations": "No patched apps installed",
|
"noInstallations": "No patched apps installed",
|
||||||
"installUpdate": "Continue to install the update?",
|
"installUpdate": "Continue to install the update?",
|
||||||
"updateSheetTitle": "Update ReVanced Manager",
|
"updateSheetTitle": "Update ReVanced Manager",
|
||||||
@@ -41,27 +37,19 @@
|
|||||||
"downloadConsentDialogTitle": "Download necessary files?",
|
"downloadConsentDialogTitle": "Download necessary files?",
|
||||||
"downloadConsentDialogText": "ReVanced Manager needs to download necessary files to work properly.",
|
"downloadConsentDialogText": "ReVanced Manager needs to download necessary files to work properly.",
|
||||||
"downloadConsentDialogText2": "This will connect you to ${url}.",
|
"downloadConsentDialogText2": "This will connect you to ${url}.",
|
||||||
"checkUpdateDialogTitle": "Check for updates?",
|
|
||||||
"checkUpdateDialogText": "Do you want ReVanced Manager to check for updates automatically?",
|
|
||||||
"notificationTitle": "Update downloaded",
|
|
||||||
"notificationText": "Tap to install the update",
|
|
||||||
"downloadingMessage": "Downloading update...",
|
"downloadingMessage": "Downloading update...",
|
||||||
"downloadedMessage": "Update downloaded",
|
"downloadedMessage": "Update downloaded",
|
||||||
"installingMessage": "Installing update...",
|
"installingMessage": "Installing update...",
|
||||||
"errorDownloadMessage": "Unable to download update",
|
"errorDownloadMessage": "Unable to download update",
|
||||||
"errorInstallMessage": "Unable to install update",
|
"errorInstallMessage": "Unable to install update",
|
||||||
"noConnection": "No internet connection",
|
"noConnection": "No internet connection"
|
||||||
"updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again."
|
|
||||||
},
|
},
|
||||||
"applicationItem": {
|
"applicationItem": {
|
||||||
"infoButton": "Info"
|
"infoButton": "Info"
|
||||||
},
|
},
|
||||||
"latestCommitCard": {
|
"latestCommitCard": {
|
||||||
"loadingLabel": "Loading...",
|
"loadingLabel": "Loading...",
|
||||||
"timeagoLabel": "${time} ago",
|
"timeagoLabel": "${time} ago"
|
||||||
"patcherLabel": "Patcher: ",
|
|
||||||
"managerLabel": "Manager: ",
|
|
||||||
"updateButton": "Update Manager"
|
|
||||||
},
|
},
|
||||||
"patcherView": {
|
"patcherView": {
|
||||||
"widgetTitle": "Patcher",
|
"widgetTitle": "Patcher",
|
||||||
@@ -75,8 +63,6 @@
|
|||||||
"widgetTitleSelected": "Selected app",
|
"widgetTitleSelected": "Selected app",
|
||||||
"widgetSubtitle": "No app selected",
|
"widgetSubtitle": "No app selected",
|
||||||
"noAppsLabel": "No applications found",
|
"noAppsLabel": "No applications found",
|
||||||
"currentVersion": "Current",
|
|
||||||
"suggestedVersion": "Suggested",
|
|
||||||
"anyVersion": "Any version"
|
"anyVersion": "Any version"
|
||||||
},
|
},
|
||||||
"patchSelectorCard": {
|
"patchSelectorCard": {
|
||||||
@@ -120,15 +106,12 @@
|
|||||||
"customValue": "Custom value",
|
"customValue": "Custom value",
|
||||||
"setToNull": "Set to null",
|
"setToNull": "Set to null",
|
||||||
"nullValue": "This option value is currently null",
|
"nullValue": "This option value is currently null",
|
||||||
"resetOptionsTooltip": "Reset patch options",
|
|
||||||
"viewTitle": "Patch options",
|
"viewTitle": "Patch options",
|
||||||
"saveOptions": "Save",
|
"saveOptions": "Save",
|
||||||
"addOptions": "Add options",
|
|
||||||
"unselectPatch": "Unselect patch",
|
"unselectPatch": "Unselect patch",
|
||||||
"tooltip": "More input options",
|
"tooltip": "More input options",
|
||||||
"selectFilePath": "Select file path",
|
"selectFilePath": "Select file path",
|
||||||
"selectFolder": "Select folder",
|
"selectFolder": "Select folder",
|
||||||
"selectOption": "Select option",
|
|
||||||
"requiredOption": "Setting this option is required",
|
"requiredOption": "Setting this option is required",
|
||||||
"unsupportedOption": "This option is not supported",
|
"unsupportedOption": "This option is not supported",
|
||||||
"requiredOptionNull": "The following options have to be set:\n\n${options}"
|
"requiredOptionNull": "The following options have to be set:\n\n${options}"
|
||||||
@@ -141,7 +124,6 @@
|
|||||||
"patchesChangeWarningDialogButton": "Use default selection"
|
"patchesChangeWarningDialogButton": "Use default selection"
|
||||||
},
|
},
|
||||||
"installerView": {
|
"installerView": {
|
||||||
"widgetTitle": "Installer",
|
|
||||||
"installType": "Select install type",
|
"installType": "Select install type",
|
||||||
"installTypeDescription": "Select the installation type to continue with.",
|
"installTypeDescription": "Select the installation type to continue with.",
|
||||||
"installButton": "Install",
|
"installButton": "Install",
|
||||||
@@ -150,7 +132,6 @@
|
|||||||
"warning": "Disable auto updates for the patched app to avoid unexpected issues.",
|
"warning": "Disable auto updates for the patched app to avoid unexpected issues.",
|
||||||
"pressBackAgain": "Press back again to cancel",
|
"pressBackAgain": "Press back again to cancel",
|
||||||
"openButton": "Open",
|
"openButton": "Open",
|
||||||
"shareButton": "Share file",
|
|
||||||
"notificationTitle": "ReVanced Manager is patching",
|
"notificationTitle": "ReVanced Manager is patching",
|
||||||
"notificationText": "Tap to return to the installer",
|
"notificationText": "Tap to return to the installer",
|
||||||
"exportApkButtonTooltip": "Export patched APK",
|
"exportApkButtonTooltip": "Export patched APK",
|
||||||
@@ -175,7 +156,6 @@
|
|||||||
"dynamicThemeHint": "Enjoy an experience closer to your device",
|
"dynamicThemeHint": "Enjoy an experience closer to your device",
|
||||||
"languageLabel": "Language",
|
"languageLabel": "Language",
|
||||||
"languageUpdated": "Language updated",
|
"languageUpdated": "Language updated",
|
||||||
"englishOption": "English",
|
|
||||||
"sourcesLabel": "Alternative sources",
|
"sourcesLabel": "Alternative sources",
|
||||||
"sourcesLabelHint": "Configure the alternative sources for ReVanced Patches and ReVanced Integrations",
|
"sourcesLabelHint": "Configure the alternative sources for ReVanced Patches and ReVanced Integrations",
|
||||||
"sourcesIntegrationsLabel": "Integrations source",
|
"sourcesIntegrationsLabel": "Integrations source",
|
||||||
@@ -205,6 +185,8 @@
|
|||||||
"showUpdateDialogHint": "Show a dialog when a new update is available",
|
"showUpdateDialogHint": "Show a dialog when a new update is available",
|
||||||
"universalPatchesLabel": "Show universal patches",
|
"universalPatchesLabel": "Show universal patches",
|
||||||
"universalPatchesHint": "Display all apps and universal patches (may slow down the app list)",
|
"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",
|
"versionCompatibilityCheckLabel": "Version compatibility check",
|
||||||
"versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version",
|
"versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version",
|
||||||
"requireSuggestedAppVersionLabel": "Require suggested app version",
|
"requireSuggestedAppVersionLabel": "Require suggested app version",
|
||||||
@@ -256,18 +238,25 @@
|
|||||||
"appInfoView": {
|
"appInfoView": {
|
||||||
"widgetTitle": "App info",
|
"widgetTitle": "App info",
|
||||||
"openButton": "Open",
|
"openButton": "Open",
|
||||||
|
"installButton": "Install",
|
||||||
"uninstallButton": "Uninstall",
|
"uninstallButton": "Uninstall",
|
||||||
"unmountButton": "Unmount",
|
"unmountButton": "Unmount",
|
||||||
|
"exportButton": "Export",
|
||||||
|
"deleteButton": "Delete",
|
||||||
"rootDialogTitle": "Error",
|
"rootDialogTitle": "Error",
|
||||||
|
"lastPatchedAppDescription": "This is a backup of the app that was last patched.",
|
||||||
"unmountDialogText": "Are you sure you want to unmount this app?",
|
"unmountDialogText": "Are you sure you want to unmount this app?",
|
||||||
"uninstallDialogText": "Are you sure you want to uninstall 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.",
|
"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",
|
"packageNameLabel": "Package name",
|
||||||
"installTypeLabel": "Installation type",
|
"installTypeLabel": "Installation type",
|
||||||
"mountTypeLabel": "Mount",
|
"mountTypeLabel": "Mount",
|
||||||
"regularTypeLabel": "Regular",
|
"regularTypeLabel": "Regular",
|
||||||
"patchedDateLabel": "Patched date",
|
"patchedDateLabel": "Patched date",
|
||||||
"appliedPatchesLabel": "Applied patches",
|
"appliedPatchesLabel": "Applied patches",
|
||||||
|
"sizeLabel": "File size",
|
||||||
"patchedDateHint": "${date} at ${time}",
|
"patchedDateHint": "${date} at ${time}",
|
||||||
"appliedPatchesHint": "${quantity} applied patches",
|
"appliedPatchesHint": "${quantity} applied patches",
|
||||||
"updateNotImplemented": "This feature has not been implemented yet"
|
"updateNotImplemented": "This feature has not been implemented yet"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ To use ReVanced on your Android device, ReVanced Manager must be first installed
|
|||||||
|
|
||||||
## ✅ Installation steps
|
## ✅ Installation steps
|
||||||
|
|
||||||
1. Download the latest version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases)
|
1. Download the latest version of ReVanced Manager at [revanced.app/download](https://revanced.app/download) or from [GitHub releases](https://github.com/ReVanced/revanced-manager/releases/latest)
|
||||||
2. Install ReVanced Manager
|
2. Install ReVanced Manager
|
||||||
|
|
||||||
## ⏭️ What's next
|
## ⏭️ What's next
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class PatchedApplication {
|
|||||||
this.isRooted = false,
|
this.isRooted = false,
|
||||||
this.isFromStorage = false,
|
this.isFromStorage = false,
|
||||||
this.appliedPatches = const [],
|
this.appliedPatches = const [],
|
||||||
|
this.patchedFilePath = '',
|
||||||
|
this.fileSize = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
||||||
@@ -33,6 +35,8 @@ class PatchedApplication {
|
|||||||
bool isRooted;
|
bool isRooted;
|
||||||
bool isFromStorage;
|
bool isFromStorage;
|
||||||
List<String> appliedPatches;
|
List<String> appliedPatches;
|
||||||
|
String patchedFilePath;
|
||||||
|
int fileSize;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class ManagerAPI {
|
|||||||
bool releaseBuild = false;
|
bool releaseBuild = false;
|
||||||
bool suggestedAppVersionSelected = true;
|
bool suggestedAppVersionSelected = true;
|
||||||
bool isDynamicThemeAvailable = false;
|
bool isDynamicThemeAvailable = false;
|
||||||
|
bool isScopedStorageAvailable = false;
|
||||||
|
int sdkVersion = 0;
|
||||||
String storedPatchesFile = '/selected-patches.json';
|
String storedPatchesFile = '/selected-patches.json';
|
||||||
String keystoreFile =
|
String keystoreFile =
|
||||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||||
@@ -55,8 +57,13 @@ class ManagerAPI {
|
|||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
isRooted = await _rootAPI.isRooted();
|
isRooted = await _rootAPI.isRooted();
|
||||||
|
if (sdkVersion == 0) {
|
||||||
|
sdkVersion = await getSdkVersion();
|
||||||
|
}
|
||||||
isDynamicThemeAvailable =
|
isDynamicThemeAvailable =
|
||||||
(await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
sdkVersion >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||||
|
isScopedStorageAvailable =
|
||||||
|
sdkVersion >= 30; // ANDROID_11_SDK_VERSION = 30
|
||||||
storedPatchesFile =
|
storedPatchesFile =
|
||||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||||
if (kReleaseMode) {
|
if (kReleaseMode) {
|
||||||
@@ -295,6 +302,14 @@ class ManagerAPI {
|
|||||||
await _prefs.setBool('requireSuggestedAppVersionEnabled', value);
|
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 {
|
Future<void> setKeystorePassword(String password) async {
|
||||||
await _prefs.setString('keystorePassword', password);
|
await _prefs.setString('keystorePassword', password);
|
||||||
}
|
}
|
||||||
@@ -327,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() {
|
List<PatchedApplication> getPatchedApps() {
|
||||||
final List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
final List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
||||||
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
||||||
@@ -685,6 +728,16 @@ class ManagerAPI {
|
|||||||
patchedApps.addAll(mountedApps);
|
patchedApps.addAll(mountedApps);
|
||||||
|
|
||||||
await setPatchedApps(patchedApps);
|
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 {
|
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
||||||
@@ -779,4 +832,82 @@ class ManagerAPI {
|
|||||||
selectedPatchesFile.deleteSync();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ class PatcherAPI {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
PatchedApplication patchedApp,
|
PatchedApplication patchedApp,
|
||||||
) async {
|
) async {
|
||||||
if (outFile != null) {
|
if (patchedApp.patchedFilePath != '') {
|
||||||
_managerAPI.ctx = context;
|
_managerAPI.ctx = context;
|
||||||
try {
|
try {
|
||||||
if (patchedApp.isRooted) {
|
if (patchedApp.isRooted) {
|
||||||
@@ -232,7 +232,7 @@ class PatcherAPI {
|
|||||||
return await _rootAPI.install(
|
return await _rootAPI.install(
|
||||||
patchedApp.packageName,
|
patchedApp.packageName,
|
||||||
patchedApp.apkFilePath,
|
patchedApp.apkFilePath,
|
||||||
outFile!.path,
|
patchedApp.patchedFilePath,
|
||||||
)
|
)
|
||||||
? 0
|
? 0
|
||||||
: 1;
|
: 1;
|
||||||
@@ -246,7 +246,7 @@ class PatcherAPI {
|
|||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
return await installApk(
|
return await installApk(
|
||||||
context,
|
context,
|
||||||
outFile!.path,
|
patchedApp.patchedFilePath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,13 +368,13 @@ class PatcherAPI {
|
|||||||
return cleanInstall ? 10 : 1;
|
return cleanInstall ? 10 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void exportPatchedFile(String appName, String version) {
|
void exportPatchedFile(PatchedApplication app) {
|
||||||
try {
|
try {
|
||||||
if (outFile != null) {
|
if (outFile != null) {
|
||||||
final String newName = _getFileName(appName, version);
|
final String newName = _getFileName(app.name, app.version);
|
||||||
FlutterFileDialog.saveFile(
|
FlutterFileDialog.saveFile(
|
||||||
params: SaveFileDialogParams(
|
params: SaveFileDialogParams(
|
||||||
sourceFilePath: outFile!.path,
|
sourceFilePath: app.patchedFilePath,
|
||||||
fileName: newName,
|
fileName: newName,
|
||||||
mimeTypesFilter: ['application/vnd.android.package-archive'],
|
mimeTypesFilter: ['application/vnd.android.package-archive'],
|
||||||
),
|
),
|
||||||
@@ -387,14 +387,14 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sharePatchedFile(String appName, String version) {
|
void sharePatchedFile(PatchedApplication app) {
|
||||||
try {
|
try {
|
||||||
if (outFile != null) {
|
if (outFile != null) {
|
||||||
final String newName = _getFileName(appName, version);
|
final String newName = _getFileName(app.name, app.version);
|
||||||
final int lastSeparator = outFile!.path.lastIndexOf('/');
|
final int lastSeparator = app.patchedFilePath.lastIndexOf('/');
|
||||||
final String newPath =
|
final String newPath =
|
||||||
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
app.patchedFilePath.substring(0, lastSeparator + 1) + newName;
|
||||||
final File shareFile = outFile!.copySync(newPath);
|
final File shareFile = File(app.patchedFilePath).copySync(newPath);
|
||||||
Share.shareXFiles([XFile(shareFile.path)]);
|
Share.shareXFiles([XFile(shareFile.path)]);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ var lightCustomTheme = ThemeData(
|
|||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: lightCustomColorScheme,
|
colorScheme: lightCustomColorScheme,
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: WidgetStateProperty.all(
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: lightCustomColorScheme.onSurface,
|
color: lightCustomColorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -31,7 +31,7 @@ var darkCustomTheme = ThemeData(
|
|||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: darkCustomColorScheme,
|
colorScheme: darkCustomColorScheme,
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: WidgetStateProperty.all(
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: darkCustomColorScheme.onSurface,
|
color: darkCustomColorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
@@ -24,14 +24,26 @@ class DynamicThemeBuilder extends StatefulWidget {
|
|||||||
|
|
||||||
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
|
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
Brightness brightness = PlatformDispatcher.instance.platformBrightness;
|
late Brightness brightness;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
brightness = PlatformDispatcher.instance.platformBrightness;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
final systemBrightness = PlatformDispatcher.instance.platformBrightness;
|
||||||
|
if (brightness != systemBrightness) {
|
||||||
|
brightness = systemBrightness;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
@@ -39,7 +51,7 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
|
|||||||
final ThemeData lightDynamicTheme = ThemeData(
|
final ThemeData lightDynamicTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: WidgetStateProperty.all(
|
||||||
GoogleFonts.roboto(
|
GoogleFonts.roboto(
|
||||||
color: lightColorScheme?.onSurface,
|
color: lightColorScheme?.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -53,7 +65,7 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
|
|||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: WidgetStateProperty.all(
|
||||||
GoogleFonts.roboto(
|
GoogleFonts.roboto(
|
||||||
color: darkColorScheme?.onSurface,
|
color: darkColorScheme?.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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/toast.dart';
|
import 'package:revanced_manager/services/toast.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/utils/about_info.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
@@ -74,11 +75,14 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
required String packageName,
|
required String packageName,
|
||||||
}) async {
|
}) async {
|
||||||
final String suggestedVersion = getSuggestedVersion(packageName);
|
final String suggestedVersion = getSuggestedVersion(packageName);
|
||||||
|
final String architecture = await AboutInfo.getInfo().then((info) {
|
||||||
|
return info['supportedArch'][0];
|
||||||
|
});
|
||||||
|
|
||||||
if (suggestedVersion.isNotEmpty) {
|
if (suggestedVersion.isNotEmpty) {
|
||||||
await openDefaultBrowser('$packageName apk version $suggestedVersion');
|
await openDefaultBrowser('$packageName apk version $suggestedVersion $architecture');
|
||||||
} else {
|
} else {
|
||||||
await openDefaultBrowser('$packageName apk');
|
await openDefaultBrowser('$packageName apk $architecture');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +182,6 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
FilledButton(
|
FilledButton(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:revanced_manager/app/app.locator.dart';
|
|||||||
import 'package:revanced_manager/gen/strings.g.dart';
|
import 'package:revanced_manager/gen/strings.g.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/widgets/homeView/installed_apps_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/installed_apps_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/homeView/last_patched_app_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
@@ -20,7 +21,9 @@ class HomeView extends StatelessWidget {
|
|||||||
viewModelBuilder: () => locator<HomeViewModel>(),
|
viewModelBuilder: () => locator<HomeViewModel>(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () => model.forceRefresh(context),
|
edgeOffset: 110.0,
|
||||||
|
displacement: 10.0,
|
||||||
|
onRefresh: () async => await model.forceRefresh(context),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
CustomSliverAppBar(
|
CustomSliverAppBar(
|
||||||
@@ -44,6 +47,21 @@ class HomeView extends StatelessWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
LatestCommitCard(model: model, parentContext: context),
|
LatestCommitCard(model: model, parentContext: context),
|
||||||
const SizedBox(height: 23),
|
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(
|
Text(
|
||||||
t.homeView.patchedSubtitle,
|
t.homeView.patchedSubtitle,
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
bool showUpdatableApps = false;
|
bool showUpdatableApps = false;
|
||||||
|
PatchedApplication? lastPatchedApp;
|
||||||
bool releaseBuild = false;
|
bool releaseBuild = false;
|
||||||
List<PatchedApplication> patchedInstalledApps = [];
|
List<PatchedApplication> patchedInstalledApps = [];
|
||||||
String _currentManagerVersion = '';
|
String _currentManagerVersion = '';
|
||||||
@@ -102,10 +103,10 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToAppInfo(PatchedApplication app) {
|
void navigateToAppInfo(PatchedApplication app, bool isLastPatchedApp) {
|
||||||
_navigationService.navigateTo(
|
_navigationService.navigateTo(
|
||||||
Routes.appInfoView,
|
Routes.appInfoView,
|
||||||
arguments: AppInfoViewArguments(app: app),
|
arguments: AppInfoViewArguments(app: app, isLastPatchedApp: isLastPatchedApp),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +124,15 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void getPatchedApps() {
|
void getPatchedApps() {
|
||||||
|
lastPatchedApp = _managerAPI.getLastPatchedApp();
|
||||||
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
|
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isLastPatchedAppEnabled() {
|
||||||
|
return _managerAPI.isLastPatchedAppEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> hasManagerUpdates() async {
|
Future<bool> hasManagerUpdates() async {
|
||||||
if (!_managerAPI.releaseBuild) {
|
if (!_managerAPI.releaseBuild) {
|
||||||
return false;
|
return false;
|
||||||
@@ -452,10 +458,6 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatesAreDisabled() {
|
|
||||||
_toast.showBottom(t.homeView.updatesDisabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showUpdateConfirmationDialog(
|
Future<void> showUpdateConfirmationDialog(
|
||||||
BuildContext parentContext,
|
BuildContext parentContext,
|
||||||
bool isPatches, [
|
bool isPatches, [
|
||||||
@@ -494,8 +496,8 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> forceRefresh(BuildContext context) async {
|
Future<void> forceRefresh(BuildContext context) async {
|
||||||
_managerAPI.clearAllData();
|
await _managerAPI.clearAllData();
|
||||||
|
await initialize(context);
|
||||||
_toast.showBottom(t.homeView.refreshSuccess);
|
_toast.showBottom(t.homeView.refreshSuccess);
|
||||||
initialize(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
});
|
});
|
||||||
await WakelockPlus.enable();
|
await WakelockPlus.enable();
|
||||||
await handlePlatformChannelMethods();
|
await handlePlatformChannelMethods();
|
||||||
await runPatcher();
|
await runPatcher(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> handlePlatformChannelMethods() async {
|
Future<dynamic> handlePlatformChannelMethods() async {
|
||||||
@@ -182,13 +182,20 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> runPatcher() async {
|
Future<void> runPatcher(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
await _patcherAPI.runPatcher(
|
await _patcherAPI.runPatcher(
|
||||||
_app.packageName,
|
_app.packageName,
|
||||||
_app.apkFilePath,
|
_app.apkFilePath,
|
||||||
_patches,
|
_patches,
|
||||||
);
|
);
|
||||||
|
_app.appliedPatches = _patches.map((p) => p.name).toList();
|
||||||
|
if (_managerAPI.isLastPatchedAppEnabled()) {
|
||||||
|
await _managerAPI.setLastPatchedApp(_app, _patcherAPI.outFile!);
|
||||||
|
} else {
|
||||||
|
_app.patchedFilePath = _patcherAPI.outFile!.path;
|
||||||
|
}
|
||||||
|
locator<HomeViewModel>().initialize(context);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
update(
|
update(
|
||||||
-100.0,
|
-100.0,
|
||||||
@@ -488,7 +495,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
||||||
isInstalling = true;
|
isInstalling = true;
|
||||||
try {
|
try {
|
||||||
_app.isRooted = installAsRoot;
|
_app.isRooted = await _managerAPI.installTypeDialog(context);
|
||||||
if (headerLogs != 'Installing...') {
|
if (headerLogs != 'Installing...') {
|
||||||
update(
|
update(
|
||||||
.85,
|
.85,
|
||||||
@@ -501,17 +508,15 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
isInstalled = true;
|
isInstalled = true;
|
||||||
_app.isFromStorage = false;
|
_app.isFromStorage = false;
|
||||||
_app.patchDate = DateTime.now();
|
_app.patchDate = DateTime.now();
|
||||||
_app.appliedPatches = _patches.map((p) => p.name).toList();
|
|
||||||
|
|
||||||
// In case a patch changed the app name or package name,
|
// In case a patch changed the app name or package name,
|
||||||
// update the app info.
|
// update the app info.
|
||||||
final app =
|
final app =
|
||||||
await DeviceApps.getAppFromStorage(_patcherAPI.outFile!.path);
|
await DeviceApps.getAppFromStorage(_patcherAPI.outFile!.path);
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
_app.name = app.appName;
|
_app.name = app.appName;
|
||||||
_app.packageName = app.packageName;
|
_app.packageName = app.packageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _managerAPI.savePatchedApp(_app);
|
await _managerAPI.savePatchedApp(_app);
|
||||||
|
|
||||||
_managerAPI
|
_managerAPI
|
||||||
@@ -544,7 +549,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
void exportResult() {
|
void exportResult() {
|
||||||
try {
|
try {
|
||||||
_patcherAPI.exportPatchedFile(_app.name, _app.version);
|
_patcherAPI.exportPatchedFile(_app);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
|
|||||||
@@ -301,9 +301,16 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> loadSelectedPatches(BuildContext context) async {
|
Future<void> loadSelectedPatches(BuildContext context) async {
|
||||||
if (_managerAPI.isPatchesChangeEnabled()) {
|
if (_managerAPI.isPatchesChangeEnabled()) {
|
||||||
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
final List<String>? appliedPatches = _managerAPI
|
||||||
locator<PatcherViewModel>().selectedApp!.packageName,
|
.getPatchedApps()
|
||||||
);
|
.firstWhereOrNull(
|
||||||
|
(app) => app.packageName == selectedApp!.packageName,
|
||||||
|
)
|
||||||
|
?.appliedPatches;
|
||||||
|
final List<String> selectedPatches = appliedPatches ??
|
||||||
|
await _managerAPI.getSelectedPatches(
|
||||||
|
selectedApp!.packageName,
|
||||||
|
);
|
||||||
if (selectedPatches.isNotEmpty) {
|
if (selectedPatches.isNotEmpty) {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
this.selectedPatches.addAll(
|
this.selectedPatches.addAll(
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class SManageSources extends BaseViewModel {
|
|||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
|
scrollable: true,
|
||||||
title: Row(
|
title: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -39,75 +40,73 @@ class SManageSources extends BaseViewModel {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
content: SingleChildScrollView(
|
content: Column(
|
||||||
child: Column(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
TextField(
|
||||||
TextField(
|
controller: _orgPatSourceController,
|
||||||
controller: _orgPatSourceController,
|
autocorrect: false,
|
||||||
autocorrect: false,
|
onChanged: (value) => notifyListeners(),
|
||||||
onChanged: (value) => notifyListeners(),
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
icon: Icon(
|
||||||
icon: Icon(
|
Icons.extension_outlined,
|
||||||
Icons.extension_outlined,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: t.settingsView.orgPatchesLabel,
|
|
||||||
hintText: patchesRepo.split('/')[0],
|
|
||||||
),
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: t.settingsView.orgPatchesLabel,
|
||||||
|
hintText: patchesRepo.split('/')[0],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
// Patches repository's name
|
const SizedBox(height: 8),
|
||||||
TextField(
|
// Patches repository's name
|
||||||
controller: _patSourceController,
|
TextField(
|
||||||
autocorrect: false,
|
controller: _patSourceController,
|
||||||
onChanged: (value) => notifyListeners(),
|
autocorrect: false,
|
||||||
decoration: InputDecoration(
|
onChanged: (value) => notifyListeners(),
|
||||||
icon: const Icon(
|
decoration: InputDecoration(
|
||||||
Icons.extension_outlined,
|
icon: const Icon(
|
||||||
color: Colors.transparent,
|
Icons.extension_outlined,
|
||||||
),
|
color: Colors.transparent,
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: t.settingsView.sourcesPatchesLabel,
|
|
||||||
hintText: patchesRepo.split('/')[1],
|
|
||||||
),
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: t.settingsView.sourcesPatchesLabel,
|
||||||
|
hintText: patchesRepo.split('/')[1],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
// Integrations owner's name
|
const SizedBox(height: 8),
|
||||||
TextField(
|
// Integrations owner's name
|
||||||
controller: _orgIntSourceController,
|
TextField(
|
||||||
autocorrect: false,
|
controller: _orgIntSourceController,
|
||||||
onChanged: (value) => notifyListeners(),
|
autocorrect: false,
|
||||||
decoration: InputDecoration(
|
onChanged: (value) => notifyListeners(),
|
||||||
icon: Icon(
|
decoration: InputDecoration(
|
||||||
Icons.merge_outlined,
|
icon: Icon(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
Icons.merge_outlined,
|
||||||
),
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: t.settingsView.orgIntegrationsLabel,
|
|
||||||
hintText: integrationsRepo.split('/')[0],
|
|
||||||
),
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: t.settingsView.orgIntegrationsLabel,
|
||||||
|
hintText: integrationsRepo.split('/')[0],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
// Integrations repository's name
|
const SizedBox(height: 8),
|
||||||
TextField(
|
// Integrations repository's name
|
||||||
controller: _intSourceController,
|
TextField(
|
||||||
autocorrect: false,
|
controller: _intSourceController,
|
||||||
onChanged: (value) => notifyListeners(),
|
autocorrect: false,
|
||||||
decoration: InputDecoration(
|
onChanged: (value) => notifyListeners(),
|
||||||
icon: const Icon(
|
decoration: InputDecoration(
|
||||||
Icons.merge_outlined,
|
icon: const Icon(
|
||||||
color: Colors.transparent,
|
Icons.merge_outlined,
|
||||||
),
|
color: Colors.transparent,
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
labelText: t.settingsView.sourcesIntegrationsLabel,
|
|
||||||
hintText: integrationsRepo.split('/')[1],
|
|
||||||
),
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: t.settingsView.sourcesIntegrationsLabel,
|
||||||
|
hintText: integrationsRepo.split('/')[1],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
Text(t.settingsView.sourcesUpdateNote),
|
const SizedBox(height: 20),
|
||||||
],
|
Text(t.settingsView.sourcesUpdateNote),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|||||||
@@ -141,6 +141,18 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isLastPatchedAppEnabled() {
|
||||||
|
return _managerAPI.isLastPatchedAppEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void useLastPatchedApp(bool value) {
|
||||||
|
_managerAPI.enableLastPatchedAppStatus(value);
|
||||||
|
if (!value) {
|
||||||
|
_managerAPI.deleteLastPatchedApp();
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
bool isVersionCompatibilityCheckEnabled() {
|
bool isVersionCompatibilityCheckEnabled() {
|
||||||
return _managerAPI.isVersionCompatibilityCheckEnabled();
|
return _managerAPI.isVersionCompatibilityCheckEnabled();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ class AppInfoView extends StatelessWidget {
|
|||||||
const AppInfoView({
|
const AppInfoView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.app,
|
required this.app,
|
||||||
|
required this.isLastPatchedApp,
|
||||||
});
|
});
|
||||||
final PatchedApplication app;
|
final PatchedApplication app;
|
||||||
|
final bool isLastPatchedApp;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -57,6 +59,14 @@ class AppInfoView extends StatelessWidget {
|
|||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
if (isLastPatchedApp) ...[
|
||||||
|
ListTile(
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
subtitle: Text(t.appInfoView.lastPatchedAppDescription),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
],
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
@@ -71,20 +81,26 @@ class AppInfoView extends StatelessWidget {
|
|||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(16.0),
|
borderRadius: BorderRadius.circular(16.0),
|
||||||
onTap: () => model.openApp(app),
|
onTap: () => isLastPatchedApp
|
||||||
|
? model.installApp(context, app)
|
||||||
|
: model.openApp(app),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
MainAxisAlignment.center,
|
MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(
|
Icon(
|
||||||
Icons.open_in_new_outlined,
|
isLastPatchedApp
|
||||||
|
? Icons.download_outlined
|
||||||
|
: Icons.open_in_new_outlined,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary,
|
.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
t.appInfoView.openButton,
|
isLastPatchedApp
|
||||||
|
? t.appInfoView.installButton
|
||||||
|
: t.appInfoView.openButton,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
@@ -108,24 +124,30 @@ class AppInfoView extends StatelessWidget {
|
|||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(16.0),
|
borderRadius: BorderRadius.circular(16.0),
|
||||||
onTap: () => model.showUninstallDialog(
|
onTap: () => isLastPatchedApp
|
||||||
context,
|
? model.exportApp(app)
|
||||||
app,
|
: model.showUninstallDialog(
|
||||||
false,
|
context,
|
||||||
),
|
app,
|
||||||
|
false,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
MainAxisAlignment.center,
|
MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(
|
Icon(
|
||||||
Icons.delete_outline,
|
isLastPatchedApp
|
||||||
|
? Icons.save
|
||||||
|
: Icons.delete_outline,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary,
|
.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
t.appInfoView.uninstallButton,
|
isLastPatchedApp
|
||||||
|
? t.appInfoView.exportButton
|
||||||
|
: t.appInfoView.uninstallButton,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
@@ -144,14 +166,57 @@ class AppInfoView extends StatelessWidget {
|
|||||||
endIndent: 12.0,
|
endIndent: 12.0,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
),
|
),
|
||||||
if (app.isRooted)
|
if (isLastPatchedApp)
|
||||||
VerticalDivider(
|
VerticalDivider(
|
||||||
color: Theme.of(context).canvasColor,
|
color: Theme.of(context).canvasColor,
|
||||||
indent: 12.0,
|
indent: 12.0,
|
||||||
endIndent: 12.0,
|
endIndent: 12.0,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
),
|
),
|
||||||
if (app.isRooted)
|
if (isLastPatchedApp)
|
||||||
|
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),
|
||||||
|
Text(
|
||||||
|
t.appInfoView.deleteButton,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!isLastPatchedApp && app.isRooted)
|
||||||
|
VerticalDivider(
|
||||||
|
color: Theme.of(context).canvasColor,
|
||||||
|
indent: 12.0,
|
||||||
|
endIndent: 12.0,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
if (!isLastPatchedApp && app.isRooted)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Material(
|
child: Material(
|
||||||
type: MaterialType.transparency,
|
type: MaterialType.transparency,
|
||||||
@@ -240,6 +305,23 @@ class AppInfoView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
|
if (isLastPatchedApp) ...[
|
||||||
|
ListTile(
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: Text(
|
||||||
|
t.appInfoView.sizeLabel,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
model.getFileSizeString(app.fileSize),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
],
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
import 'dart:math';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@@ -20,6 +21,23 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
final RootAPI _rootAPI = RootAPI();
|
final RootAPI _rootAPI = RootAPI();
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
|
|
||||||
|
Future<void> installApp(
|
||||||
|
BuildContext context,
|
||||||
|
PatchedApplication app,
|
||||||
|
) async {
|
||||||
|
app.isRooted = await _managerAPI.installTypeDialog(context);
|
||||||
|
final int statusCode = await _patcherAPI.installPatchedFile(context, app);
|
||||||
|
if (statusCode == 0) {
|
||||||
|
locator<HomeViewModel>().initialize(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> exportApp(
|
||||||
|
PatchedApplication app,
|
||||||
|
) async {
|
||||||
|
_patcherAPI.exportPatchedFile(app);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> uninstallApp(
|
Future<void> uninstallApp(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
PatchedApplication app,
|
PatchedApplication app,
|
||||||
@@ -123,6 +141,34 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showDeleteDialog(
|
||||||
|
BuildContext context,
|
||||||
|
PatchedApplication app,
|
||||||
|
) async {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(t.appInfoView.removeAppDialogTitle),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
content: Text(t.appInfoView.removeAppDialogText),
|
||||||
|
actions: <Widget>[
|
||||||
|
OutlinedButton(
|
||||||
|
child: Text(t.cancelButton),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
child: Text(t.okButton),
|
||||||
|
onPressed: () => {
|
||||||
|
_managerAPI.deleteLastPatchedApp(),
|
||||||
|
Navigator.of(context)..pop()..pop(),
|
||||||
|
locator<HomeViewModel>().initialize(context),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String getPrettyDate(BuildContext context, DateTime dateTime) {
|
String getPrettyDate(BuildContext context, DateTime dateTime) {
|
||||||
return DateFormat.yMMMMd(Localizations.localeOf(context).languageCode)
|
return DateFormat.yMMMMd(Localizations.localeOf(context).languageCode)
|
||||||
.format(dateTime);
|
.format(dateTime);
|
||||||
@@ -133,6 +179,12 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
.format(dateTime);
|
.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(
|
Future<void> showAppliedPatchesDialog(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
PatchedApplication app,
|
PatchedApplication app,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class InstalledAppsCard extends StatelessWidget {
|
|||||||
name: app.name,
|
name: app.name,
|
||||||
patchDate: app.patchDate,
|
patchDate: app.patchDate,
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
locator<HomeViewModel>().navigateToAppInfo(app),
|
locator<HomeViewModel>().navigateToAppInfo(app, false),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|||||||
49
lib/ui/widgets/homeView/last_patched_app_card.dart
Normal file
49
lib/ui/widgets/homeView/last_patched_app_card.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/gen/strings.g.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/shared/application_item.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
|
//ignore: must_be_immutable
|
||||||
|
class LastPatchedAppCard extends StatelessWidget {
|
||||||
|
LastPatchedAppCard({super.key});
|
||||||
|
PatchedApplication? app = locator<HomeViewModel>().lastPatchedApp;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return app == null
|
||||||
|
? CustomCard(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(
|
||||||
|
size: 40,
|
||||||
|
Icons.update_disabled,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
t.homeView.noSavedAppFound,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ApplicationItem(
|
||||||
|
icon: app!.icon,
|
||||||
|
name: app!.name,
|
||||||
|
patchDate: app!.patchDate,
|
||||||
|
onPressed: () =>
|
||||||
|
locator<HomeViewModel>().navigateToAppInfo(app!, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,7 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
if (widget.isUnsupported &&
|
if (widget.isUnsupported &&
|
||||||
widget._managerAPI.isVersionCompatibilityCheckEnabled()) {
|
widget._managerAPI.isVersionCompatibilityCheckEnabled()) {
|
||||||
widget.isSelected = false;
|
widget.isSelected = false;
|
||||||
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
|
widget.toast.showBottom(t.patchItem.unsupportedPatchVersion);
|
||||||
} else if (widget.isChangeEnabled) {
|
} else if (widget.isChangeEnabled) {
|
||||||
if (!widget.isSelected) {
|
if (!widget.isSelected) {
|
||||||
if (widget.hasUnsupportedPatchOption) {
|
if (widget.hasUnsupportedPatchOption) {
|
||||||
@@ -103,7 +103,7 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
.isVersionCompatibilityCheckEnabled()) {
|
.isVersionCompatibilityCheckEnabled()) {
|
||||||
widget.isSelected = false;
|
widget.isSelected = false;
|
||||||
widget.toast.showBottom(
|
widget.toast.showBottom(
|
||||||
'patchItem.unsupportedPatchVersion',
|
t.patchItem.unsupportedPatchVersion,
|
||||||
);
|
);
|
||||||
} else if (widget.isChangeEnabled) {
|
} else if (widget.isChangeEnabled) {
|
||||||
if (!widget.isSelected) {
|
if (!widget.isSelected) {
|
||||||
@@ -170,7 +170,7 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
_showUnsupportedWarningDialog(),
|
_showUnsupportedWarningDialog(),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
shape: MaterialStateProperty.all(
|
shape: WidgetStateProperty.all(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(8),
|
BorderRadius.circular(8),
|
||||||
@@ -181,12 +181,10 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor:
|
backgroundColor: WidgetStateProperty.all(
|
||||||
MaterialStateProperty.all(
|
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
),
|
),
|
||||||
foregroundColor:
|
foregroundColor: WidgetStateProperty.all(
|
||||||
MaterialStateProperty.all(
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/gen/strings.g.dart';
|
import 'package:revanced_manager/gen/strings.g.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
@@ -398,6 +401,7 @@ class TextFieldForPatchOption extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||||
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
String? selectedKey;
|
String? selectedKey;
|
||||||
String? defaultValue;
|
String? defaultValue;
|
||||||
@@ -524,29 +528,49 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
onSelected: (String selection) async {
|
onSelected: (String selection) async {
|
||||||
switch (selection) {
|
// manageExternalStorage permission is required for file/folder selection
|
||||||
case 'file':
|
// otherwise, the app will not complain, but the patches will error out
|
||||||
final String? result = await FlutterFileDialog.pickFile();
|
// the same way as if the user selected an empty file/folder.
|
||||||
if (result != null) {
|
// Android 11 and above requires the manageExternalStorage permission
|
||||||
controller.text = result;
|
final Map<String, dynamic> availableActions = {
|
||||||
widget.onChanged(controller.text);
|
t.patchOptionsView.selectFilePath: () async {
|
||||||
|
if (_managerAPI.isScopedStorageAvailable) {
|
||||||
|
final permission =
|
||||||
|
await Permission.manageExternalStorage.request();
|
||||||
|
if (!permission.isGranted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
final FilePickerResult? result =
|
||||||
case 'folder':
|
await FilePicker.platform.pickFiles();
|
||||||
final DirectoryLocation? result =
|
if (result == null) {
|
||||||
await FlutterFileDialog.pickDirectory();
|
return;
|
||||||
if (result != null) {
|
|
||||||
controller.text = result.toString();
|
|
||||||
widget.onChanged(controller.text);
|
|
||||||
}
|
}
|
||||||
break;
|
controller.text = result.files.single.path!;
|
||||||
case 'remove':
|
widget.onChanged(controller.text);
|
||||||
|
},
|
||||||
|
t.patchOptionsView.selectFolder: () async {
|
||||||
|
if (_managerAPI.isScopedStorageAvailable) {
|
||||||
|
final permission =
|
||||||
|
await Permission.manageExternalStorage.request();
|
||||||
|
if (!permission.isGranted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final String? result =
|
||||||
|
await FilePicker.platform.getDirectoryPath();
|
||||||
|
if (result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.text = result;
|
||||||
|
widget.onChanged(controller.text);
|
||||||
|
},
|
||||||
|
t.remove: () {
|
||||||
widget.removeValue!();
|
widget.removeValue!();
|
||||||
break;
|
},
|
||||||
case 'null':
|
};
|
||||||
controller.text = '';
|
if (availableActions.containsKey(selection)) {
|
||||||
widget.onChanged(null);
|
await availableActions[selection]!();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:revanced_manager/gen/strings.g.dart';
|
import 'package:revanced_manager/gen/strings.g.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_last_patched_app.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_require_suggested_app_version.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_require_suggested_app_version.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_show_update_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_show_update_dialog.dart';
|
||||||
@@ -24,6 +25,7 @@ class SAdvancedSection extends StatelessWidget {
|
|||||||
SRequireSuggestedAppVersion(),
|
SRequireSuggestedAppVersion(),
|
||||||
SVersionCompatibilityCheck(),
|
SVersionCompatibilityCheck(),
|
||||||
SUniversalPatches(),
|
SUniversalPatches(),
|
||||||
|
SLastPatchedApp(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
37
lib/ui/widgets/settingsView/settings_last_patched_app.dart
Normal file
37
lib/ui/widgets/settingsView/settings_last_patched_app.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:revanced_manager/gen/strings.g.dart';
|
||||||
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
|
||||||
|
class SLastPatchedApp extends StatefulWidget {
|
||||||
|
const SLastPatchedApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SLastPatchedApp> createState() =>
|
||||||
|
_SLastPatchedAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
|
class _SLastPatchedAppState
|
||||||
|
extends State<SLastPatchedApp> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SwitchListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: Text(
|
||||||
|
t.settingsView.lastPatchedAppLabel,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(t.settingsView.lastPatchedAppHint),
|
||||||
|
value: _settingsViewModel.isLastPatchedAppEnabled(),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_settingsViewModel.useLastPatchedApp(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ class _ApplicationItemState extends State<ApplicationItem> {
|
|||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
|
onTap: widget.onPressed,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ class CustomSliverAppBar extends StatelessWidget {
|
|||||||
onPressed:
|
onPressed:
|
||||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
backgroundColor: MaterialStateColor.resolveWith(
|
backgroundColor: WidgetStateColor.resolveWith(
|
||||||
(states) => states.contains(MaterialState.scrolledUnder)
|
(states) => states.contains(WidgetState.scrolledUnder)
|
||||||
? Theme.of(context).colorScheme.surface
|
? Theme.of(context).colorScheme.surface
|
||||||
: Theme.of(context).canvasColor,
|
: Theme.of(context).canvasColor,
|
||||||
),
|
),
|
||||||
|
|||||||
48
pubspec.lock
48
pubspec.lock
@@ -306,6 +306,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
sha256: "2ca051989f69d1b2ca012b2cf3ccf78c70d40144f0861ff2c063493f7c8c3d45"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.5"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -385,10 +393,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: "9921f9deda326f8a885e202b1e35237eadfc1345239a0f6f0f1ff287e047547f"
|
sha256: "2e8a801b1ded5ea001a4529c97b1f213dcb11c6b20668e081cafb23468593514"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.1"
|
version: "0.7.3"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.20"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -571,18 +587,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.5"
|
version: "10.0.4"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.3"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -644,18 +660,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.8.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
|
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.0"
|
version: "1.12.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1148,10 +1164,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
|
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.1"
|
version: "0.7.0"
|
||||||
timeago:
|
timeago:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1276,10 +1292,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
|
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.2"
|
version: "14.2.1"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1361,5 +1377,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.19.2"
|
flutter: ">=3.22.0"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ homepage: https://github.com/ReVanced/revanced-manager
|
|||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.21.0-dev.4+101800020
|
version: 1.21.0-dev.7+101800023
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -23,6 +23,7 @@ dependencies:
|
|||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
dynamic_themes: ^1.1.0
|
dynamic_themes: ^1.1.0
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
|
file_picker: ^8.0.3
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_background:
|
flutter_background:
|
||||||
@@ -34,7 +35,7 @@ dependencies:
|
|||||||
flutter_local_notifications: ^17.1.0
|
flutter_local_notifications: ^17.1.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_markdown: ^0.7.1
|
flutter_markdown: ^0.7.3
|
||||||
fluttertoast: ^8.2.5
|
fluttertoast: ^8.2.5
|
||||||
font_awesome_flutter: ^10.7.0
|
font_awesome_flutter: ^10.7.0
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
|
|||||||
Reference in New Issue
Block a user