mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2025-05-20 07:56:47 +08:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
5838550188 | |||
e0e01ae3ee | |||
0983ba8a0f | |||
0bfa776ce7 | |||
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 | |||
8e2cfbddc5 |
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
|
|
2
.github/workflows/pr-build.yml
vendored
2
.github/workflows/pr-build.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
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
|
||||||
|
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`
|
||||||
|
@ -85,7 +85,7 @@ 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:14.2.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")
|
||||||
|
@ -25,8 +25,7 @@
|
|||||||
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"
|
||||||
|
@ -11,6 +11,9 @@ 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.patch.PatchResult
|
import app.revanced.patcher.patch.PatchResult
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
@ -24,7 +27,6 @@ import java.io.StringWriter
|
|||||||
import java.util.logging.LogRecord
|
import java.util.logging.LogRecord
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private lateinit var installerChannel: MethodChannel
|
private lateinit var installerChannel: MethodChannel
|
||||||
@ -90,6 +92,32 @@ class MainActivity : FlutterActivity() {
|
|||||||
stopResult = result
|
stopResult = result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"getPatches" -> {
|
||||||
|
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
|
||||||
|
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||||
|
|
||||||
|
if (patchBundleFilePath != null) {
|
||||||
|
val patches = PatchBundleLoader.Dex(
|
||||||
|
File(patchBundleFilePath),
|
||||||
|
optimizedDexDirectory = File(cacheDirPath)
|
||||||
|
).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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +210,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
updateProgress(0.1, "Loading patches...", "Loading patches")
|
updateProgress(0.1, "Loading patches...", "Loading patches")
|
||||||
|
|
||||||
val patches = PatchBundleLoader.Dex(
|
val patches = PatchBundleLoader.Dex(
|
||||||
File(patchBundleFilePath)
|
File(patchBundleFilePath),
|
||||||
|
optimizedDexDirectory = cacheDir
|
||||||
).filter { patch ->
|
).filter { patch ->
|
||||||
val isCompatible = patch.compatiblePackages?.any {
|
val isCompatible = patch.compatiblePackages?.any {
|
||||||
it.name == patcher.context.packageMetadata.packageName
|
it.name == patcher.context.packageMetadata.packageName
|
||||||
|
@ -178,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",
|
||||||
|
@ -222,10 +222,8 @@ class GithubAPI {
|
|||||||
final String downloadUrl = asset['browser_download_url'];
|
final String downloadUrl = asset['browser_download_url'];
|
||||||
if (extension == '.apk') {
|
if (extension == '.apk') {
|
||||||
_managerAPI.setIntegrationsDownloadURL(downloadUrl);
|
_managerAPI.setIntegrationsDownloadURL(downloadUrl);
|
||||||
} else if (extension == '.json') {
|
|
||||||
_managerAPI.setPatchesDownloadURL(downloadUrl, false);
|
|
||||||
} else {
|
} else {
|
||||||
_managerAPI.setPatchesDownloadURL(downloadUrl, true);
|
_managerAPI.setPatchesDownloadURL(downloadUrl);
|
||||||
}
|
}
|
||||||
return await DefaultCacheManager().getSingleFile(
|
return await DefaultCacheManager().getSingleFile(
|
||||||
downloadUrl,
|
downloadUrl,
|
||||||
|
@ -11,6 +11,7 @@ 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/ui/widgets/shared/custom_material_button.dart';
|
||||||
@ -26,6 +27,7 @@ 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 =
|
||||||
@ -41,11 +43,11 @@ class ManagerAPI {
|
|||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
String? integrationsVersion = '';
|
String? integrationsVersion = '';
|
||||||
bool isDefaultPatchesRepo() {
|
bool isDefaultPatchesRepo() {
|
||||||
return getPatchesRepo() == 'revanced/revanced-patches';
|
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDefaultIntegrationsRepo() {
|
bool isDefaultIntegrationsRepo() {
|
||||||
return getIntegrationsRepo() == 'revanced/revanced-integrations';
|
return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
@ -79,12 +81,12 @@ class ManagerAPI {
|
|||||||
await _prefs.setString('repoUrl', url);
|
await _prefs.setString('repoUrl', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPatchesDownloadURL(bool bundle) {
|
String getPatchesDownloadURL() {
|
||||||
return _prefs.getString('patchesDownloadURL-$bundle') ?? '';
|
return _prefs.getString('patchesDownloadURL') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setPatchesDownloadURL(String value, bool bundle) async {
|
Future<void> setPatchesDownloadURL(String value) async {
|
||||||
await _prefs.setString('patchesDownloadURL-$bundle', value);
|
await _prefs.setString('patchesDownloadURL', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPatchesRepo() {
|
String getPatchesRepo() {
|
||||||
@ -197,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() {
|
||||||
@ -300,28 +302,46 @@ 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();
|
|
||||||
final String url = getPatchesDownloadURL(false);
|
|
||||||
return await _githubAPI.getPatches(
|
|
||||||
repoName,
|
|
||||||
currentVersion,
|
|
||||||
url,
|
|
||||||
);
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
final File? patchBundleFile = await downloadPatches();
|
||||||
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
|
Directory('${appCache.path}/cache').createSync();
|
||||||
|
final Directory workDir =
|
||||||
|
Directory('${appCache.path}/cache').createTempSync('tmp-');
|
||||||
|
final Directory cacheDir = Directory('${workDir.path}/cache');
|
||||||
|
cacheDir.createSync();
|
||||||
|
|
||||||
|
if (patchBundleFile != null) {
|
||||||
|
try {
|
||||||
|
final patchesObject = await PatcherAPI.patcherChannel.invokeMethod(
|
||||||
|
'getPatches',
|
||||||
|
{
|
||||||
|
'patchBundleFilePath': patchBundleFile.path,
|
||||||
|
'cacheDirPath': cacheDir.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(true);
|
final String url = getPatchesDownloadURL();
|
||||||
return await _githubAPI.getPatchesReleaseFile(
|
return await _githubAPI.getPatchesReleaseFile(
|
||||||
'.jar',
|
'.jar',
|
||||||
repoName,
|
repoName,
|
||||||
@ -447,8 +467,7 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<void> setCurrentPatchesVersion(String version) async {
|
Future<void> setCurrentPatchesVersion(String version) async {
|
||||||
await _prefs.setString('patchesVersion', version);
|
await _prefs.setString('patchesVersion', version);
|
||||||
await setPatchesDownloadURL('', false);
|
await setPatchesDownloadURL('');
|
||||||
await setPatchesDownloadURL('', true);
|
|
||||||
await downloadPatches();
|
await downloadPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,12 +25,13 @@ 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.downloadPatches();
|
|
||||||
await _managerAPI.downloadIntegrations();
|
await _managerAPI.downloadIntegrations();
|
||||||
final Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
||||||
@ -45,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) {
|
||||||
@ -56,6 +75,9 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
_patches = List.empty();
|
_patches = List.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_compatiblePackages = getCompatiblePackages();
|
||||||
|
_universalPatches = getUniversalPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||||
@ -63,48 +85,43 @@ 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 (!_compatiblePackages.contains(packageName)) {
|
||||||
|
return _universalPatches;
|
||||||
|
}
|
||||||
|
|
||||||
final List<Patch> patches = _patches
|
final List<Patch> patches = _patches
|
||||||
.where(
|
.where(
|
||||||
(patch) =>
|
(patch) =>
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(64.0),
|
preferredSize: const Size.fromHeight(66.0),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8.0,
|
vertical: 8.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,23 @@ class InstallerView extends StatelessWidget {
|
|||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
floatingActionButton: Visibility(
|
floatingActionButton: Visibility(
|
||||||
visible: !model.isPatching,
|
visible: !model.isPatching && !model.hasErrors,
|
||||||
child: FloatingActionButton.extended(
|
child: FloatingActionButton.extended(
|
||||||
label: I18nText('installerView.installButton'),
|
label: I18nText(
|
||||||
icon: const Icon(Icons.file_download_outlined),
|
model.isInstalled
|
||||||
onPressed: () => model.installTypeDialog(context),
|
? '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,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -182,52 +182,54 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
icon: const Icon(Icons.file_download_outlined),
|
icon: const Icon(Icons.file_download_outlined),
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
content: ValueListenableBuilder(
|
content: SingleChildScrollView(
|
||||||
valueListenable: installType,
|
child: ValueListenableBuilder(
|
||||||
builder: (context, value, child) {
|
valueListenable: installType,
|
||||||
return Column(
|
builder: (context, value, child) {
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.symmetric(
|
Padding(
|
||||||
horizontal: 20,
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 10,
|
horizontal: 20,
|
||||||
),
|
vertical: 10,
|
||||||
child: I18nText(
|
),
|
||||||
'installerView.installTypeDescription',
|
child: I18nText(
|
||||||
child: Text(
|
'installerView.installTypeDescription',
|
||||||
'',
|
child: Text(
|
||||||
style: TextStyle(
|
'',
|
||||||
fontSize: 16,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: 16,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
RadioListTile(
|
||||||
RadioListTile(
|
title: I18nText('installerView.installNonRootType'),
|
||||||
title: I18nText('installerView.installNonRootType'),
|
subtitle: I18nText('installerView.installRecommendedType'),
|
||||||
subtitle: I18nText('installerView.installRecommendedType'),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
value: 0,
|
||||||
value: 0,
|
groupValue: value,
|
||||||
groupValue: value,
|
onChanged: (selected) {
|
||||||
onChanged: (selected) {
|
installType.value = selected!;
|
||||||
installType.value = selected!;
|
},
|
||||||
},
|
),
|
||||||
),
|
RadioListTile(
|
||||||
RadioListTile(
|
title: I18nText('installerView.installRootType'),
|
||||||
title: I18nText('installerView.installRootType'),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
|
value: 1,
|
||||||
value: 1,
|
groupValue: value,
|
||||||
groupValue: value,
|
onChanged: (selected) {
|
||||||
onChanged: (selected) {
|
installType.value = selected!;
|
||||||
installType.value = selected!;
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
|
@ -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.platformBrightnessOf(context) != 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(
|
||||||
|
@ -81,7 +81,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
@ -99,7 +99,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
),
|
),
|
||||||
CustomPopupMenu(
|
CustomPopupMenu(
|
||||||
onSelected: (value) =>
|
onSelected: (value) =>
|
||||||
{model.onMenuSelection(value, context)},
|
{model.onMenuSelection(value, context)},
|
||||||
children: {
|
children: {
|
||||||
0: I18nText(
|
0: I18nText(
|
||||||
'patchesSelectorView.loadPatchesSelection',
|
'patchesSelectorView.loadPatchesSelection',
|
||||||
@ -114,7 +114,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(64.0),
|
preferredSize: const Size.fromHeight(66.0),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8.0,
|
vertical: 8.0,
|
||||||
@ -199,7 +199,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
supportedPackageVersions:
|
supportedPackageVersions:
|
||||||
model.getSupportedVersions(patch),
|
model.getSupportedVersions(patch),
|
||||||
isUnsupported: !isPatchSupported(patch),
|
isUnsupported: !isPatchSupported(patch),
|
||||||
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
|
isChangeEnabled:
|
||||||
|
_managerAPI.isPatchesChangeEnabled(),
|
||||||
isNew: model.isPatchNew(
|
isNew: model.isPatchNew(
|
||||||
patch,
|
patch,
|
||||||
model.getAppInfo().packageName,
|
model.getAppInfo().packageName,
|
||||||
@ -221,8 +222,23 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 10.0,
|
vertical: 10.0,
|
||||||
),
|
),
|
||||||
child: I18nText(
|
child: Container(
|
||||||
'patchesSelectorView.universalPatches',
|
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) {
|
...model.getQueriedPatches(_query).map((patch) {
|
||||||
@ -236,7 +252,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
supportedPackageVersions:
|
supportedPackageVersions:
|
||||||
model.getSupportedVersions(patch),
|
model.getSupportedVersions(patch),
|
||||||
isUnsupported: !isPatchSupported(patch),
|
isUnsupported: !isPatchSupported(patch),
|
||||||
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
|
isChangeEnabled:
|
||||||
|
_managerAPI.isPatchesChangeEnabled(),
|
||||||
isNew: false,
|
isNew: false,
|
||||||
isSelected: model.isSelected(patch),
|
isSelected: model.isSelected(patch),
|
||||||
onChanged: (value) => model.selectPatch(
|
onChanged: (value) => model.selectPatch(
|
||||||
|
@ -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(),
|
||||||
|
@ -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',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
|
|||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.9.4+100900400
|
version: 1.10.3+101000300
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
Reference in New Issue
Block a user