mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-22 02:33:56 +00:00
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b09936d1 | ||
|
|
68e9f0f7c1 | ||
|
|
c3d345de80 | ||
|
|
385c0e246a | ||
|
|
5ead49a5b7 | ||
|
|
c0760b1347 | ||
|
|
e01b323aee | ||
|
|
6f4866ef63 | ||
|
|
1b6d72661c | ||
|
|
c59d4aea81 | ||
|
|
6260a80738 | ||
|
|
e75d3c8273 | ||
|
|
b7acb475e9 | ||
|
|
42b6bbff7c | ||
|
|
4b8542b35b | ||
|
|
9ad1d6cbfb | ||
|
|
4cdd9acd73 | ||
|
|
f4b0a695d6 | ||
|
|
b525ea1ba4 | ||
|
|
c1fc2c4766 | ||
|
|
5c733932c7 | ||
|
|
d1218616ec | ||
|
|
2bf6a03d56 | ||
|
|
b6ee63c1ea | ||
|
|
6d08efdcd7 | ||
|
|
a0a43a5651 | ||
|
|
3af2f5b032 | ||
|
|
8f54b226b4 | ||
|
|
9f64011b26 | ||
|
|
c5fc54e721 | ||
|
|
fc8a4fc5b6 | ||
|
|
6f9ab232ae | ||
|
|
8cb96f1e45 | ||
|
|
5733acb77a | ||
|
|
e49bcb2a69 | ||
|
|
42e41c399f | ||
|
|
166a3180d3 | ||
|
|
3bf4982f23 | ||
|
|
f4e1cccfac | ||
|
|
7911a8f49e | ||
|
|
64a96fc3ce | ||
|
|
8e2cfbddc5 | ||
|
|
45fae3f0fd | ||
|
|
e45a7824c1 | ||
|
|
5d72c48a76 | ||
|
|
d6169c6fa2 | ||
|
|
9df6d52e2d | ||
|
|
239de8e923 | ||
|
|
7d553a87f3 | ||
|
|
557b42bc56 | ||
|
|
8423914748 | ||
|
|
07dce23794 | ||
|
|
18fd0552db | ||
|
|
d537d48f8e | ||
|
|
b456512bbb | ||
|
|
d9953b1473 | ||
|
|
c6ac898390 | ||
|
|
43f98cec43 | ||
|
|
63175cdec6 | ||
|
|
6436a1ec61 | ||
|
|
c400619338 | ||
|
|
dcd5ba41cf | ||
|
|
7525e52fab | ||
|
|
72fd24e624 | ||
|
|
11d8f9fd30 | ||
|
|
4b0c8cecc8 | ||
|
|
372ce174c9 | ||
|
|
381daff980 | ||
|
|
94acebbebd | ||
|
|
f90f6e81ee | ||
|
|
97e37f304b | ||
|
|
f5e45ead26 | ||
|
|
14f765f4b4 | ||
|
|
b77d46b2c2 | ||
|
|
1442916b20 | ||
|
|
c6a5f42d23 | ||
|
|
580d50eb8d | ||
|
|
9a66b6e50d | ||
|
|
acec064cb7 | ||
|
|
ea05d13a1f | ||
|
|
fd741f2ccf | ||
|
|
055c52178f | ||
|
|
722a5859a5 | ||
|
|
1714c3fa86 | ||
|
|
131df28110 | ||
|
|
1be284f8d8 | ||
|
|
218e53ae75 | ||
|
|
264d8d90c4 | ||
|
|
0b529c2629 | ||
|
|
5abcc7191f | ||
|
|
5346f6e1bf | ||
|
|
2f471b3de4 | ||
|
|
3cf06efddf | ||
|
|
1f4f0a3bb7 |
97
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
97
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: 🐞 Bug report
|
name: 🐞 Bug report
|
||||||
description: Report a very clearly broken issue.
|
description: Create a new bug report.
|
||||||
title: 'bug: <title>'
|
title: 'bug: <title>'
|
||||||
labels: [bug]
|
labels: [bug]
|
||||||
body:
|
body:
|
||||||
@@ -8,53 +8,20 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
# ReVanced Manager bug report
|
# ReVanced Manager bug report
|
||||||
|
|
||||||
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug).
|
Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: Type
|
|
||||||
options:
|
|
||||||
- Error while running the manager
|
|
||||||
- Error at runtime
|
|
||||||
- Cosmetic
|
|
||||||
- Other
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Bug description
|
label: Bug description
|
||||||
description: How did you find the bug? Any additional details that might help?
|
description: |
|
||||||
|
- Describe your bug in detail
|
||||||
|
- Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...)
|
||||||
|
- Add images and videos if possible
|
||||||
|
- List selected patches if applicable
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to reproduce
|
label: Version of ReVanced Manager and version & name of application you tried to patch
|
||||||
description: Add the steps to reproduce this bug, including your environment.
|
|
||||||
placeholder: Step 1. Download some files. Step 2. ...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Android version
|
|
||||||
description: Android version used.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Manager version
|
|
||||||
description: Manager version used.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Target package name
|
|
||||||
description: App you tried to patch.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Target package version.
|
|
||||||
description: Version of the app you tried to patch.
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
@@ -64,57 +31,31 @@ body:
|
|||||||
- Non-root
|
- Non-root
|
||||||
- Root
|
- Root
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Patches selected.
|
label: Device logs
|
||||||
description: Patches you selected for the app.
|
description: Export logs in ReVanced Manager settings.
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Device logs (exported using Manager settings).
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so there is no need for backticks.
|
|
||||||
render: shell
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Installer logs (exported using Installer menu option) [unneeded if the issue is not during patching].
|
label: Patcher logs
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so there is no need for backticks.
|
description: Export logs in "Patcher" screen.
|
||||||
render: shell
|
render: shell
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Screenshots or video
|
|
||||||
description: Add screenshots or videos that show the bug here.
|
|
||||||
placeholder: Drag and drop the screenshots/videos into this box.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Solution
|
|
||||||
description: If applicable, add a possible solution.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Additional context
|
|
||||||
description: Add additional context here.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: acknowledgments
|
|
||||||
attributes:
|
attributes:
|
||||||
label: Acknowledgments
|
label: Acknowledgements
|
||||||
description: Your issue will be closed if you haven't done these steps.
|
description: Your issue will be closed if you don't follow the checklist below!
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues; this is new and no duplicate or related to another open issue.
|
- label: This request is not a duplicate of an existing issue.
|
||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have chosen an appropriate title.
|
||||||
required: true
|
required: true
|
||||||
- label: I properly filled out all of the requested information in this issue.
|
- label: All requested information has been provided properly.
|
||||||
required: true
|
required: true
|
||||||
- label: The issue is solely related to ReVanced Manager and not caused by patches.
|
- label: The issue is solely related to the ReVanced Manager
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
40
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
40
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
@@ -1,52 +1,42 @@
|
|||||||
name: ⭐ Feature request
|
name: ⭐ Feature request
|
||||||
description: Create a detailed feature request.
|
description: Create a new feature request.
|
||||||
title: 'feat: <title>'
|
title: 'feat: <title>'
|
||||||
labels: [feature-request]
|
labels: [feature-request]
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Type
|
value: |
|
||||||
options:
|
# ReVanced Manager feature request
|
||||||
- Functionality
|
|
||||||
- Cosmetic
|
Please check for existing feature requests [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
|
||||||
- Other
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Issue
|
label: Feature description
|
||||||
description: What is the current problem. Why does it require a feature request?
|
description: Describe your feature in detail.
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Feature
|
|
||||||
description: Describe your feature in detail. How does it solve the issue?
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Motivation
|
label: Motivation
|
||||||
description: Why should your feature should be considered?
|
description: Explain why the lack of it is a problem.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional context
|
label: Additional context
|
||||||
description: Add additional context here.
|
description: In case there is something else you want to add.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: acknowledgements
|
|
||||||
attributes:
|
attributes:
|
||||||
label: Acknowledgements
|
label: Acknowledgements
|
||||||
description: Your issue will be closed if you haven't done these steps.
|
description: Your issue will be closed if you don't follow the checklist below!
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
- label: This request is not a duplicate of an existing issue.
|
||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have chosen an appropriate title.
|
||||||
required: true
|
required: true
|
||||||
- label: I filled out all of the requested information in this issue properly.
|
- label: All requested information has been provided properly.
|
||||||
required: true
|
required: true
|
||||||
- label: The issue is related solely to the ReVanced Manager
|
- label: The issue is solely related to the ReVanced Manager
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
38
.github/workflows/analyze.yml
vendored
38
.github/workflows/analyze.yml
vendored
@@ -1,38 +0,0 @@
|
|||||||
name: Analyze Code
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "dev" ]
|
|
||||||
paths:
|
|
||||||
- "**.dart"
|
|
||||||
- ".github/workflows/analyze.yml"
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main", "dev" ]
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- reopened
|
|
||||||
- synchronize
|
|
||||||
- ready_for_review
|
|
||||||
paths:
|
|
||||||
- "**.dart"
|
|
||||||
- ".github/workflows/analyze.yml"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: "Static analysis & format check"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Setup Flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: 'stable'
|
|
||||||
cache: true
|
|
||||||
- name: Install Flutter dependencies
|
|
||||||
run: flutter pub get
|
|
||||||
- name: Generate files with Builder
|
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
- name: Analyze code
|
|
||||||
uses: ValentinVignal/action-dart-analyze@v0.15
|
|
||||||
with:
|
|
||||||
fail-on: warning
|
|
||||||
14
.github/workflows/pr-build.yml
vendored
14
.github/workflows/pr-build.yml
vendored
@@ -2,14 +2,19 @@ name: PR Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/pr-build.yml"
|
||||||
|
- "android/**"
|
||||||
|
- "assets/**"
|
||||||
|
- "lib/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Make sure the release step uses its own credentials:
|
# Make sure the release step uses its own credentials:
|
||||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
@@ -20,7 +25,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '11'
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
cache: 'gradle'
|
|
||||||
- name: Setup Flutter
|
- name: Setup Flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -33,9 +37,9 @@ jobs:
|
|||||||
- name: Build with Flutter
|
- name: Build with Flutter
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: flutter build apk
|
run: flutter build apk --debug
|
||||||
- name: Upload build
|
- name: Upload build
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: revanced-manager
|
name: revanced-manager
|
||||||
path: build/app/outputs/flutter-apk/app-release.apk
|
path: build/app/outputs/flutter-apk/app-debug.apk
|
||||||
|
|||||||
2
.github/workflows/release-build.yml
vendored
2
.github/workflows/release-build.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 11
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -58,6 +58,7 @@ unlinked.ds
|
|||||||
unlinked_spec.ds
|
unlinked_spec.ds
|
||||||
|
|
||||||
# Android related
|
# Android related
|
||||||
|
.gradle/
|
||||||
**/android/**/gradle-wrapper.jar
|
**/android/**/gradle-wrapper.jar
|
||||||
**/android/.gradle
|
**/android/.gradle
|
||||||
**/android/captures/
|
**/android/captures/
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -3,29 +3,33 @@
|
|||||||
The official ReVanced Manager based on Flutter.
|
The official ReVanced Manager based on Flutter.
|
||||||
|
|
||||||
## 🔽 Download
|
## 🔽 Download
|
||||||
To download latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
|
||||||
|
You can obtain ReVanced Manager by downloading it from either [revanced.app/download](https://revanced.app/download) or [GitHub Releases](https://github.com/ReVanced/revanced-manager/releases)
|
||||||
|
|
||||||
## 📝 Prerequisites
|
## 📝 Prerequisites
|
||||||
|
|
||||||
1. Android 8 or higher
|
1. Android 8 or higher
|
||||||
2. Does not work on some armv7 devices
|
2. Incompatible with certain ARMv7 devices
|
||||||
|
|
||||||
|
## 📃 Documentation
|
||||||
|
The documentation can be found [here](https://github.com/revanced/revanced-manager/tree/main/docs).
|
||||||
|
|
||||||
## 🔴 Issues
|
## 🔴 Issues
|
||||||
|
|
||||||
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
||||||
|
|
||||||
## 💭 Discussion
|
|
||||||
If you wish to discuss the Manager, a thread has been made under the [#development](https://discord.com/channels/952946952348270622/1002922226443632761) channel in the Discord server, please note that this thread may be temporary and may be removed in the future.
|
|
||||||
|
|
||||||
|
|
||||||
## 🌐 Translation
|
## 🌐 Translation
|
||||||
|
|
||||||
[](https://crowdin.com/project/revanced)
|
[](https://crowdin.com/project/revanced)
|
||||||
|
|
||||||
If you wish to translate ReVanced Manager, we're accepting translations on [Crowdin](https://translate.revanced.app)
|
We're accepting translations on [Crowdin](https://translate.revanced.app).
|
||||||
|
|
||||||
## 🛠️ Building Manager from source
|
## 🛠️ Building Manager from source
|
||||||
|
|
||||||
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||||
2. Clone the repository locally
|
2. Clone the repository locally
|
||||||
3. Add your github token in gradle.properties like [this](/docs/4_building.md)
|
3. Add your GitHub token in gradle.properties like [this](/docs/4_building.md)
|
||||||
4. Open the project in terminal
|
4. Open the project in terminal
|
||||||
5. Run `flutter pub get` in terminal
|
5. Run `flutter pub get` in terminal
|
||||||
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
||||||
7. To build release apk run `flutter build apk`
|
7. To build release APK run `flutter build apk`
|
||||||
|
|||||||
@@ -71,12 +71,11 @@ linter:
|
|||||||
- flutter_style_todos
|
- flutter_style_todos
|
||||||
- hash_and_equals
|
- hash_and_equals
|
||||||
- implementation_imports
|
- implementation_imports
|
||||||
- iterable_contains_unrelated_type
|
- collection_methods_unrelated_type
|
||||||
- leading_newlines_in_multiline_strings
|
- leading_newlines_in_multiline_strings
|
||||||
- library_names
|
- library_names
|
||||||
- library_prefixes
|
- library_prefixes
|
||||||
- library_private_types_in_public_api
|
- library_private_types_in_public_api
|
||||||
- list_remove_unrelated_type
|
|
||||||
- missing_whitespace_between_adjacent_strings
|
- missing_whitespace_between_adjacent_strings
|
||||||
- no_adjacent_strings_in_list
|
- no_adjacent_strings_in_list
|
||||||
- no_duplicate_case_values
|
- no_duplicate_case_values
|
||||||
|
|||||||
@@ -54,7 +54,21 @@ android {
|
|||||||
release {
|
release {
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
|
resValue "string", "app_name", "ReVanced Manager"
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
shrinkResources false
|
||||||
|
minifyEnabled false
|
||||||
|
resValue "string", "app_name", "ReVanced Manager Debug"
|
||||||
|
applicationIdSuffix ".debug"
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
ndk {
|
||||||
|
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,10 +85,9 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation "app.revanced:revanced-patcher:12.1.1"
|
implementation "app.revanced:revanced-patcher:14.2.2"
|
||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
implementation("com.android.tools.build:apksig:7.2.2")
|
implementation("com.android.tools.build:apksig:7.2.2")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,12 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
<application
|
<application
|
||||||
android:label="ReVanced Manager"
|
android:label="@string/app_name"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true">
|
||||||
android:enableOnBackInvokedCallback="true">
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
package app.revanced.manager.flutter
|
package app.revanced.manager.flutter
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.NonNull
|
|
||||||
import app.revanced.manager.flutter.utils.Aapt
|
import app.revanced.manager.flutter.utils.Aapt
|
||||||
import app.revanced.manager.flutter.utils.aligning.ZipAligner
|
import app.revanced.manager.flutter.utils.aligning.ZipAligner
|
||||||
import app.revanced.manager.flutter.utils.signing.Signer
|
import app.revanced.manager.flutter.utils.signing.Signer
|
||||||
import app.revanced.manager.flutter.utils.zip.ZipFile
|
import app.revanced.manager.flutter.utils.zip.ZipFile
|
||||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
||||||
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
||||||
import app.revanced.patcher.PatcherOptions
|
import app.revanced.patcher.PatcherOptions
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.description
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.include
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.logging.Logger
|
import app.revanced.patcher.patch.PatchResult
|
||||||
import app.revanced.patcher.util.patch.PatchBundle
|
|
||||||
import dalvik.system.DexClassLoader
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.PrintWriter
|
||||||
private const val PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher"
|
import java.io.StringWriter
|
||||||
private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer"
|
import java.util.logging.LogRecord
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
@@ -30,10 +33,18 @@ class MainActivity : FlutterActivity() {
|
|||||||
private var cancel: Boolean = false
|
private var cancel: Boolean = false
|
||||||
private var stopResult: MethodChannel.Result? = null
|
private var stopResult: MethodChannel.Result? = null
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
val mainChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, PATCHER_CHANNEL)
|
|
||||||
installerChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL)
|
val patcherChannel = "app.revanced.manager.flutter/patcher"
|
||||||
|
val installerChannel = "app.revanced.manager.flutter/installer"
|
||||||
|
|
||||||
|
val mainChannel =
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, patcherChannel)
|
||||||
|
|
||||||
|
this.installerChannel =
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, installerChannel)
|
||||||
|
|
||||||
mainChannel.setMethodCallHandler { call, result ->
|
mainChannel.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"runPatcher" -> {
|
"runPatcher" -> {
|
||||||
@@ -73,14 +84,37 @@ class MainActivity : FlutterActivity() {
|
|||||||
keyStoreFilePath,
|
keyStoreFilePath,
|
||||||
keystorePassword
|
keystorePassword
|
||||||
)
|
)
|
||||||
} else {
|
} else result.notImplemented()
|
||||||
result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"stopPatcher" -> {
|
"stopPatcher" -> {
|
||||||
cancel = true
|
cancel = true
|
||||||
stopResult = result
|
stopResult = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"getPatches" -> {
|
||||||
|
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
|
||||||
|
if (patchBundleFilePath != null) {
|
||||||
|
val patches = PatchBundleLoader.Dex(
|
||||||
|
File(patchBundleFilePath)
|
||||||
|
).map { patch ->
|
||||||
|
val map = HashMap<String, Any>()
|
||||||
|
map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\""
|
||||||
|
map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\""
|
||||||
|
map["\"excluded\""] = !patch.include
|
||||||
|
map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList<Any>()
|
||||||
|
map["\"compatiblePackages\""] = patch.compatiblePackages?.map {
|
||||||
|
val map2 = HashMap<String, Any>()
|
||||||
|
map2["\"name\""] = "\"${it.name}\""
|
||||||
|
map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" }
|
||||||
|
map2
|
||||||
|
} ?: emptyList<Any>()
|
||||||
|
map
|
||||||
|
}
|
||||||
|
result.success(patches)
|
||||||
|
} else result.notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,179 +139,141 @@ class MainActivity : FlutterActivity() {
|
|||||||
val outFile = File(outFilePath)
|
val outFile = File(outFilePath)
|
||||||
val integrations = File(integrationsPath)
|
val integrations = File(integrationsPath)
|
||||||
val keyStoreFile = File(keyStoreFilePath)
|
val keyStoreFile = File(keyStoreFilePath)
|
||||||
|
val cacheDir = File(cacheDirPath)
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
try {
|
fun updateProgress(progress: Double, header: String, log: String) {
|
||||||
handler.post {
|
handler.post {
|
||||||
installerChannel.invokeMethod(
|
installerChannel.invokeMethod(
|
||||||
"update",
|
"update",
|
||||||
mapOf(
|
mapOf(
|
||||||
"progress" to 0.1,
|
"progress" to progress,
|
||||||
"header" to "",
|
"header" to header,
|
||||||
"log" to "Copying original apk"
|
"log" to log
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(cancel) {
|
fun postStop() = handler.post { stopResult!!.success(null) }
|
||||||
handler.post { stopResult!!.success(null) }
|
|
||||||
|
// Setup logger
|
||||||
|
Logger.getLogger("").apply {
|
||||||
|
handlers.forEach {
|
||||||
|
it.close()
|
||||||
|
removeHandler(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
object : java.util.logging.Handler() {
|
||||||
|
override fun publish(record: LogRecord) =
|
||||||
|
updateProgress(-1.0, "", record.message)
|
||||||
|
|
||||||
|
override fun flush() = Unit
|
||||||
|
override fun close() = flush()
|
||||||
|
}.let(::addHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateProgress(0.0, "", "Copying APK")
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
originalFile.copyTo(inputFile, true)
|
originalFile.copyTo(inputFile, true)
|
||||||
|
|
||||||
handler.post {
|
if (cancel) {
|
||||||
installerChannel.invokeMethod(
|
postStop()
|
||||||
"update",
|
return@Thread
|
||||||
mapOf(
|
}
|
||||||
"progress" to 0.2,
|
|
||||||
"header" to "Unpacking apk...",
|
updateProgress(0.05, "Reading APK...", "Reading APK")
|
||||||
"log" to "Unpacking input apk"
|
|
||||||
)
|
val patcher = Patcher(
|
||||||
|
PatcherOptions(
|
||||||
|
inputFile,
|
||||||
|
cacheDir,
|
||||||
|
Aapt.binary(applicationContext).absolutePath,
|
||||||
|
cacheDir.path,
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
|
|
||||||
if(cancel) {
|
if (cancel) {
|
||||||
handler.post { stopResult!!.success(null) }
|
postStop()
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
val patcher =
|
updateProgress(0.1, "Loading patches...", "Loading patches")
|
||||||
Patcher(
|
|
||||||
PatcherOptions(
|
|
||||||
inputFile,
|
|
||||||
cacheDirPath,
|
|
||||||
Aapt.binary(applicationContext).absolutePath,
|
|
||||||
cacheDirPath,
|
|
||||||
logger = ManagerLogger()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if(cancel) {
|
val patches = PatchBundleLoader.Dex(
|
||||||
handler.post { stopResult!!.success(null) }
|
File(patchBundleFilePath),
|
||||||
|
optimizedDexDirectory = cacheDir
|
||||||
|
).filter { patch ->
|
||||||
|
val isCompatible = patch.compatiblePackages?.any {
|
||||||
|
it.name == patcher.context.packageMetadata.packageName
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
val compatibleOrUniversal =
|
||||||
|
isCompatible || patch.compatiblePackages.isNullOrEmpty()
|
||||||
|
|
||||||
|
compatibleOrUniversal && selectedPatches.any { it == patch.patchName }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.post {
|
updateProgress(0.15, "Executing...", "")
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.4,
|
|
||||||
"header" to "Merging integrations...",
|
|
||||||
"log" to "Merging integrations"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cancel) {
|
// Update the progress bar every time a patch is executed from 0.15 to 0.7
|
||||||
handler.post { stopResult!!.success(null) }
|
val totalPatchesCount = patches.size
|
||||||
return@Thread
|
val progressStep = 0.55 / totalPatchesCount
|
||||||
}
|
var progress = 0.15
|
||||||
|
|
||||||
patcher.addIntegrations(listOf(integrations)) {}
|
patcher.apply {
|
||||||
|
acceptIntegrations(listOf(integrations))
|
||||||
|
acceptPatches(patches)
|
||||||
|
|
||||||
if(cancel) {
|
runBlocking {
|
||||||
handler.post { stopResult!!.success(null) }
|
apply(false).collect { patchResult: PatchResult ->
|
||||||
return@Thread
|
if (cancel) {
|
||||||
}
|
handler.post { stopResult!!.success(null) }
|
||||||
|
this.cancel()
|
||||||
|
this@apply.close()
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
|
||||||
handler.post {
|
val msg = patchResult.exception?.let {
|
||||||
installerChannel.invokeMethod(
|
val writer = StringWriter()
|
||||||
"update",
|
it.printStackTrace(PrintWriter(writer))
|
||||||
mapOf(
|
"${patchResult.patchName} failed: $writer"
|
||||||
"progress" to 0.5,
|
} ?: run {
|
||||||
"header" to "Applying patches...",
|
"${patchResult.patchName} succeeded"
|
||||||
"log" to ""
|
}
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cancel) {
|
updateProgress(progress, "", msg)
|
||||||
handler.post { stopResult!!.success(null) }
|
progress += progressStep
|
||||||
return@Thread
|
|
||||||
}
|
|
||||||
|
|
||||||
val patches = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
|
|
||||||
PatchBundle.Dex(
|
|
||||||
patchBundleFilePath,
|
|
||||||
DexClassLoader(
|
|
||||||
patchBundleFilePath,
|
|
||||||
cacheDirPath,
|
|
||||||
null,
|
|
||||||
javaClass.classLoader
|
|
||||||
)
|
|
||||||
).loadPatches().filter { patch ->
|
|
||||||
(patch.compatiblePackages?.any { it.name == patcher.context.packageMetadata.packageName } == true || patch.compatiblePackages.isNullOrEmpty()) &&
|
|
||||||
selectedPatches.any { it == patch.patchName }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TODO("VERSION.SDK_INT < CUPCAKE")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cancel) {
|
|
||||||
handler.post { stopResult!!.success(null) }
|
|
||||||
return@Thread
|
|
||||||
}
|
|
||||||
|
|
||||||
patcher.addPatches(patches)
|
|
||||||
patcher.executePatches().forEach { (patch, res) ->
|
|
||||||
if (res.isSuccess) {
|
|
||||||
val msg = "Applied $patch"
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.5,
|
|
||||||
"header" to "",
|
|
||||||
"log" to msg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if(cancel) {
|
|
||||||
handler.post { stopResult!!.success(null) }
|
|
||||||
return@Thread
|
|
||||||
}
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val msg =
|
|
||||||
"Failed to apply $patch: " + "${res.exceptionOrNull()!!.message ?: res.exceptionOrNull()!!.cause!!::class.simpleName}"
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to 0.5, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(cancel) {
|
|
||||||
handler.post { stopResult!!.success(null) }
|
|
||||||
return@Thread
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.post {
|
if (cancel) {
|
||||||
installerChannel.invokeMethod(
|
postStop()
|
||||||
"update",
|
patcher.close()
|
||||||
mapOf(
|
|
||||||
"progress" to 0.7,
|
|
||||||
"header" to "Repacking apk...",
|
|
||||||
"log" to "Repacking patched apk"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if(cancel) {
|
|
||||||
handler.post { stopResult!!.success(null) }
|
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
val res = patcher.save()
|
|
||||||
|
updateProgress(0.8, "Building...", "")
|
||||||
|
|
||||||
|
val res = patcher.get()
|
||||||
|
patcher.close()
|
||||||
|
|
||||||
ZipFile(patchedFile).use { file ->
|
ZipFile(patchedFile).use { file ->
|
||||||
res.dexFiles.forEach {
|
res.dexFiles.forEach {
|
||||||
if(cancel) {
|
if (cancel) {
|
||||||
handler.post { stopResult!!.success(null) }
|
postStop()
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
file.addEntryCompressData(
|
file.addEntryCompressData(
|
||||||
@@ -296,90 +292,35 @@ class MainActivity : FlutterActivity() {
|
|||||||
ZipAligner::getEntryAlignment
|
ZipAligner::getEntryAlignment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if(cancel) {
|
|
||||||
handler.post { stopResult!!.success(null) }
|
if (cancel) {
|
||||||
|
postStop()
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
updateProgress(0.9, "Signing...", "Signing APK")
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 0.9,
|
|
||||||
"header" to "Signing apk...",
|
|
||||||
"log" to ""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Signer("ReVanced", keystorePassword).signApk(
|
Signer("ReVanced", keystorePassword)
|
||||||
patchedFile,
|
.signApk(patchedFile, outFile, keyStoreFile)
|
||||||
outFile,
|
|
||||||
keyStoreFile
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
//log to console
|
print("Error signing APK: ${e.message}")
|
||||||
print("Error signing apk: ${e.message}")
|
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.post {
|
updateProgress(1.0, "Patched", "Patched")
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf(
|
|
||||||
"progress" to 1.0,
|
|
||||||
"header" to "Finished!",
|
|
||||||
"log" to "Finished!"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
val stack = ex.stackTraceToString()
|
if (!cancel) {
|
||||||
handler.post {
|
val stack = ex.stackTraceToString()
|
||||||
installerChannel.invokeMethod(
|
updateProgress(
|
||||||
"update",
|
-100.0,
|
||||||
mapOf(
|
"Aborted",
|
||||||
"progress" to -100.0,
|
"An error occurred:\n$stack"
|
||||||
"header" to "Aborted...",
|
|
||||||
"log" to "An error occurred! Aborted\nError:\n$stack"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.post { result.success(null) }
|
handler.post { result.success(null) }
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ManagerLogger : Logger {
|
|
||||||
override fun error(msg: String) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel
|
|
||||||
.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to -1.0, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun warn(msg: String) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to -1.0, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun info(msg: String) {
|
|
||||||
handler.post {
|
|
||||||
installerChannel.invokeMethod(
|
|
||||||
"update",
|
|
||||||
mapOf("progress" to -1.0, "header" to "", "log" to msg)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun trace(_msg: String) { /* unused */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.7.10'
|
ext.kotlin_version = '1.9.0'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -22,6 +22,7 @@ allprojects {
|
|||||||
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
|
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mavenLocal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
"yesButton": "Yes",
|
"yesButton": "Yes",
|
||||||
"noButton": "No",
|
"noButton": "No",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
|
"notice": "Notice",
|
||||||
|
"noShowAgain": "Don't show this again",
|
||||||
|
"new": "New",
|
||||||
"navigationView": {
|
"navigationView": {
|
||||||
"dashboardTab": "Dashboard",
|
"dashboardTab": "Dashboard",
|
||||||
"patcherTab": "Patcher",
|
"patcherTab": "Patcher",
|
||||||
@@ -33,10 +36,10 @@
|
|||||||
"updatePatchesDialogTitle": "Update ReVanced Patches",
|
"updatePatchesDialogTitle": "Update ReVanced Patches",
|
||||||
"updateChangelogTitle": "Changelog",
|
"updateChangelogTitle": "Changelog",
|
||||||
|
|
||||||
"patchesConsentDialogText": "ReVanced Patches need to be downloaded to patch apps.",
|
"patchesConsentDialogText": "ReVanced Patches needs to be downloaded.",
|
||||||
"patchesConsentDialogText2": "This will reveal your IP address to {url}.",
|
"patchesConsentDialogText2": "This will connect you to {url}.",
|
||||||
"patchesConsentDialogText3": "Auto update",
|
"patchesConsentDialogText3": "Auto update?",
|
||||||
"patchesConsentDialogText3Sub": "You can still change this in the settings later",
|
"patchesConsentDialogText3Sub": "You can change this in settings at a later time.",
|
||||||
|
|
||||||
"notificationTitle": "Update downloaded",
|
"notificationTitle": "Update downloaded",
|
||||||
"notificationText": "Tap to install the update",
|
"notificationText": "Tap to install the update",
|
||||||
@@ -70,7 +73,8 @@
|
|||||||
|
|
||||||
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
|
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
|
||||||
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
|
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
|
||||||
"splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?"
|
"splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?",
|
||||||
|
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?"
|
||||||
},
|
},
|
||||||
"appSelectorCard": {
|
"appSelectorCard": {
|
||||||
"widgetTitle": "Select an application",
|
"widgetTitle": "Select an application",
|
||||||
@@ -112,6 +116,7 @@
|
|||||||
"patchesSelectorView": {
|
"patchesSelectorView": {
|
||||||
"viewTitle": "Select patches",
|
"viewTitle": "Select patches",
|
||||||
"searchBarHint": "Search patches",
|
"searchBarHint": "Search patches",
|
||||||
|
"universalPatches": "Universal patches",
|
||||||
|
|
||||||
"doneButton": "Done",
|
"doneButton": "Done",
|
||||||
|
|
||||||
@@ -129,12 +134,24 @@
|
|||||||
},
|
},
|
||||||
"patchItem": {
|
"patchItem": {
|
||||||
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
||||||
"unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed."
|
"unsupportedPatchVersion": "Patch is not supported for this app version. Enable the experimental toggle in settings to proceed.",
|
||||||
|
|
||||||
|
"newPatchDialogText": "This is a new patch that has been added since the last time you have patched this app.",
|
||||||
|
"newPatch": "New patch",
|
||||||
|
|
||||||
|
"patchesChangeWarningDialogText": "It is recommended to use the default selection of patches because changing it may cause unexpected issues.\n\nIf you know what you are doing, you can enable \"Enable changing selection\" in the settings.",
|
||||||
|
"patchesChangeWarningDialogButton": "Use default selection"
|
||||||
},
|
},
|
||||||
"installerView": {
|
"installerView": {
|
||||||
"widgetTitle": "Installer",
|
"widgetTitle": "Installer",
|
||||||
|
"installType": "Select install type",
|
||||||
|
"installTypeDescription": "Select the installation type to proceed with.",
|
||||||
|
|
||||||
"installButton": "Install",
|
"installButton": "Install",
|
||||||
"installRootButton": "Install as Root",
|
"installRootType": "Root",
|
||||||
|
"installNonRootType": "Non-root",
|
||||||
|
"installRecommendedType": "Recommended",
|
||||||
|
|
||||||
"pressBackAgain": "Press back again to cancel",
|
"pressBackAgain": "Press back again to cancel",
|
||||||
"openButton": "Open",
|
"openButton": "Open",
|
||||||
"shareButton": "Share file",
|
"shareButton": "Share file",
|
||||||
@@ -142,9 +159,8 @@
|
|||||||
"notificationTitle": "ReVanced Manager is patching",
|
"notificationTitle": "ReVanced Manager is patching",
|
||||||
"notificationText": "Tap to return to the installer",
|
"notificationText": "Tap to return to the installer",
|
||||||
|
|
||||||
"shareApkMenuOption": "Share APK",
|
"exportApkButtonTooltip": "Export patched APK",
|
||||||
"exportApkMenuOption": "Export APK",
|
"exportLogButtonTooltip": "Export log",
|
||||||
"shareLogMenuOption": "Share log",
|
|
||||||
|
|
||||||
"installErrorDialogTitle": "Error",
|
"installErrorDialogTitle": "Error",
|
||||||
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
|
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
|
||||||
@@ -162,8 +178,10 @@
|
|||||||
"exportSectionTitle": "Import & export",
|
"exportSectionTitle": "Import & export",
|
||||||
"logsSectionTitle": "Logs",
|
"logsSectionTitle": "Logs",
|
||||||
|
|
||||||
"darkThemeLabel": "Dark mode",
|
"themeModeLabel": "App theme",
|
||||||
"darkThemeHint": "Welcome to the dark side",
|
"systemThemeLabel": "System",
|
||||||
|
"lightThemeLabel": "Light",
|
||||||
|
"darkThemeLabel": "Dark",
|
||||||
|
|
||||||
"dynamicThemeLabel": "Material You",
|
"dynamicThemeLabel": "Material You",
|
||||||
"dynamicThemeHint": "Enjoy an experience closer to your device",
|
"dynamicThemeHint": "Enjoy an experience closer to your device",
|
||||||
@@ -193,6 +211,12 @@
|
|||||||
"logsLabel": "Logs",
|
"logsLabel": "Logs",
|
||||||
"logsHint": "Share Manager's logs",
|
"logsHint": "Share Manager's logs",
|
||||||
|
|
||||||
|
"enablePatchesSelectionLabel": "Enable changing selection",
|
||||||
|
"enablePatchesSelectionHint": "Enable changing the selection of patches.",
|
||||||
|
"enablePatchesSelectionWarningText": "Changing the default selection of patches may cause unexpected issues.\n\nEnable anyways?",
|
||||||
|
"disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?",
|
||||||
|
|
||||||
|
"autoUpdatePatchesLabel": "Auto update patches",
|
||||||
"autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version",
|
"autoUpdatePatchesHint": "Automatically update ReVanced Patches to the latest version",
|
||||||
"experimentalUniversalPatchesLabel": "Experimental universal patches support",
|
"experimentalUniversalPatchesLabel": "Experimental universal patches support",
|
||||||
"experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower",
|
"experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
In order to use ReVanced on your Android device, ReVanced Manager must be installed.
|
In order to use ReVanced on your Android device, ReVanced Manager must be installed.
|
||||||
|
|
||||||
## 🪜 Installation steps
|
## ✅ Installation steps
|
||||||
|
|
||||||
1. Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest)
|
1. Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest)
|
||||||
2. Install ReVanced Manager
|
2. Install ReVanced Manager
|
||||||
@@ -11,4 +11,4 @@ In order to use ReVanced on your Android device, ReVanced Manager must be instal
|
|||||||
|
|
||||||
The next page will guide you through using ReVanced Manager.
|
The next page will guide you through using ReVanced Manager.
|
||||||
|
|
||||||
Continue: [🪛 Usage](2_usage.md)
|
Continue: [🛠️ Usage](2_usage.md)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The following pages will guide you through using ReVanced Manager to patch apps.
|
The following pages will guide you through using ReVanced Manager to patch apps.
|
||||||
|
|
||||||
## 🪜 Steps to patch apps
|
## ✅ Steps to patch apps
|
||||||
|
|
||||||
1. Navigate to the **Patcher** tab from the bottom navigation bar
|
1. Navigate to the **Patcher** tab from the bottom navigation bar
|
||||||
2. Tap on the **Select an app** card
|
2. Tap on the **Select an app** card
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
After patching an app, you may want to manage it. This page will guide you through managing patched apps.
|
After patching an app, you may want to manage it. This page will guide you through managing patched apps.
|
||||||
|
|
||||||
## 🪜 Steps to manage patched apps
|
## ✅ Steps to manage patched apps
|
||||||
|
|
||||||
1. Tap on the **Dashboard** tab in the bottom navigation bar
|
1. Tap on the **Dashboard** tab in the bottom navigation bar
|
||||||
2. Select the **Installed** chip
|
2. Tap on the **Info** button for the app you want to manage
|
||||||
3. Tap on the **Info** button for the app you want to manage
|
3. Choose one of the options from the menu
|
||||||
4. Choose one of the options from the menu
|
|
||||||
|
|
||||||
## ⏭️ What's next
|
## ⏭️ What's next
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date.
|
In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date.
|
||||||
|
|
||||||
## 🪜 Updating steps
|
## ✅ Updating steps
|
||||||
|
|
||||||
1. Navigate to the **Dashboard** tab from the bottom navigation bar
|
1. Navigate to the **Dashboard** tab from the bottom navigation bar
|
||||||
2. Tap on the **Update** button in the **Updates** section
|
2. Tap on the **Update** button in the **Updates** section
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
ReVanced Manager has settings that can be configured to your liking.
|
ReVanced Manager has settings that can be configured to your liking.
|
||||||
|
|
||||||
## 🪛 Essential settings
|
## ⭐ Essential settings
|
||||||
|
|
||||||
- ### 🔗 API URL
|
- ### 🔗 API URL
|
||||||
|
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ The following pages will guide you through using ReVanced Manager to patch apps,
|
|||||||
|
|
||||||
The next page will guide you through troubleshooting ReVanced Manager.
|
The next page will guide you through troubleshooting ReVanced Manager.
|
||||||
|
|
||||||
Continue: [🛟 Troubleshooting](3_troubleshooting.md)
|
Continue: [❔ Troubleshooting](3_troubleshooting.md)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 🛟 Troubleshooting
|
# ❔ Troubleshooting
|
||||||
|
|
||||||
In case you encounter any issues while using ReVanced Manager, please refer to this page for possible solutions.
|
In case you encounter any issues while using ReVanced Manager, please refer to this page for possible solutions.
|
||||||
|
|
||||||
@@ -28,4 +28,4 @@ In case you encounter any issues while using ReVanced Manager, please refer to t
|
|||||||
|
|
||||||
The next page will teach you how to build ReVanced Manager from source.
|
The next page will teach you how to build ReVanced Manager from source.
|
||||||
|
|
||||||
Continue: [🛠️ Building from source](4_building.md)
|
Continue: [🔨 Building from source](4_building.md)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ This documentation explains how to use [ReVanced Manager](https://github.com/rev
|
|||||||
2. [🧰 Managing patched apps](2_2_managing.md)
|
2. [🧰 Managing patched apps](2_2_managing.md)
|
||||||
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
|
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
|
||||||
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
|
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
|
||||||
3. [🛟 Troubleshooting](3_troubleshooting.md)
|
3. [❔ Troubleshooting](3_troubleshooting.md)
|
||||||
4. [🛠 Building from source](4_building.md)
|
4. [🔨 Building from source](4_building.md)
|
||||||
|
|
||||||
## ⏭️ Start here
|
## ⏭️ Start here
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class MyApp extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
GlobalWidgetsLocalizations.delegate
|
GlobalWidgetsLocalizations.delegate,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.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/services/manager_api.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class GithubAPI {
|
class GithubAPI {
|
||||||
late Dio _dio = Dio();
|
late Dio _dio = Dio();
|
||||||
|
late final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
final _cacheOptions = CacheOptions(
|
final _cacheOptions = CacheOptions(
|
||||||
store: MemCacheStore(),
|
store: MemCacheStore(),
|
||||||
@@ -201,8 +203,14 @@ class GithubAPI {
|
|||||||
String extension,
|
String extension,
|
||||||
String repoName,
|
String repoName,
|
||||||
String version,
|
String version,
|
||||||
|
String url,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
|
if (url.isNotEmpty) {
|
||||||
|
return await DefaultCacheManager().getSingleFile(
|
||||||
|
url,
|
||||||
|
);
|
||||||
|
}
|
||||||
final Map<String, dynamic>? release =
|
final Map<String, dynamic>? release =
|
||||||
await getPatchesRelease(repoName, version);
|
await getPatchesRelease(repoName, version);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
@@ -211,8 +219,14 @@ class GithubAPI {
|
|||||||
(asset) => (asset['name'] as String).endsWith(extension),
|
(asset) => (asset['name'] as String).endsWith(extension),
|
||||||
);
|
);
|
||||||
if (asset != null) {
|
if (asset != null) {
|
||||||
|
final String downloadUrl = asset['browser_download_url'];
|
||||||
|
if (extension == '.apk') {
|
||||||
|
_managerAPI.setIntegrationsDownloadURL(downloadUrl);
|
||||||
|
} else {
|
||||||
|
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
||||||
|
}
|
||||||
return await DefaultCacheManager().getSingleFile(
|
return await DefaultCacheManager().getSingleFile(
|
||||||
asset['browser_download_url'],
|
downloadUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,10 +238,19 @@ class GithubAPI {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Patch>> getPatches(String repoName, String version) async {
|
Future<List<Patch>> getPatches(
|
||||||
|
String repoName,
|
||||||
|
String version,
|
||||||
|
String url,
|
||||||
|
) async {
|
||||||
List<Patch> patches = [];
|
List<Patch> patches = [];
|
||||||
try {
|
try {
|
||||||
final File? f = await getPatchesReleaseFile('.json', repoName, version);
|
final File? f = await getPatchesReleaseFile(
|
||||||
|
'.json',
|
||||||
|
repoName,
|
||||||
|
version,
|
||||||
|
url,
|
||||||
|
);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
final List<dynamic> list = jsonDecode(f.readAsStringSync());
|
final List<dynamic> list = jsonDecode(f.readAsStringSync());
|
||||||
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -9,8 +11,10 @@ import 'package:revanced_manager/app/app.locator.dart';
|
|||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
@@ -23,12 +27,13 @@ class ManagerAPI {
|
|||||||
final String patcherRepo = 'revanced-patcher';
|
final String patcherRepo = 'revanced-patcher';
|
||||||
final String cliRepo = 'revanced-cli';
|
final String cliRepo = 'revanced-cli';
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
List<Patch> patches = [];
|
||||||
bool isRooted = false;
|
bool isRooted = false;
|
||||||
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';
|
||||||
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
||||||
String defaultApiUrl = 'https://releases.revanced.app/';
|
String defaultApiUrl = 'https://api.revanced.app/';
|
||||||
String defaultRepoUrl = 'https://api.github.com';
|
String defaultRepoUrl = 'https://api.github.com';
|
||||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
||||||
String defaultPatchesRepo = 'revanced/revanced-patches';
|
String defaultPatchesRepo = 'revanced/revanced-patches';
|
||||||
@@ -36,8 +41,13 @@ class ManagerAPI {
|
|||||||
String defaultCliRepo = 'revanced/revanced-cli';
|
String defaultCliRepo = 'revanced/revanced-cli';
|
||||||
String defaultManagerRepo = 'revanced/revanced-manager';
|
String defaultManagerRepo = 'revanced/revanced-manager';
|
||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
|
String? integrationsVersion = '';
|
||||||
bool isDefaultPatchesRepo() {
|
bool isDefaultPatchesRepo() {
|
||||||
return getPatchesRepo() == 'revanced/revanced-patches';
|
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDefaultIntegrationsRepo() {
|
||||||
|
return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
@@ -71,6 +81,14 @@ class ManagerAPI {
|
|||||||
await _prefs.setString('repoUrl', url);
|
await _prefs.setString('repoUrl', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getPatchesDownloadURL() {
|
||||||
|
return _prefs.getString('patchesDownloadURL') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setPatchesDownloadURL(String value) async {
|
||||||
|
await _prefs.setString('patchesDownloadURL', value);
|
||||||
|
}
|
||||||
|
|
||||||
String getPatchesRepo() {
|
String getPatchesRepo() {
|
||||||
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
|
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
|
||||||
}
|
}
|
||||||
@@ -94,10 +112,74 @@ class ManagerAPI {
|
|||||||
return _prefs.getBool('patchesAutoUpdate') ?? false;
|
return _prefs.getBool('patchesAutoUpdate') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPatchesChangeEnabled() {
|
||||||
|
return _prefs.getBool('patchesChangeEnabled') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPatchesChangeEnabled(bool value) {
|
||||||
|
_prefs.setBool('patchesChangeEnabled', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool showPatchesChangeWarning() {
|
||||||
|
return _prefs.getBool('showPatchesChangeWarning') ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPatchesChangeWarning(bool value) {
|
||||||
|
_prefs.setBool('showPatchesChangeWarning', !value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isChangingToggleModified() {
|
||||||
|
return _prefs.getBool('isChangingToggleModified') ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setChangingToggleModified(bool value) {
|
||||||
|
_prefs.setBool('isChangingToggleModified', value);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setPatchesAutoUpdate(bool value) async {
|
Future<void> setPatchesAutoUpdate(bool value) async {
|
||||||
await _prefs.setBool('patchesAutoUpdate', value);
|
await _prefs.setBool('patchesAutoUpdate', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Patch> getSavedPatches(String packageName) {
|
||||||
|
final List<String> patchesJson =
|
||||||
|
_prefs.getStringList('savedPatches-$packageName') ?? [];
|
||||||
|
final List<Patch> patches = patchesJson.map((String patchJson) {
|
||||||
|
return Patch.fromJson(jsonDecode(patchJson));
|
||||||
|
}).toList();
|
||||||
|
return patches;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> savePatches(List<Patch> patches, String packageName) async {
|
||||||
|
final List<String> patchesJson = patches.map((Patch patch) {
|
||||||
|
return jsonEncode(patch.toJson());
|
||||||
|
}).toList();
|
||||||
|
await _prefs.setStringList('savedPatches-$packageName', patchesJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getIntegrationsDownloadURL() {
|
||||||
|
return _prefs.getString('integrationsDownloadURL') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setIntegrationsDownloadURL(String value) async {
|
||||||
|
await _prefs.setString('integrationsDownloadURL', value);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Patch> getUsedPatches(String packageName) {
|
||||||
|
final List<String> patchesJson =
|
||||||
|
_prefs.getStringList('usedPatches-$packageName') ?? [];
|
||||||
|
final List<Patch> patches = patchesJson.map((String patchJson) {
|
||||||
|
return Patch.fromJson(jsonDecode(patchJson));
|
||||||
|
}).toList();
|
||||||
|
return patches;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setUsedPatches(List<Patch> patches, String packageName) async {
|
||||||
|
final List<String> patchesJson = patches.map((Patch patch) {
|
||||||
|
return jsonEncode(patch.toJson());
|
||||||
|
}).toList();
|
||||||
|
await _prefs.setStringList('usedPatches-$packageName', patchesJson);
|
||||||
|
}
|
||||||
|
|
||||||
String getIntegrationsRepo() {
|
String getIntegrationsRepo() {
|
||||||
return _prefs.getString('integrationsRepo') ?? defaultIntegrationsRepo;
|
return _prefs.getString('integrationsRepo') ?? defaultIntegrationsRepo;
|
||||||
}
|
}
|
||||||
@@ -117,12 +199,12 @@ class ManagerAPI {
|
|||||||
await _prefs.setBool('useDynamicTheme', value);
|
await _prefs.setBool('useDynamicTheme', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getUseDarkTheme() {
|
int getThemeMode() {
|
||||||
return _prefs.getBool('useDarkTheme') ?? false;
|
return _prefs.getInt('themeMode') ?? 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setUseDarkTheme(bool value) async {
|
Future<void> setThemeMode(int value) async {
|
||||||
await _prefs.setBool('useDarkTheme', value);
|
await _prefs.setInt('themeMode', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool areUniversalPatchesEnabled() {
|
bool areUniversalPatchesEnabled() {
|
||||||
@@ -220,26 +302,43 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
try {
|
if (patches.isNotEmpty) {
|
||||||
final String repoName = getPatchesRepo();
|
return patches;
|
||||||
final String currentVersion = await getCurrentPatchesVersion();
|
|
||||||
return await _githubAPI.getPatches(repoName, currentVersion);
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
final File? patchBundleFile = await downloadPatches();
|
||||||
|
if (patchBundleFile != null) {
|
||||||
|
try {
|
||||||
|
final patchesObject = await PatcherAPI.patcherChannel.invokeMethod(
|
||||||
|
'getPatches',
|
||||||
|
{
|
||||||
|
'patchBundleFilePath': patchBundleFile.path,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final List<Map<String, dynamic>> patchesMap = [];
|
||||||
|
patchesObject.forEach((patch) {
|
||||||
|
patchesMap.add(jsonDecode('$patch'));
|
||||||
|
});
|
||||||
|
patches = patchesMap.map((patch) => Patch.fromJson(patch)).toList();
|
||||||
|
return patches;
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadPatches() async {
|
Future<File?> downloadPatches() async {
|
||||||
try {
|
try {
|
||||||
final String repoName = getPatchesRepo();
|
final String repoName = getPatchesRepo();
|
||||||
final String currentVersion = await getCurrentPatchesVersion();
|
final String currentVersion = await getCurrentPatchesVersion();
|
||||||
|
final String url = getPatchesDownloadURL();
|
||||||
return await _githubAPI.getPatchesReleaseFile(
|
return await _githubAPI.getPatchesReleaseFile(
|
||||||
'.jar',
|
'.jar',
|
||||||
repoName,
|
repoName,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
|
url,
|
||||||
);
|
);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@@ -252,14 +351,14 @@ class ManagerAPI {
|
|||||||
Future<File?> downloadIntegrations() async {
|
Future<File?> downloadIntegrations() async {
|
||||||
try {
|
try {
|
||||||
final String repoName = getIntegrationsRepo();
|
final String repoName = getIntegrationsRepo();
|
||||||
if (repoName == defaultIntegrationsRepo) {
|
final String currentVersion = await getCurrentIntegrationsVersion();
|
||||||
return await _revancedAPI.getLatestReleaseFile(
|
final String url = getIntegrationsDownloadURL();
|
||||||
'.apk',
|
return await _githubAPI.getPatchesReleaseFile(
|
||||||
defaultIntegrationsRepo,
|
'.apk',
|
||||||
);
|
repoName,
|
||||||
} else {
|
currentVersion,
|
||||||
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
url,
|
||||||
}
|
);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@@ -308,6 +407,22 @@ class ManagerAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> getLatestIntegrationsVersion() async {
|
||||||
|
if (isDefaultIntegrationsRepo()) {
|
||||||
|
return await _revancedAPI.getLatestReleaseVersion(
|
||||||
|
'.apk',
|
||||||
|
defaultIntegrationsRepo,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final release = await _githubAPI.getLatestRelease(getIntegrationsRepo());
|
||||||
|
if (release != null) {
|
||||||
|
return release['tag_name'];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> getLatestPatchesVersion() async {
|
Future<String?> getLatestPatchesVersion() async {
|
||||||
if (isDefaultPatchesRepo()) {
|
if (isDefaultPatchesRepo()) {
|
||||||
return await _revancedAPI.getLatestReleaseVersion(
|
return await _revancedAPI.getLatestReleaseVersion(
|
||||||
@@ -315,7 +430,8 @@ class ManagerAPI {
|
|||||||
defaultPatchesRepo,
|
defaultPatchesRepo,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final release = await _githubAPI.getLatestPatchesRelease(getPatchesRepo());
|
final release =
|
||||||
|
await _githubAPI.getLatestPatchesRelease(getPatchesRepo());
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
return release['tag_name'];
|
return release['tag_name'];
|
||||||
} else {
|
} else {
|
||||||
@@ -332,14 +448,38 @@ class ManagerAPI {
|
|||||||
Future<String> getCurrentPatchesVersion() async {
|
Future<String> getCurrentPatchesVersion() async {
|
||||||
patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0';
|
patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0';
|
||||||
if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) {
|
if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) {
|
||||||
patchesVersion = await getLatestPatchesVersion() ?? '0.0.0';
|
final String newPatchesVersion =
|
||||||
await setCurrentPatchesVersion(patchesVersion!);
|
await getLatestPatchesVersion() ?? '0.0.0';
|
||||||
|
if (patchesVersion != newPatchesVersion && newPatchesVersion != '0.0.0') {
|
||||||
|
await setCurrentPatchesVersion(newPatchesVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return patchesVersion!;
|
return patchesVersion!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setCurrentPatchesVersion(String version) async {
|
Future<void> setCurrentPatchesVersion(String version) async {
|
||||||
await _prefs.setString('patchesVersion', version);
|
await _prefs.setString('patchesVersion', version);
|
||||||
|
await setPatchesDownloadURL('');
|
||||||
|
await downloadPatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getCurrentIntegrationsVersion() async {
|
||||||
|
integrationsVersion = _prefs.getString('integrationsVersion') ?? '0.0.0';
|
||||||
|
if (integrationsVersion == '0.0.0' || isPatchesAutoUpdate()) {
|
||||||
|
final String newIntegrationsVersion =
|
||||||
|
await getLatestIntegrationsVersion() ?? '0.0.0';
|
||||||
|
if (integrationsVersion != newIntegrationsVersion &&
|
||||||
|
newIntegrationsVersion != '0.0.0') {
|
||||||
|
await setCurrentIntegrationsVersion(newIntegrationsVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return integrationsVersion!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setCurrentIntegrationsVersion(String version) async {
|
||||||
|
await _prefs.setString('integrationsVersion', version);
|
||||||
|
await setIntegrationsDownloadURL('');
|
||||||
|
await downloadIntegrations();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<PatchedApplication>> getAppsToRemove(
|
Future<List<PatchedApplication>> getAppsToRemove(
|
||||||
@@ -413,6 +553,63 @@ class ManagerAPI {
|
|||||||
return unsavedApps;
|
return unsavedApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showPatchesChangeWarningDialog(BuildContext context) {
|
||||||
|
final ValueNotifier<bool> noShow =
|
||||||
|
ValueNotifier(!showPatchesChangeWarning());
|
||||||
|
return showDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => WillPopScope(
|
||||||
|
onWillPop: () async => false,
|
||||||
|
child: AlertDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('warning'),
|
||||||
|
content: ValueListenableBuilder(
|
||||||
|
valueListenable: noShow,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
I18nText(
|
||||||
|
'patchItem.patchesChangeWarningDialogText',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
CheckboxListTile(
|
||||||
|
value: value,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
title: I18nText(
|
||||||
|
'noShowAgain',
|
||||||
|
),
|
||||||
|
onChanged: (selected) {
|
||||||
|
noShow.value = selected!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('okButton'),
|
||||||
|
onPressed: () {
|
||||||
|
setPatchesChangeWarning(noShow.value);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> reAssessSavedApps() async {
|
Future<void> reAssessSavedApps() async {
|
||||||
final List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
final List<PatchedApplication> unsavedApps =
|
final List<PatchedApplication> unsavedApps =
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ class PatcherAPI {
|
|||||||
late Directory _tmpDir;
|
late Directory _tmpDir;
|
||||||
late File _keyStoreFile;
|
late File _keyStoreFile;
|
||||||
List<Patch> _patches = [];
|
List<Patch> _patches = [];
|
||||||
|
List<Patch> _universalPatches = [];
|
||||||
|
List<String> _compatiblePackages = [];
|
||||||
Map filteredPatches = <String, List<Patch>>{};
|
Map filteredPatches = <String, List<Patch>>{};
|
||||||
File? _outFile;
|
File? _outFile;
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
await _loadPatches();
|
await _loadPatches();
|
||||||
|
await _managerAPI.downloadIntegrations();
|
||||||
final Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
||||||
_tmpDir = Directory('${appCache.path}/patcher');
|
_tmpDir = Directory('${appCache.path}/patcher');
|
||||||
@@ -43,6 +46,24 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> getCompatiblePackages() {
|
||||||
|
final List<String> compatiblePackages = [];
|
||||||
|
for (final Patch patch in _patches) {
|
||||||
|
for (final Package package in patch.compatiblePackages) {
|
||||||
|
if (!compatiblePackages.contains(package.name)) {
|
||||||
|
compatiblePackages.add(package.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compatiblePackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Patch> getUniversalPatches() {
|
||||||
|
return _patches
|
||||||
|
.where((patch) => patch.compatiblePackages.isEmpty)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadPatches() async {
|
Future<void> _loadPatches() async {
|
||||||
try {
|
try {
|
||||||
if (_patches.isEmpty) {
|
if (_patches.isEmpty) {
|
||||||
@@ -54,6 +75,9 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
_patches = List.empty();
|
_patches = List.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_compatiblePackages = getCompatiblePackages();
|
||||||
|
_universalPatches = getUniversalPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||||
@@ -61,58 +85,57 @@ class PatcherAPI {
|
|||||||
) async {
|
) async {
|
||||||
final List<ApplicationWithIcon> filteredApps = [];
|
final List<ApplicationWithIcon> filteredApps = [];
|
||||||
final bool allAppsIncluded =
|
final bool allAppsIncluded =
|
||||||
_patches.any((patch) => patch.compatiblePackages.isEmpty) &&
|
_universalPatches.isNotEmpty &&
|
||||||
showUniversalPatches;
|
showUniversalPatches;
|
||||||
if (allAppsIncluded) {
|
if (allAppsIncluded) {
|
||||||
final allPackages = await DeviceApps.getInstalledApplications(
|
final appList = await DeviceApps.getInstalledApplications(
|
||||||
includeAppIcons: true,
|
includeAppIcons: true,
|
||||||
onlyAppsWithLaunchIntent: true,
|
onlyAppsWithLaunchIntent: true,
|
||||||
);
|
);
|
||||||
for (final pkg in allPackages) {
|
|
||||||
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
|
for(final app in appList) {
|
||||||
final appInfo = await DeviceApps.getApp(
|
filteredApps.add(app as ApplicationWithIcon);
|
||||||
pkg.packageName,
|
|
||||||
true,
|
|
||||||
) as ApplicationWithIcon?;
|
|
||||||
if (appInfo != null) {
|
|
||||||
filteredApps.add(appInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final Patch patch in _patches) {
|
for (final packageName in _compatiblePackages) {
|
||||||
for (final Package package in patch.compatiblePackages) {
|
try {
|
||||||
try {
|
if (!filteredApps.any((app) => app.packageName == packageName)) {
|
||||||
if (!filteredApps.any((app) => app.packageName == package.name)) {
|
final ApplicationWithIcon? app = await DeviceApps.getApp(
|
||||||
final ApplicationWithIcon? app = await DeviceApps.getApp(
|
packageName,
|
||||||
package.name,
|
true,
|
||||||
true,
|
) as ApplicationWithIcon?;
|
||||||
) as ApplicationWithIcon?;
|
if (app != null) {
|
||||||
if (app != null) {
|
filteredApps.add(app);
|
||||||
filteredApps.add(app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filteredApps;
|
return filteredApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Patch> getFilteredPatches(String packageName) {
|
List<Patch> getFilteredPatches(String packageName) {
|
||||||
if (!filteredPatches.keys.contains(packageName)) {
|
if (!_compatiblePackages.contains(packageName)) {
|
||||||
final List<Patch> patches = _patches
|
return _universalPatches;
|
||||||
.where(
|
}
|
||||||
(patch) =>
|
|
||||||
patch.compatiblePackages.isEmpty ||
|
final List<Patch> patches = _patches
|
||||||
!patch.name.contains('settings') &&
|
.where(
|
||||||
patch.compatiblePackages
|
(patch) =>
|
||||||
.any((pack) => pack.name == packageName),
|
patch.compatiblePackages.isEmpty ||
|
||||||
)
|
!patch.name.contains('settings') &&
|
||||||
|
patch.compatiblePackages
|
||||||
|
.any((pack) => pack.name == packageName),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||||
|
filteredPatches[packageName] = patches
|
||||||
|
.where((patch) => patch.compatiblePackages.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
|
} else {
|
||||||
filteredPatches[packageName] = patches;
|
filteredPatches[packageName] = patches;
|
||||||
}
|
}
|
||||||
return filteredPatches[packageName];
|
return filteredPatches[packageName];
|
||||||
@@ -279,7 +302,7 @@ class PatcherAPI {
|
|||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sharePatcherLog(String logs) async {
|
Future<void> exportPatcherLog(String logs) async {
|
||||||
final Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
final Directory logDir = Directory('${appCache.path}/logs');
|
final Directory logDir = Directory('${appCache.path}/logs');
|
||||||
logDir.createSync();
|
logDir.createSync();
|
||||||
@@ -289,10 +312,15 @@ class PatcherAPI {
|
|||||||
.replaceAll(':', '')
|
.replaceAll(':', '')
|
||||||
.replaceAll('T', '')
|
.replaceAll('T', '')
|
||||||
.replaceAll('.', '');
|
.replaceAll('.', '');
|
||||||
final File log =
|
final String fileName = 'revanced-manager_patcher_$dateTime.log';
|
||||||
File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
|
final File log = File('${logDir.path}/$fileName');
|
||||||
log.writeAsStringSync(logs);
|
log.writeAsStringSync(logs);
|
||||||
ShareExtend.share(log.path, 'file');
|
CRFileSaver.saveFileWithDialog(
|
||||||
|
SaveFileDialogParams(
|
||||||
|
sourceFilePath: log.path,
|
||||||
|
destinationFileName: fileName,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getSuggestedVersion(String packageName) {
|
String getSuggestedVersion(String packageName) {
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installServiceDScript(String packageName) async {
|
Future<void> installServiceDScript(String packageName) async {
|
||||||
|
await Root.exec(
|
||||||
|
cmd: 'mkdir -p "$_serviceDDirPath"',
|
||||||
|
);
|
||||||
final String content = '#!/system/bin/sh\n'
|
final String content = '#!/system/bin/sh\n'
|
||||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
||||||
'base_path=$_revancedDirPath/$packageName/base.apk\n'
|
'base_path=$_revancedDirPath/$packageName/base.apk\n'
|
||||||
@@ -166,6 +169,9 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installPostFsDataScript(String packageName) async {
|
Future<void> installPostFsDataScript(String packageName) async {
|
||||||
|
await Root.exec(
|
||||||
|
cmd: 'mkdir -p "$_postFsDataDirPath"',
|
||||||
|
);
|
||||||
final String content = '#!/system/bin/sh\n'
|
final String content = '#!/system/bin/sh\n'
|
||||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||||
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
import 'dart:ui';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:dynamic_themes/dynamic_themes.dart';
|
import 'package:dynamic_themes/dynamic_themes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/app/app.router.dart';
|
import 'package:revanced_manager/app/app.router.dart';
|
||||||
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/theme.dart';
|
import 'package:revanced_manager/theme.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
class DynamicThemeBuilder extends StatelessWidget {
|
class DynamicThemeBuilder extends StatefulWidget {
|
||||||
const DynamicThemeBuilder({
|
const DynamicThemeBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -17,6 +21,35 @@ class DynamicThemeBuilder extends StatelessWidget {
|
|||||||
final Widget home;
|
final Widget home;
|
||||||
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DynamicThemeBuilder> createState() => _DynamicThemeBuilderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsBindingObserver {
|
||||||
|
Brightness brightness = PlatformDispatcher.instance.platformBrightness;
|
||||||
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangePlatformBrightness() {
|
||||||
|
setState(() {
|
||||||
|
brightness = PlatformDispatcher.instance.platformBrightness;
|
||||||
|
});
|
||||||
|
if (_managerAPI.getThemeMode() < 2) {
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
SystemUiOverlayStyle(
|
||||||
|
systemNavigationBarIconBrightness:
|
||||||
|
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
@@ -50,24 +83,32 @@ class DynamicThemeBuilder extends StatelessWidget {
|
|||||||
return DynamicTheme(
|
return DynamicTheme(
|
||||||
themeCollection: ThemeCollection(
|
themeCollection: ThemeCollection(
|
||||||
themes: {
|
themes: {
|
||||||
0: lightCustomTheme,
|
0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
|
||||||
1: darkCustomTheme,
|
1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme,
|
||||||
2: lightDynamicTheme,
|
2: lightCustomTheme,
|
||||||
3: darkDynamicTheme,
|
3: lightDynamicTheme,
|
||||||
|
4: darkCustomTheme,
|
||||||
|
5: darkDynamicTheme,
|
||||||
},
|
},
|
||||||
fallbackTheme: lightCustomTheme,
|
fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
|
||||||
),
|
),
|
||||||
builder: (context, theme) => MaterialApp(
|
builder: (context, theme) => MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: title,
|
title: widget.title,
|
||||||
navigatorKey: StackedService.navigatorKey,
|
navigatorKey: StackedService.navigatorKey,
|
||||||
onGenerateRoute: StackedRouter().onGenerateRoute,
|
onGenerateRoute: StackedRouter().onGenerateRoute,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
home: home,
|
home: widget.home,
|
||||||
localizationsDelegates: localizationsDelegates,
|
localizationsDelegates: widget.localizationsDelegates,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
||||||
.copyWith(
|
.copyWith(
|
||||||
bottom:
|
bottom:
|
||||||
MediaQuery.of(context).viewPadding.bottom + 8.0,
|
MediaQuery.viewPaddingOf(context).bottom + 8.0,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -133,6 +133,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
const SizedBox(height: 70.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -90,10 +90,14 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
if (await checkSplitApk(packageName) && !isRooted) {
|
if (await checkSplitApk(packageName) && !isRooted) {
|
||||||
return showSelectFromStorageDialog(context);
|
if (context.mounted) {
|
||||||
|
return showSelectFromStorageDialog(context);
|
||||||
|
}
|
||||||
} else if (!await checkSplitApk(packageName) || isRooted) {
|
} else if (!await checkSplitApk(packageName) || isRooted) {
|
||||||
selectApp(app);
|
selectApp(app);
|
||||||
Navigator.pop(context);
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class ContributorsView extends StatelessWidget {
|
|||||||
title: 'contributorsView.managerContributors',
|
title: 'contributorsView.managerContributors',
|
||||||
contributors: model.managerContributors,
|
contributors: model.managerContributors,
|
||||||
),
|
),
|
||||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||||
if(!_managerAPI.getPatchesConsent()){
|
if (!_managerAPI.getPatchesConsent()) {
|
||||||
await showPatchesConsent(context);
|
await showPatchesConsent(context);
|
||||||
}
|
}
|
||||||
await _patcherAPI.initialize();
|
await _patcherAPI.initialize();
|
||||||
@@ -168,13 +168,13 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showPatchesConsent(BuildContext context) async{
|
Future<void> showPatchesConsent(BuildContext context) async {
|
||||||
final ValueNotifier<bool> autoUpdate = ValueNotifier(true);
|
final ValueNotifier<bool> autoUpdate = ValueNotifier(true);
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('ReVanced Patches'),
|
title: const Text('Download ReVanced Patches?'),
|
||||||
content: ValueListenableBuilder(
|
content: ValueListenableBuilder(
|
||||||
valueListenable: autoUpdate,
|
valueListenable: autoUpdate,
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
@@ -197,7 +197,9 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
child: I18nText(
|
child: I18nText(
|
||||||
'homeView.patchesConsentDialogText2',
|
'homeView.patchesConsentDialogText2',
|
||||||
translationParams: {'url': _managerAPI.defaultApiUrl.split('/')[2]},
|
translationParams: {
|
||||||
|
'url': _managerAPI.defaultApiUrl.split('/')[2],
|
||||||
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -211,8 +213,12 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
value: value,
|
value: value,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: I18nText('homeView.patchesConsentDialogText3',),
|
title: I18nText(
|
||||||
subtitle: I18nText('homeView.patchesConsentDialogText3Sub',),
|
'homeView.patchesConsentDialogText3',
|
||||||
|
),
|
||||||
|
subtitle: I18nText(
|
||||||
|
'homeView.patchesConsentDialogText3Sub',
|
||||||
|
),
|
||||||
onChanged: (selected) {
|
onChanged: (selected) {
|
||||||
autoUpdate.value = selected!;
|
autoUpdate.value = selected!;
|
||||||
},
|
},
|
||||||
@@ -237,7 +243,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -247,9 +253,12 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
_toast.showBottom('homeView.downloadingMessage');
|
_toast.showBottom('homeView.downloadingMessage');
|
||||||
final String patchesVersion =
|
final String patchesVersion =
|
||||||
await _managerAPI.getLatestPatchesVersion() ?? '0.0.0';
|
await _managerAPI.getLatestPatchesVersion() ?? '0.0.0';
|
||||||
if (patchesVersion != '0.0.0') {
|
final String integrationsVersion =
|
||||||
_toast.showBottom('homeView.downloadedMessage');
|
await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0';
|
||||||
|
if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') {
|
||||||
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
|
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
|
||||||
|
await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion);
|
||||||
|
_toast.showBottom('homeView.downloadedMessage');
|
||||||
forceRefresh(context);
|
forceRefresh(context);
|
||||||
} else {
|
} else {
|
||||||
_toast.showBottom('homeView.errorDownloadMessage');
|
_toast.showBottom('homeView.errorDownloadMessage');
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
|
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.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';
|
||||||
|
|
||||||
@@ -22,6 +20,57 @@ class InstallerView extends StatelessWidget {
|
|||||||
top: false,
|
top: false,
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
floatingActionButton: Visibility(
|
||||||
|
visible: !model.isPatching && !model.hasErrors,
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
|
label: I18nText(
|
||||||
|
model.isInstalled
|
||||||
|
? 'installerView.openButton'
|
||||||
|
: 'installerView.installButton',
|
||||||
|
),
|
||||||
|
icon: model.isInstalled
|
||||||
|
? const Icon(Icons.open_in_new)
|
||||||
|
: const Icon(Icons.file_download_outlined),
|
||||||
|
onPressed: model.isInstalled
|
||||||
|
? () => {
|
||||||
|
model.openApp(),
|
||||||
|
model.cleanPatcher(),
|
||||||
|
Navigator.of(context).pop(),
|
||||||
|
}
|
||||||
|
: () => model.installTypeDialog(context),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButtonLocation:
|
||||||
|
FloatingActionButtonLocation.endContained,
|
||||||
|
bottomNavigationBar: Visibility(
|
||||||
|
visible: !model.isPatching,
|
||||||
|
child: BottomAppBar(
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Visibility(
|
||||||
|
visible: !model.hasErrors,
|
||||||
|
child: IconButton.filledTonal(
|
||||||
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'installerView.exportApkButtonTooltip',
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
onPressed: () => model.onButtonPressed(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton.filledTonal(
|
||||||
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'installerView.exportLogButtonTooltip',
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.post_add),
|
||||||
|
onPressed: () => model.onButtonPressed(1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
controller: model.scrollController,
|
controller: model.scrollController,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
@@ -35,44 +84,6 @@ class InstallerView extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
onBackButtonPressed: () => model.onWillPop(context),
|
onBackButtonPressed: () => model.onWillPop(context),
|
||||||
actions: <Widget>[
|
|
||||||
Visibility(
|
|
||||||
visible: !model.isPatching,
|
|
||||||
child: CustomPopupMenu(
|
|
||||||
onSelected: (value) => model.onMenuSelection(value),
|
|
||||||
children: {
|
|
||||||
if (!model.hasErrors)
|
|
||||||
0: I18nText(
|
|
||||||
'installerView.shareApkMenuOption',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
1: I18nText(
|
|
||||||
'installerView.exportApkMenuOption',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
2: I18nText(
|
|
||||||
'installerView.shareLogMenuOption',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size(double.infinity, 1.0),
|
preferredSize: const Size(double.infinity, 1.0),
|
||||||
child: GradientProgressIndicator(progress: model.progress),
|
child: GradientProgressIndicator(progress: model.progress),
|
||||||
@@ -96,72 +107,6 @@ class InstallerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverFillRemaining(
|
|
||||||
hasScrollBody: false,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Visibility(
|
|
||||||
visible: !model.isPatching && !model.hasErrors,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(20.0).copyWith(top: 0.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Visibility(
|
|
||||||
visible: model.isInstalled,
|
|
||||||
child: CustomMaterialButton(
|
|
||||||
label: I18nText('installerView.openButton'),
|
|
||||||
isExpanded: true,
|
|
||||||
onPressed: () {
|
|
||||||
model.openApp();
|
|
||||||
model.cleanPatcher();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: !model.isInstalled && model.isRooted,
|
|
||||||
child: CustomMaterialButton(
|
|
||||||
isFilled: false,
|
|
||||||
label:
|
|
||||||
I18nText('installerView.installRootButton'),
|
|
||||||
isExpanded: true,
|
|
||||||
onPressed: () => model.installResult(
|
|
||||||
context,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: !model.isInstalled,
|
|
||||||
child: const SizedBox(
|
|
||||||
width: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Visibility(
|
|
||||||
visible: !model.isInstalled,
|
|
||||||
child: CustomMaterialButton(
|
|
||||||
label: I18nText('installerView.installButton'),
|
|
||||||
isExpanded: true,
|
|
||||||
onPressed: () => model.installResult(
|
|
||||||
context,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverFillRemaining(
|
|
||||||
hasScrollBody: false,
|
|
||||||
child: SizedBox(
|
|
||||||
height: MediaQuery.of(context).viewPadding.bottom,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(double value, String header, String log) {
|
Future<void> update(double value, String header, String log) async {
|
||||||
if (value >= 0.0) {
|
if (value >= 0.0) {
|
||||||
progress = value;
|
progress = value;
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,11 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
} else if (value == 1.0) {
|
} else if (value == 1.0) {
|
||||||
isPatching = false;
|
isPatching = false;
|
||||||
hasErrors = false;
|
hasErrors = false;
|
||||||
|
await _managerAPI.savePatches(
|
||||||
|
_patcherAPI.getFilteredPatches(_app.packageName),
|
||||||
|
_app.packageName,
|
||||||
|
);
|
||||||
|
await _managerAPI.setUsedPatches(_patches, _app.packageName);
|
||||||
} else if (value == -100.0) {
|
} else if (value == -100.0) {
|
||||||
isPatching = false;
|
isPatching = false;
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
@@ -164,6 +169,91 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> installTypeDialog(BuildContext context) async {
|
||||||
|
final ValueNotifier<int> installType = ValueNotifier(0);
|
||||||
|
if (isRooted) {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: I18nText(
|
||||||
|
'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: I18nText(
|
||||||
|
'installerView.installTypeDescription',
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: I18nText('installerView.installNonRootType'),
|
||||||
|
subtitle: I18nText('installerView.installRecommendedType'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
value: 0,
|
||||||
|
groupValue: value,
|
||||||
|
onChanged: (selected) {
|
||||||
|
installType.value = selected!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: I18nText('installerView.installRootType'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
value: 1,
|
||||||
|
groupValue: value,
|
||||||
|
onChanged: (selected) {
|
||||||
|
installType.value = selected!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('cancelButton'),
|
||||||
|
isFilled: false,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('installerView.installButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
installResult(context, installType.value == 1);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
installResult(context, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> stopPatcher() async {
|
Future<void> stopPatcher() async {
|
||||||
try {
|
try {
|
||||||
isCanceled = true;
|
isCanceled = true;
|
||||||
@@ -203,7 +293,7 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -248,18 +338,8 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shareResult() {
|
void exportLog() {
|
||||||
try {
|
_patcherAPI.exportPatcherLog(logs);
|
||||||
_patcherAPI.sharePatchedFile(_app.name, _app.version);
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void shareLog() {
|
|
||||||
_patcherAPI.sharePatcherLog(logs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> cleanPatcher() async {
|
Future<void> cleanPatcher() async {
|
||||||
@@ -279,16 +359,13 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
DeviceApps.openApp(_app.packageName);
|
DeviceApps.openApp(_app.packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMenuSelection(int value) {
|
void onButtonPressed(int value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 0:
|
case 0:
|
||||||
shareResult();
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
exportResult();
|
exportResult();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 1:
|
||||||
shareLog();
|
exportLog();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:dynamic_themes/dynamic_themes.dart';
|
import 'package:dynamic_themes/dynamic_themes.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -18,25 +19,35 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
|||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
locator<Toast>().initialize(context);
|
locator<Toast>().initialize(context);
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
requestManageExternalStorage();
|
await requestManageExternalStorage();
|
||||||
|
|
||||||
if (prefs.getBool('permissionsRequested') == null) {
|
if (prefs.getBool('permissionsRequested') == null) {
|
||||||
await Permission.storage.request();
|
await Permission.storage.request();
|
||||||
await Permission.manageExternalStorage.request();
|
|
||||||
await prefs.setBool('permissionsRequested', true);
|
await prefs.setBool('permissionsRequested', true);
|
||||||
RootAPI().hasRootPermissions().then(
|
await RootAPI().hasRootPermissions().then(
|
||||||
(value) => Permission.requestInstallPackages.request().then(
|
(value) => Permission.requestInstallPackages.request().then(
|
||||||
(value) => Permission.ignoreBatteryOptimizations.request(),
|
(value) => Permission.ignoreBatteryOptimizations.request(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.getBool('useDarkTheme') == null) {
|
final dynamicTheme = DynamicTheme.of(context)!;
|
||||||
final bool isDark =
|
if (prefs.getInt('themeMode') == null) {
|
||||||
MediaQuery.of(context).platformBrightness != Brightness.light;
|
await prefs.setInt('themeMode', 0);
|
||||||
await prefs.setBool('useDarkTheme', isDark);
|
await dynamicTheme.setTheme(0);
|
||||||
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force disable Material You on Android 11 and below
|
||||||
|
if (dynamicTheme.themeId.isOdd) {
|
||||||
|
const int ANDROID_12_SDK_VERSION = 31;
|
||||||
|
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||||
|
if (info.version.sdkInt < ANDROID_12_SDK_VERSION) {
|
||||||
|
await prefs.setInt('themeMode', 0);
|
||||||
|
await prefs.setBool('useDynamicTheme', false);
|
||||||
|
await dynamicTheme.setTheme(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
SystemUiOverlayStyle(
|
SystemUiOverlayStyle(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class PatcherView extends StatelessWidget {
|
|||||||
child: FloatingActionButton.extended(
|
child: FloatingActionButton.extended(
|
||||||
label: I18nText('patcherView.patchButton'),
|
label: I18nText('patcherView.patchButton'),
|
||||||
icon: const Icon(Icons.build),
|
icon: const Icon(Icons.build),
|
||||||
onPressed: () => model.showPatchConfirmationDialog(context),
|
onPressed: () => model.showRemovedPatchesDialog(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
|
|||||||
@@ -11,6 +11,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/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
PatchedApplication? selectedApp;
|
PatchedApplication? selectedApp;
|
||||||
List<Patch> selectedPatches = [];
|
List<Patch> selectedPatches = [];
|
||||||
|
List<String> removedPatches = [];
|
||||||
|
|
||||||
void navigateToAppSelector() {
|
void navigateToAppSelector() {
|
||||||
_navigationService.navigateTo(Routes.appSelectorView);
|
_navigationService.navigateTo(Routes.appSelectorView);
|
||||||
@@ -77,7 +79,7 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
showArmv7WarningDialog(context);
|
showArmv7WarningDialog(context);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -85,6 +87,38 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showRemovedPatchesDialog(BuildContext context) async {
|
||||||
|
if (removedPatches.isNotEmpty) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: I18nText('notice'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
content: I18nText(
|
||||||
|
'patcherView.removedPatchesWarningDialogText',
|
||||||
|
translationParams: {'patches': removedPatches.join('\n')},
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('noButton'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('yesButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
navigateToInstaller();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showArmv7WarningDialog(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> showArmv7WarningDialog(BuildContext context) async {
|
Future<void> showArmv7WarningDialog(BuildContext context) async {
|
||||||
final bool armv7 = await AboutInfo.getInfo().then((info) {
|
final bool armv7 = await AboutInfo.getInfo().then((info) {
|
||||||
final List<String> archs = info['supportedArch'];
|
final List<String> archs = info['supportedArch'];
|
||||||
@@ -110,7 +144,7 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
navigateToInstaller();
|
navigateToInstaller();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -149,6 +183,7 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> loadLastSelectedPatches() async {
|
Future<void> loadLastSelectedPatches() async {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
|
removedPatches.clear();
|
||||||
final List<String> selectedPatches =
|
final List<String> selectedPatches =
|
||||||
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
||||||
final List<Patch> patches =
|
final List<Patch> patches =
|
||||||
@@ -156,6 +191,24 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
this
|
this
|
||||||
.selectedPatches
|
.selectedPatches
|
||||||
.addAll(patches.where((patch) => selectedPatches.contains(patch.name)));
|
.addAll(patches.where((patch) => selectedPatches.contains(patch.name)));
|
||||||
|
if (!_managerAPI.isPatchesChangeEnabled()) {
|
||||||
|
this.selectedPatches.clear();
|
||||||
|
this.selectedPatches.addAll(patches.where((patch) => !patch.excluded));
|
||||||
|
}
|
||||||
|
if (!_managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
|
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
||||||
|
}
|
||||||
|
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||||
|
this
|
||||||
|
.selectedPatches
|
||||||
|
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
||||||
|
}
|
||||||
|
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.originalPackageName);
|
||||||
|
for (final patch in usedPatches){
|
||||||
|
if (!patches.any((p) => p.name == patch.name)){
|
||||||
|
removedPatches.add('\u2022 ${patch.name}');
|
||||||
|
}
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart' hide SearchBar;
|
import 'package:flutter/material.dart' hide SearchBar;
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/services/manager_api.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/patchesSelectorView/patch_item.dart';
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
||||||
@@ -16,6 +18,18 @@ class PatchesSelectorView extends StatefulWidget {
|
|||||||
|
|
||||||
class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||||
String _query = '';
|
String _query = '';
|
||||||
|
final _managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (!_managerAPI.isPatchesChangeEnabled() &&
|
||||||
|
_managerAPI.showPatchesChangeWarning()) {
|
||||||
|
_managerAPI.showPatchesChangeWarningDialog(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -30,7 +44,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
label: Row(
|
label: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
I18nText('patchesSelectorView.doneButton'),
|
I18nText('patchesSelectorView.doneButton'),
|
||||||
Text(' (${model.selectedPatches.length})')
|
Text(' (${model.selectedPatches.length})'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.check),
|
icon: const Icon(Icons.check),
|
||||||
@@ -84,7 +98,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomPopupMenu(
|
CustomPopupMenu(
|
||||||
onSelected: (value) => {model.onMenuSelection(value)},
|
onSelected: (value) =>
|
||||||
|
{model.onMenuSelection(value, context)},
|
||||||
children: {
|
children: {
|
||||||
0: I18nText(
|
0: I18nText(
|
||||||
'patchesSelectorView.loadPatchesSelection',
|
'patchesSelectorView.loadPatchesSelection',
|
||||||
@@ -136,7 +151,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
: Padding(
|
: Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
|
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
|
||||||
bottom: MediaQuery.of(context).viewPadding.bottom + 8.0,
|
bottom: MediaQuery.viewPaddingOf(context).bottom + 8.0,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -149,7 +164,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
'patchesSelectorView.defaultTooltip',
|
'patchesSelectorView.defaultTooltip',
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.selectDefaultPatches();
|
if (_managerAPI.isPatchesChangeEnabled()) {
|
||||||
|
model.selectDefaultPatches();
|
||||||
|
} else {
|
||||||
|
model.showPatchesChangeDialog(context);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -160,28 +179,96 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
'patchesSelectorView.noneTooltip',
|
'patchesSelectorView.noneTooltip',
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.clearPatches();
|
if (_managerAPI.isPatchesChangeEnabled()) {
|
||||||
|
model.clearPatches();
|
||||||
|
} else {
|
||||||
|
model.showPatchesChangeDialog(context);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...model
|
...model.getQueriedPatches(_query).map(
|
||||||
.getQueriedPatches(_query)
|
(patch) {
|
||||||
.map(
|
if (patch.compatiblePackages.isNotEmpty) {
|
||||||
(patch) => PatchItem(
|
return PatchItem(
|
||||||
name: patch.name,
|
name: patch.name,
|
||||||
simpleName: patch.getSimpleName(),
|
simpleName: patch.getSimpleName(),
|
||||||
description: patch.description,
|
description: patch.description,
|
||||||
packageVersion: model.getAppVersion(),
|
packageVersion: model.getAppInfo().version,
|
||||||
supportedPackageVersions:
|
supportedPackageVersions:
|
||||||
model.getSupportedVersions(patch),
|
model.getSupportedVersions(patch),
|
||||||
isUnsupported: !isPatchSupported(patch),
|
isUnsupported: !isPatchSupported(patch),
|
||||||
|
isChangeEnabled:
|
||||||
|
_managerAPI.isPatchesChangeEnabled(),
|
||||||
|
isNew: model.isPatchNew(
|
||||||
|
patch,
|
||||||
|
model.getAppInfo().packageName,
|
||||||
|
),
|
||||||
isSelected: model.isSelected(patch),
|
isSelected: model.isSelected(patch),
|
||||||
onChanged: (value) =>
|
onChanged: (value) =>
|
||||||
model.selectPatch(patch, value),
|
model.selectPatch(patch, value, context),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (_managerAPI.areUniversalPatchesEnabled())
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10.0,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 10.0,
|
||||||
|
bottom: 10.0,
|
||||||
|
left: 5.0,
|
||||||
|
),
|
||||||
|
child: I18nText(
|
||||||
|
'patchesSelectorView.universalPatches',
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
...model.getQueriedPatches(_query).map((patch) {
|
||||||
.toList(),
|
if (patch.compatiblePackages.isEmpty) {
|
||||||
|
return PatchItem(
|
||||||
|
name: patch.name,
|
||||||
|
simpleName: patch.getSimpleName(),
|
||||||
|
description: patch.description,
|
||||||
|
packageVersion:
|
||||||
|
model.getAppInfo().version,
|
||||||
|
supportedPackageVersions:
|
||||||
|
model.getSupportedVersions(patch),
|
||||||
|
isUnsupported: !isPatchSupported(patch),
|
||||||
|
isChangeEnabled:
|
||||||
|
_managerAPI.isPatchesChangeEnabled(),
|
||||||
|
isNew: false,
|
||||||
|
isSelected: model.isSelected(patch),
|
||||||
|
onChanged: (value) => model.selectPatch(
|
||||||
|
patch,
|
||||||
|
value,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 70.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
@@ -6,6 +8,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/ui/widgets/shared/custom_material_button.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';
|
||||||
|
|
||||||
@@ -15,6 +18,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
final List<Patch> patches = [];
|
final List<Patch> patches = [];
|
||||||
final List<Patch> selectedPatches =
|
final List<Patch> selectedPatches =
|
||||||
locator<PatcherViewModel>().selectedPatches;
|
locator<PatcherViewModel>().selectedPatches;
|
||||||
|
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
|
||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
bool isDefaultPatchesRepo() {
|
bool isDefaultPatchesRepo() {
|
||||||
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
|
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
|
||||||
@@ -24,10 +28,17 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
getPatchesVersion().whenComplete(() => notifyListeners());
|
getPatchesVersion().whenComplete(() => notifyListeners());
|
||||||
patches.addAll(
|
patches.addAll(
|
||||||
_patcherAPI.getFilteredPatches(
|
_patcherAPI.getFilteredPatches(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
selectedApp!.originalPackageName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
patches.sort((a, b) => a.name.compareTo(b.name));
|
patches.sort((a, b) {
|
||||||
|
if (isPatchNew(a, selectedApp!.packageName) ==
|
||||||
|
isPatchNew(b, selectedApp!.packageName)) {
|
||||||
|
return a.name.compareTo(b.name);
|
||||||
|
} else {
|
||||||
|
return isPatchNew(b, selectedApp!.packageName) ? 1 : -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,31 +48,70 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectPatch(Patch patch, bool isSelected) {
|
void selectPatch(Patch patch, bool isSelected, BuildContext context) {
|
||||||
if (isSelected && !selectedPatches.contains(patch)) {
|
if (_managerAPI.isPatchesChangeEnabled()) {
|
||||||
selectedPatches.add(patch);
|
if (isSelected && !selectedPatches.contains(patch)) {
|
||||||
|
selectedPatches.add(patch);
|
||||||
|
} else {
|
||||||
|
selectedPatches.remove(patch);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
selectedPatches.remove(patch);
|
showPatchesChangeDialog(context);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
}
|
||||||
|
|
||||||
|
Future<void> showPatchesChangeDialog(BuildContext context) async {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('warning'),
|
||||||
|
content: I18nText(
|
||||||
|
'patchItem.patchesChangeWarningDialogText',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('okButton'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('patchItem.patchesChangeWarningDialogButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
..pop()
|
||||||
|
..pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectDefaultPatches() {
|
void selectDefaultPatches() {
|
||||||
selectedPatches.clear();
|
selectedPatches.clear();
|
||||||
|
if (locator<PatcherViewModel>().selectedApp?.originalPackageName != null) {
|
||||||
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
|
|
||||||
selectedPatches.addAll(
|
selectedPatches.addAll(
|
||||||
patches.where(
|
_patcherAPI
|
||||||
(element) => element.excluded == false && isPatchSupported(element),
|
.getFilteredPatches(
|
||||||
),
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(element) =>
|
||||||
|
!element.excluded &&
|
||||||
|
(_managerAPI.areExperimentalPatchesEnabled() ||
|
||||||
|
isPatchSupported(element)),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_managerAPI.areExperimentalPatchesEnabled()) {
|
|
||||||
selectedPatches
|
|
||||||
.addAll(patches.where((element) => element.excluded == false));
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +131,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Patch> getQueriedPatches(String query) {
|
List<Patch> getQueriedPatches(String query) {
|
||||||
return patches
|
final List<Patch> patch = patches
|
||||||
.where(
|
.where(
|
||||||
(patch) =>
|
(patch) =>
|
||||||
query.isEmpty ||
|
query.isEmpty ||
|
||||||
@@ -90,10 +140,27 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
if (_managerAPI.areUniversalPatchesEnabled()) {
|
||||||
|
return patch;
|
||||||
|
} else {
|
||||||
|
return patch
|
||||||
|
.where((patch) => patch.compatiblePackages.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getAppVersion() {
|
PatchedApplication getAppInfo() {
|
||||||
return locator<PatcherViewModel>().selectedApp!.version;
|
return locator<PatcherViewModel>().selectedApp!;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPatchNew(Patch patch, String packageName) {
|
||||||
|
final List<Patch> savedPatches = _managerAPI.getSavedPatches(packageName);
|
||||||
|
if (savedPatches.isEmpty) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return !savedPatches
|
||||||
|
.any((p) => p.getSimpleName() == patch.getSimpleName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getSupportedVersions(Patch patch) {
|
List<String> getSupportedVersions(Patch patch) {
|
||||||
@@ -108,10 +175,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMenuSelection(value) {
|
void onMenuSelection(value, BuildContext context) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 0:
|
case 0:
|
||||||
loadSelectedPatches();
|
loadSelectedPatches(context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,18 +192,25 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadSelectedPatches() async {
|
Future<void> loadSelectedPatches(BuildContext context) async {
|
||||||
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
if (_managerAPI.isPatchesChangeEnabled()) {
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
||||||
);
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
if (selectedPatches.isNotEmpty) {
|
);
|
||||||
this.selectedPatches.clear();
|
if (selectedPatches.isNotEmpty) {
|
||||||
this.selectedPatches.addAll(
|
this.selectedPatches.clear();
|
||||||
patches.where((patch) => selectedPatches.contains(patch.name)),
|
this.selectedPatches.addAll(
|
||||||
);
|
patches.where((patch) => selectedPatches.contains(patch.name)),
|
||||||
|
);
|
||||||
|
if (!_managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
|
this.selectedPatches.removeWhere((patch) => !isPatchSupported(patch));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
showPatchesChangeDialog(context);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
icon: const Icon(Icons.manage_history_outlined),
|
icon: const Icon(Icons.manage_history_outlined),
|
||||||
onPressed: () => showApiUrlResetDialog(context),
|
onPressed: () => showApiUrlResetDialog(context),
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
@@ -69,7 +69,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
_managerAPI.setApiUrl(apiUrl);
|
_managerAPI.setApiUrl(apiUrl);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -97,7 +97,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
..pop()
|
..pop()
|
||||||
..pop();
|
..pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class SManageKeystorePassword extends BaseViewModel {
|
|||||||
onPressed: () => _keystorePasswordController.text =
|
onPressed: () => _keystorePasswordController.text =
|
||||||
_managerAPI.defaultKeystorePassword,
|
_managerAPI.defaultKeystorePassword,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
@@ -62,7 +62,7 @@ class SManageKeystorePassword extends BaseViewModel {
|
|||||||
_managerAPI.setKeystorePassword(passwd);
|
_managerAPI.setKeystorePassword(passwd);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class SManageSources extends BaseViewModel {
|
|||||||
icon: const Icon(Icons.manage_history_outlined),
|
icon: const Icon(Icons.manage_history_outlined),
|
||||||
onPressed: () => showResetConfirmationDialog(context),
|
onPressed: () => showResetConfirmationDialog(context),
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
@@ -102,7 +102,7 @@ class SManageSources extends BaseViewModel {
|
|||||||
onChanged: (value) => notifyListeners(),
|
onChanged: (value) => notifyListeners(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
I18nText('settingsView.sourcesUpdateNote')
|
I18nText('settingsView.sourcesUpdateNote'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -129,10 +129,11 @@ class SManageSources extends BaseViewModel {
|
|||||||
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
|
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
|
||||||
);
|
);
|
||||||
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
||||||
|
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -158,12 +159,13 @@ class SManageSources extends BaseViewModel {
|
|||||||
_managerAPI.setPatchesRepo('');
|
_managerAPI.setPatchesRepo('');
|
||||||
_managerAPI.setIntegrationsRepo('');
|
_managerAPI.setIntegrationsRepo('');
|
||||||
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
_managerAPI.setCurrentPatchesVersion('0.0.0');
|
||||||
|
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
..pop()
|
..pop()
|
||||||
..pop();
|
..pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:revanced_manager/app/app.locator.dart';
|
|||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.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/shared/custom_material_button.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
final _settingViewModel = SettingsViewModel();
|
final _settingViewModel = SettingsViewModel();
|
||||||
@@ -24,37 +25,114 @@ class SUpdateTheme extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
||||||
await _managerAPI.setUseDynamicTheme(value);
|
await _managerAPI.setUseDynamicTheme(value);
|
||||||
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2;
|
||||||
if (currentTheme.isEven) {
|
await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0));
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
|
|
||||||
} else {
|
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 3 : 1);
|
|
||||||
}
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getDarkThemeStatus() {
|
int getThemeMode() {
|
||||||
return _managerAPI.getUseDarkTheme();
|
return _managerAPI.getThemeMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setUseDarkTheme(BuildContext context, bool value) async {
|
Future<void> setThemeMode(BuildContext context, int value) async {
|
||||||
await _managerAPI.setUseDarkTheme(value);
|
await _managerAPI.setThemeMode(value);
|
||||||
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven;
|
||||||
if (currentTheme < 2) {
|
await DynamicTheme.of(context)!.setTheme(value * 2 + (isDynamicTheme ? 0 : 1));
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
|
final bool isLight = value != 2 && (value == 1 || DynamicTheme.of(context)!.theme.brightness == Brightness.light);
|
||||||
} else {
|
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 3 : 2);
|
|
||||||
}
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
SystemUiOverlayStyle(
|
SystemUiOverlayStyle(
|
||||||
systemNavigationBarIconBrightness:
|
systemNavigationBarIconBrightness:
|
||||||
value ? Brightness.light : Brightness.dark,
|
isLight ? Brightness.dark : Brightness.light,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
I18nText getThemeModeName() {
|
||||||
|
switch (getThemeMode()) {
|
||||||
|
case 0:
|
||||||
|
return I18nText('settingsView.systemThemeLabel');
|
||||||
|
case 1:
|
||||||
|
return I18nText('settingsView.lightThemeLabel');
|
||||||
|
case 2:
|
||||||
|
return I18nText('settingsView.darkThemeLabel');
|
||||||
|
default:
|
||||||
|
return I18nText('settingsView.systemThemeLabel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showThemeDialog(BuildContext context) async {
|
||||||
|
final ValueNotifier<int> newTheme = ValueNotifier(getThemeMode());
|
||||||
|
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: I18nText('settingsView.themeModeLabel'),
|
||||||
|
icon: const Icon(Icons.palette),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable: newTheme,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
RadioListTile(
|
||||||
|
title: I18nText('settingsView.systemThemeLabel'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
value: 0,
|
||||||
|
groupValue: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
newTheme.value = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: I18nText('settingsView.lightThemeLabel'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
value: 1,
|
||||||
|
groupValue: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
newTheme.value = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: I18nText('settingsView.darkThemeLabel'),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
value: 2,
|
||||||
|
groupValue: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
newTheme.value = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('cancelButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('okButton'),
|
||||||
|
onPressed: () {
|
||||||
|
setThemeMode(context, newTheme.value);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final sUpdateTheme = SUpdateTheme();
|
||||||
class SUpdateThemeUI extends StatelessWidget {
|
class SUpdateThemeUI extends StatelessWidget {
|
||||||
const SUpdateThemeUI({super.key});
|
const SUpdateThemeUI({super.key});
|
||||||
|
|
||||||
@@ -63,10 +141,10 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
return SettingsSection(
|
return SettingsSection(
|
||||||
title: 'settingsView.appearanceSectionTitle',
|
title: 'settingsView.appearanceSectionTitle',
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.darkThemeLabel',
|
'settingsView.themeModeLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -75,12 +153,11 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.darkThemeHint'),
|
trailing: CustomMaterialButton(
|
||||||
value: SUpdateTheme().getDarkThemeStatus(),
|
label: sUpdateTheme.getThemeModeName(),
|
||||||
onChanged: (value) => SUpdateTheme().setUseDarkTheme(
|
onPressed: () => { sUpdateTheme.showThemeDialog(context) },
|
||||||
context,
|
|
||||||
value,
|
|
||||||
),
|
),
|
||||||
|
onTap: () => { sUpdateTheme.showThemeDialog(context) },
|
||||||
),
|
),
|
||||||
FutureBuilder<int>(
|
FutureBuilder<int>(
|
||||||
future: _settingViewModel.getSdkVersion(),
|
future: _settingViewModel.getSdkVersion(),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:cr_file_saver/file_saver.dart';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:logcat/logcat.dart';
|
import 'package:logcat/logcat.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
@@ -10,8 +12,10 @@ import 'package:revanced_manager/app/app.router.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';
|
||||||
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/settings/settingsFragment/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_extend/share_extend.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
@@ -19,6 +23,9 @@ import 'package:stacked_services/stacked_services.dart';
|
|||||||
class SettingsViewModel extends BaseViewModel {
|
class SettingsViewModel extends BaseViewModel {
|
||||||
final NavigationService _navigationService = locator<NavigationService>();
|
final NavigationService _navigationService = locator<NavigationService>();
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
final PatchesSelectorViewModel _patchesSelectorViewModel =
|
||||||
|
PatchesSelectorViewModel();
|
||||||
|
final PatcherViewModel _patcherViewModel = locator<PatcherViewModel>();
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
|
|
||||||
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
|
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
|
||||||
@@ -37,6 +44,88 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPatchesChangeEnabled() {
|
||||||
|
return _managerAPI.isPatchesChangeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showPatchesChangeEnableDialog(
|
||||||
|
bool value,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
if (value) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('warning'),
|
||||||
|
content: I18nText(
|
||||||
|
'settingsView.enablePatchesSelectionWarningText',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('noButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('yesButton'),
|
||||||
|
onPressed: () {
|
||||||
|
_managerAPI.setChangingToggleModified(true);
|
||||||
|
_managerAPI.setPatchesChangeEnabled(true);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('warning'),
|
||||||
|
content: I18nText(
|
||||||
|
'settingsView.disablePatchesSelectionWarningText',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
label: I18nText('noButton'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('yesButton'),
|
||||||
|
onPressed: () {
|
||||||
|
_managerAPI.setChangingToggleModified(true);
|
||||||
|
_patchesSelectorViewModel.selectDefaultPatches();
|
||||||
|
_managerAPI.setPatchesChangeEnabled(false);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool areUniversalPatchesEnabled() {
|
bool areUniversalPatchesEnabled() {
|
||||||
return _managerAPI.areUniversalPatchesEnabled();
|
return _managerAPI.areUniversalPatchesEnabled();
|
||||||
}
|
}
|
||||||
@@ -90,26 +179,30 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> importPatches() async {
|
Future<void> importPatches(BuildContext context) async {
|
||||||
try {
|
if (isPatchesChangeEnabled()) {
|
||||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
try {
|
||||||
type: FileType.custom,
|
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
allowedExtensions: ['json'],
|
type: FileType.custom,
|
||||||
);
|
allowedExtensions: ['json'],
|
||||||
if (result != null && result.files.single.path != null) {
|
);
|
||||||
final File inFile = File(result.files.single.path!);
|
if (result != null && result.files.single.path != null) {
|
||||||
inFile.copySync(_managerAPI.storedPatchesFile);
|
final File inFile = File(result.files.single.path!);
|
||||||
inFile.delete();
|
inFile.copySync(_managerAPI.storedPatchesFile);
|
||||||
if (locator<PatcherViewModel>().selectedApp != null) {
|
inFile.delete();
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
if (_patcherViewModel.selectedApp != null) {
|
||||||
|
_patcherViewModel.loadLastSelectedPatches();
|
||||||
|
}
|
||||||
|
_toast.showBottom('settingsView.importedPatches');
|
||||||
}
|
}
|
||||||
_toast.showBottom('settingsView.importedPatches');
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
_toast.showBottom('settingsView.jsonSelectorErrorMessage');
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} else {
|
||||||
if (kDebugMode) {
|
_managerAPI.showPatchesChangeWarningDialog(context);
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
_toast.showBottom('settingsView.jsonSelectorErrorMessage');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -103,7 +103,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -140,7 +140,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class AppSkeletonLoader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: 7,
|
itemCount: 7,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
|||||||
context,
|
context,
|
||||||
'installed',
|
'installed',
|
||||||
translationParams: {
|
translationParams: {
|
||||||
'version': 'v${widget.installedVersion}'
|
'version': 'v${widget.installedVersion}',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class InstalledAppsCard extends StatelessWidget {
|
|||||||
Theme.of(context).colorScheme.secondary,
|
Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
? model.updatePatches(context)
|
? model.updatePatches(context)
|
||||||
: model.updateManager(context);
|
: model.updateManager(context);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class _GradientProgressIndicatorState extends State<GradientProgressIndicator> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
height: 5,
|
height: 5,
|
||||||
width: MediaQuery.of(context).size.width * widget.progress!,
|
width: MediaQuery.sizeOf(context).width * widget.progress!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.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';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
|
||||||
@@ -17,8 +16,10 @@ class PatchItem extends StatefulWidget {
|
|||||||
required this.packageVersion,
|
required this.packageVersion,
|
||||||
required this.supportedPackageVersions,
|
required this.supportedPackageVersions,
|
||||||
required this.isUnsupported,
|
required this.isUnsupported,
|
||||||
|
required this.isNew,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
|
required this.isChangeEnabled,
|
||||||
this.child,
|
this.child,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
final String name;
|
final String name;
|
||||||
@@ -27,8 +28,10 @@ class PatchItem extends StatefulWidget {
|
|||||||
final String packageVersion;
|
final String packageVersion;
|
||||||
final List<String> supportedPackageVersions;
|
final List<String> supportedPackageVersions;
|
||||||
final bool isUnsupported;
|
final bool isUnsupported;
|
||||||
|
final bool isNew;
|
||||||
bool isSelected;
|
bool isSelected;
|
||||||
final Function(bool) onChanged;
|
final Function(bool) onChanged;
|
||||||
|
final bool isChangeEnabled;
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final toast = locator<Toast>();
|
final toast = locator<Toast>();
|
||||||
final _managerAPI = locator<ManagerAPI>();
|
final _managerAPI = locator<ManagerAPI>();
|
||||||
@@ -57,11 +60,13 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
!widget._managerAPI.areExperimentalPatchesEnabled()) {
|
!widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
widget.isSelected = false;
|
widget.isSelected = false;
|
||||||
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
|
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
|
||||||
} else {
|
} else if (widget.isChangeEnabled) {
|
||||||
widget.isSelected = !widget.isSelected;
|
widget.isSelected = !widget.isSelected;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
widget.onChanged(widget.isSelected);
|
if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
|
widget.onChanged(widget.isSelected);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -123,28 +128,24 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
widget.toast.showBottom(
|
widget.toast.showBottom(
|
||||||
'patchItem.unsupportedPatchVersion',
|
'patchItem.unsupportedPatchVersion',
|
||||||
);
|
);
|
||||||
} else {
|
} else if (widget.isChangeEnabled) {
|
||||||
widget.isSelected = newValue!;
|
widget.isSelected = newValue!;
|
||||||
}
|
}
|
||||||
if (widget.isUnsupported &&
|
|
||||||
widget.isSelected &&
|
|
||||||
!selectedUnsupportedPatches
|
|
||||||
.contains(widget.name)) {
|
|
||||||
selectedUnsupportedPatches.add(widget.name);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
widget.onChanged(widget.isSelected);
|
if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
|
widget.onChanged(widget.isSelected);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (widget.isUnsupported &&
|
Row(
|
||||||
widget._managerAPI.areExperimentalPatchesEnabled())
|
children: [
|
||||||
Row(
|
if (widget.isUnsupported &&
|
||||||
children: <Widget>[
|
widget._managerAPI.areExperimentalPatchesEnabled())
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8, right: 8),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
label: I18nText('warning'),
|
label: I18nText('warning'),
|
||||||
icon: const Icon(Icons.warning, size: 20.0),
|
icon: const Icon(Icons.warning, size: 20.0),
|
||||||
@@ -167,10 +168,33 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
if (widget.isNew)
|
||||||
)
|
Padding(
|
||||||
else
|
padding: const EdgeInsets.only(top: 8),
|
||||||
Container(),
|
child: TextButton.icon(
|
||||||
|
label: I18nText('new'),
|
||||||
|
icon: const Icon(Icons.star, size: 20.0),
|
||||||
|
onPressed: () => _showNewPatchDialog(),
|
||||||
|
style: ButtonStyle(
|
||||||
|
shape: MaterialStateProperty.all(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Colors.transparent,
|
||||||
|
),
|
||||||
|
foregroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
widget.child ?? const SizedBox(),
|
widget.child ?? const SizedBox(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -197,7 +221,26 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
)
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showNewPatchDialog() {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: I18nText('patchItem.newPatch'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
content: I18nText(
|
||||||
|
'patchItem.newPatchDialogText',
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
CustomMaterialButton(
|
||||||
|
label: I18nText('okButton'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ class OptionsTextField extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final sHeight = MediaQuery.of(context).size.height;
|
final size = MediaQuery.sizeOf(context);
|
||||||
final sWidth = MediaQuery.of(context).size.width;
|
final sHeight = size.height;
|
||||||
|
final sWidth = size.width;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class CustomSwitch extends StatelessWidget {
|
|||||||
color: Colors.black12.withOpacity(0.1),
|
color: Colors.black12.withOpacity(0.1),
|
||||||
spreadRadius: 0.5,
|
spreadRadius: 0.5,
|
||||||
blurRadius: 1,
|
blurRadius: 1,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
|||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.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_experimental_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
|
||||||
@@ -25,6 +26,7 @@ class SAdvancedSection extends StatelessWidget {
|
|||||||
SManageSourcesUI(),
|
SManageSourcesUI(),
|
||||||
// SManageKeystorePasswordUI(),
|
// SManageKeystorePasswordUI(),
|
||||||
SAutoUpdatePatches(),
|
SAutoUpdatePatches(),
|
||||||
|
SEnablePatchesSelection(),
|
||||||
SExperimentalUniversalPatches(),
|
SExperimentalUniversalPatches(),
|
||||||
SExperimentalPatches(),
|
SExperimentalPatches(),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -93,9 +95,9 @@ class SAdvancedSection extends StatelessWidget {
|
|||||||
label: I18nText('yesButton'),
|
label: I18nText('yesButton'),
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
_settingsViewModel.deleteKeystore()
|
_settingsViewModel.deleteKeystore(),
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class _SAutoUpdatePatchesState extends State<SAutoUpdatePatches> {
|
|||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'homeView.patchesConsentDialogText3',
|
'settingsView.autoUpdatePatchesLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
|
||||||
|
class SEnablePatchesSelection extends StatefulWidget {
|
||||||
|
const SEnablePatchesSelection({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SEnablePatchesSelection> createState() => _SEnablePatchesSelectionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
|
class _SEnablePatchesSelectionState extends State<SEnablePatchesSelection> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SwitchListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
title: I18nText(
|
||||||
|
'settingsView.enablePatchesSelectionLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: I18nText('settingsView.enablePatchesSelectionHint'),
|
||||||
|
value: _settingsViewModel.isPatchesChangeEnabled(),
|
||||||
|
onChanged: (value) async {
|
||||||
|
await _settingsViewModel.showPatchesChangeEnableDialog(value, context);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.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/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
|
|
||||||
class SExperimentalPatches extends StatefulWidget {
|
class SExperimentalPatches extends StatefulWidget {
|
||||||
const SExperimentalPatches({super.key});
|
const SExperimentalPatches({super.key});
|
||||||
@@ -11,7 +13,8 @@ class SExperimentalPatches extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
final List<String> selectedUnsupportedPatches = [];
|
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||||
|
final _patcherViewModel = PatcherViewModel();
|
||||||
|
|
||||||
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
||||||
@override
|
@override
|
||||||
@@ -35,12 +38,10 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
|||||||
_settingsViewModel.useExperimentalPatches(value);
|
_settingsViewModel.useExperimentalPatches(value);
|
||||||
});
|
});
|
||||||
if (!value) {
|
if (!value) {
|
||||||
for (final patch in selectedUnsupportedPatches) {
|
_patcherViewModel.selectedPatches
|
||||||
PatchesSelectorViewModel()
|
.removeWhere((patch) => !isPatchSupported(patch));
|
||||||
.selectedPatches
|
_patchesSelectorViewModel.selectedPatches
|
||||||
.removeWhere((element) => patch == element.name);
|
.removeWhere((patch) => !isPatchSupported(patch));
|
||||||
}
|
|
||||||
selectedUnsupportedPatches.clear();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.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/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
|
|
||||||
class SExperimentalUniversalPatches extends StatefulWidget {
|
class SExperimentalUniversalPatches extends StatefulWidget {
|
||||||
@@ -11,6 +13,8 @@ class SExperimentalUniversalPatches extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||||
|
final _patcherViewModel = PatcherViewModel();
|
||||||
|
|
||||||
class _SExperimentalUniversalPatchesState
|
class _SExperimentalUniversalPatchesState
|
||||||
extends State<SExperimentalUniversalPatches> {
|
extends State<SExperimentalUniversalPatches> {
|
||||||
@@ -34,6 +38,12 @@ class _SExperimentalUniversalPatchesState
|
|||||||
setState(() {
|
setState(() {
|
||||||
_settingsViewModel.showUniversalPatches(value);
|
_settingsViewModel.showUniversalPatches(value);
|
||||||
});
|
});
|
||||||
|
if (!value) {
|
||||||
|
_patcherViewModel.selectedPatches
|
||||||
|
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
||||||
|
_patchesSelectorViewModel.selectedPatches
|
||||||
|
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class SExportSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.importPatchesHint'),
|
subtitle: I18nText('settingsView.importPatchesHint'),
|
||||||
onTap: () => _settingsViewModel.importPatches(),
|
onTap: () => _settingsViewModel.importPatches(context),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
@@ -73,10 +73,10 @@ class SExportSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.importKeystoreHint'),
|
subtitle: I18nText('settingsView.importKeystoreHint'),
|
||||||
onTap: () async{
|
onTap: () async {
|
||||||
await _settingsViewModel.importKeystore();
|
await _settingsViewModel.importKeystore();
|
||||||
final sManageKeystorePassword = SManageKeystorePassword();
|
final sManageKeystorePassword = SManageKeystorePassword();
|
||||||
if(context.mounted){
|
if (context.mounted) {
|
||||||
sManageKeystorePassword.showKeystoreDialog(context);
|
sManageKeystorePassword.showKeystoreDialog(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -119,9 +119,9 @@ class SExportSection extends StatelessWidget {
|
|||||||
label: I18nText('yesButton'),
|
label: I18nText('yesButton'),
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
_settingsViewModel.resetSelectedPatches()
|
_settingsViewModel.resetSelectedPatches(),
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ class SocialMediaWidget extends StatelessWidget {
|
|||||||
SocialMediaItem(
|
SocialMediaItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.youtube),
|
icon: FaIcon(FontAwesomeIcons.youtube),
|
||||||
title: Text('YouTube'),
|
title: Text('YouTube'),
|
||||||
subtitle: Text('youtube.com/revanced'),
|
subtitle: Text('youtube.com/@revanced'),
|
||||||
url: 'https://youtube.com/revanced',
|
url: 'https://youtube.com/@revanced',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class AboutInfo {
|
|||||||
'flavor': kReleaseMode ? 'release' : 'debug',
|
'flavor': kReleaseMode ? 'release' : 'debug',
|
||||||
'model': info.model,
|
'model': info.model,
|
||||||
'androidVersion': info.version.release,
|
'androidVersion': info.version.release,
|
||||||
'supportedArch': info.supportedAbis
|
'supportedArch': info.supportedAbis,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
|
|||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.6.0+100600000
|
version: 1.10.2+101000200
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|||||||
Reference in New Issue
Block a user