Compare commits

...

36 Commits

Author SHA1 Message Date
semantic-release-bot
65feb34242 chore(release): 1.21.0-dev.7 [skip ci]
# [1.21.0-dev.7](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.6...v1.21.0-dev.7) (2024-07-29)

### Features

* Include primary architecture in external search ([#2068](https://github.com/ReVanced/revanced-manager/issues/2068)) ([23690a9](23690a98df))
2024-07-29 16:04:12 +00:00
Pun Butrach
23690a98df feat: Include primary architecture in external search (#2068) 2024-07-29 22:56:00 +07:00
Pun Butrach
7449d4e318 docs: Link user to the latest version of app (#2077) 2024-07-25 09:27:29 +07:00
validcube
c6f9e36f4b refactor: Migrate deprecated member 2024-07-13 17:32:34 +07:00
Pun Butrach
e9cee0abe2 ci: Prefer installing NPM dependencies from lock 2024-07-13 17:04:43 +07:00
Benjamin
9440f23b55 chore: Remove NDK constraint (#2016)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2024-07-13 16:55:02 +07:00
dependabot[bot]
c67b4b438c build(deps): bump flutter_markdown from 0.7.1 to 0.7.3 (#2022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-12 14:07:16 +07:00
kitadai31
1bdb820aed refactor: Remove unused strings (#2010) 2024-07-11 11:28:02 +07:00
semantic-release-bot
a28d77bc65 chore(release): 1.21.0-dev.6 [skip ci]
# [1.21.0-dev.6](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.5...v1.21.0-dev.6) (2024-06-30)

### Bug Fixes

* Add missing import to patch options field ([d60f9aa](d60f9aa1d8))
* Follow system theme immediately ([#1942](https://github.com/ReVanced/revanced-manager/issues/1942)) ([694f2a9](694f2a9fae))
* Handle selecting files and folders for patch options correctly ([#1941](https://github.com/ReVanced/revanced-manager/issues/1941)) ([b26760b](b26760b216))
* Increase dashboard RefreshIndicator edge offset ([#1859](https://github.com/ReVanced/revanced-manager/issues/1859)) ([232b702](232b702789))
* Select previously applied patches when loading patch selection ([#1865](https://github.com/ReVanced/revanced-manager/issues/1865)) ([7ef8f04](7ef8f0454b))
* Unsupported patch toast says "patchItem.unsupportedPatchVersion" ([#2011](https://github.com/ReVanced/revanced-manager/issues/2011)) ([3209c0e](3209c0e430))

### Features

* Save last patched app ([#1414](https://github.com/ReVanced/revanced-manager/issues/1414)) ([7720408](77204087bb))
2024-06-30 20:45:38 +00:00
Benjamin Halko
d60f9aa1d8 fix: Add missing import to patch options field
Patch options fields was missing the `patch_options_viewmodel` import, preventing building to complete successfully
2024-06-30 13:38:01 -07:00
kitadai31
3209c0e430 fix: Unsupported patch toast says "patchItem.unsupportedPatchVersion" (#2011) 2024-06-30 15:24:25 +07:00
aAbed
7ef8f0454b fix: Select previously applied patches when loading patch selection (#1865) 2024-06-29 14:40:20 +02:00
DMzS
232b702789 fix: Increase dashboard RefreshIndicator edge offset (#1859) 2024-06-29 14:40:04 +02:00
Snehith
694f2a9fae fix: Follow system theme immediately (#1942)
Co-authored-by: surya-technovert <surya.m@technovert.com>
2024-06-29 14:39:00 +02:00
Benjamin
77204087bb feat: Save last patched app (#1414)
Co-authored-by: aAbed <39409020+TheAabedKhan@users.noreply.github.com>
Co-authored-by: Ushie <ushiekane@gmail.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Mr. X <79870712+n30mrx@users.noreply.github.com>
Co-authored-by: festry0 <153519925+festry0@users.noreply.github.com>
2024-06-29 14:38:00 +02:00
Francesco Marastoni
b26760b216 fix: Handle selecting files and folders for patch options correctly (#1941) 2024-06-29 14:34:34 +02:00
semantic-release-bot
3c36950aeb chore(release): 1.21.0-dev.5 [skip ci]
# [1.21.0-dev.5](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.4...v1.21.0-dev.5) (2024-06-26)

### Bug Fixes

* Adjust scroll from clipping children form fields in `AlertDialog` from `showSourcesDialog` ([#1782](https://github.com/ReVanced/revanced-manager/issues/1782)) ([bbeb836](bbeb836923))
2024-06-26 22:53:56 +00:00
DMzS
bbeb836923 fix: Adjust scroll from clipping children form fields in AlertDialog from showSourcesDialog (#1782) 2024-06-27 05:45:55 +07:00
semantic-release-bot
a99406f0a9 chore(release): 1.21.0-dev.4 [skip ci]
# [1.21.0-dev.4](https://github.com/ReVanced/revanced-manager/compare/v1.21.0-dev.3...v1.21.0-dev.4) (2024-06-24)

### Bug Fixes

* Cache external API calls  ([#1911](https://github.com/ReVanced/revanced-manager/issues/1911)) ([2c3e2e6](2c3e2e639f))
* Follow language update immediately ([#1944](https://github.com/ReVanced/revanced-manager/issues/1944)) ([c13827e](c13827e8e1))
* SecurityException when patching application ([#1856](https://github.com/ReVanced/revanced-manager/issues/1856)) ([e0a6de2](e0a6de2c2b))
* Update dialog shows dev version & loading gets stuck in certain circumstances ([#1792](https://github.com/ReVanced/revanced-manager/issues/1792)) ([fc52560](fc52560244))

### Features

* Add ability to set `null` in patch options ([#1947](https://github.com/ReVanced/revanced-manager/issues/1947)) ([5c68d51](5c68d513a3))
2024-06-24 17:03:46 +00:00
Pun Butrach
73368b58be build: Support for Flutter 3.22 (#1921)
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-06-24 23:55:37 +07:00
ReVanced Bot
ca14e77ba3 chore: Sync translations (#1899) 2024-06-24 23:55:03 +07:00
Pun Butrach
cafdfcda47 ci: Don't fail validation on unimportant warnings 2024-06-24 23:52:23 +07:00
aAbed
5c68d513a3 feat: Add ability to set null in patch options (#1947) 2024-06-24 23:37:16 +07:00
kitadai31
fc52560244 fix: Update dialog shows dev version & loading gets stuck in certain circumstances (#1792)
Signed-off-by: validcube <pun.butrach@gmail.com>
Co-authored-by: validcube <pun.butrach@gmail.com>
2024-06-19 14:44:09 +07:00
Pun Butrach
46f6a49a7a ci: Always run on dev branch only 2024-06-15 17:36:25 +07:00
Snehith
c13827e8e1 fix: Follow language update immediately (#1944)
Co-authored-by: surya-technovert <surya.m@technovert.com>
2024-06-15 17:21:47 +07:00
Pun Butrach
e0a6de2c2b fix: SecurityException when patching application (#1856) 2024-05-28 11:36:31 +07:00
yonggamer
afdba00722 build: Fix invalid Gradle wrapper checksum (#1919) 2024-05-28 09:31:48 +07:00
oSumAtrIX
9084c71aa3 build: Bump dependencies 2024-05-26 01:21:14 +02:00
oSumAtrIX
8fc5fb6a80 docs: Improve issue templates 2024-05-26 00:43:38 +02:00
validcube
5f762c5442 build: Update Dart dependencies
Signed-off-by: validcube <pun.butrach@gmail.com>
2024-05-19 19:15:35 +07:00
Pun Butrach
8b21ec1ea3 ci: Switch to Flutter instead 2024-05-19 18:54:48 +07:00
Pun Butrach
e83fbb864e ci: Run slang first before validating translation 2024-05-19 18:46:49 +07:00
KobeW50
f03af17f71 docs: Fix issue template mistakes (#1910) 2024-05-19 01:33:18 +07:00
kitadai31
2c3e2e639f fix: Cache external API calls (#1911) 2024-05-18 10:52:13 -07:00
KobeW50
cc85b393dc docs: Fix punctuation in issue forms (#1909) 2024-05-18 01:18:29 +07:00
63 changed files with 1473 additions and 1088 deletions

View File

@@ -70,7 +70,7 @@ body:
Before creating a new bug report, please keep the following in mind: Before creating a new bug report, please keep the following in mind:
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-manager/labels/Bug%20report). - **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-manager/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager/blob/main/CONTRIBUTING.md). - **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea - type: textarea
@@ -99,14 +99,14 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: ReVanced Manager logs label: ReVanced Manager logs
description: Export logs in ReVanced Manager settings. description: Export logs from the ReVanced Manager settings.
render: shell render: shell
validations: validations:
required: true required: true
- type: textarea - type: textarea
attributes: attributes:
label: Patch logs label: Patch logs
description: Export logs in "Patcher" screen. description: Export logs from the "Patcher" screen.
render: shell render: shell
validations: validations:
required: false required: false
@@ -116,11 +116,11 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below. description: Your bug report will be closed if you don't follow the checklist below.
options: options:
- label: This issue is not a duplicate of an existing bug report. - label: I have checked all open and closed bug reports and this is not a duplicate.
required: true required: true
- label: I have chosen an appropriate title. - label: I have chosen an appropriate title.
required: true required: true
- label: All requested information has been provided properly. - label: All requested information has been provided properly.
required: true required: true
- label: The bug is only related to ReVanced Manager - label: The bug is only related to ReVanced Manager.
required: true required: true

View File

@@ -70,7 +70,7 @@ body:
Before creating a new feature request, please keep the following in mind: Before creating a new feature request, please keep the following in mind:
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-manager//labels/Feature%20request). - **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-manager/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager/blob/main/CONTRIBUTING.md). - **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-manager/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app). - **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea - type: textarea
@@ -79,7 +79,6 @@ body:
description: | description: |
- Describe your feature in detail - Describe your feature in detail
- Add images, videos, links, examples, references, etc. if possible - Add images, videos, links, examples, references, etc. if possible
- Add the target application name in case you request a new patch
- type: textarea - type: textarea
attributes: attributes:
label: Motivation label: Motivation
@@ -98,9 +97,9 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your feature request will be closed if you don't follow the checklist below. description: Your feature request will be closed if you don't follow the checklist below.
options: options:
- label: This issue is not a duplicate of an existing feature request. - label: I have checked all open and closed feature requests and this is not a duplicate.
required: true required: true
- label: I have chosen an appropriate title. - label: I have chosen an appropriate title.
required: true required: true
- label: The feature request is only related to ReVanced Manager - label: The feature request is only related to ReVanced Manager.
required: true required: true

View File

@@ -61,6 +61,7 @@ jobs:
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"
flutter-version: 3.22.x
cache: ${{ inputs.flutter-cache }} cache: ${{ inputs.flutter-cache }}
- name: Get dependencies - name: Get dependencies

View File

@@ -36,9 +36,10 @@ jobs:
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"
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

View File

@@ -5,6 +5,7 @@ on:
schedule: schedule:
- cron: 00 12 * * 1 - cron: 00 12 * * 1
push: push:
branches: dev
paths: paths:
- assets/i18n/*.json - assets/i18n/*.json
- assets/i18n/*.dart - assets/i18n/*.dart
@@ -20,8 +21,11 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Dart - name: Setup Flutter
uses: dart-lang/setup-dart@v1 uses: subosito/flutter-action@v2
with:
cache: true
flutter-version: 3.22.x
- name: Sync translations from Crowdin - name: Sync translations from Crowdin
uses: crowdin/github-action@v1 uses: crowdin/github-action@v1
@@ -51,7 +55,9 @@ jobs:
- name: Validation of Translation Strings - name: Validation of Translation Strings
run: | run: |
dart analyze lib/gen/strings.g.dart dart pub get
dart run slang
flutter analyze lib/gen/strings.g.dart --no-fatal-infos --no-fatal-warnings
- name: Commit translations - name: Commit translations
run: | run: |

View File

@@ -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

View File

@@ -23,10 +23,10 @@ if (flutterVersionName == null) {
} }
android { android {
compileSdk flutter.compileSdkVersion compileSdk 34
ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17
} }
@@ -113,6 +113,7 @@ flutter {
} }
dependencies { dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") // https://pub.dev/packages/flutter_local_notifications#gradle-setup
implementation("app.revanced:revanced-patcher:19.3.1") implementation("app.revanced:revanced-patcher:19.3.1")
implementation("app.revanced:revanced-library:2.2.1") implementation("app.revanced:revanced-library:2.2.1")
} }

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -18,8 +18,8 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.2" apply false id "com.android.application" version "8.4.1" apply false
id "org.jetbrains.kotlin.android" version "1.9.23" apply false id "org.jetbrains.kotlin.android" version "2.0.0" apply false
} }
include ":app" include ":app"

View File

@@ -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": {
@@ -118,16 +104,15 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Custom value", "customValue": "Custom value",
"resetOptionsTooltip": "Reset patch options", "setToNull": "Set to null",
"nullValue": "This option value is currently null",
"viewTitle": "Patch options", "viewTitle": "Patch options",
"saveOptions": "Save", "saveOptions": "Save",
"addOptions": "Add options", "unselectPatch": "Unselect patch",
"deselectPatch": "Deselect 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": "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}"
}, },
@@ -139,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",
@@ -148,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",
@@ -173,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",
@@ -203,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",
@@ -254,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"

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "تخصيص القيمة", "customValue": "تخصيص القيمة",
"setToNull": "تعيين إلى فارغ",
"nullValue": "قيمة هذا الخيار فارغة حاليا",
"resetOptionsTooltip": "إعادة تعيين خيارات التعديل", "resetOptionsTooltip": "إعادة تعيين خيارات التعديل",
"viewTitle": "خيارات التعديل", "viewTitle": "خيارات التعديل",
"saveOptions": "حفظ", "saveOptions": "حفظ",
"addOptions": "إضافة خيارات", "addOptions": "إضافة خيارات",
"deselectPatch": "إلغاء تحديد التعديل", "unselectPatch": "إلغاء تحديد التصحيح",
"tooltip": "المزيد من خيارات الإدخال", "tooltip": "المزيد من خيارات الإدخال",
"selectFilePath": "تحديد مسار الملف", "selectFilePath": "تحديد مسار الملف",
"selectFolder": "تحديد مجلد", "selectFolder": "تحديد مجلد",
"selectOption": "تحديد خيار", "selectOption": "تحديد خيار",
"requiredOption": "هذا الخيار مطلوب", "requiredOption": "إعداد هذا الخيار مطلوب",
"unsupportedOption": "هذا الخيار غير مدعوم", "unsupportedOption": "هذا الخيار غير مدعوم",
"requiredOptionNull": "يجب تعيين الخيارات التالية:\n\n${options}" "requiredOptionNull": "يجب تعيين الخيارات التالية:\n\n${options}"
}, },
@@ -143,7 +145,7 @@
"installType": "تحديد نوع التثبيت", "installType": "تحديد نوع التثبيت",
"installTypeDescription": "تحديد نوع التثبيت للمتابعة.", "installTypeDescription": "تحديد نوع التثبيت للمتابعة.",
"installButton": "تثبيت", "installButton": "تثبيت",
"installRootType": "تحميل", "installRootType": "Mount",
"installNonRootType": "عادي", "installNonRootType": "عادي",
"warning": "قم بتعطيل التحديثات التلقائية للتطبيق المعدل لتجنب المشكلات غير المتوقعة.", "warning": "قم بتعطيل التحديثات التلقائية للتطبيق المعدل لتجنب المشكلات غير المتوقعة.",
"pressBackAgain": "اضغط رجوع مرة اخرى للإلغاء", "pressBackAgain": "اضغط رجوع مرة اخرى للإلغاء",
@@ -255,14 +257,14 @@
"widgetTitle": "معلومات التطبيق", "widgetTitle": "معلومات التطبيق",
"openButton": "فتح", "openButton": "فتح",
"uninstallButton": "إلغاء التثبيت", "uninstallButton": "إلغاء التثبيت",
"unmountButton": "إلغاء التحميل", "unmountButton": "Unmount",
"rootDialogTitle": "خطأ", "rootDialogTitle": "خطأ",
"unmountDialogText": "هل أنت متأكد أنك تريد إلغاء تحميل هذا التطبيق؟", "unmountDialogText": "هل أنت متأكد من أنك تريد Unmount لهذا التطبيق؟",
"uninstallDialogText": "هل أنت متأكد من أنك تريد إلغاء تثبيت هذا التطبيق؟", "uninstallDialogText": "هل أنت متأكد من أنك تريد إلغاء تثبيت هذا التطبيق؟",
"rootDialogText": "تم تثبيت التطبيق بأذونات المستخدم المتميز، لكن ReVanced Manager ليس لديه أذونات حاليًا.\nالرجاء منح أذونات المستخدم المتميز أولاً.", "rootDialogText": "تم تثبيت التطبيق بأذونات المستخدم المتميز، لكن ReVanced Manager ليس لديه أذونات حاليًا.\nالرجاء منح أذونات المستخدم المتميز أولاً.",
"packageNameLabel": "اسم الحُزْمَة", "packageNameLabel": "اسم الحُزْمَة",
"installTypeLabel": "نوع التثبيت", "installTypeLabel": "نوع التثبيت",
"mountTypeLabel": "تحميل", "mountTypeLabel": "Mount",
"regularTypeLabel": "عادي", "regularTypeLabel": "عادي",
"patchedDateLabel": "تاريخ التعديل", "patchedDateLabel": "تاريخ التعديل",
"appliedPatchesLabel": "التعديلات المطبقة", "appliedPatchesLabel": "التعديلات المطبقة",

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Vlastní hodnota", "customValue": "Vlastní hodnota",
"setToNull": "Nastavit na null",
"nullValue": "Tato hodnota volby je v současné době null",
"resetOptionsTooltip": "Obnovit nastavení záplat", "resetOptionsTooltip": "Obnovit nastavení záplat",
"viewTitle": "Nastavení záplat", "viewTitle": "Nastavení záplat",
"saveOptions": "Uložit", "saveOptions": "Uložit",
"addOptions": "Přidat možnosti", "addOptions": "Přidat možnosti",
"deselectPatch": "Odznačit záplatu", "unselectPatch": "Zrušit výběr patch",
"tooltip": "Další možnosti vstupu", "tooltip": "Další možnosti vstupu",
"selectFilePath": "Zvolte cestu k souboru", "selectFilePath": "Zvolte cestu k souboru",
"selectFolder": "Vybrat složku", "selectFolder": "Vybrat složku",
"selectOption": "Vybrat možnost", "selectOption": "Vybrat možnost",
"requiredOption": "Tato možnost je vyžadována", "requiredOption": "Nastavení této možnosti je vyžadováno",
"unsupportedOption": "Tato možnost není podporována", "unsupportedOption": "Tato možnost není podporována",
"requiredOptionNull": "Tyto možnosti musí být nastaveny:\n\n${options}" "requiredOptionNull": "Tyto možnosti musí být nastaveny:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Tilpasset værdi", "customValue": "Tilpasset værdi",
"setToNull": "Sæt til nul",
"nullValue": "Denne valgmulighed værdi er i øjeblikket nul",
"resetOptionsTooltip": "Nulstil patch indstillinger", "resetOptionsTooltip": "Nulstil patch indstillinger",
"viewTitle": "Patch indstillinger", "viewTitle": "Patch indstillinger",
"saveOptions": "Gem", "saveOptions": "Gem",
"addOptions": "Tilføj indstillinger", "addOptions": "Tilføj indstillinger",
"deselectPatch": "Fravælg patch", "unselectPatch": "Fravælg patch",
"tooltip": "Flere input-indstillinger", "tooltip": "Flere input-indstillinger",
"selectFilePath": "Vælg fil sti", "selectFilePath": "Vælg fil sti",
"selectFolder": "Vælg mappe", "selectFolder": "Vælg mappe",
"selectOption": "Vælg indstilling", "selectOption": "Vælg indstilling",
"requiredOption": "Denne indstilling er påkrævet", "requiredOption": "Indstilling af denne indstilling er påkrævet",
"unsupportedOption": "Denne indstilling understøttes ikke", "unsupportedOption": "Denne indstilling understøttes ikke",
"requiredOptionNull": "Følgende indstillinger skal indstilles:\n\n${options}" "requiredOptionNull": "Følgende indstillinger skal indstilles:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Benutzerdefinierter Wert", "customValue": "Benutzerdefinierter Wert",
"setToNull": "Auf Null setzen",
"nullValue": "Dieser Optionswert ist derzeit null",
"resetOptionsTooltip": "Patch-Optionen zurücksetzen", "resetOptionsTooltip": "Patch-Optionen zurücksetzen",
"viewTitle": "Patch-Optionen", "viewTitle": "Patch-Optionen",
"saveOptions": "Speichern", "saveOptions": "Speichern",
"addOptions": "Option hinzufügen", "addOptions": "Option hinzufügen",
"deselectPatch": "Patch abwählen", "unselectPatch": "Patch entfernen",
"tooltip": "Weitere Eingabeoptionen", "tooltip": "Weitere Eingabeoptionen",
"selectFilePath": "Dateipfad auswählen", "selectFilePath": "Dateipfad auswählen",
"selectFolder": "Ordner auswählen", "selectFolder": "Ordner auswählen",
"selectOption": "Option auswählen", "selectOption": "Option auswählen",
"requiredOption": "Diese Option ist erforderlich.", "requiredOption": "Einstellung dieser Option ist erforderlich",
"unsupportedOption": "Dieser Vorgang ist nicht unterstützt.", "unsupportedOption": "Dieser Vorgang ist nicht unterstützt.",
"requiredOptionNull": "Die folgenden Optionen müssen gesetzt sein:\n\n${options}" "requiredOptionNull": "Die folgenden Optionen müssen gesetzt sein:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Προσαρμοσμένη τιμή", "customValue": "Προσαρμοσμένη τιμή",
"setToNull": "Ορισμός σε null",
"nullValue": "Αυτή η επιλογή είναι κενή",
"resetOptionsTooltip": "Επαναφορά επιλογών τροποποιήσεων", "resetOptionsTooltip": "Επαναφορά επιλογών τροποποιήσεων",
"viewTitle": "Επιλογές τροποποιήσεων", "viewTitle": "Επιλογές τροποποιήσεων",
"saveOptions": "Αποθήκευση", "saveOptions": "Αποθήκευση",
"addOptions": "Προσθήκη επιλογών", "addOptions": "Προσθήκη επιλογών",
"deselectPatch": "Αποεπιλέξτε τροποποιήσεις", "unselectPatch": "Αποεπιλογή patch",
"tooltip": "Περισσότερες επιλογές εισόδου", "tooltip": "Περισσότερες επιλογές εισόδου",
"selectFilePath": "Επιλογή τοποθεσίας αρχείου", "selectFilePath": "Επιλογή τοποθεσίας αρχείου",
"selectFolder": "Επιλογή φακέλου", "selectFolder": "Επιλογή φακέλου",
"selectOption": "Επιλογή ρύθμισης", "selectOption": "Επιλογή ρύθμισης",
"requiredOption": "Αυτή η επιλογή απαιτείται", "requiredOption": "Απαιτείται ρύθμιση αυτής της επιλογής",
"unsupportedOption": "Αυτή η επιλογή δεν υποστηρίζεται", "unsupportedOption": "Αυτή η επιλογή δεν υποστηρίζεται",
"requiredOptionNull": "Πρέπει να οριστούν οι παρακάτω επιλογές:\n\n${options}" "requiredOptionNull": "Πρέπει να οριστούν οι παρακάτω επιλογές:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valor personalizado", "customValue": "Valor personalizado",
"setToNull": "Establecer a nulo",
"nullValue": "Este valor de opción es nulo actualmente",
"resetOptionsTooltip": "Restablecer a los valores por defecto", "resetOptionsTooltip": "Restablecer a los valores por defecto",
"viewTitle": "Configuración\\ndel parche", "viewTitle": "Configuración\\ndel parche",
"saveOptions": "Guardar configuración", "saveOptions": "Guardar configuración",
"addOptions": "Agregar configuración", "addOptions": "Agregar configuración",
"deselectPatch": "Deseleccionar parche", "unselectPatch": "Deseleccionar parche",
"tooltip": "Más opciones de entrada", "tooltip": "Más opciones de entrada",
"selectFilePath": "Seleccionar ruta del archivo", "selectFilePath": "Seleccionar ruta del archivo",
"selectFolder": "Seleccionar carpeta", "selectFolder": "Seleccionar carpeta",
"selectOption": "Seleccionar opción", "selectOption": "Seleccionar opción",
"requiredOption": "Esta opción es requerida", "requiredOption": "La configuración de esta opción es necesaria",
"unsupportedOption": "Esta opción no es compatible", "unsupportedOption": "Esta opción no es compatible",
"requiredOptionNull": "Tenés que configurar las siguientes opciones:\\n\\n${options}" "requiredOptionNull": "Tenés que configurar las siguientes opciones:\\n\\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valor personalizado", "customValue": "Valor personalizado",
"setToNull": "Establecer a nulo",
"nullValue": "Este valor de opción es nulo actualmente",
"resetOptionsTooltip": "Restablecer las opciones de parche", "resetOptionsTooltip": "Restablecer las opciones de parche",
"viewTitle": "Opciones de parche", "viewTitle": "Opciones de parche",
"saveOptions": "Guardar", "saveOptions": "Guardar",
"addOptions": "Añadir opciones", "addOptions": "Añadir opciones",
"deselectPatch": "Deseleccionar parche", "unselectPatch": "Deseleccionar parche",
"tooltip": "Más opciones de entrada", "tooltip": "Más opciones de entrada",
"selectFilePath": "Seleccionar ruta del archivo", "selectFilePath": "Seleccionar ruta del archivo",
"selectFolder": "Seleccionar carpeta", "selectFolder": "Seleccionar carpeta",
"selectOption": "Seleccionar opción", "selectOption": "Seleccionar opción",
"requiredOption": "Esta opción es necesaria", "requiredOption": "La configuración de esta opción es necesaria",
"unsupportedOption": "Esta opción no está disponible", "unsupportedOption": "Esta opción no está disponible",
"requiredOptionNull": "Hay que establecer las siguientes opciones:\n\n${options}" "requiredOptionNull": "Hay que establecer las siguientes opciones:\n\n${options}"
}, },

View File

@@ -41,7 +41,7 @@
"downloadConsentDialogTitle": "¿Descargar archivos necesarios?", "downloadConsentDialogTitle": "¿Descargar archivos necesarios?",
"downloadConsentDialogText": "ReVanced Manager necesita descargar los archivos necesarios para funcionar correctamente.", "downloadConsentDialogText": "ReVanced Manager necesita descargar los archivos necesarios para funcionar correctamente.",
"downloadConsentDialogText2": "Esto te conectará a ${url}.", "downloadConsentDialogText2": "Esto te conectará a ${url}.",
"checkUpdateDialogTitle": "¿Buscar actualizaciones?", "checkUpdateDialogTitle": "¿Comprobar actualizaciones?",
"checkUpdateDialogText": "¿Quieres que ReVanced Manager compruebe si hay actualizaciones automáticamente?", "checkUpdateDialogText": "¿Quieres que ReVanced Manager compruebe si hay actualizaciones automáticamente?",
"notificationTitle": "Actualización descargada", "notificationTitle": "Actualización descargada",
"notificationText": "Toca para instalar la actualización", "notificationText": "Toca para instalar la actualización",
@@ -91,14 +91,14 @@
}, },
"appSelectorView": { "appSelectorView": {
"viewTitle": "Seleccionar una aplicación", "viewTitle": "Seleccionar una aplicación",
"searchBarHint": "Buscar app", "searchBarHint": "Buscar aplicación",
"storageButton": "Almacenamiento", "storageButton": "Almacenamiento",
"selectFromStorageButton": "Seleccionar desde almacenamiento", "selectFromStorageButton": "Seleccionar desde almacenamiento",
"errorMessage": "No se puede usar la aplicación seleccionada", "errorMessage": "No se puede usar la aplicación seleccionada",
"downloadToast": "La función de descarga aún no está disponible", "downloadToast": "La función de descarga aún no está disponible",
"requireSuggestedAppVersionDialogText": "La versión de la aplicación que has seleccionado no coincide con la versión sugerida, lo que podría causar errores inesperados. Por favor, utiliza la versión sugerida.\n\nVersión seleccionada: ${selected}\nVersión sugerida: ${suggested}\n\nPara continuar de todas formas, desactiva \"Requerir versión sugerida de la aplicación\" en la configuración.", "requireSuggestedAppVersionDialogText": "La versión de la aplicación que has seleccionado no coincide con la versión sugerida, lo que podría causar errores inesperados. Por favor, utiliza la versión sugerida.\n\nVersión seleccionada: ${selected}\nVersión sugerida: ${suggested}\n\nPara continuar de todas formas, desactiva \"Requerir versión sugerida de la aplicación\" en la configuración.",
"featureNotAvailable": "Característica no implementada", "featureNotAvailable": "Característica no implementada",
"featureNotAvailableText": "Esta aplicación es un APK dividido y solo puede ser parcheada e instalada de forma fiable mediante el montaje con permisos de root. Sin embargo, puedes parchear e instalar un APK completo seleccionándolo del almacenamiento." "featureNotAvailableText": "Esta aplicación es una APK dividida y solo puede ser parcheada e instalada confiablemente al montar con permisos de root. Sin embargo, puedes parchear e instalar una APK completa seleccionándola en el almacenamiento."
}, },
"patchesSelectorView": { "patchesSelectorView": {
"viewTitle": "Selecciona parches", "viewTitle": "Selecciona parches",
@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valor personalizado", "customValue": "Valor personalizado",
"setToNull": "Establecer a nulo",
"nullValue": "Este valor de opción es nulo actualmente",
"resetOptionsTooltip": "Restablecer las opciones de parche", "resetOptionsTooltip": "Restablecer las opciones de parche",
"viewTitle": "Opciones de parche", "viewTitle": "Opciones de parche",
"saveOptions": "Guardar", "saveOptions": "Guardar",
"addOptions": "Añadir opciones", "addOptions": "Añadir opciones",
"deselectPatch": "Deseleccionar parche", "unselectPatch": "Deseleccionar parche",
"tooltip": "Más opciones de entrada", "tooltip": "Más opciones de entrada",
"selectFilePath": "Selecciona la ruta del archivo", "selectFilePath": "Selecciona la ruta del archivo",
"selectFolder": "Selecciona la carpeta", "selectFolder": "Selecciona la carpeta",
"selectOption": "Seleccionar opción", "selectOption": "Seleccionar opción",
"requiredOption": "Esta opción es requerida", "requiredOption": "La configuración de esta opción es necesaria",
"unsupportedOption": "Esta opción no está disponible", "unsupportedOption": "Esta opción no está disponible",
"requiredOptionNull": "Hay que configurar las siguientes opciones:\n\n${options}" "requiredOptionNull": "Hay que configurar las siguientes opciones:\n\n${options}"
}, },
@@ -196,7 +198,7 @@
"enablePatchesSelectionLabel": "Permitir cambiar la selección de parches", "enablePatchesSelectionLabel": "Permitir cambiar la selección de parches",
"enablePatchesSelectionHint": "No prevenir la selección o deselección de parches", "enablePatchesSelectionHint": "No prevenir la selección o deselección de parches",
"enablePatchesSelectionWarningText": "Cambiar la selección de parches puede cauar problemas inespereados.\n\n¿Quieres activarlo de todas formas?", "enablePatchesSelectionWarningText": "Cambiar la selección de parches puede cauar problemas inespereados.\n\n¿Quieres activarlo de todas formas?",
"disablePatchesSelectionWarningText": "Estás a punto de desactivar cambiar la selección de parches.\nLa selección predeterminada de parches se restaura.\n\n¿Deshabilitar de todos modos?", "disablePatchesSelectionWarningText": "Estás a punto de desactivar cambiar la selección de los parches.\nLa selección predeterminada de los parches se restaurada.\n\n¿Desactivar de todos modos?",
"autoUpdatePatchesLabel": "Actualizar parches automáticamente", "autoUpdatePatchesLabel": "Actualizar parches automáticamente",
"autoUpdatePatchesHint": "Actualiza los parches a la última versión automáticamente", "autoUpdatePatchesHint": "Actualiza los parches a la última versión automáticamente",
"showUpdateDialogLabel": "Mostrar diálogo de actualización", "showUpdateDialogLabel": "Mostrar diálogo de actualización",
@@ -252,7 +254,7 @@
"keystoreSelectorErrorMessage": "No se puede utilizar el archivo de repositorio de claves seleccionado" "keystoreSelectorErrorMessage": "No se puede utilizar el archivo de repositorio de claves seleccionado"
}, },
"appInfoView": { "appInfoView": {
"widgetTitle": "Informacion de la applicacion", "widgetTitle": "Informacion de la aplicación",
"openButton": "Abrir", "openButton": "Abrir",
"uninstallButton": "Desinstalar", "uninstallButton": "Desinstalar",
"unmountButton": "Desmontar", "unmountButton": "Desmontar",

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Oma arvo", "customValue": "Oma arvo",
"setToNull": "Aseta tyhjäksi",
"nullValue": "Tämä valinta arvo on tällä hetkellä nolla",
"resetOptionsTooltip": "Palauta paikkausasetukset", "resetOptionsTooltip": "Palauta paikkausasetukset",
"viewTitle": "Paikkausasetukset", "viewTitle": "Paikkausasetukset",
"saveOptions": "Tallenna", "saveOptions": "Tallenna",
"addOptions": "Lisää asetuksia", "addOptions": "Lisää asetuksia",
"deselectPatch": "Poista paikkauksen valinta", "unselectPatch": "Peru korjauksen valinta",
"tooltip": "Enemmän syöteasetuksia", "tooltip": "Enemmän syöteasetuksia",
"selectFilePath": "Valitse tiedostosijainti", "selectFilePath": "Valitse tiedostosijainti",
"selectFolder": "Valitse kansio", "selectFolder": "Valitse kansio",
"selectOption": "Valitse asetus", "selectOption": "Valitse asetus",
"requiredOption": "Tämä asetus vaaditaan", "requiredOption": "Tämän asetuksen asettaminen on pakollinen",
"unsupportedOption": "Tätä asetusta ei tueta", "unsupportedOption": "Tätä asetusta ei tueta",
"requiredOptionNull": "Seuraavat asetukset on määritettävä:\n\n${options}" "requiredOptionNull": "Seuraavat asetukset on määritettävä:\n\n${options}"
}, },

View File

@@ -29,7 +29,7 @@
"updatesSubtitle": "Mises à jour", "updatesSubtitle": "Mises à jour",
"patchedSubtitle": "Applications patchées", "patchedSubtitle": "Applications patchées",
"changeLaterSubtitle": "Vous pouvez changer cela dans les paramètres ultérieurement.", "changeLaterSubtitle": "Vous pouvez changer cela dans les paramètres ultérieurement.",
"noUpdates": "Aucune mise à jour n'est disponible", "noUpdates": "Aucune mise à jour disponible",
"WIP": "Bientôt disponible...", "WIP": "Bientôt disponible...",
"noInstallations": "Aucune application patchée installée", "noInstallations": "Aucune application patchée installée",
"installUpdate": "Continuer à installer la mise à jour ?", "installUpdate": "Continuer à installer la mise à jour ?",
@@ -51,7 +51,7 @@
"errorDownloadMessage": "Impossible de télécharger la mise à jour", "errorDownloadMessage": "Impossible de télécharger la mise à jour",
"errorInstallMessage": "Impossible d'installer la mise à jour", "errorInstallMessage": "Impossible d'installer la mise à jour",
"noConnection": "Aucune connexion internet", "noConnection": "Aucune connexion internet",
"updatesDisabled": "La mise à jour d'une application patchée est actuellement désactivée. Repatchez l'application à nouveau." "updatesDisabled": "La mise à jour d'une application patchée est actuellement désactivée. Repatchez l'application."
}, },
"applicationItem": { "applicationItem": {
"infoButton": "Info" "infoButton": "Info"
@@ -82,8 +82,8 @@
"patchSelectorCard": { "patchSelectorCard": {
"widgetTitle": "Sélectionner les patchs", "widgetTitle": "Sélectionner les patchs",
"widgetTitleSelected": "Patchs sélectionnés", "widgetTitleSelected": "Patchs sélectionnés",
"widgetSubtitle": "Choisissez d'abord une application", "widgetSubtitle": "Sélectionnez d'abord une application",
"widgetEmptySubtitle": "Aucun patch n'est sélectionné" "widgetEmptySubtitle": "Aucun patch sélectionné"
}, },
"socialMediaCard": { "socialMediaCard": {
"widgetTitle": "Réseaux sociaux", "widgetTitle": "Réseaux sociaux",
@@ -118,24 +118,26 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valeur personnalisée", "customValue": "Valeur personnalisée",
"setToNull": "Définir à NULL",
"nullValue": "Cette valeur d'option est actuellement nulle",
"resetOptionsTooltip": "Réinitialiser les options de patch", "resetOptionsTooltip": "Réinitialiser les options de patch",
"viewTitle": "Options de patch", "viewTitle": "Options de patch",
"saveOptions": "Enregistrer", "saveOptions": "Enregistrer",
"addOptions": "Ajouter des options", "addOptions": "Ajouter des options",
"deselectPatch": "Désélectionner tous les patchs", "unselectPatch": "Désélectionner le patch",
"tooltip": "Plus d'options d'entrée", "tooltip": "Plus d'options d'entrée",
"selectFilePath": "Sélectionner l'emplacement du fichier", "selectFilePath": "Sélectionner l'emplacement du fichier",
"selectFolder": "Sélectionner le dossier", "selectFolder": "Sélectionner le dossier",
"selectOption": "Sélectionner une option", "selectOption": "Sélectionner une option",
"requiredOption": "Cette option est obligatoire", "requiredOption": "Définir cette option est nécessaire",
"unsupportedOption": "Cette option n'est pas supportée", "unsupportedOption": "Cette option n'est pas prise en charge",
"requiredOptionNull": "Les options suivantes doivent être définies :\n\n${options}" "requiredOptionNull": "Les options suivantes doivent être définies :\n\n${options}"
}, },
"patchItem": { "patchItem": {
"unsupportedDialogText": "Sélectionner ce patch pourrait entrainer des erreurs dans la modification.\n\nVersion de l'application : ${packageVersion}\nVersions supportées :\n${supportedVersions}", "unsupportedDialogText": "Sélectionner ce patch peut entrainer des erreurs dans la modification.\n\nVersion de l'application : ${packageVersion}\nVersions prises en charge :\n${supportedVersions}",
"unsupportedPatchVersion": "Le patch n'est pas supporté pour cette version de l'application.", "unsupportedPatchVersion": "Le patch n'est pas pris en charge pour cette version de l'application.",
"unsupportedRequiredOption": "Ce patch contient une option requise qui n'est pas supporté par cette application", "unsupportedRequiredOption": "Ce patch contient une option requise qui n'est pas prise en charge par cette application",
"patchesChangeWarningDialogText": "Il est recommandé d'utiliser les patchs par défaut ainsi que les options. Leur modification peut entraîner des problèmes inattendus.\n\nVous aurez besoin d'activer \"Autoriser la modification de la sélection du patch\" dans les paramètres avant de modifier toute sélection de patch.", "patchesChangeWarningDialogText": "Il est recommandé d'utiliser les patchs et options par défaut. Leur modification peut entraîner des problèmes inattendus.\n\nVous aurez besoin d'activer « Autoriser la modification de la sélection de patchs » dans les paramètres avant de modifier toute sélection de patchs.",
"patchesChangeWarningDialogButton": "Utiliser la sélection par défaut" "patchesChangeWarningDialogButton": "Utiliser la sélection par défaut"
}, },
"installerView": { "installerView": {
@@ -193,7 +195,7 @@
"contributorsHint": "Liste des contributeurs de ReVanced", "contributorsHint": "Liste des contributeurs de ReVanced",
"logsLabel": "Partager les journaux", "logsLabel": "Partager les journaux",
"logsHint": "Partager les logs de ReVanced Manager", "logsHint": "Partager les logs de ReVanced Manager",
"enablePatchesSelectionLabel": "Autoriser la modification de la sélection du patch", "enablePatchesSelectionLabel": "Autoriser la modification de la sélection de patchs",
"enablePatchesSelectionHint": "Ne pas empêcher la sélection ou la désélection des patchs", "enablePatchesSelectionHint": "Ne pas empêcher la sélection ou la désélection des patchs",
"enablePatchesSelectionWarningText": "Le changement de sélection par défaut des patchs peut causer des problèmes inattendus \n\nActiver quand même?", "enablePatchesSelectionWarningText": "Le changement de sélection par défaut des patchs peut causer des problèmes inattendus \n\nActiver quand même?",
"disablePatchesSelectionWarningText": "Vous êtes sur le point de désactiver le changement de sélection par défaut des patchs.\nLa sélection par défaut des patchs sera restaurée.\n\nDésactiver quand même ?", "disablePatchesSelectionWarningText": "Vous êtes sur le point de désactiver le changement de sélection par défaut des patchs.\nLa sélection par défaut des patchs sera restaurée.\n\nDésactiver quand même ?",

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valore personalizzato", "customValue": "Valore personalizzato",
"setToNull": "Imposta a nullo",
"nullValue": "Questo valore di opzione è al momento nullo",
"resetOptionsTooltip": "Ripristina opzioni patch", "resetOptionsTooltip": "Ripristina opzioni patch",
"viewTitle": "Opzioni patch", "viewTitle": "Opzioni patch",
"saveOptions": "Salva", "saveOptions": "Salva",
"addOptions": "Aggiungi opzioni", "addOptions": "Aggiungi opzioni",
"deselectPatch": "Deseleziona patch", "unselectPatch": "Deseleziona patch",
"tooltip": "Più opzioni di input", "tooltip": "Più opzioni di input",
"selectFilePath": "Seleziona percorso file", "selectFilePath": "Seleziona percorso file",
"selectFolder": "Seleziona cartella", "selectFolder": "Seleziona cartella",
"selectOption": "Seleziona opzione", "selectOption": "Seleziona opzione",
"requiredOption": "Questa opzione è richiesta", "requiredOption": "L'impostazione di questa opzione è obbligatoria",
"unsupportedOption": "Questa opzione non è supportata", "unsupportedOption": "Questa opzione non è supportata",
"requiredOptionNull": "È necessario impostare le seguenti opzioni:\n\n${options}" "requiredOptionNull": "È necessario impostare le seguenti opzioni:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "カスタム値", "customValue": "カスタム値",
"setToNull": "null に設定",
"nullValue": "このオプション値は現在nullです",
"resetOptionsTooltip": "パッチ設定をリセット", "resetOptionsTooltip": "パッチ設定をリセット",
"viewTitle": "パッチ設定", "viewTitle": "パッチ設定",
"saveOptions": "保存", "saveOptions": "保存",
"addOptions": "オプションを追加", "addOptions": "オプションを追加",
"deselectPatch": "パッチの選択を解除", "unselectPatch": "パッチの選択を解除",
"tooltip": "他の入力オプション", "tooltip": "他の入力オプション",
"selectFilePath": "ファイルパスを選択", "selectFilePath": "ファイルパスを選択",
"selectFolder": "フォルダーを選択", "selectFolder": "フォルダーを選択",
"selectOption": "オプションを選択", "selectOption": "オプションを選択",
"requiredOption": "このオプションは必須です", "requiredOption": "このオプションを設定する必要があります",
"unsupportedOption": "この設定はサポートされていません", "unsupportedOption": "この設定はサポートされていません",
"requiredOptionNull": "以下のオプションを設定する必要があります:\n\n${options}" "requiredOptionNull": "以下のオプションを設定する必要があります:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Aangepaste waarde", "customValue": "Aangepaste waarde",
"setToNull": "Zet op nul",
"nullValue": "Deze optiewaarde is momenteel leeg",
"resetOptionsTooltip": "Reset patch opties", "resetOptionsTooltip": "Reset patch opties",
"viewTitle": "Patch opties", "viewTitle": "Patch opties",
"saveOptions": "Opslaan", "saveOptions": "Opslaan",
"addOptions": "Opties toevoegen", "addOptions": "Opties toevoegen",
"deselectPatch": "Deselecteer patch", "unselectPatch": "Patch deselecteren",
"tooltip": "Meer invoeropties", "tooltip": "Meer invoeropties",
"selectFilePath": "Bestandspad selecteren", "selectFilePath": "Bestandspad selecteren",
"selectFolder": "Map selecteren", "selectFolder": "Map selecteren",
"selectOption": "Selecteer optie", "selectOption": "Selecteer optie",
"requiredOption": "Deze optie is vereist", "requiredOption": "Het instellen van deze optie is vereist",
"unsupportedOption": "Deze optie wordt niet ondersteund", "unsupportedOption": "Deze optie wordt niet ondersteund",
"requiredOptionNull": "De volgende opties moeten worden ingesteld:\n\n${options}" "requiredOptionNull": "De volgende opties moeten worden ingesteld:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Niestandardowa wartość", "customValue": "Niestandardowa wartość",
"setToNull": "Ustaw na zerowy",
"nullValue": "Ta opcja jest obecnie pusta",
"resetOptionsTooltip": "Zresetuj opcje od łatek", "resetOptionsTooltip": "Zresetuj opcje od łatek",
"viewTitle": "Opcje łatek", "viewTitle": "Opcje łatek",
"saveOptions": "Zapisz", "saveOptions": "Zapisz",
"addOptions": "Dodaj opcje", "addOptions": "Dodaj opcje",
"deselectPatch": "Odznacz łatkę", "unselectPatch": "Odznacz łatkę",
"tooltip": "Więcej opcji wejściowych", "tooltip": "Więcej opcji wejściowych",
"selectFilePath": "Wybierz ścieżkę pliku", "selectFilePath": "Wybierz ścieżkę pliku",
"selectFolder": "Wybierz folder", "selectFolder": "Wybierz folder",
"selectOption": "Wybierz opcję", "selectOption": "Wybierz opcję",
"requiredOption": "Ta opcja jest wymagana", "requiredOption": "Ustawienie tej opcji jest wymagane",
"unsupportedOption": "Ta opcja nie jest wspierana", "unsupportedOption": "Ta opcja nie jest wspierana",
"requiredOptionNull": "Należy ustawić następujące opcje:\n\n${options}" "requiredOptionNull": "Należy ustawić następujące opcje:\n\n${options}"
}, },

View File

@@ -27,7 +27,7 @@
"refreshSuccess": "Atualizado com sucesso", "refreshSuccess": "Atualizado com sucesso",
"widgetTitle": "Painel", "widgetTitle": "Painel",
"updatesSubtitle": "Atualizações", "updatesSubtitle": "Atualizações",
"patchedSubtitle": "Aplicativos patcheados", "patchedSubtitle": "Aplicativos patcheados ",
"changeLaterSubtitle": "Você pode ajustar essa opção nas configurações mais tarde.", "changeLaterSubtitle": "Você pode ajustar essa opção nas configurações mais tarde.",
"noUpdates": "Nenhuma atualização encontrada", "noUpdates": "Nenhuma atualização encontrada",
"WIP": "Trabalho em progresso...", "WIP": "Trabalho em progresso...",
@@ -40,7 +40,7 @@
"updateDialogText": "Uma nova atualização está disponível para ${file}.\n\nA versão atualmente instalada é a ${version}.", "updateDialogText": "Uma nova atualização está disponível para ${file}.\n\nA versão atualmente instalada é a ${version}.",
"downloadConsentDialogTitle": "Baixar os arquivos necessários?", "downloadConsentDialogTitle": "Baixar os arquivos necessários?",
"downloadConsentDialogText": "O ReVanced Manager precisará baixar os arquivos necessários para funcionar corretamente.", "downloadConsentDialogText": "O ReVanced Manager precisará baixar os arquivos necessários para funcionar corretamente.",
"downloadConsentDialogText2": "Isso irá te conectar em ${url}.", "downloadConsentDialogText2": "Isso vai conectar você em ${url}.",
"checkUpdateDialogTitle": "Procurar atualizações?", "checkUpdateDialogTitle": "Procurar atualizações?",
"checkUpdateDialogText": "Você quer que o ReVanced Manager procure atualizações automaticamente?", "checkUpdateDialogText": "Você quer que o ReVanced Manager procure atualizações automaticamente?",
"notificationTitle": "Atualização baixada", "notificationTitle": "Atualização baixada",
@@ -71,9 +71,9 @@
"requiredOptionDialogText": "Algumas opções de patch tiveram que ser definidas." "requiredOptionDialogText": "Algumas opções de patch tiveram que ser definidas."
}, },
"appSelectorCard": { "appSelectorCard": {
"widgetTitle": "Selecione um aplicativo", "widgetTitle": "Selecione um app",
"widgetTitleSelected": "Aplicativo selecionado", "widgetTitleSelected": "App selecionado",
"widgetSubtitle": "Nenhum aplicativo selecionado", "widgetSubtitle": "Nenhum app selecionado",
"noAppsLabel": "Nenhum aplicativo foi encontrado", "noAppsLabel": "Nenhum aplicativo foi encontrado",
"currentVersion": "Atual", "currentVersion": "Atual",
"suggestedVersion": "Sugeridos/sugestões", "suggestedVersion": "Sugeridos/sugestões",
@@ -96,7 +96,7 @@
"selectFromStorageButton": "Selecionar no armazenamento", "selectFromStorageButton": "Selecionar no armazenamento",
"errorMessage": "Não foi possível usar o app selecionado", "errorMessage": "Não foi possível usar o app selecionado",
"downloadToast": "A função de download não está disponível no momento", "downloadToast": "A função de download não está disponível no momento",
"requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version which can lead to unexpected issues. Please use the suggested version.\n\nSelected version: ${selected}\nSuggested version: ${suggested}\n\nTo continue anyway, disable \"Require suggested app version\" in the settings.", "requireSuggestedAppVersionDialogText": "A versão do app selecionada não corresponde à versão sugerida, o que pode causar problemas inesperados. Por favor, use a versão sugerida.\n\nVersão selecionada: ${selected} \nVersão sugerida: ${suggested}\n\nPara continuar mesmo assim, desative \"Exigir versão sugerida do app\" nas configurações.",
"featureNotAvailable": "Recurso não implementado", "featureNotAvailable": "Recurso não implementado",
"featureNotAvailableText": "Este aplicativo é um APK dividido e só pode ser patcheado e instalado de forma confiável ao ser montado com permissões de root. No entanto, você pode patchear e instalar um APK completo ao selecioná-lo do armazenamento." "featureNotAvailableText": "Este aplicativo é um APK dividido e só pode ser patcheado e instalado de forma confiável ao ser montado com permissões de root. No entanto, você pode patchear e instalar um APK completo ao selecioná-lo do armazenamento."
}, },
@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valor personalizado", "customValue": "Valor personalizado",
"resetOptionsTooltip": "Redefinir opções de patch", "setToNull": "Definir como nulo",
"nullValue": "Atualmente, este valor de opção é nulo",
"resetOptionsTooltip": "Redefinir as opções de patch",
"viewTitle": "Opções de patch", "viewTitle": "Opções de patch",
"saveOptions": "Salvar", "saveOptions": "Salvar",
"addOptions": "Adicionar opções", "addOptions": "Adicionar opções",
"deselectPatch": "Deselecionar patch", "unselectPatch": "Desmarque o patch",
"tooltip": "Mais opções de entrada", "tooltip": "Mais opções de entrada",
"selectFilePath": "Selecione o caminho do arquivo", "selectFilePath": "Selecione o caminho do arquivo",
"selectFolder": "Selecione a pasta", "selectFolder": "Selecione a pasta",
"selectOption": "Selecione uma opção", "selectOption": "Selecione uma opção",
"requiredOption": "Essa opção é necessária", "requiredOption": "Definir esta opção é necessário",
"unsupportedOption": "Essa opção não é suportada", "unsupportedOption": "Essa opção não é suportada",
"requiredOptionNull": "As seguintes opções precisam ser definidas:\n\n${options}" "requiredOptionNull": "As seguintes opções precisam ser definidas:\n\n${options}"
}, },
@@ -178,7 +180,7 @@
"sourcesLabelHint": "Conigure as fontes alternativas para ReVanced Patches e ReVanced Integrations", "sourcesLabelHint": "Conigure as fontes alternativas para ReVanced Patches e ReVanced Integrations",
"sourcesIntegrationsLabel": "Fonte das integrações", "sourcesIntegrationsLabel": "Fonte das integrações",
"useAlternativeSources": "Usar fontes alternativas", "useAlternativeSources": "Usar fontes alternativas",
"useAlternativeSourcesHint": "Use alternative sources for ReVanced Patches and ReVanced Integrations instead of the API", "useAlternativeSourcesHint": "Use fontes alternativas para Patches do ReVanced e Integrações do ReVanced em vez da API",
"sourcesResetDialogTitle": "Redefinir", "sourcesResetDialogTitle": "Redefinir",
"sourcesResetDialogText": "Você tem certeza que deseja redefinir as fontes para os valores padrão?", "sourcesResetDialogText": "Você tem certeza que deseja redefinir as fontes para os valores padrão?",
"apiURLResetDialogText": "Are you sure you want to reset your API URL to its default value?", "apiURLResetDialogText": "Are you sure you want to reset your API URL to its default value?",
@@ -199,15 +201,15 @@
"disablePatchesSelectionWarningText": "Você irá desabilitar a mudança da pré-seleção dos patches.\nA seleção padrão dos patches será restaurada.\n\nDesabilitar mesmo assim?", "disablePatchesSelectionWarningText": "Você irá desabilitar a mudança da pré-seleção dos patches.\nA seleção padrão dos patches será restaurada.\n\nDesabilitar mesmo assim?",
"autoUpdatePatchesLabel": "Atualizar patches automaticamente", "autoUpdatePatchesLabel": "Atualizar patches automaticamente",
"autoUpdatePatchesHint": "Atualize automaticamente os patches para a versão mais recente", "autoUpdatePatchesHint": "Atualize automaticamente os patches para a versão mais recente",
"showUpdateDialogLabel": "Show update dialog", "showUpdateDialogLabel": "Mostrar alerta de atualização",
"showUpdateDialogHint": "Show a dialog when a new update is available", "showUpdateDialogHint": "Mostra um aviso quando uma nova atualização estiver disponível",
"universalPatchesLabel": "Mostrar patches universais", "universalPatchesLabel": "Mostrar patches universais",
"universalPatchesHint": "Mostra todos os aplicativos e patches universais (pode deixar a lista de aplicativos mais lenta)", "universalPatchesHint": "Mostra todos os aplicativos e patches universais (pode deixar a lista de aplicativos mais lenta)",
"versionCompatibilityCheckLabel": "Verificar compatibilidade de versão", "versionCompatibilityCheckLabel": "Verificar compatibilidade de versão",
"versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version", "versionCompatibilityCheckHint": "Impedir a seleção de patches que não são compatíveis com a versão do app selecionado",
"requireSuggestedAppVersionLabel": "Requer a versão sugerida do app", "requireSuggestedAppVersionLabel": "Exigir versão sugerida do app",
"requireSuggestedAppVersionHint": "Evite selecionar um aplicativo com uma versão diferente da sugerida", "requireSuggestedAppVersionHint": "Evite selecionar um app com uma versão diferente da sugerida",
"requireSuggestedAppVersionDialogText": "Selecionar um aplicativo que não seja a versão sugerida pode causar problemas inesperados.\n\nVocê quer prosseguir mesmo assim?", "requireSuggestedAppVersionDialogText": "Selecionar um app que não seja a versão sugerida pode causar problemas inesperados.\n\nVocê quer prosseguir mesmo assim?",
"aboutLabel": "Sobre", "aboutLabel": "Sobre",
"snackbarMessage": "Copiado para a área de transferência", "snackbarMessage": "Copiado para a área de transferência",
"restartAppForChanges": "Reinicie o aplicativo para aplicar as mudanças", "restartAppForChanges": "Reinicie o aplicativo para aplicar as mudanças",
@@ -221,16 +223,16 @@
"importPatchesLabel": "Importar seleção de patch", "importPatchesLabel": "Importar seleção de patch",
"importPatchesHint": "Importar seleção de patch de um arquivo JSON", "importPatchesHint": "Importar seleção de patch de um arquivo JSON",
"importedPatches": "Seleção de patch importada", "importedPatches": "Seleção de patch importada",
"resetStoredPatchesLabel": "Redefinir seleção de patch", "resetStoredPatchesLabel": "Redefinir a seleção de patch",
"resetStoredPatchesHint": "Redefinir a seleção de patch armazenada", "resetStoredPatchesHint": "Redefinir a seleção de patch armazenada",
"resetStoredPatchesDialogTitle": "Redefinir seleção de patch?", "resetStoredPatchesDialogTitle": "Redefinir a seleção de patch?",
"resetStoredPatchesDialogText": "A seleção padrão de patches será restaurada.", "resetStoredPatchesDialogText": "A seleção padrão de patches será restaurada.",
"resetStoredPatches": "A seleção de patch foi redefinida", "resetStoredPatches": "A seleção de patch foi redefinida",
"resetStoredOptionsLabel": "Redefinir opções de patch", "resetStoredOptionsLabel": "Redefinir as opções de patch",
"resetStoredOptionsHint": "Resetar todas as opções de patch", "resetStoredOptionsHint": "Redefinir todas as opções de patch",
"resetStoredOptionsDialogTitle": "Reset patch options?", "resetStoredOptionsDialogTitle": "Redefinir as opções de patch?",
"resetStoredOptionsDialogText": "Resetting patch options will remove all saved options.", "resetStoredOptionsDialogText": "Redefinir as opções de patch vai remover todas as opções salvas.",
"resetStoredOptions": "As opções foram resetadas", "resetStoredOptions": "As opções foram redefinidas",
"deleteLogsLabel": "Limpar registros", "deleteLogsLabel": "Limpar registros",
"deleteLogsHint": "Delete collected ReVanced Manager logs", "deleteLogsHint": "Delete collected ReVanced Manager logs",
"deletedLogs": "Registros apagados", "deletedLogs": "Registros apagados",
@@ -286,7 +288,7 @@
"status_failure_incompatible": "Instalação incompatível", "status_failure_incompatible": "Instalação incompatível",
"status_failure_timeout": "Tempo limite de instalação", "status_failure_timeout": "Tempo limite de instalação",
"status_unknown": "Falha na instalação", "status_unknown": "Falha na instalação",
"mount_version_mismatch_description": "A instalação falhou devido ao aplicativo instalado ser uma versão diferente do aplicativo patcheado.\n\nInstale a versão do aplicativo que você está montando e tente novamente.", "mount_version_mismatch_description": "A instalação falhou porque o app instalado era de uma versão diferente do app patcheado.\n\nInstale a versão do app que você está montando e tente de novo.",
"mount_no_root_description": "A instalação falhou devido ao acesso root não ter sido concedido.\n\nConceda acesso root ao Gerenciador ReVanced e tente novamente.", "mount_no_root_description": "A instalação falhou devido ao acesso root não ter sido concedido.\n\nConceda acesso root ao Gerenciador ReVanced e tente novamente.",
"mount_missing_installation_description": "A instalação falhou devido ao aplicativo não patcheado não estar instalado neste dispositivo para ser montado sobre ele.\n\nInstale o aplicativo não patcheado antes de montar e tente novamente.", "mount_missing_installation_description": "A instalação falhou devido ao aplicativo não patcheado não estar instalado neste dispositivo para ser montado sobre ele.\n\nInstale o aplicativo não patcheado antes de montar e tente novamente.",
"status_failure_timeout_description": "A instalação demorou muito para terminar.\n\nVocê gostaria de tentar novamente?", "status_failure_timeout_description": "A instalação demorou muito para terminar.\n\nVocê gostaria de tentar novamente?",
@@ -296,7 +298,7 @@
"status_failure_conflict_description": "A instalação foi impedida por uma instalação existente do aplicativo.\n\nDesinstale o aplicativo instalado e tente novamente", "status_failure_conflict_description": "A instalação foi impedida por uma instalação existente do aplicativo.\n\nDesinstale o aplicativo instalado e tente novamente",
"status_failure_blocked_description": "A instalação foi bloqueada por ${packageName}.\n\nAjuste suas configurações de segurança e tente novamente.", "status_failure_blocked_description": "A instalação foi bloqueada por ${packageName}.\n\nAjuste suas configurações de segurança e tente novamente.",
"install_failed_verification_failure_description": "A instalação falhou devido a um problema de verificação.\n\nAjuste suas configurações de segurança e tente novamente.", "install_failed_verification_failure_description": "A instalação falhou devido a um problema de verificação.\n\nAjuste suas configurações de segurança e tente novamente.",
"install_failed_version_downgrade_description": "A instalação falhou devido ao aplicativo patcheado ser uma versão mais baixa que o aplicativo instalado.\n\nDesinstale o aplicativo e tente novamente.", "install_failed_version_downgrade_description": "A instalação falhou porque o app patcheado era uma versão mais baixa que o app instalado.\n\nDesinstalar o app e tentar de novo?",
"status_unknown_description": "A instalação falhou por um motivo desconhecido. Por favor, tente novamente." "status_unknown_description": "A instalação falhou por um motivo desconhecido. Por favor, tente novamente."
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"okButton": "OK", "okButton": "OK",
"cancelButton": "Cancelar", "cancelButton": "Cancelar",
"dismissButton": "Dispensar", "dismissButton": "Ignorar",
"quitButton": "Sair", "quitButton": "Sair",
"updateButton": "Atualizar", "updateButton": "Atualizar",
"enabledLabel": "Ativado", "enabledLabel": "Ativado",
@@ -27,11 +27,11 @@
"refreshSuccess": "Atualizado com sucesso", "refreshSuccess": "Atualizado com sucesso",
"widgetTitle": "Painel de Controlo", "widgetTitle": "Painel de Controlo",
"updatesSubtitle": "Atualizações", "updatesSubtitle": "Atualizações",
"patchedSubtitle": "Aplicações Modificadas", "patchedSubtitle": "Apps patcheados",
"changeLaterSubtitle": "Podes modificar esta definição mais tarde.", "changeLaterSubtitle": "Podes modificar esta definição mais tarde.",
"noUpdates": "Nenhuma atualização disponível", "noUpdates": "Nenhuma atualização disponível",
"WIP": "Trabalho em progresso...", "WIP": "Trabalho em progresso...",
"noInstallations": "Nenhuma aplicação modificada instalada", "noInstallations": "Nenhum app patcheado instalado",
"installUpdate": "Continuar para instalar a atualização?", "installUpdate": "Continuar para instalar a atualização?",
"updateSheetTitle": "Atualizar o ReVanced Manager", "updateSheetTitle": "Atualizar o ReVanced Manager",
"updateDialogTitle": "Nova atualização disponível", "updateDialogTitle": "Nova atualização disponível",
@@ -51,7 +51,7 @@
"errorDownloadMessage": "Não é possível transferir a atualização", "errorDownloadMessage": "Não é possível transferir a atualização",
"errorInstallMessage": "Não foi possível instalar a atualização", "errorInstallMessage": "Não foi possível instalar a atualização",
"noConnection": "Sem ligação à Internet", "noConnection": "Sem ligação à Internet",
"updatesDisabled": "Atualizar uma aplicação modificada está atualmente desabilitado. Volta a modificar a aplicação." "updatesDisabled": "A atualização de um app patcheado está desativada no momento. Repatch o app de novo."
}, },
"applicationItem": { "applicationItem": {
"infoButton": "Informação" "infoButton": "Informação"
@@ -98,7 +98,7 @@
"downloadToast": "A função de transferência não está disponível", "downloadToast": "A função de transferência não está disponível",
"requireSuggestedAppVersionDialogText": "A versão da aplicação que selecionaste não corresponde à versão sugerida, o que pode levar a problemas inesperados. Utiliza a versão recomendada.\n\nVersão selecionada: ${selected}\nVersão recomendada: ${suggested}\n\nPara continuar na mesma, desactive a opção \"Exigir a versão recomendada da aplicação\" nas definições.", "requireSuggestedAppVersionDialogText": "A versão da aplicação que selecionaste não corresponde à versão sugerida, o que pode levar a problemas inesperados. Utiliza a versão recomendada.\n\nVersão selecionada: ${selected}\nVersão recomendada: ${suggested}\n\nPara continuar na mesma, desactive a opção \"Exigir a versão recomendada da aplicação\" nas definições.",
"featureNotAvailable": "Recurso não implementado", "featureNotAvailable": "Recurso não implementado",
"featureNotAvailableText": "Esta aplicação é um APK dividido e só pode ser modificado e instalado de forma fiável através da montagem com permissões root. No entanto, é possível corrigir e instalar um APK completo selecionando-o a partir do armazenamento." "featureNotAvailableText": "Esse app é um APK dividido e só pode ser modificado e instalado de forma fiável através da montagem com permissões root. No entanto, é possível patchear e instalar um APK completo selecionando ele a partir do armazenamento."
}, },
"patchesSelectorView": { "patchesSelectorView": {
"viewTitle": "Selecionar modificações", "viewTitle": "Selecionar modificações",
@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valor personalizado", "customValue": "Valor personalizado",
"setToNull": "Definir como nulo",
"nullValue": "Atualmente, este valor de opção é nulo",
"resetOptionsTooltip": "Reiniciar as opções da modificação", "resetOptionsTooltip": "Reiniciar as opções da modificação",
"viewTitle": "Opções de modificação", "viewTitle": "Opções de modificação",
"saveOptions": "Guardar", "saveOptions": "Guardar",
"addOptions": "Adicionar opções", "addOptions": "Adicionar opções",
"deselectPatch": "Desselecionar modificação", "unselectPatch": "Desmarque o patch",
"tooltip": "Mais opções de entrada", "tooltip": "Mais opções de entrada",
"selectFilePath": "Selecionar caminho do arquivo", "selectFilePath": "Selecionar caminho do arquivo",
"selectFolder": "Selecionar pasta", "selectFolder": "Selecionar pasta",
"selectOption": "Seleccionar opção", "selectOption": "Seleccionar opção",
"requiredOption": "Esta opção é obrigatória", "requiredOption": "Definir esta opção é necessário",
"unsupportedOption": "Esta opção não é suportada", "unsupportedOption": "Esta opção não é suportada",
"requiredOptionNull": "As seguintes opções devem ser definidas:\n\n${options}" "requiredOptionNull": "As seguintes opções devem ser definidas:\n\n${options}"
}, },
@@ -145,13 +147,13 @@
"installButton": "Instalar", "installButton": "Instalar",
"installRootType": "Montar", "installRootType": "Montar",
"installNonRootType": "Normal", "installNonRootType": "Normal",
"warning": "Desativa as atualizações automáticas da aplicação modificada para evitar problemas inesperados.", "warning": "Desative as atualizações automáticas do app patcheado para evitar problemas inesperados.",
"pressBackAgain": "Pressione voltar novamente para cancelar", "pressBackAgain": "Pressione voltar novamente para cancelar",
"openButton": "Abrir", "openButton": "Abrir",
"shareButton": "Partilhar ficheiro", "shareButton": "Partilhar ficheiro",
"notificationTitle": "O ReVanced Manager está a fazer as modificações", "notificationTitle": "O ReVanced Manager está a fazer as modificações",
"notificationText": "Toca para voltar ao instalador", "notificationText": "Toca para voltar ao instalador",
"exportApkButtonTooltip": "Exportar APK modificado", "exportApkButtonTooltip": "Exportar APK patcheado",
"exportLogButtonTooltip": "Exportar registo", "exportLogButtonTooltip": "Exportar registo",
"screenshotDetected": "Foi detetada uma captura de ecrã. Se estiver a tentar partilhar o registo, partilhe antes uma cópia de texto.\n\nCopiar o registo para a área de transferência?", "screenshotDetected": "Foi detetada uma captura de ecrã. Se estiver a tentar partilhar o registo, partilhe antes uma cópia de texto.\n\nCopiar o registo para a área de transferência?",
"copiedToClipboard": "Registo copiado para a área de transferência", "copiedToClipboard": "Registo copiado para a área de transferência",
@@ -258,13 +260,13 @@
"unmountButton": "Desmontar", "unmountButton": "Desmontar",
"rootDialogTitle": "Erro", "rootDialogTitle": "Erro",
"unmountDialogText": "Tens a certeza que queres remover as modificações desta aplicação?", "unmountDialogText": "Tens a certeza que queres remover as modificações desta aplicação?",
"uninstallDialogText": "Tens a certeza que queres desinstalar esta aplicação?", "uninstallDialogText": "Tem certeza que quer desinstalar esse app?",
"rootDialogText": "A aplicação foi instalada com permissões de Super-Utilizador, mas atualmente o ReVanced Manager não tem permissões.\nPor favor, conceda permissões de Super-Utilizador primeiro.", "rootDialogText": "A aplicação foi instalada com permissões de Super-Utilizador, mas atualmente o ReVanced Manager não tem permissões.\nPor favor, conceda permissões de Super-Utilizador primeiro.",
"packageNameLabel": "Nome do pacote", "packageNameLabel": "Nome do pacote",
"installTypeLabel": "Tipo de instalação", "installTypeLabel": "Tipo de instalação",
"mountTypeLabel": "Montar", "mountTypeLabel": "Montar",
"regularTypeLabel": "Normal", "regularTypeLabel": "Normal",
"patchedDateLabel": "Data da Modificação", "patchedDateLabel": "Data do patch",
"appliedPatchesLabel": "Modificações aplicadas", "appliedPatchesLabel": "Modificações aplicadas",
"patchedDateHint": "${date} às ${time}", "patchedDateHint": "${date} às ${time}",
"appliedPatchesHint": "${quantity} modificação/ões aplicada/s", "appliedPatchesHint": "${quantity} modificação/ões aplicada/s",
@@ -293,10 +295,10 @@
"status_failure_storage_description": "A instalação falhou devido ao armazenamento insuficiente.\n\nLiberta algum espaço e tenta novamente.", "status_failure_storage_description": "A instalação falhou devido ao armazenamento insuficiente.\n\nLiberta algum espaço e tenta novamente.",
"status_failure_invalid_description": "A instalação falhou devido ao facto da aplicação modificada ser inválida.\n\nDesinstalar a aplicação e tentar novamente?", "status_failure_invalid_description": "A instalação falhou devido ao facto da aplicação modificada ser inválida.\n\nDesinstalar a aplicação e tentar novamente?",
"status_failure_incompatible_description": "O aplicativo é incompatível com este dispositivo.\n\nEntre em contacto com o desenvolvedor da aplicação e peça suporte.", "status_failure_incompatible_description": "O aplicativo é incompatível com este dispositivo.\n\nEntre em contacto com o desenvolvedor da aplicação e peça suporte.",
"status_failure_conflict_description": "A instalação foi impedida por uma instalação existente da mesma aplicação.\n\nDesinstalar a aplicação instalada e tentar novamente?", "status_failure_conflict_description": "A instalação foi impedida por uma instalação existente do app.\n\nDesinstalar o app instalado e tentar de novo?",
"status_failure_blocked_description": "A instalação foi bloqueada por ${packageName}.\n\nAjuste as suas definições de segurança e tenta novamente.", "status_failure_blocked_description": "A instalação foi bloqueada por ${packageName}.\n\nAjuste as suas definições de segurança e tenta novamente.",
"install_failed_verification_failure_description": "A instalação falhou por problemas de verificação.\n\nAjusta as tuas definições de segurança e tenta novamente.", "install_failed_verification_failure_description": "A instalação falhou por problemas de verificação.\n\nAjusta as tuas definições de segurança e tenta novamente.",
"install_failed_version_downgrade_description": "A instalação falhou devido ao facto da aplicação modificada ser uma versão inferior à da aplicação instalada.\n\nDesinstalar a aplicação e tentar novamente?", "install_failed_version_downgrade_description": "A instalação falhou porque o app patcheado era uma versão inferior ao aplicativo instalado.\n\nDesinstalar o app e tentar de novo?",
"status_unknown_description": "A instalação falhou por razões desconhecidas. Por favor, tenta novamente." "status_unknown_description": "A instalação falhou por razões desconhecidas. Por favor, tenta novamente."
} }
} }

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Valoare personalizată", "customValue": "Valoare personalizată",
"setToNull": "Setează ca nul",
"nullValue": "Această valoare este în prezent nulă",
"resetOptionsTooltip": "Resetează opțiunile patch-ului", "resetOptionsTooltip": "Resetează opțiunile patch-ului",
"viewTitle": "Opțiuni patch", "viewTitle": "Opțiuni patch",
"saveOptions": "Salvează", "saveOptions": "Salvează",
"addOptions": "Adaugă opțiuni", "addOptions": "Adaugă opțiuni",
"deselectPatch": "Deselectați toate patch-urile", "unselectPatch": "Deselectează patch-ul",
"tooltip": "Mai multe opțiuni de intrare", "tooltip": "Mai multe opțiuni de intrare",
"selectFilePath": "Selectați calea fișierului", "selectFilePath": "Selectați calea fișierului",
"selectFolder": "Selectați dosarul", "selectFolder": "Selectați dosarul",
"selectOption": "Selectați opțiunea", "selectOption": "Selectați opțiunea",
"requiredOption": "Această opțiune este necesară", "requiredOption": "Setarea acestei opțiuni este necesară",
"unsupportedOption": "Această opțiune nu este acceptată", "unsupportedOption": "Această opțiune nu este acceptată",
"requiredOptionNull": "Următoarele opțiuni trebuie setate:\n\n${options}" "requiredOptionNull": "Următoarele opțiuni trebuie setate:\n\n${options}"
}, },

View File

@@ -118,11 +118,13 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Пользовательское значение", "customValue": "Пользовательское значение",
"setToNull": "Установить null",
"nullValue": "Значение этого параметра в настоящее время является нулевым",
"resetOptionsTooltip": "Сброс параметров патчей", "resetOptionsTooltip": "Сброс параметров патчей",
"viewTitle": "Параметры патчей", "viewTitle": "Параметры патчей",
"saveOptions": "Сохранить", "saveOptions": "Сохранить",
"addOptions": "Добавить параметры", "addOptions": "Добавить параметры",
"deselectPatch": "Снять выделение с патча", "unselectPatch": "Отменить выбор патча",
"tooltip": "Другие параметры ввода", "tooltip": "Другие параметры ввода",
"selectFilePath": "Выберите путь к файлу", "selectFilePath": "Выберите путь к файлу",
"selectFolder": "Выберите папку", "selectFolder": "Выберите папку",

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Anpassat värde", "customValue": "Anpassat värde",
"setToNull": "Sätt till noll",
"nullValue": "Detta alternativ värde är för närvarande noll",
"resetOptionsTooltip": "Återställ patchalternativ", "resetOptionsTooltip": "Återställ patchalternativ",
"viewTitle": "Patchalternativ", "viewTitle": "Patchalternativ",
"saveOptions": "Spara", "saveOptions": "Spara",
"addOptions": "Lägg till alternativ", "addOptions": "Lägg till alternativ",
"deselectPatch": "Avmarkera patch", "unselectPatch": "Avmarkera patch",
"tooltip": "Fler inmatningsalternativ", "tooltip": "Fler inmatningsalternativ",
"selectFilePath": "Välj filsökväg", "selectFilePath": "Välj filsökväg",
"selectFolder": "Välj mapp", "selectFolder": "Välj mapp",
"selectOption": "Välj alternativ", "selectOption": "Välj alternativ",
"requiredOption": "Detta alternativ är nödvändigt", "requiredOption": "Inställning av detta alternativ krävs",
"unsupportedOption": "Detta alternativ stöds ej", "unsupportedOption": "Detta alternativ stöds ej",
"requiredOptionNull": "Följande alternativ måste anges:\n\n${options}" "requiredOptionNull": "Följande alternativ måste anges:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "Власне значення", "customValue": "Власне значення",
"setToNull": "Встановити в null",
"nullValue": "Значення опції в даний час null",
"resetOptionsTooltip": "Скинути параметри патчу", "resetOptionsTooltip": "Скинути параметри патчу",
"viewTitle": "Параметри патчу", "viewTitle": "Параметри патчу",
"saveOptions": "Зберегти", "saveOptions": "Зберегти",
"addOptions": "Додати параметри", "addOptions": "Додати параметри",
"deselectPatch": "Зняти вибір патчу", "unselectPatch": "Скасувати вибір патча",
"tooltip": "Більше варіантів вводу", "tooltip": "Більше варіантів вводу",
"selectFilePath": "Оберіть шлях до файлу", "selectFilePath": "Оберіть шлях до файлу",
"selectFolder": "Оберіть теку", "selectFolder": "Оберіть теку",
"selectOption": "Вибрати параметр", "selectOption": "Вибрати параметр",
"requiredOption": "Цей параметр є обовязковим", "requiredOption": "Встановлення цього параметра є обов'язковим",
"unsupportedOption": "Цей параметр не підтримується", "unsupportedOption": "Цей параметр не підтримується",
"requiredOptionNull": "Необхідно встановити наступні параметри:\n\n${options}" "requiredOptionNull": "Необхідно встановити наступні параметри:\n\n${options}"
}, },

View File

@@ -118,16 +118,18 @@
}, },
"patchOptionsView": { "patchOptionsView": {
"customValue": "自定义值", "customValue": "自定义值",
"setToNull": "设置为为空",
"nullValue": "此选项值当前为空",
"resetOptionsTooltip": "重置补丁选项", "resetOptionsTooltip": "重置补丁选项",
"viewTitle": "补丁选项", "viewTitle": "补丁选项",
"saveOptions": "保存", "saveOptions": "保存",
"addOptions": "添加选项", "addOptions": "添加选项",
"deselectPatch": "取消选择补丁", "unselectPatch": "取消选择补丁",
"tooltip": "更多输入选项", "tooltip": "更多输入选项",
"selectFilePath": "选择文件路径", "selectFilePath": "选择文件路径",
"selectFolder": "选择文件夹", "selectFolder": "选择文件夹",
"selectOption": "选择选项", "selectOption": "选择选项",
"requiredOption": "必须填写此选项", "requiredOption": "设置此选项是必需的",
"unsupportedOption": "不支持此选项", "unsupportedOption": "不支持此选项",
"requiredOptionNull": "必须设置以下选项:\n\n${options}" "requiredOptionNull": "必须设置以下选项:\n\n${options}"
}, },

View File

@@ -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

View File

@@ -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);

View File

@@ -6,12 +6,14 @@ import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/download_manager.dart'; import 'package:revanced_manager/services/download_manager.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:synchronized/synchronized.dart';
@lazySingleton @lazySingleton
class GithubAPI { class GithubAPI {
late final Dio _dio; late final Dio _dio;
late final ManagerAPI _managerAPI = locator<ManagerAPI>(); late final ManagerAPI _managerAPI = locator<ManagerAPI>();
late final DownloadManager _downloadManager = locator<DownloadManager>(); late final DownloadManager _downloadManager = locator<DownloadManager>();
final Map<String, Lock> _lockMap = {};
Future<void> initialize(String repoUrl) async { Future<void> initialize(String repoUrl) async {
_dio = _downloadManager.initDio(repoUrl); _dio = _downloadManager.initDio(repoUrl);
@@ -21,11 +23,21 @@ class GithubAPI {
await _downloadManager.clearAllCache(); await _downloadManager.clearAllCache();
} }
Future<Response> _dioGetSynchronously(String path) async {
// Create a new Lock for each path
if (!_lockMap.containsKey(path)) {
_lockMap[path] = Lock();
}
return _lockMap[path]!.synchronized(() async {
return await _dio.get(path);
});
}
Future<Map<String, dynamic>?> getLatestRelease( Future<Map<String, dynamic>?> getLatestRelease(
String repoName, String repoName,
) async { ) async {
try { try {
final response = await _dio.get( final response = await _dioGetSynchronously(
'/repos/$repoName/releases/latest', '/repos/$repoName/releases/latest',
); );
return response.data; return response.data;
@@ -37,36 +49,27 @@ class GithubAPI {
} }
} }
Future<Map<String, dynamic>?> getLatestManagerRelease( Future<String?> getManagerChangelogs() async {
String repoName,
) async {
try { try {
final response = await _dio.get( final response = await _dioGetSynchronously(
'/repos/$repoName/releases', '/repos/${_managerAPI.defaultManagerRepo}/releases?per_page=50',
); );
final Map<String, dynamic> releases = response.data[0]; final buffer = StringBuffer();
int updates = 0;
final String currentVersion = final String currentVersion =
await _managerAPI.getCurrentManagerVersion(); await _managerAPI.getCurrentManagerVersion();
while (response.data[updates]['tag_name'] != currentVersion) { for (final release in response.data) {
updates++; if (release['tag_name'] == currentVersion) {
} if (buffer.isEmpty) {
for (int i = 1; i < updates; i++) { buffer.writeln(release['body']);
if (response.data[i]['prerelease']) { }
break;
}
if (release['prerelease']) {
continue; continue;
} }
releases.update( buffer.writeln(release['body']);
'body',
(value) =>
value +
'\n' +
'# ' +
response.data[i]['tag_name'] +
'\n' +
response.data[i]['body'],
);
} }
return releases; return buffer.toString();
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@@ -87,7 +90,7 @@ class GithubAPI {
url, url,
); );
} }
final response = await _dio.get( final response = await _dioGetSynchronously(
'/repos/$repoName/releases/tags/$version', '/repos/$repoName/releases/tags/$version',
); );
final Map<String, dynamic>? release = response.data; final Map<String, dynamic>? release = response.data;

View File

@@ -31,7 +31,6 @@ class ManagerAPI {
final String cliRepo = 'revanced-cli'; final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs; late SharedPreferences _prefs;
List<Patch> patches = []; List<Patch> patches = [];
List<Option> modifiedOptions = [];
List<Option> options = []; List<Option> options = [];
Patch? selectedPatch; Patch? selectedPatch;
BuildContext? ctx; BuildContext? ctx;
@@ -39,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';
@@ -56,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) {
@@ -296,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);
} }
@@ -328,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();
@@ -686,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 {
@@ -780,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;
}
} }

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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(

View File

@@ -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,

View File

@@ -35,11 +35,13 @@ 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 = '';
String _currentPatchesVersion = ''; String _currentPatchesVersion = '';
String? _latestManagerVersion = ''; String? latestManagerVersion;
String? latestPatchesVersion;
File? downloadedApk; File? downloadedApk;
Future<void> initialize(BuildContext context) async { Future<void> initialize(BuildContext context) async {
@@ -50,7 +52,6 @@ class HomeViewModel extends BaseViewModel {
await forceRefresh(context); await forceRefresh(context);
return; return;
} }
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
_currentPatchesVersion = await _managerAPI.getCurrentPatchesVersion(); _currentPatchesVersion = await _managerAPI.getCurrentPatchesVersion();
if (_managerAPI.showUpdateDialog() && await hasManagerUpdates()) { if (_managerAPI.showUpdateDialog() && await hasManagerUpdates()) {
showUpdateDialog(context, false); showUpdateDialog(context, false);
@@ -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,29 +124,34 @@ 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;
} }
_latestManagerVersion = latestManagerVersion =
await _managerAPI.getLatestManagerVersion() ?? _currentManagerVersion; await _managerAPI.getLatestManagerVersion() ?? _currentManagerVersion;
if (_latestManagerVersion != _currentManagerVersion) { if (latestManagerVersion != _currentManagerVersion) {
return true; return true;
} }
return false; return false;
} }
Future<bool> hasPatchesUpdates() async { Future<bool> hasPatchesUpdates() async {
final String? latestVersion = await _managerAPI.getLatestPatchesVersion(); latestPatchesVersion = await _managerAPI.getLatestPatchesVersion();
if (latestVersion != null) { if (latestPatchesVersion != null) {
try { try {
final int latestVersionInt = final int latestVersionInt =
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), '')); int.parse(latestPatchesVersion!.replaceAll(RegExp('[^0-9]'), ''));
final int currentVersionInt = final int currentVersionInt =
int.parse(_currentPatchesVersion.replaceAll(RegExp('[^0-9]'), '')); int.parse(_currentPatchesVersion.replaceAll(RegExp('[^0-9]'), ''));
return latestVersionInt > currentVersionInt; return latestVersionInt > currentVersionInt;
@@ -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, [
@@ -475,12 +477,14 @@ class HomeViewModel extends BaseViewModel {
); );
} }
Future<Map<String, dynamic>?> getLatestManagerRelease() { Future<String?> getManagerChangelogs() {
return _githubAPI.getLatestManagerRelease(_managerAPI.defaultManagerRepo); return _githubAPI.getManagerChangelogs();
} }
Future<Map<String, dynamic>?> getLatestPatchesRelease() { Future<String?> getLatestPatchesChangelog() async {
return _githubAPI.getLatestRelease(_managerAPI.defaultPatchesRepo); final release =
await _githubAPI.getLatestRelease(_managerAPI.defaultPatchesRepo);
return release?['body'];
} }
Future<String?> getLatestPatchesReleaseTime() { Future<String?> getLatestPatchesReleaseTime() {
@@ -492,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);
} }
} }

View File

@@ -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);

View File

@@ -37,79 +37,35 @@ class PatchOptionsView extends StatelessWidget {
color: Theme.of(context).textTheme.titleLarge!.color, color: Theme.of(context).textTheme.titleLarge!.color,
), ),
), ),
actions: [
IconButton(
onPressed: () {
model.resetOptions();
},
icon: const Icon(
Icons.history,
),
tooltip: t.patchOptionsView.resetOptionsTooltip,
),
],
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
children: [ children: [
for (final Option option in model.visibleOptions) for (final Option option in model.modifiedOptions)
if (option.valueType == 'String' || if (option.valueType == 'String' ||
option.valueType == 'Int') option.valueType == 'Int')
IntAndStringPatchOption( IntAndStringPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { model: model,
model.removeOption(option);
},
onChanged: (value, option) {
model.modifyOptions(value, option);
},
) )
else if (option.valueType == 'Boolean') else if (option.valueType == 'Boolean')
BooleanPatchOption( BooleanPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { model: model,
model.removeOption(option);
},
onChanged: (value, option) {
model.modifyOptions(value, option);
},
) )
else if (option.valueType == 'StringArray' || else if (option.valueType == 'StringArray' ||
option.valueType == 'IntArray' || option.valueType == 'IntArray' ||
option.valueType == 'LongArray') option.valueType == 'LongArray')
IntStringLongListPatchOption( IntStringLongListPatchOption(
patchOption: option, patchOption: option,
removeOption: (option) { model: model,
model.removeOption(option);
},
onChanged: (value, option) {
model.modifyOptions(value, option);
},
) )
else else
UnsupportedPatchOption( UnsupportedPatchOption(
patchOption: option, patchOption: option,
), ),
if (model.visibleOptions.length !=
model.options.length) ...[
const SizedBox(
height: 8,
),
FilledButton(
onPressed: () {
model.showAddOptionDialog(context);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.add),
Text(t.patchOptionsView.addOptions),
],
),
),
],
const SizedBox( const SizedBox(
height: 80, height: 80,
), ),

View File

@@ -5,7 +5,6 @@ import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.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/ui/views/patches_selector/patches_selector_viewmodel.dart'; import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatchOptionsViewModel extends BaseViewModel { class PatchOptionsViewModel extends BaseViewModel {
@@ -14,7 +13,7 @@ class PatchOptionsViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp!.packageName; locator<PatcherViewModel>().selectedApp!.packageName;
List<Option> options = []; List<Option> options = [];
List<Option> savedOptions = []; List<Option> savedOptions = [];
List<Option> visibleOptions = []; List<Option> modifiedOptions = [];
Future<void> initialize() async { Future<void> initialize() async {
options = getDefaultOptions(); options = getDefaultOptions();
@@ -28,36 +27,18 @@ class PatchOptionsViewModel extends BaseViewModel {
savedOptions.add(savedOption); savedOptions.add(savedOption);
} }
} }
if (savedOptions.isNotEmpty) { modifiedOptions = [
visibleOptions = [ ...savedOptions,
...savedOptions, ...options.where(
...options.where( (option) => !savedOptions.any((sOption) => sOption.key == option.key),
(option) => ),
option.required && ];
!savedOptions.any((sOption) => sOption.key == option.key),
),
];
} else {
visibleOptions = [
...options.where((option) => option.required),
];
}
}
void addOption(Option option) {
visibleOptions.add(option);
notifyListeners();
}
void removeOption(Option option) {
visibleOptions.removeWhere((vOption) => vOption.key == option.key);
notifyListeners();
} }
bool saveOptions(BuildContext context) { bool saveOptions(BuildContext context) {
final List<Option> requiredNullOptions = []; final List<Option> requiredNullOptions = [];
for (final Option option in options) { for (final Option option in options) {
if (!visibleOptions.any((vOption) => vOption.key == option.key)) { if (modifiedOptions.any((mOption) => mOption.key == option.key)) {
_managerAPI.clearPatchOption( _managerAPI.clearPatchOption(
selectedApp, selectedApp,
_managerAPI.selectedPatch!.name, _managerAPI.selectedPatch!.name,
@@ -65,7 +46,7 @@ class PatchOptionsViewModel extends BaseViewModel {
); );
} }
} }
for (final Option option in visibleOptions) { for (final Option option in modifiedOptions) {
if (option.required && option.value == null) { if (option.required && option.value == null) {
requiredNullOptions.add(option); requiredNullOptions.add(option);
} else { } else {
@@ -98,11 +79,8 @@ class PatchOptionsViewModel extends BaseViewModel {
required: option.required, required: option.required,
key: option.key, key: option.key,
); );
visibleOptions[visibleOptions modifiedOptions.removeWhere((mOption) => mOption.key == option.key);
.indexWhere((vOption) => vOption.key == option.key)] = modifiedOption; modifiedOptions.add(modifiedOption);
_managerAPI.modifiedOptions
.removeWhere((mOption) => mOption.key == option.key);
_managerAPI.modifiedOptions.add(modifiedOption);
} }
List<Option> getDefaultOptions() { List<Option> getDefaultOptions() {
@@ -122,93 +100,11 @@ class PatchOptionsViewModel extends BaseViewModel {
return defaultOptions; return defaultOptions;
} }
void resetOptions() { dynamic getDefaultValue(Option patchOption) => _managerAPI.options
_managerAPI.modifiedOptions.clear(); .firstWhere(
visibleOptions = (option) => option.key == patchOption.key,
getDefaultOptions().where((option) => option.required).toList(); )
notifyListeners(); .value;
}
Future<void> showAddOptionDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
t.patchOptionsView.addOptions,
),
Text(
'',
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
],
),
actions: [
FilledButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(t.cancelButton),
),
],
contentPadding: const EdgeInsets.all(8),
content: Wrap(
spacing: 14,
runSpacing: 14,
children: options
.where(
(option) =>
!visibleOptions.any((vOption) => vOption.key == option.key),
)
.map((e) {
return CustomCard(
padding: const EdgeInsets.all(4),
backgroundColor: Theme.of(context).colorScheme.surface,
onTap: () {
addOption(e);
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
e.title,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Text(
e.description,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.onSurface,
),
),
],
),
),
],
),
),
);
}).toList(),
),
),
);
}
} }
Future<void> showRequiredOptionNullDialog( Future<void> showRequiredOptionNullDialog(
@@ -248,7 +144,7 @@ Future<void> showRequiredOptionNullDialog(
PatchesSelectorViewModel().showPatchesChangeDialog(context); PatchesSelectorViewModel().showPatchesChangeDialog(context);
} }
}, },
child: Text(t.patchOptionsView.deselectPatch), child: Text(t.patchOptionsView.unselectPatch),
), ),
FilledButton( FilledButton(
onPressed: () { onPressed: () {

View File

@@ -61,7 +61,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
void navigateToPatchOptions(List<Option> setOptions, Patch patch) { void navigateToPatchOptions(List<Option> setOptions, Patch patch) {
_managerAPI.options = setOptions; _managerAPI.options = setOptions;
_managerAPI.selectedPatch = patch; _managerAPI.selectedPatch = patch;
_managerAPI.modifiedOptions.clear();
_navigationService.navigateToPatchOptionsView(); _navigationService.navigateToPatchOptionsView();
} }
@@ -302,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(

View File

@@ -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(

View File

@@ -3,6 +3,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:language_code/language_code.dart'; import 'package:language_code/language_code.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/gen/strings.g.dart'; import 'package:revanced_manager/gen/strings.g.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
@@ -10,8 +11,10 @@ import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
final _settingViewModel = SettingsViewModel(); final _settingViewModel = SettingsViewModel();
final _navigationService = NavigationService();
class SUpdateLanguage extends BaseViewModel { class SUpdateLanguage extends BaseViewModel {
final Toast _toast = locator<Toast>(); final Toast _toast = locator<Toast>();
@@ -108,10 +111,9 @@ class SUpdateLanguage extends BaseViewModel {
child: Text(t.cancelButton), child: Text(t.cancelButton),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () async {
// TODO(nullcube): Translation will not update until we refresh the page.
updateLocale(selectedLanguageCode.value.languageTag); updateLocale(selectedLanguageCode.value.languageTag);
Navigator.of(context).pop(); await _navigationService.navigateToNavigationView();
}, },
child: Text(t.okButton), child: Text(t.okButton),
), ),

View File

@@ -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();
} }

View File

@@ -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),

View File

@@ -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,

View File

@@ -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(),

View 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),
);
}
}

View File

@@ -14,6 +14,7 @@ class UpdateConfirmationSheet extends StatelessWidget {
final bool isPatches; final bool isPatches;
final bool changelog; final bool changelog;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final HomeViewModel model = locator<HomeViewModel>(); final HomeViewModel model = locator<HomeViewModel>();
@@ -25,100 +26,99 @@ class UpdateConfirmationSheet extends StatelessWidget {
builder: (_, scrollController) => SingleChildScrollView( builder: (_, scrollController) => SingleChildScrollView(
controller: scrollController, controller: scrollController,
child: SafeArea( child: SafeArea(
child: FutureBuilder<Map<String, dynamic>?>( child: Column(
future: !isPatches crossAxisAlignment: CrossAxisAlignment.start,
? model.getLatestManagerRelease() children: [
: model.getLatestPatchesRelease(), if (!changelog)
builder: (_, snapshot) { Padding(
if (!snapshot.hasData) { padding: const EdgeInsets.only(
return const SizedBox( top: 40.0,
height: 300, left: 24.0,
child: Center( right: 24.0,
child: CircularProgressIndicator(), bottom: 20.0,
), ),
); child: Row(
} children: [
Expanded(
return Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (!changelog) Text(
Padding( isPatches
padding: const EdgeInsets.only( ? t.homeView.updatePatchesSheetTitle
top: 40.0, : t.homeView.updateSheetTitle,
left: 24.0, style: const TextStyle(
right: 24.0, fontSize: 24,
bottom: 20.0, fontWeight: FontWeight.bold,
), ),
child: Row( ),
children: [ const SizedBox(height: 4.0),
Expanded( Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(
Icons.new_releases_outlined,
color:
Theme.of(context).colorScheme.secondary,
),
const SizedBox(width: 8.0),
Text( Text(
isPatches isPatches
? t.homeView.updatePatchesSheetTitle ? model.latestPatchesVersion ?? 'Unknown'
: t.homeView.updateSheetTitle, : model.latestManagerVersion ?? 'Unknown',
style: const TextStyle( style: TextStyle(
fontSize: 24, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w500,
color:
Theme.of(context).colorScheme.secondary,
), ),
), ),
const SizedBox(height: 4.0),
Row(
children: [
Icon(
Icons.new_releases_outlined,
color: Theme.of(context)
.colorScheme
.secondary,
),
const SizedBox(width: 8.0),
Text(
snapshot.data!['tag_name'] ?? 'Unknown',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.colorScheme
.secondary,
),
),
],
),
], ],
), ),
), ],
FilledButton( ),
onPressed: () {
Navigator.of(context).pop();
isPatches
? model.updatePatches(context)
: model.updateManager(context);
},
child: Text(t.updateButton),
),
],
), ),
), FilledButton(
Padding( onPressed: () {
padding: const EdgeInsets.only( Navigator.of(context).pop();
top: 12.0, isPatches
left: 24.0, ? model.updatePatches(context)
bottom: 12.0, : model.updateManager(context);
), },
child: Text( child: Text(t.updateButton),
t.homeView.updateChangelogTitle,
style: TextStyle(
fontSize: changelog ? 24 : 20,
fontWeight: FontWeight.w500,
color:
Theme.of(context).colorScheme.onSecondaryContainer,
), ),
), ],
), ),
Container( ),
Padding(
padding: const EdgeInsets.only(
top: 12.0,
left: 24.0,
bottom: 12.0,
),
child: Text(
t.homeView.updateChangelogTitle,
style: TextStyle(
fontSize: changelog ? 24 : 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
),
),
FutureBuilder<String?>(
future: !isPatches
? model.getManagerChangelogs()
: model.getLatestPatchesChangelog(),
builder: (_, snapshot) {
if (!snapshot.hasData) {
return Padding(
padding: EdgeInsets.only(top: changelog ? 96 : 24),
child: const Center(
child: CircularProgressIndicator(),
),
);
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0), margin: const EdgeInsets.symmetric(horizontal: 24.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer, color: Theme.of(context).colorScheme.secondaryContainer,
@@ -139,12 +139,12 @@ class UpdateConfirmationSheet extends StatelessWidget {
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
data: snapshot.data!['body'] ?? '', data: snapshot.data ?? '',
), ),
), );
], },
); ),
}, ],
), ),
), ),
), ),

View File

@@ -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,
), ),
), ),

View File

@@ -1,21 +1,23 @@
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/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class BooleanPatchOption extends StatelessWidget { class BooleanPatchOption extends StatelessWidget {
const BooleanPatchOption({ const BooleanPatchOption({
super.key, super.key,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.model,
required this.onChanged,
}); });
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final PatchOptionsViewModel model;
final void Function(dynamic value, Option option) onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -30,88 +32,94 @@ class BooleanPatchOption extends StatelessWidget {
value: value ?? false, value: value ?? false,
onChanged: (bool value) { onChanged: (bool value) {
patchOptionValue.value = value; patchOptionValue.value = value;
onChanged(value, patchOption); model.modifyOptions(value, patchOption);
}, },
); );
}, },
), ),
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (Option option) { patchOptionValue: patchOptionValue,
removeOption(option); model: model,
},
); );
} }
} }
class IntAndStringPatchOption extends StatelessWidget { class IntAndStringPatchOption extends StatefulWidget {
const IntAndStringPatchOption({ const IntAndStringPatchOption({
super.key, super.key,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.model,
required this.onChanged,
}); });
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final PatchOptionsViewModel model;
final void Function(dynamic value, Option option) onChanged;
@override
State<IntAndStringPatchOption> createState() =>
_IntAndStringPatchOptionState();
}
class _IntAndStringPatchOptionState extends State<IntAndStringPatchOption> {
ValueNotifier? patchOptionValue;
String getKey() {
if (patchOptionValue!.value != null && widget.patchOption.values != null) {
final List values = widget.patchOption.values!.entries
.where((e) => e.value == patchOptionValue!.value)
.toList();
if (values.isNotEmpty) {
return values.first.key;
}
}
return '';
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ValueNotifier patchOptionValue = ValueNotifier(patchOption.value); patchOptionValue ??= ValueNotifier(widget.patchOption.value);
String getKey() {
if (patchOption.value != null && patchOption.values != null) {
final List values = patchOption.values!.entries
.where((e) => e.value == patchOption.value)
.toList();
if (values.isNotEmpty) {
return values.first.key;
}
}
return '';
}
return PatchOption( return PatchOption(
widget: Column( widget: ValueListenableBuilder(
crossAxisAlignment: CrossAxisAlignment.start, valueListenable: patchOptionValue!,
children: [ builder: (context, value, child) {
TextFieldForPatchOption( return Column(
value: patchOption.value, crossAxisAlignment: CrossAxisAlignment.start,
values: patchOption.values, children: [
optionType: patchOption.valueType, TextFieldForPatchOption(
selectedKey: getKey(), value: value,
onChanged: (value) { patchOption: widget.patchOption,
patchOptionValue.value = value; selectedKey: getKey(),
onChanged(value, patchOption); onChanged: (value) {
}, patchOptionValue!.value = value;
), widget.model.modifyOptions(value, widget.patchOption);
ValueListenableBuilder( },
valueListenable: patchOptionValue, ),
builder: (context, value, child) { if (value == null)
if (patchOption.required && value == null) { Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
t.patchOptionsView.requiredOption, widget.patchOption.required
? t.patchOptionsView.requiredOption
: t.patchOptionsView.nullValue,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.error, color: widget.patchOption.required
? Theme.of(context).colorScheme.error
: Theme.of(context)
.colorScheme
.onSecondaryContainer
.withOpacity(0.6),
), ),
), ),
], ],
); ),
} else { ],
return const SizedBox(); );
} },
},
),
],
), ),
patchOption: patchOption, patchOption: widget.patchOption,
removeOption: (Option option) { patchOptionValue: patchOptionValue!,
removeOption(option); model: widget.model,
},
); );
} }
} }
@@ -120,13 +128,11 @@ class IntStringLongListPatchOption extends StatelessWidget {
const IntStringLongListPatchOption({ const IntStringLongListPatchOption({
super.key, super.key,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.model,
required this.onChanged,
}); });
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final PatchOptionsViewModel model;
final void Function(dynamic value, Option option) onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -172,8 +178,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
final e = values[index]; final e = values[index];
return TextFieldForPatchOption( return TextFieldForPatchOption(
value: e.toString(), value: e.toString(),
values: patchOption.values, patchOption: patchOption,
optionType: type,
selectedKey: value.length > 1 ? '' : getKey(e), selectedKey: value.length > 1 ? '' : getKey(e),
showDropdown: index == 0, showDropdown: index == 0,
onChanged: (newValue) { onChanged: (newValue) {
@@ -205,13 +210,13 @@ class IntStringLongListPatchOption extends StatelessWidget {
} }
} }
patchOptionValue.value = List.from(values); patchOptionValue.value = List.from(values);
onChanged(values, patchOption); model.modifyOptions(values, patchOption);
}, },
removeValue: () { removeValue: () {
patchOptionValue.value = List.from(patchOptionValue.value) patchOptionValue.value = List.from(patchOptionValue.value)
..removeAt(index); ..removeAt(index);
values.removeAt(index); values.removeAt(index);
onChanged(values, patchOption); model.modifyOptions(values, patchOption);
}, },
); );
}, },
@@ -231,7 +236,7 @@ class IntStringLongListPatchOption extends StatelessWidget {
List.from(patchOptionValue.value)..add(0); List.from(patchOptionValue.value)..add(0);
values.add(0); values.add(0);
} }
onChanged(values, patchOption); model.modifyOptions(values, patchOption);
}, },
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -254,9 +259,8 @@ class IntStringLongListPatchOption extends StatelessWidget {
}, },
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (Option option) { patchOptionValue: patchOptionValue,
removeOption(option); model: model,
},
); );
} }
} }
@@ -282,7 +286,8 @@ class UnsupportedPatchOption extends StatelessWidget {
), ),
), ),
patchOption: patchOption, patchOption: patchOption,
removeOption: (_) {}, patchOptionValue: ValueNotifier(null),
model: PatchOptionsViewModel(),
); );
} }
} }
@@ -292,15 +297,18 @@ class PatchOption extends StatelessWidget {
super.key, super.key,
required this.widget, required this.widget,
required this.patchOption, required this.patchOption,
required this.removeOption, required this.patchOptionValue,
required this.model,
}); });
final Widget widget; final Widget widget;
final Option patchOption; final Option patchOption;
final void Function(Option option) removeOption; final ValueNotifier patchOptionValue;
final PatchOptionsViewModel model;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final defaultValue = model.getDefaultValue(patchOption);
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: CustomCard( child: CustomCard(
@@ -337,11 +345,24 @@ class PatchOption extends StatelessWidget {
], ],
), ),
), ),
if (!patchOption.required) ValueListenableBuilder(
IconButton( valueListenable: patchOptionValue,
onPressed: () => removeOption(patchOption), builder: (context, value, child) {
icon: const Icon(Icons.delete), if (defaultValue != patchOptionValue.value) {
), return IconButton(
onPressed: () {
patchOptionValue.value = defaultValue;
model.modifyOptions(
defaultValue,
patchOption,
);
},
icon: const Icon(Icons.history),
);
}
return const SizedBox();
},
),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@@ -360,17 +381,15 @@ class TextFieldForPatchOption extends StatefulWidget {
const TextFieldForPatchOption({ const TextFieldForPatchOption({
super.key, super.key,
required this.value, required this.value,
required this.values, required this.patchOption,
this.removeValue, this.removeValue,
required this.onChanged, required this.onChanged,
required this.optionType,
required this.selectedKey, required this.selectedKey,
this.showDropdown = true, this.showDropdown = true,
}); });
final String? value; final String? value;
final Map<String, dynamic>? values; final Option patchOption;
final String optionType;
final String selectedKey; final String selectedKey;
final bool showDropdown; final bool showDropdown;
final void Function()? removeValue; final void Function()? removeValue;
@@ -382,26 +401,26 @@ 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;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isStringOption = widget.optionType.contains('String'); final bool isStringOption = widget.patchOption.valueType.contains('String');
final bool isArrayOption = widget.optionType.contains('Array'); final bool isArrayOption = widget.patchOption.valueType.contains('Array');
selectedKey ??= widget.selectedKey; selectedKey = selectedKey == '' ? selectedKey : widget.selectedKey;
controller.text = !isStringOption && final bool isValueArray = widget.value?.startsWith('[') ?? false;
isArrayOption && final bool shouldResetValue =
selectedKey == '' && !isStringOption && isArrayOption && selectedKey == '' && isValueArray;
(widget.value != null && widget.value.toString().startsWith('[')) controller.text = shouldResetValue ? '' : widget.value ?? '';
? ''
: widget.value ?? '';
defaultValue ??= controller.text; defaultValue ??= controller.text;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (widget.showDropdown && (widget.values?.isNotEmpty ?? false)) if (widget.showDropdown &&
(widget.patchOption.values?.isNotEmpty ?? false))
DropdownButton<String>( DropdownButton<String>(
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
@@ -410,11 +429,12 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
dropdownColor: Theme.of(context).colorScheme.secondaryContainer, dropdownColor: Theme.of(context).colorScheme.secondaryContainer,
isExpanded: true, isExpanded: true,
value: selectedKey, value: selectedKey,
items: widget.values!.entries items: widget.patchOption.values!.entries
.map( .map(
(e) => DropdownMenuItem( (e) => DropdownMenuItem(
value: e.key, value: e.key,
child: RichText( child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan( text: TextSpan(
text: e.key, text: e.key,
style: TextStyle( style: TextStyle(
@@ -427,7 +447,7 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
TextSpan( TextSpan(
text: ' ${e.value}', text: ' ${e.value}',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 16,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onSecondaryContainer .onSecondaryContainer
@@ -447,9 +467,7 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
t.patchOptionsView.customValue, t.patchOptionsView.customValue,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Theme.of(context) color: Theme.of(context).colorScheme.onSecondaryContainer,
.colorScheme
.onSecondaryContainer,
), ),
), ),
), ),
@@ -459,9 +477,11 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
controller.text = defaultValue!; controller.text = defaultValue!;
widget.onChanged(controller.text); widget.onChanged(controller.text);
} else { } else {
controller.text = widget.values![value].toString(); controller.text = widget.patchOption.values![value].toString();
widget.onChanged( widget.onChanged(
isArrayOption ? widget.values![value] : controller.text, isArrayOption
? widget.patchOption.values![value]
: controller.text,
); );
} }
setState(() { setState(() {
@@ -472,9 +492,9 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
if (selectedKey == '') if (selectedKey == '')
TextFormField( TextFormField(
inputFormatters: [ inputFormatters: [
if (widget.optionType.contains('Int')) if (widget.patchOption.valueType.contains('Int'))
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
if (widget.optionType.contains('Long')) if (widget.patchOption.valueType.contains('Long'))
FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')), FilteringTextInputFormatter.allow(RegExp(r'^[0-9]*\.?[0-9]*')),
], ],
controller: controller, controller: controller,
@@ -487,41 +507,70 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
return [ return [
if (isArrayOption) if (isArrayOption)
PopupMenuItem( PopupMenuItem(
value: t.remove, value: 'remove',
child: Text(t.remove), child: Text(t.remove),
), ),
if (isStringOption) ...[ if (isStringOption) ...[
PopupMenuItem( PopupMenuItem(
value: t.patchOptionsView.selectFilePath, value: 'file',
child: Text(t.patchOptionsView.selectFilePath), child: Text(t.patchOptionsView.selectFilePath),
), ),
PopupMenuItem( PopupMenuItem(
value: t.patchOptionsView.selectFolder, value: 'folder',
child: Text(t.patchOptionsView.selectFolder), child: Text(t.patchOptionsView.selectFolder),
), ),
], ],
if (!widget.patchOption.required)
PopupMenuItem(
value: 'null',
child: Text(t.patchOptionsView.setToNull),
),
]; ];
}, },
onSelected: (String selection) async { onSelected: (String selection) async {
switch (selection) { // manageExternalStorage permission is required for file/folder selection
case 'patchOptionsView.selectFilePath': // 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 'patchOptionsView.selectFolder': 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; },
};
if (availableActions.containsKey(selection)) {
await availableActions[selection]!();
} }
}, },
), ),

View File

@@ -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(),
], ],
); );
} }

View 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);
});
},
);
}
}

View File

@@ -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: [

View File

@@ -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,
), ),

763
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,6 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3", "@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"semantic-release": "^23.0.7" "semantic-release": "^23.0.8"
} }
} }

View File

@@ -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:
@@ -323,9 +331,9 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "560d21c4148b53933313573e7eafca0b0eb9aadf" ref: "900a81501f1f0e4996fe9e2cbf55f1ea8df08b49"
resolved-ref: "560d21c4148b53933313573e7eafca0b0eb9aadf" resolved-ref: "900a81501f1f0e4996fe9e2cbf55f1ea8df08b49"
url: "https://github.com/BenjaminHalko/flutter_background" url: "https://github.com/validcube/flutter_background"
source: git source: git
version: "1.2.0" version: "1.2.0"
flutter_cache_manager: flutter_cache_manager:
@@ -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
@@ -499,10 +515,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: injectable name: injectable
sha256: fb722c86cf8233008e4db41c696a6145721f45dc8aeba91103e3128c3d63c9c6 sha256: "3c8355a29d11ff28c0311bed754649761f345ef7a13ff66a714380954af51226"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.2"
injectable_generator: injectable_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -732,10 +748,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@@ -989,10 +1005,11 @@ packages:
skeletons: skeletons:
dependency: "direct main" dependency: "direct main"
description: description:
name: skeletons path: "."
sha256: "5b2d08ae7f908ee1f7007ca99f8dcebb4bfc1d3cb2143dec8d112a5be5a45c8f" ref: "326fbb4223ac4d8a6301cc2f16b6112ecd308c71"
url: "https://pub.dev" resolved-ref: "326fbb4223ac4d8a6301cc2f16b6112ecd308c71"
source: hosted url: "https://github.com/Ofceab-Studio/skeletons"
source: git
version: "0.0.3" version: "0.0.3"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
@@ -1211,10 +1228,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.5" version: "6.3.0"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@@ -1227,10 +1244,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.2.0"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1360,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"

View File

@@ -4,7 +4,7 @@ homepage: https://github.com/ReVanced/revanced-manager
publish_to: 'none' publish_to: 'none'
version: 1.21.0-dev.3+101800019 version: 1.21.0-dev.7+101800023
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
@@ -23,23 +23,24 @@ 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:
git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged git: # remove once https://github.com/JulianAssmann/flutter_background/pull/83 is merged
url: https://github.com/BenjaminHalko/flutter_background url: https://github.com/validcube/flutter_background
ref: 560d21c4148b53933313573e7eafca0b0eb9aadf # Branch: specify-namespace ref: 900a81501f1f0e4996fe9e2cbf55f1ea8df08b49 # Branch: specify-namespace
flutter_cache_manager: ^3.3.2 flutter_cache_manager: ^3.3.2
flutter_file_dialog: ^3.0.2 flutter_file_dialog: ^3.0.2
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
injectable: ^2.4.0 injectable: ^2.4.0
intl: ^0.18.1 intl: 0.19.0
json_annotation: ^4.9.0 json_annotation: ^4.9.0
language_code: ^0.5.3+2 language_code: ^0.5.3+2
logcat: logcat:
@@ -59,7 +60,10 @@ dependencies:
ref: 1a1616ac91e16cd1f3dd170a81febf27ffce3587 # Branch: master ref: 1a1616ac91e16cd1f3dd170a81febf27ffce3587 # Branch: master
share_plus: ^9.0.0 share_plus: ^9.0.0
shared_preferences: ^2.2.3 shared_preferences: ^2.2.3
skeletons: ^0.0.3 skeletons:
git: # remove once https://github.com/badjio/skeletons/pull/11 is merged
url: https://github.com/Ofceab-Studio/skeletons
ref: 326fbb4223ac4d8a6301cc2f16b6112ecd308c71 # Branch: master
slang: ^3.30.2 slang: ^3.30.2
slang_flutter: ^3.30.0 slang_flutter: ^3.30.0
stacked: ^3.4.2 stacked: ^3.4.2