Compare commits

...

84 Commits

Author SHA1 Message Date
5838550188 build: bump version to v1.10.3 2023-09-23 13:10:46 +03:00
e0e01ae3ee chore: merge dev to main (#1300) 2023-09-23 13:10:08 +03:00
0983ba8a0f fix: search bar overflow (#1301) 2023-09-23 00:51:25 +03:00
0bfa776ce7 fix: npe when loading patch bundle on android 8 2023-09-23 00:24:17 +03:00
d2b09936d1 chore: merge dev to main (#1295) 2023-09-22 22:08:13 +07:00
68e9f0f7c1 build: bump version 1.10.2
!!
2023-09-22 22:05:31 +07:00
c3d345de80 fix: force disable material you on Android 11 and below (#1293) 2023-09-22 21:05:13 +07:00
385c0e246a build: use correct version code
The user won't notice it :shhh: we're fine, continue as normal.
2023-09-21 19:32:10 +07:00
5ead49a5b7 build: bump version to v1.10.1 2023-09-20 20:06:19 -07:00
c0760b1347 chore: merge dev to main (#1289) 2023-09-20 20:04:10 -07:00
e01b323aee fix: make entire theme item clickable 2023-09-20 20:03:47 -07:00
6f4866ef63 fix: default theme not following system (#1288) 2023-09-20 20:03:15 -07:00
1b6d72661c build: bump version to v1.10.0 2023-09-20 17:40:23 -07:00
c59d4aea81 chore: merge dev to main (#1239) 2023-09-20 17:35:01 -07:00
6260a80738 feat(settings - appearance): add system option (#1279)
Closes #1260
2023-09-21 03:25:23 +03:00
e75d3c8273 fix(install-type): update padding and enable radio list scrolling (#1287)
fix(install-type): update padding and enable radio list scrolling
2023-09-20 17:24:37 -07:00
b7acb475e9 fix: update install type dialog padding 2023-09-20 16:57:13 -07:00
42b6bbff7c fix: update youtube link (#1286) 2023-09-21 02:16:55 +03:00
4b8542b35b fix: load patches via PatchBundle (#1242) 2023-09-21 01:35:32 +03:00
9ad1d6cbfb fix(custom-sources): ignore casing when checking if default repo is being used (#1281) 2023-09-21 00:42:29 +03:00
4cdd9acd73 docs(readme): add documentation and minor fixes (#1264)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
2023-09-16 17:38:06 +07:00
f4b0a695d6 fix: improve app list loading speed (#1166) 2023-09-15 22:58:15 +07:00
b525ea1ba4 ci: remove analyze workflow (#1262)
Code style is not enforced so analysis is not needed.
2023-09-12 23:40:00 +07:00
c1fc2c4766 ci: bump actions/checkout to v4 2023-09-10 14:43:58 +07:00
5c733932c7 ci(pr-build): revert "sign apk with keystore (#1231)"
This reverts commit 8bf08ff4641451db64c80fddc9a3281cdf73098b, as it fails for PRs originating from forks
2023-09-08 01:14:13 +02:00
d1218616ec docs(readme): minor improvements 2023-09-08 01:14:13 +02:00
2bf6a03d56 fix: back button closing the app from any page 2023-09-08 01:14:13 +02:00
b6ee63c1ea ci(pr-build): sign apk with keystore (#1231) 2023-09-08 01:14:13 +02:00
6d08efdcd7 chore: fix issue template 2023-09-08 01:13:42 +02:00
a0a43a5651 build: bump version to v1.9.5 2023-09-08 01:13:42 +02:00
3af2f5b032 chore: merge dev to main (#1177) 2023-09-04 04:15:12 +03:00
8f54b226b4 refactor(patches-selector): improve universal patches header 2023-09-03 21:14:09 +03:00
9f64011b26 fix: npe when patching on android 8 2023-09-03 20:54:42 +03:00
c5fc54e721 fix(installer): open the patched app after install (#1233) 2023-09-03 21:48:28 +07:00
fc8a4fc5b6 fix: Don't use 'BuildContext's across async gaps. (#1148) 2023-09-03 01:47:20 +03:00
6f9ab232ae chore: ignore the root .gradle folder (#1160) 2023-09-02 09:29:49 -07:00
8cb96f1e45 fix: permissions handling at first launch 2023-08-31 19:34:12 +05:45
5733acb77a build(dependency): update patcher to v14.2.2
https://github.com/ReVanced/revanced-patcher/releases/tag/v14.2.2
2023-08-31 20:35:24 +07:00
e49bcb2a69 chore: simplify issue templates (#1165) 2023-08-31 17:36:29 +07:00
42e41c399f fix: hide install button on error 2023-08-28 19:05:30 +03:00
166a3180d3 build: correct version code 2023-08-28 03:20:15 +03:00
3bf4982f23 chore: merge dev to main (#1163) 2023-08-28 02:47:47 +03:00
f4e1cccfac build: bump version to v1.9.4 2023-08-28 01:44:46 +02:00
7911a8f49e fix: properly log messages and progress 2023-08-28 01:44:46 +02:00
64a96fc3ce fix: close before returning 2023-08-28 01:44:46 +02:00
8e2cfbddc5 fix: ignore the root .gradle folder 2023-08-27 14:05:39 -07:00
45fae3f0fd build: bump ReVanced Patcher back to v14.2.1
This reverts the previous regression with the dependency to ReVanced Patcher.
2023-08-27 22:35:03 +02:00
e45a7824c1 build: bump version to v1.9.3 2023-08-27 13:27:53 +07:00
5d72c48a76 chore: merge dev to main (#1157) 2023-08-27 13:27:01 +07:00
d6169c6fa2 fix: broken settings page 2023-08-27 11:55:21 +05:45
9df6d52e2d build: bump version to v1.9.2 2023-08-27 05:40:50 +03:00
239de8e923 chore: merge dev to main (#1156) 2023-08-27 05:40:13 +03:00
7d553a87f3 build: revert patcher to v11.0.4 2023-08-27 05:39:24 +03:00
557b42bc56 build: bump version to 1.9.1 2023-08-27 03:58:30 +03:00
8423914748 chore: merge dev to main (#1155) 2023-08-27 03:57:57 +03:00
07dce23794 build: bump patcher to v14.2.0 2023-08-27 03:55:56 +03:00
18fd0552db build: bump version to v1.9.0 2023-08-27 02:23:36 +03:00
d537d48f8e chore: merge dev to main (#1125) 2023-08-27 02:21:48 +03:00
b456512bbb build: bump patcher to v14.1.0 (#1153)
Co-authored-by: aAbed <aabedhkhan@gmail.com>
2023-08-27 02:21:16 +03:00
d9953b1473 fix(installer): use correct elevation level 2023-08-24 19:00:50 +07:00
c6ac898390 fix: universal patches not selectable 2023-08-17 18:25:23 +05:45
43f98cec43 build: make variant more identifiable
Release variant will use the "ReVanced Manager" app name, Debug will use "ReVanced Manager Debug"
2023-08-15 21:31:34 +07:00
63175cdec6 ci(build): create debug variant instead of release 2023-08-15 20:55:11 +07:00
6436a1ec61 build: debug apk come with application suffix
Instead of using the same one as Release, app.revanced.manager.flutter - We add .debug to it, yay, co-installation of two variants!
2023-08-15 20:37:43 +07:00
c400619338 feat: disable changing patches selection by default (#1132)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-08-15 16:05:27 +07:00
dcd5ba41cf build: add mavenlocal to repositories 2023-08-14 03:40:05 +03:00
7525e52fab fix(installer): use correct bg colour for dialog 2023-08-12 15:07:04 +07:00
72fd24e624 fix: use i18n translation for installer page 2023-08-12 15:06:17 +07:00
11d8f9fd30 refactor: apply changes according to Dart
trailing commas
2023-08-12 14:53:54 +07:00
4b0c8cecc8 ci(build): update event trigger
This add ".github/workflows/pr-build.yml" to build on every change to the CI Build to check if there's no error when it got modified.

And also remove "fastlane/**" because that's not related to the ReVanced Manager's code
2023-08-12 13:56:18 +07:00
372ce174c9 fix: overlapping issue in application selection page (#1128) 2023-08-11 19:19:44 +03:00
381daff980 fix: exclude x86 aapt2 binary from release builds (#1126) 2023-08-11 19:19:33 +03:00
94acebbebd docs(fixed): replaced some emoji icons, not rendering on chromium based browsers on windows (#1123) 2023-08-11 10:17:46 +07:00
f90f6e81ee feat: patch apps without internet (#1114) 2023-08-11 08:11:19 +07:00
97e37f304b refactor: migrate MediaQuery properties to InheritedModel (#1115)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-08-10 15:25:07 +03:00
f5e45ead26 ci(build): don't build on every pull request unrelated to application 2023-08-10 19:14:13 +07:00
14f765f4b4 ci(build): don't cache Gradle
Gradle take up so much space in cache that it wouldn't make sense for us to cache it since we are rapidly hitting the cache limit every time.
2023-08-10 19:12:15 +07:00
b77d46b2c2 feat(installer): redesign utility options (#1062)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-08-10 19:02:30 +07:00
1442916b20 build: bump version to 1.8.0 2023-08-10 03:01:03 +03:00
c6a5f42d23 chore: merge dev to main (#1120) 2023-08-10 03:00:27 +03:00
580d50eb8d fix: use right label for auto patch 2023-08-10 00:05:47 +03:00
9a66b6e50d fix(root-install): create service and postfs directories if they dont exist (#1116)
Fixes #860
2023-08-09 23:57:09 +03:00
acec064cb7 feat(patcher-view): show notice for removed patches that were used previously (#1107) 2023-08-09 22:32:13 +03:00
ea05d13a1f fix(patches-selector): New tag showing for old patches (#1113) 2023-08-09 22:31:37 +03:00
49 changed files with 1224 additions and 708 deletions

View File

@ -1,5 +1,5 @@
name: 🐞 Bug report
description: Report a very clearly broken issue.
description: Create a new bug report.
title: 'bug: <title>'
labels: [bug]
body:
@ -8,53 +8,20 @@ body:
value: |
# 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).
- type: dropdown
attributes:
label: Type
options:
- Error while running the manager
- Error at runtime
- Cosmetic
- Other
validations:
required: true
Please check for existing issues [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
- type: textarea
attributes:
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:
required: true
- type: textarea
attributes:
label: Steps to reproduce
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.
label: Version of ReVanced Manager and version & name of application you tried to patch
validations:
required: true
- type: dropdown
@ -64,57 +31,31 @@ body:
- Non-root
- Root
validations:
required: true
required: false
- type: textarea
attributes:
label: Patches selected.
description: Patches you selected for the app.
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.
label: Device logs
description: Export logs in ReVanced Manager settings.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Installer logs (exported using Installer menu option) [unneeded if the issue is not during patching].
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so there is no need for backticks.
label: Patcher logs
description: Export logs in "Patcher" screen.
render: shell
validations:
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
id: acknowledgments
attributes:
label: Acknowledgments
description: Your issue will be closed if you haven't done these steps.
label: Acknowledgements
description: Your issue will be closed if you don't follow the checklist below!
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
- label: I have written a short but informative title.
- label: I have chosen an appropriate title.
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
- 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

View File

@ -1,52 +1,42 @@
name: ⭐ Feature request
description: Create a detailed feature request.
description: Create a new feature request.
title: 'feat: <title>'
labels: [feature-request]
body:
- type: dropdown
- type: markdown
attributes:
label: Type
options:
- Functionality
- Cosmetic
- Other
validations:
required: true
value: |
# ReVanced Manager feature request
Please check for existing feature requests [here](https://github.com/revanced/revanced-manager/labels/bug) before creating a new one.
- type: textarea
attributes:
label: Issue
description: What is the current problem. Why does it require a feature request?
validations:
required: true
- type: textarea
attributes:
label: Feature
description: Describe your feature in detail. How does it solve the issue?
label: Feature description
description: Describe your feature in detail.
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: Why should your feature should be considered?
description: Explain why the lack of it is a problem.
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add additional context here.
description: In case there is something else you want to add.
validations:
required: false
- type: checkboxes
id: acknowledgements
attributes:
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:
- 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
- label: I have written a short but informative title.
- label: I have chosen an appropriate title.
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
- label: The issue is related solely to the ReVanced Manager
- label: The issue is solely related to the ReVanced Manager
required: true

View File

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

View File

@ -2,14 +2,19 @@ name: PR Build
on:
pull_request:
paths:
- ".github/workflows/pr-build.yml"
- "android/**"
- "assets/**"
- "lib/**"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
@ -20,7 +25,6 @@ jobs:
with:
java-version: '11'
distribution: 'zulu'
cache: 'gradle'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
@ -33,9 +37,9 @@ jobs:
- name: Build with Flutter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: flutter build apk
run: flutter build apk --debug
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: revanced-manager
path: build/app/outputs/flutter-apk/app-release.apk
path: build/app/outputs/flutter-apk/app-debug.apk

View File

@ -9,7 +9,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK 11

1
.gitignore vendored
View File

@ -58,6 +58,7 @@ unlinked.ds
unlinked_spec.ds
# Android related
.gradle/
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/

View File

@ -3,29 +3,33 @@
The official ReVanced Manager based on Flutter.
## 🔽 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
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
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
[![Crowdin](https://badges.crowdin.net/revanced/localized.svg)](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
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
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
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)
7. To build release apk run `flutter build apk`
7. To build release APK run `flutter build apk`

View File

@ -54,7 +54,21 @@ android {
release {
shrinkResources false
minifyEnabled false
resValue "string", "app_name", "ReVanced Manager"
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"
// ReVanced
implementation "app.revanced:revanced-patcher:11.0.4"
implementation "app.revanced:revanced-patcher:14.2.2"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2")
}

View File

@ -20,13 +20,12 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application
android:label="ReVanced Manager"
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:extractNativeLibs="true"
android:enableOnBackInvokedCallback="true">
android:extractNativeLibs="true">
<activity
android:name=".MainActivity"
android:exported="true"

View File

@ -1,28 +1,31 @@
package app.revanced.manager.flutter
import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.annotation.NonNull
import app.revanced.manager.flutter.utils.Aapt
import app.revanced.manager.flutter.utils.aligning.ZipAligner
import app.revanced.manager.flutter.utils.signing.Signer
import app.revanced.manager.flutter.utils.zip.ZipFile
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
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.logging.Logger
import app.revanced.patcher.util.patch.PatchBundle
import dalvik.system.DexClassLoader
import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import java.io.File
private const val PATCHER_CHANNEL = "app.revanced.manager.flutter/patcher"
private const val INSTALLER_CHANNEL = "app.revanced.manager.flutter/installer"
import java.io.PrintWriter
import java.io.StringWriter
import java.util.logging.LogRecord
import java.util.logging.Logger
class MainActivity : FlutterActivity() {
private val handler = Handler(Looper.getMainLooper())
@ -30,10 +33,18 @@ class MainActivity : FlutterActivity() {
private var cancel: Boolean = false
private var stopResult: MethodChannel.Result? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
override fun configureFlutterEngine(flutterEngine: 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 ->
when (call.method) {
"runPatcher" -> {
@ -73,14 +84,40 @@ class MainActivity : FlutterActivity() {
keyStoreFilePath,
keystorePassword
)
} else {
result.notImplemented()
}
} else result.notImplemented()
}
"stopPatcher" -> {
cancel = true
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()
}
}
@ -105,179 +142,141 @@ class MainActivity : FlutterActivity() {
val outFile = File(outFilePath)
val integrations = File(integrationsPath)
val keyStoreFile = File(keyStoreFilePath)
val cacheDir = File(cacheDirPath)
Thread {
try {
fun updateProgress(progress: Double, header: String, log: String) {
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.1,
"header" to "",
"log" to "Copying original apk"
"progress" to progress,
"header" to header,
"log" to log
)
)
}
}
if(cancel) {
handler.post { stopResult!!.success(null) }
fun postStop() = 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
}
originalFile.copyTo(inputFile, true)
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.2,
"header" to "Unpacking apk...",
"log" to "Unpacking input apk"
)
if (cancel) {
postStop()
return@Thread
}
updateProgress(0.05, "Reading APK...", "Reading APK")
val patcher = Patcher(
PatcherOptions(
inputFile,
cacheDir,
Aapt.binary(applicationContext).absolutePath,
cacheDir.path,
)
}
)
if(cancel) {
handler.post { stopResult!!.success(null) }
if (cancel) {
postStop()
return@Thread
}
val patcher =
Patcher(
PatcherOptions(
inputFile,
cacheDirPath,
Aapt.binary(applicationContext).absolutePath,
cacheDirPath,
logger = ManagerLogger()
)
)
updateProgress(0.1, "Loading patches...", "Loading patches")
if(cancel) {
handler.post { stopResult!!.success(null) }
val patches = PatchBundleLoader.Dex(
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
}
handler.post {
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"
)
)
}
updateProgress(0.15, "Executing...", "")
if(cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
// Update the progress bar every time a patch is executed from 0.15 to 0.7
val totalPatchesCount = patches.size
val progressStep = 0.55 / totalPatchesCount
var progress = 0.15
patcher.addIntegrations(listOf(integrations)) {}
patcher.apply {
acceptIntegrations(listOf(integrations))
acceptPatches(patches)
if(cancel) {
handler.post { stopResult!!.success(null) }
return@Thread
}
runBlocking {
apply(false).collect { patchResult: PatchResult ->
if (cancel) {
handler.post { stopResult!!.success(null) }
this.cancel()
this@apply.close()
return@collect
}
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.5,
"header" to "Applying patches...",
"log" to ""
)
)
}
val msg = patchResult.exception?.let {
val writer = StringWriter()
it.printStackTrace(PrintWriter(writer))
"${patchResult.patchName} failed: $writer"
} ?: run {
"${patchResult.patchName} succeeded"
}
if(cancel) {
handler.post { stopResult!!.success(null) }
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
)
)
updateProgress(progress, "", msg)
progress += progressStep
}
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 {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.7,
"header" to "Repacking apk...",
"log" to "Repacking patched apk"
)
)
}
if(cancel) {
handler.post { stopResult!!.success(null) }
if (cancel) {
postStop()
patcher.close()
return@Thread
}
val res = patcher.save()
updateProgress(0.8, "Building...", "")
val res = patcher.get()
patcher.close()
ZipFile(patchedFile).use { file ->
res.dexFiles.forEach {
if(cancel) {
handler.post { stopResult!!.success(null) }
if (cancel) {
postStop()
return@Thread
}
file.addEntryCompressData(
@ -296,90 +295,35 @@ class MainActivity : FlutterActivity() {
ZipAligner::getEntryAlignment
)
}
if(cancel) {
handler.post { stopResult!!.success(null) }
if (cancel) {
postStop()
return@Thread
}
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.9,
"header" to "Signing apk...",
"log" to ""
)
)
}
updateProgress(0.9, "Signing...", "Signing APK")
try {
Signer("ReVanced", keystorePassword).signApk(
patchedFile,
outFile,
keyStoreFile
)
Signer("ReVanced", keystorePassword)
.signApk(patchedFile, outFile, keyStoreFile)
} catch (e: Exception) {
//log to console
print("Error signing apk: ${e.message}")
print("Error signing APK: ${e.message}")
e.printStackTrace()
}
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 1.0,
"header" to "Finished!",
"log" to "Finished!"
)
)
}
updateProgress(1.0, "Patched", "Patched")
} catch (ex: Throwable) {
val stack = ex.stackTraceToString()
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to -100.0,
"header" to "Aborted...",
"log" to "An error occurred! Aborted\nError:\n$stack"
)
if (!cancel) {
val stack = ex.stackTraceToString()
updateProgress(
-100.0,
"Aborted",
"An error occurred:\n$stack"
)
}
}
handler.post { result.success(null) }
}.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 */
}
}
}

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.kotlin_version = '1.9.0'
repositories {
google()
mavenCentral()
@ -22,6 +22,7 @@ allprojects {
password = (project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")) as String
}
}
mavenLocal()
}
}

View File

@ -10,6 +10,8 @@
"yesButton": "Yes",
"noButton": "No",
"warning": "Warning",
"notice": "Notice",
"noShowAgain": "Don't show this again",
"new": "New",
"navigationView": {
"dashboardTab": "Dashboard",
@ -71,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?",
"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": {
"widgetTitle": "Select an application",
@ -134,12 +137,21 @@
"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"
"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": {
"widgetTitle": "Installer",
"installType": "Select install type",
"installTypeDescription": "Select the installation type to proceed with.",
"installButton": "Install",
"installRootButton": "Install as Root",
"installRootType": "Root",
"installNonRootType": "Non-root",
"installRecommendedType": "Recommended",
"pressBackAgain": "Press back again to cancel",
"openButton": "Open",
"shareButton": "Share file",
@ -147,9 +159,8 @@
"notificationTitle": "ReVanced Manager is patching",
"notificationText": "Tap to return to the installer",
"shareApkMenuOption": "Share APK",
"exportApkMenuOption": "Export APK",
"shareLogMenuOption": "Share log",
"exportApkButtonTooltip": "Export patched APK",
"exportLogButtonTooltip": "Export log",
"installErrorDialogTitle": "Error",
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
@ -167,8 +178,10 @@
"exportSectionTitle": "Import & export",
"logsSectionTitle": "Logs",
"darkThemeLabel": "Dark mode",
"darkThemeHint": "Welcome to the dark side",
"themeModeLabel": "App theme",
"systemThemeLabel": "System",
"lightThemeLabel": "Light",
"darkThemeLabel": "Dark",
"dynamicThemeLabel": "Material You",
"dynamicThemeHint": "Enjoy an experience closer to your device",
@ -198,6 +211,12 @@
"logsLabel": "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",
"experimentalUniversalPatchesLabel": "Experimental universal patches support",
"experimentalUniversalPatchesHint": "Display all applications to use with universal patches, loading list of apps may be slower",

View File

@ -2,7 +2,7 @@
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)
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.
Continue: [🪛 Usage](2_usage.md)
Continue: [🛠️ Usage](2_usage.md)

View File

@ -2,7 +2,7 @@
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
2. Tap on the **Select an app** card

View File

@ -2,7 +2,7 @@
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
2. Tap on the **Info** button for the app you want to manage

View File

@ -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.
## 🪜 Updating steps
## Updating steps
1. Navigate to the **Dashboard** tab from the bottom navigation bar
2. Tap on the **Update** button in the **Updates** section

View File

@ -2,7 +2,7 @@
ReVanced Manager has settings that can be configured to your liking.
## 🪛 Essential settings
## Essential settings
- ### 🔗 API URL

View File

@ -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.
Continue: [🛟 Troubleshooting](3_troubleshooting.md)
Continue: [ Troubleshooting](3_troubleshooting.md)

View File

@ -1,4 +1,4 @@
# 🛟 Troubleshooting
# Troubleshooting
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.
Continue: [🛠️ Building from source](4_building.md)
Continue: [🔨 Building from source](4_building.md)

View File

@ -11,8 +11,8 @@ This documentation explains how to use [ReVanced Manager](https://github.com/rev
2. [🧰 Managing patched apps](2_2_managing.md)
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
3. [🛟 Troubleshooting](3_troubleshooting.md)
4. [🛠 Building from source](4_building.md)
3. [ Troubleshooting](3_troubleshooting.md)
4. [🔨 Building from source](4_building.md)
## ⏭️ Start here

View File

@ -6,12 +6,14 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.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/services/manager_api.dart';
@lazySingleton
class GithubAPI {
late Dio _dio = Dio();
late final ManagerAPI _managerAPI = locator<ManagerAPI>();
final _cacheOptions = CacheOptions(
store: MemCacheStore(),
@ -201,8 +203,14 @@ class GithubAPI {
String extension,
String repoName,
String version,
String url,
) async {
try {
if (url.isNotEmpty) {
return await DefaultCacheManager().getSingleFile(
url,
);
}
final Map<String, dynamic>? release =
await getPatchesRelease(repoName, version);
if (release != null) {
@ -211,8 +219,14 @@ class GithubAPI {
(asset) => (asset['name'] as String).endsWith(extension),
);
if (asset != null) {
final String downloadUrl = asset['browser_download_url'];
if (extension == '.apk') {
_managerAPI.setIntegrationsDownloadURL(downloadUrl);
} else {
_managerAPI.setPatchesDownloadURL(downloadUrl);
}
return await DefaultCacheManager().getSingleFile(
asset['browser_download_url'],
downloadUrl,
);
}
}
@ -224,10 +238,19 @@ class GithubAPI {
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 = [];
try {
final File? f = await getPatchesReleaseFile('.json', repoName, version);
final File? f = await getPatchesReleaseFile(
'.json',
repoName,
version,
url,
);
if (f != null) {
final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();

View File

@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:io';
import 'package:device_apps/device_apps.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:package_info_plus/package_info_plus.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/patched_application.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/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:shared_preferences/shared_preferences.dart';
import 'package:timeago/timeago.dart';
@ -23,6 +27,7 @@ class ManagerAPI {
final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs;
List<Patch> patches = [];
bool isRooted = false;
String storedPatchesFile = '/selected-patches.json';
String keystoreFile =
@ -38,11 +43,11 @@ class ManagerAPI {
String? patchesVersion = '';
String? integrationsVersion = '';
bool isDefaultPatchesRepo() {
return getPatchesRepo() == 'revanced/revanced-patches';
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
}
bool isDefaultIntegrationsRepo() {
return getIntegrationsRepo() == 'revanced/revanced-integrations';
return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations';
}
Future<void> initialize() async {
@ -76,6 +81,14 @@ class ManagerAPI {
await _prefs.setString('repoUrl', url);
}
String getPatchesDownloadURL() {
return _prefs.getString('patchesDownloadURL') ?? '';
}
Future<void> setPatchesDownloadURL(String value) async {
await _prefs.setString('patchesDownloadURL', value);
}
String getPatchesRepo() {
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
}
@ -99,12 +112,37 @@ class ManagerAPI {
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 {
await _prefs.setBool('patchesAutoUpdate', value);
}
List<Patch> getSavedPatches(String packageName) {
final List<String> patchesJson = _prefs.getStringList('savedPatches-$packageName') ?? [];
final List<String> patchesJson =
_prefs.getStringList('savedPatches-$packageName') ?? [];
final List<Patch> patches = patchesJson.map((String patchJson) {
return Patch.fromJson(jsonDecode(patchJson));
}).toList();
@ -118,6 +156,30 @@ class ManagerAPI {
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() {
return _prefs.getString('integrationsRepo') ?? defaultIntegrationsRepo;
}
@ -137,12 +199,12 @@ class ManagerAPI {
await _prefs.setBool('useDynamicTheme', value);
}
bool getUseDarkTheme() {
return _prefs.getBool('useDarkTheme') ?? false;
int getThemeMode() {
return _prefs.getInt('themeMode') ?? 2;
}
Future<void> setUseDarkTheme(bool value) async {
await _prefs.setBool('useDarkTheme', value);
Future<void> setThemeMode(int value) async {
await _prefs.setInt('themeMode', value);
}
bool areUniversalPatchesEnabled() {
@ -240,26 +302,51 @@ class ManagerAPI {
}
Future<List<Patch>> getPatches() async {
try {
final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion();
return await _githubAPI.getPatches(repoName, currentVersion);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return [];
if (patches.isNotEmpty) {
return patches;
}
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 {
try {
final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion();
final String url = getPatchesDownloadURL();
return await _githubAPI.getPatchesReleaseFile(
'.jar',
repoName,
currentVersion,
url,
);
} on Exception catch (e) {
if (kDebugMode) {
@ -273,10 +360,12 @@ class ManagerAPI {
try {
final String repoName = getIntegrationsRepo();
final String currentVersion = await getCurrentIntegrationsVersion();
final String url = getIntegrationsDownloadURL();
return await _githubAPI.getPatchesReleaseFile(
'.apk',
repoName,
currentVersion,
url,
);
} on Exception catch (e) {
if (kDebugMode) {
@ -367,27 +456,38 @@ class ManagerAPI {
Future<String> getCurrentPatchesVersion() async {
patchesVersion = _prefs.getString('patchesVersion') ?? '0.0.0';
if (patchesVersion == '0.0.0' || isPatchesAutoUpdate()) {
patchesVersion = await getLatestPatchesVersion() ?? '0.0.0';
await setCurrentPatchesVersion(patchesVersion!);
final String newPatchesVersion =
await getLatestPatchesVersion() ?? '0.0.0';
if (patchesVersion != newPatchesVersion && newPatchesVersion != '0.0.0') {
await setCurrentPatchesVersion(newPatchesVersion);
}
}
return patchesVersion!;
}
Future<void> setCurrentPatchesVersion(String version) async {
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()) {
integrationsVersion = await getLatestIntegrationsVersion() ?? '0.0.0';
await setCurrentIntegrationsVersion(integrationsVersion!);
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(
@ -461,6 +561,63 @@ class ManagerAPI {
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 {
final List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> unsavedApps =

View File

@ -25,11 +25,14 @@ class PatcherAPI {
late Directory _tmpDir;
late File _keyStoreFile;
List<Patch> _patches = [];
List<Patch> _universalPatches = [];
List<String> _compatiblePackages = [];
Map filteredPatches = <String, List<Patch>>{};
File? _outFile;
Future<void> initialize() async {
await _loadPatches();
await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache;
_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 {
try {
if (_patches.isEmpty) {
@ -54,6 +75,9 @@ class PatcherAPI {
}
_patches = List.empty();
}
_compatiblePackages = getCompatiblePackages();
_universalPatches = getUniversalPatches();
}
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
@ -61,48 +85,43 @@ class PatcherAPI {
) async {
final List<ApplicationWithIcon> filteredApps = [];
final bool allAppsIncluded =
_patches.any((patch) => patch.compatiblePackages.isEmpty) &&
_universalPatches.isNotEmpty &&
showUniversalPatches;
if (allAppsIncluded) {
final allPackages = await DeviceApps.getInstalledApplications(
final appList = await DeviceApps.getInstalledApplications(
includeAppIcons: true,
onlyAppsWithLaunchIntent: true,
);
for (final pkg in allPackages) {
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
final appInfo = await DeviceApps.getApp(
pkg.packageName,
true,
) as ApplicationWithIcon?;
if (appInfo != null) {
filteredApps.add(appInfo);
}
}
for(final app in appList) {
filteredApps.add(app as ApplicationWithIcon);
}
}
for (final Patch patch in _patches) {
for (final Package package in patch.compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == package.name)) {
final ApplicationWithIcon? app = await DeviceApps.getApp(
package.name,
true,
) as ApplicationWithIcon?;
if (app != null) {
filteredApps.add(app);
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
for (final packageName in _compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == packageName)) {
final ApplicationWithIcon? app = await DeviceApps.getApp(
packageName,
true,
) as ApplicationWithIcon?;
if (app != null) {
filteredApps.add(app);
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
return filteredApps;
}
List<Patch> getFilteredPatches(String packageName) {
if (!_compatiblePackages.contains(packageName)) {
return _universalPatches;
}
final List<Patch> patches = _patches
.where(
(patch) =>
@ -283,7 +302,7 @@ class PatcherAPI {
return newName;
}
Future<void> sharePatcherLog(String logs) async {
Future<void> exportPatcherLog(String logs) async {
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
@ -293,10 +312,15 @@ class PatcherAPI {
.replaceAll(':', '')
.replaceAll('T', '')
.replaceAll('.', '');
final File log =
File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
final String fileName = 'revanced-manager_patcher_$dateTime.log';
final File log = File('${logDir.path}/$fileName');
log.writeAsStringSync(logs);
ShareExtend.share(log.path, 'file');
CRFileSaver.saveFileWithDialog(
SaveFileDialogParams(
sourceFilePath: log.path,
destinationFileName: fileName,
),
);
}
String getSuggestedVersion(String packageName) {

View File

@ -153,6 +153,9 @@ class RootAPI {
}
Future<void> installServiceDScript(String packageName) async {
await Root.exec(
cmd: 'mkdir -p "$_serviceDDirPath"',
);
final String content = '#!/system/bin/sh\n'
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
'base_path=$_revancedDirPath/$packageName/base.apk\n'
@ -166,6 +169,9 @@ class RootAPI {
}
Future<void> installPostFsDataScript(String packageName) async {
await Root.exec(
cmd: 'mkdir -p "$_postFsDataDirPath"',
);
final String content = '#!/system/bin/sh\n'
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
r'[ ! -z $stock_path ] && umount -l $stock_path';

View File

@ -1,12 +1,16 @@
import 'dart:ui';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:dynamic_themes/dynamic_themes.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/services/manager_api.dart';
import 'package:revanced_manager/theme.dart';
import 'package:stacked_services/stacked_services.dart';
class DynamicThemeBuilder extends StatelessWidget {
class DynamicThemeBuilder extends StatefulWidget {
const DynamicThemeBuilder({
Key? key,
required this.title,
@ -17,6 +21,35 @@ class DynamicThemeBuilder extends StatelessWidget {
final Widget home;
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
Widget build(BuildContext context) {
return DynamicColorBuilder(
@ -50,24 +83,32 @@ class DynamicThemeBuilder extends StatelessWidget {
return DynamicTheme(
themeCollection: ThemeCollection(
themes: {
0: lightCustomTheme,
1: darkCustomTheme,
2: lightDynamicTheme,
3: darkDynamicTheme,
0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme,
2: lightCustomTheme,
3: lightDynamicTheme,
4: darkCustomTheme,
5: darkDynamicTheme,
},
fallbackTheme: lightCustomTheme,
fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
),
builder: (context, theme) => MaterialApp(
debugShowCheckedModeBanner: false,
title: title,
navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute,
theme: theme,
home: home,
localizationsDelegates: localizationsDelegates,
),
debugShowCheckedModeBanner: false,
title: widget.title,
navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute,
theme: theme,
home: widget.home,
localizationsDelegates: widget.localizationsDelegates,
),
);
},
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}

View File

@ -54,7 +54,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
onPressed: () => Navigator.of(context).pop(),
),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(64.0),
preferredSize: const Size.fromHeight(66.0),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
@ -94,7 +94,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith(
bottom:
MediaQuery.of(context).viewPadding.bottom + 8.0,
MediaQuery.viewPaddingOf(context).bottom + 8.0,
),
child: Column(
children: [
@ -133,6 +133,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
),
)
.toList(),
const SizedBox(height: 70.0),
],
),
),

View File

@ -90,10 +90,14 @@ class AppSelectorViewModel extends BaseViewModel {
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
if (app != null) {
if (await checkSplitApk(packageName) && !isRooted) {
return showSelectFromStorageDialog(context);
if (context.mounted) {
return showSelectFromStorageDialog(context);
}
} else if (!await checkSplitApk(packageName) || isRooted) {
selectApp(app);
Navigator.pop(context);
if (context.mounted) {
Navigator.pop(context);
}
}
}
}

View File

@ -57,7 +57,7 @@ class ContributorsView extends StatelessWidget {
title: 'contributorsView.managerContributors',
contributors: model.managerContributors,
),
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
],
),
),

View File

@ -256,9 +256,9 @@ class HomeViewModel extends BaseViewModel {
final String integrationsVersion =
await _managerAPI.getLatestIntegrationsVersion() ?? '0.0.0';
if (patchesVersion != '0.0.0' && integrationsVersion != '0.0.0') {
_toast.showBottom('homeView.downloadedMessage');
await _managerAPI.setCurrentPatchesVersion(patchesVersion);
await _managerAPI.setCurrentIntegrationsVersion(integrationsVersion);
_toast.showBottom('homeView.downloadedMessage');
forceRefresh(context);
} else {
_toast.showBottom('homeView.errorDownloadMessage');

View File

@ -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/widgets/installerView/gradient_progress_indicator.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:stacked/stacked.dart';
@ -22,6 +20,57 @@ class InstallerView extends StatelessWidget {
top: false,
bottom: false,
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(
controller: model.scrollController,
slivers: <Widget>[
@ -35,44 +84,6 @@ class InstallerView extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
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(
preferredSize: const Size(double.infinity, 1.0),
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,
),
),
],
),
),

View File

@ -101,6 +101,7 @@ class InstallerViewModel extends BaseViewModel {
_patcherAPI.getFilteredPatches(_app.packageName),
_app.packageName,
);
await _managerAPI.setUsedPatches(_patches, _app.packageName);
} else if (value == -100.0) {
isPatching = false;
hasErrors = true;
@ -168,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 {
try {
isCanceled = true;
@ -252,18 +338,8 @@ class InstallerViewModel extends BaseViewModel {
}
}
void shareResult() {
try {
_patcherAPI.sharePatchedFile(_app.name, _app.version);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
void shareLog() {
_patcherAPI.sharePatcherLog(logs);
void exportLog() {
_patcherAPI.exportPatcherLog(logs);
}
Future<void> cleanPatcher() async {
@ -283,16 +359,13 @@ class InstallerViewModel extends BaseViewModel {
DeviceApps.openApp(_app.packageName);
}
void onMenuSelection(int value) {
void onButtonPressed(int value) {
switch (value) {
case 0:
shareResult();
break;
case 1:
exportResult();
break;
case 2:
shareLog();
case 1:
exportLog();
break;
}
}

View File

@ -1,4 +1,5 @@
// 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:flutter/material.dart';
import 'package:flutter/services.dart';
@ -18,25 +19,35 @@ class NavigationViewModel extends IndexTrackingViewModel {
Future<void> initialize(BuildContext context) async {
locator<Toast>().initialize(context);
final SharedPreferences prefs = await SharedPreferences.getInstance();
requestManageExternalStorage();
await requestManageExternalStorage();
if (prefs.getBool('permissionsRequested') == null) {
await Permission.storage.request();
await Permission.manageExternalStorage.request();
await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions().then(
await RootAPI().hasRootPermissions().then(
(value) => Permission.requestInstallPackages.request().then(
(value) => Permission.ignoreBatteryOptimizations.request(),
),
);
}
if (prefs.getBool('useDarkTheme') == null) {
final bool isDark =
MediaQuery.of(context).platformBrightness != Brightness.light;
await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
final dynamicTheme = DynamicTheme.of(context)!;
if (prefs.getInt('themeMode') == null) {
await prefs.setInt('themeMode', 0);
await dynamicTheme.setTheme(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.setSystemUIOverlayStyle(
SystemUiOverlayStyle(

View File

@ -22,7 +22,7 @@ class PatcherView extends StatelessWidget {
child: FloatingActionButton.extended(
label: I18nText('patcherView.patchButton'),
icon: const Icon(Icons.build),
onPressed: () => model.showPatchConfirmationDialog(context),
onPressed: () => model.showRemovedPatchesDialog(context),
),
),
body: CustomScrollView(

View File

@ -22,6 +22,7 @@ class PatcherViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
PatchedApplication? selectedApp;
List<Patch> selectedPatches = [];
List<String> removedPatches = [];
void navigateToAppSelector() {
_navigationService.navigateTo(Routes.appSelectorView);
@ -86,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 {
final bool armv7 = await AboutInfo.getInfo().then((info) {
final List<String> archs = info['supportedArch'];
@ -150,6 +183,7 @@ class PatcherViewModel extends BaseViewModel {
Future<void> loadLastSelectedPatches() async {
this.selectedPatches.clear();
removedPatches.clear();
final List<String> selectedPatches =
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
final List<Patch> patches =
@ -157,6 +191,10 @@ class PatcherViewModel extends BaseViewModel {
this
.selectedPatches
.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));
}
@ -165,6 +203,12 @@ class PatcherViewModel extends BaseViewModel {
.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();
}
}

View File

@ -20,6 +20,17 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
String _query = '';
final _managerAPI = locator<ManagerAPI>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!_managerAPI.isPatchesChangeEnabled() &&
_managerAPI.showPatchesChangeWarning()) {
_managerAPI.showPatchesChangeWarningDialog(context);
}
});
}
@override
Widget build(BuildContext context) {
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
@ -70,7 +81,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
child: Container(
margin: const EdgeInsets.only(top: 12, bottom: 12),
padding:
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
@ -87,7 +98,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
),
),
CustomPopupMenu(
onSelected: (value) => {model.onMenuSelection(value)},
onSelected: (value) =>
{model.onMenuSelection(value, context)},
children: {
0: I18nText(
'patchesSelectorView.loadPatchesSelection',
@ -102,7 +114,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(64.0),
preferredSize: const Size.fromHeight(66.0),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
@ -139,7 +151,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
bottom: MediaQuery.of(context).viewPadding.bottom + 8.0,
bottom: MediaQuery.viewPaddingOf(context).bottom + 8.0,
),
child: Column(
children: [
@ -152,7 +164,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
'patchesSelectorView.defaultTooltip',
),
onPressed: () {
model.selectDefaultPatches();
if (_managerAPI.isPatchesChangeEnabled()) {
model.selectDefaultPatches();
} else {
model.showPatchesChangeDialog(context);
}
},
),
const SizedBox(width: 8),
@ -163,7 +179,11 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
'patchesSelectorView.noneTooltip',
),
onPressed: () {
model.clearPatches();
if (_managerAPI.isPatchesChangeEnabled()) {
model.clearPatches();
} else {
model.showPatchesChangeDialog(context);
}
},
),
],
@ -179,13 +199,15 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
isChangeEnabled:
_managerAPI.isPatchesChangeEnabled(),
isNew: model.isPatchNew(
patch,
model.getAppInfo().packageName,
),
isSelected: model.isSelected(patch),
onChanged: (value) =>
model.selectPatch(patch, value),
model.selectPatch(patch, value, context),
);
} else {
return Container();
@ -200,8 +222,23 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
padding: const EdgeInsets.symmetric(
vertical: 10.0,
),
child: I18nText(
'patchesSelectorView.universalPatches',
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) {
@ -215,10 +252,15 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
isChangeEnabled:
_managerAPI.isPatchesChangeEnabled(),
isNew: false,
isSelected: model.isSelected(patch),
onChanged: (value) =>
model.selectPatch(patch, value),
onChanged: (value) => model.selectPatch(
patch,
value,
context,
),
);
} else {
return Container();

View File

@ -1,4 +1,6 @@
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/models/patch.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/toast.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:stacked/stacked.dart';
@ -45,31 +48,70 @@ class PatchesSelectorViewModel extends BaseViewModel {
);
}
void selectPatch(Patch patch, bool isSelected) {
if (isSelected && !selectedPatches.contains(patch)) {
selectedPatches.add(patch);
void selectPatch(Patch patch, bool isSelected, BuildContext context) {
if (_managerAPI.isPatchesChangeEnabled()) {
if (isSelected && !selectedPatches.contains(patch)) {
selectedPatches.add(patch);
} else {
selectedPatches.remove(patch);
}
notifyListeners();
} 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() {
selectedPatches.clear();
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
if (locator<PatcherViewModel>().selectedApp?.originalPackageName != null) {
selectedPatches.addAll(
patches.where(
(element) => element.excluded == false && isPatchSupported(element),
),
_patcherAPI
.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();
}
@ -117,7 +159,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
return false;
} else {
return !savedPatches
.any((p) => p.name == patch.name.toLowerCase().replaceAll(' ', '-'));
.any((p) => p.getSimpleName() == patch.getSimpleName());
}
}
@ -133,10 +175,10 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
}
void onMenuSelection(value) {
void onMenuSelection(value, BuildContext context) {
switch (value) {
case 0:
loadSelectedPatches();
loadSelectedPatches(context);
break;
}
}
@ -150,18 +192,25 @@ class PatchesSelectorViewModel extends BaseViewModel {
);
}
Future<void> loadSelectedPatches() async {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
);
if (selectedPatches.isNotEmpty) {
this.selectedPatches.clear();
this.selectedPatches.addAll(
patches.where((patch) => selectedPatches.contains(patch.name)),
);
Future<void> loadSelectedPatches(BuildContext context) async {
if (_managerAPI.isPatchesChangeEnabled()) {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
);
if (selectedPatches.isNotEmpty) {
this.selectedPatches.clear();
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 {
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
showPatchesChangeDialog(context);
}
notifyListeners();
}
}

View File

@ -129,6 +129,7 @@ class SManageSources extends BaseViewModel {
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
);
_managerAPI.setCurrentPatchesVersion('0.0.0');
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop();
},
@ -158,6 +159,7 @@ class SManageSources extends BaseViewModel {
_managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo('');
_managerAPI.setCurrentPatchesVersion('0.0.0');
_managerAPI.setCurrentIntegrationsVersion('0.0.0');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context)
..pop()

View File

@ -8,6 +8,7 @@ import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.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/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart';
final _settingViewModel = SettingsViewModel();
@ -24,37 +25,114 @@ class SUpdateTheme extends BaseViewModel {
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
await _managerAPI.setUseDynamicTheme(value);
final int currentTheme = DynamicTheme.of(context)!.themeId;
if (currentTheme.isEven) {
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
} else {
await DynamicTheme.of(context)!.setTheme(value ? 3 : 1);
}
final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2;
await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0));
notifyListeners();
}
bool getDarkThemeStatus() {
return _managerAPI.getUseDarkTheme();
int getThemeMode() {
return _managerAPI.getThemeMode();
}
Future<void> setUseDarkTheme(BuildContext context, bool value) async {
await _managerAPI.setUseDarkTheme(value);
final int currentTheme = DynamicTheme.of(context)!.themeId;
if (currentTheme < 2) {
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
} else {
await DynamicTheme.of(context)!.setTheme(value ? 3 : 2);
}
Future<void> setThemeMode(BuildContext context, int value) async {
await _managerAPI.setThemeMode(value);
final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven;
await DynamicTheme.of(context)!.setTheme(value * 2 + (isDynamicTheme ? 0 : 1));
final bool isLight = value != 2 && (value == 1 || DynamicTheme.of(context)!.theme.brightness == Brightness.light);
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarIconBrightness:
value ? Brightness.light : Brightness.dark,
isLight ? Brightness.dark : Brightness.light,
),
);
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 {
const SUpdateThemeUI({super.key});
@ -63,10 +141,10 @@ class SUpdateThemeUI extends StatelessWidget {
return SettingsSection(
title: 'settingsView.appearanceSectionTitle',
children: <Widget>[
SwitchListTile(
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.darkThemeLabel',
'settingsView.themeModeLabel',
child: const Text(
'',
style: TextStyle(
@ -75,12 +153,11 @@ class SUpdateThemeUI extends StatelessWidget {
),
),
),
subtitle: I18nText('settingsView.darkThemeHint'),
value: SUpdateTheme().getDarkThemeStatus(),
onChanged: (value) => SUpdateTheme().setUseDarkTheme(
context,
value,
trailing: CustomMaterialButton(
label: sUpdateTheme.getThemeModeName(),
onPressed: () => { sUpdateTheme.showThemeDialog(context) },
),
onTap: () => { sUpdateTheme.showThemeDialog(context) },
),
FutureBuilder<int>(
future: _settingViewModel.getSdkVersion(),

View File

@ -3,6 +3,8 @@ import 'package:cr_file_saver/file_saver.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:file_picker/file_picker.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:path_provider/path_provider.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/toast.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_theme.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:share_extend/share_extend.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
@ -19,6 +23,9 @@ import 'package:stacked_services/stacked_services.dart';
class SettingsViewModel extends BaseViewModel {
final NavigationService _navigationService = locator<NavigationService>();
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatchesSelectorViewModel _patchesSelectorViewModel =
PatchesSelectorViewModel();
final PatcherViewModel _patcherViewModel = locator<PatcherViewModel>();
final Toast _toast = locator<Toast>();
final SUpdateLanguage sUpdateLanguage = SUpdateLanguage();
@ -37,6 +44,88 @@ class SettingsViewModel extends BaseViewModel {
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() {
return _managerAPI.areUniversalPatchesEnabled();
}
@ -90,26 +179,30 @@ class SettingsViewModel extends BaseViewModel {
}
}
Future<void> importPatches() async {
try {
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (result != null && result.files.single.path != null) {
final File inFile = File(result.files.single.path!);
inFile.copySync(_managerAPI.storedPatchesFile);
inFile.delete();
if (locator<PatcherViewModel>().selectedApp != null) {
locator<PatcherViewModel>().loadLastSelectedPatches();
Future<void> importPatches(BuildContext context) async {
if (isPatchesChangeEnabled()) {
try {
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (result != null && result.files.single.path != null) {
final File inFile = File(result.files.single.path!);
inFile.copySync(_managerAPI.storedPatchesFile);
inFile.delete();
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) {
if (kDebugMode) {
print(e);
}
_toast.showBottom('settingsView.jsonSelectorErrorMessage');
} else {
_managerAPI.showPatchesChangeWarningDialog(context);
}
}

View File

@ -7,7 +7,7 @@ class AppSkeletonLoader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenWidth = MediaQuery.sizeOf(context).width;
return ListView.builder(
shrinkWrap: true,
itemCount: 7,

View File

@ -25,7 +25,7 @@ class _GradientProgressIndicatorState extends State<GradientProgressIndicator> {
),
),
height: 5,
width: MediaQuery.of(context).size.width * widget.progress!,
width: MediaQuery.sizeOf(context).width * widget.progress!,
),
);
}

View File

@ -19,6 +19,7 @@ class PatchItem extends StatefulWidget {
required this.isNew,
required this.isSelected,
required this.onChanged,
required this.isChangeEnabled,
this.child,
}) : super(key: key);
final String name;
@ -30,6 +31,7 @@ class PatchItem extends StatefulWidget {
final bool isNew;
bool isSelected;
final Function(bool) onChanged;
final bool isChangeEnabled;
final Widget? child;
final toast = locator<Toast>();
final _managerAPI = locator<ManagerAPI>();
@ -58,11 +60,13 @@ class _PatchItemState extends State<PatchItem> {
!widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.isSelected = false;
widget.toast.showBottom('patchItem.unsupportedPatchVersion');
} else {
} else if (widget.isChangeEnabled) {
widget.isSelected = !widget.isSelected;
}
});
widget.onChanged(widget.isSelected);
if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.onChanged(widget.isSelected);
}
},
child: Column(
children: <Widget>[
@ -124,11 +128,13 @@ class _PatchItemState extends State<PatchItem> {
widget.toast.showBottom(
'patchItem.unsupportedPatchVersion',
);
} else {
} else if (widget.isChangeEnabled) {
widget.isSelected = newValue!;
}
});
widget.onChanged(widget.isSelected);
if (!widget.isUnsupported || widget._managerAPI.areExperimentalPatchesEnabled()) {
widget.onChanged(widget.isSelected);
}
},
),
),
@ -186,7 +192,7 @@ class _PatchItemState extends State<PatchItem> {
),
),
),
)
),
],
),
widget.child ?? const SizedBox(),

View File

@ -8,8 +8,9 @@ class OptionsTextField extends StatelessWidget {
@override
Widget build(BuildContext context) {
final sHeight = MediaQuery.of(context).size.height;
final sWidth = MediaQuery.of(context).size.width;
final size = MediaQuery.sizeOf(context);
final sHeight = size.height;
final sWidth = size.width;
return Container(
margin: const EdgeInsets.only(top: 12, bottom: 6),
padding: EdgeInsets.zero,

View File

@ -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_sources.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_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
@ -25,6 +26,7 @@ class SAdvancedSection extends StatelessWidget {
SManageSourcesUI(),
// SManageKeystorePasswordUI(),
SAutoUpdatePatches(),
SEnablePatchesSelection(),
SExperimentalUniversalPatches(),
SExperimentalPatches(),
ListTile(

View File

@ -17,7 +17,7 @@ class _SAutoUpdatePatchesState extends State<SAutoUpdatePatches> {
return SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'homeView.patchesConsentDialogText3',
'settingsView.autoUpdatePatchesLabel',
child: const Text(
'',
style: TextStyle(

View File

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

View File

@ -43,7 +43,7 @@ class SExportSection extends StatelessWidget {
),
),
subtitle: I18nText('settingsView.importPatchesHint'),
onTap: () => _settingsViewModel.importPatches(),
onTap: () => _settingsViewModel.importPatches(context),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
@ -73,10 +73,10 @@ class SExportSection extends StatelessWidget {
),
),
subtitle: I18nText('settingsView.importKeystoreHint'),
onTap: () async{
onTap: () async {
await _settingsViewModel.importKeystore();
final sManageKeystorePassword = SManageKeystorePassword();
if(context.mounted){
if (context.mounted) {
sManageKeystorePassword.showKeystoreDialog(context);
}
},

View File

@ -75,8 +75,8 @@ class SocialMediaWidget extends StatelessWidget {
SocialMediaItem(
icon: FaIcon(FontAwesomeIcons.youtube),
title: Text('YouTube'),
subtitle: Text('youtube.com/revanced'),
url: 'https://youtube.com/revanced',
subtitle: Text('youtube.com/@revanced'),
url: 'https://youtube.com/@revanced',
),
],
),

View File

@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none'
version: 1.7.0+100700000
version: 1.10.3+101000300
environment:
sdk: '>=3.0.0 <4.0.0'