Compare commits

...

59 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
25 changed files with 586 additions and 555 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

@ -14,7 +14,7 @@ jobs:
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

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

@ -52,6 +52,8 @@ android {
buildTypes {
release {
shrinkResources false
minifyEnabled false
resValue "string", "app_name", "ReVanced Manager"
signingConfig signingConfigs.debug
ndk {
@ -83,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

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

View File

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

View File

@ -222,10 +222,8 @@ class GithubAPI {
final String downloadUrl = asset['browser_download_url'];
if (extension == '.apk') {
_managerAPI.setIntegrationsDownloadURL(downloadUrl);
} else if (extension == '.json') {
_managerAPI.setPatchesDownloadURL(downloadUrl, false);
} else {
_managerAPI.setPatchesDownloadURL(downloadUrl, true);
_managerAPI.setPatchesDownloadURL(downloadUrl);
}
return await DefaultCacheManager().getSingleFile(
downloadUrl,

View File

@ -11,6 +11,7 @@ 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';
@ -26,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 =
@ -41,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 {
@ -79,12 +81,12 @@ class ManagerAPI {
await _prefs.setString('repoUrl', url);
}
String getPatchesDownloadURL(bool bundle) {
return _prefs.getString('patchesDownloadURL-$bundle') ?? '';
String getPatchesDownloadURL() {
return _prefs.getString('patchesDownloadURL') ?? '';
}
Future<void> setPatchesDownloadURL(String value, bool bundle) async {
await _prefs.setString('patchesDownloadURL-$bundle', value);
Future<void> setPatchesDownloadURL(String value) async {
await _prefs.setString('patchesDownloadURL', value);
}
String getPatchesRepo() {
@ -111,17 +113,6 @@ class ManagerAPI {
}
bool isPatchesChangeEnabled() {
if (getPatchedApps().isNotEmpty && !isChangingToggleModified()) {
for (final apps in getPatchedApps()) {
if (getSavedPatches(apps.originalPackageName)
.indexWhere((patch) => patch.excluded) !=
-1) {
setPatchesChangeWarning(false);
setPatchesChangeEnabled(true);
break;
}
}
}
return _prefs.getBool('patchesChangeEnabled') ?? false;
}
@ -208,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() {
@ -311,28 +302,46 @@ class ManagerAPI {
}
Future<List<Patch>> getPatches() async {
try {
final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion();
final String url = getPatchesDownloadURL(false);
return await _githubAPI.getPatches(
repoName,
currentVersion,
url,
);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return [];
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(true);
final String url = getPatchesDownloadURL();
return await _githubAPI.getPatchesReleaseFile(
'.jar',
repoName,
@ -458,8 +467,7 @@ class ManagerAPI {
Future<void> setCurrentPatchesVersion(String version) async {
await _prefs.setString('patchesVersion', version);
await setPatchesDownloadURL('', false);
await setPatchesDownloadURL('', true);
await setPatchesDownloadURL('');
await downloadPatches();
}

View File

@ -25,12 +25,13 @@ 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.downloadPatches();
await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache;
@ -45,6 +46,24 @@ class PatcherAPI {
}
}
List<String> getCompatiblePackages() {
final List<String> compatiblePackages = [];
for (final Patch patch in _patches) {
for (final Package package in patch.compatiblePackages) {
if (!compatiblePackages.contains(package.name)) {
compatiblePackages.add(package.name);
}
}
}
return compatiblePackages;
}
List<Patch> getUniversalPatches() {
return _patches
.where((patch) => patch.compatiblePackages.isEmpty)
.toList();
}
Future<void> _loadPatches() async {
try {
if (_patches.isEmpty) {
@ -56,6 +75,9 @@ class PatcherAPI {
}
_patches = List.empty();
}
_compatiblePackages = getCompatiblePackages();
_universalPatches = getUniversalPatches();
}
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
@ -63,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) =>

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,

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

@ -21,11 +21,23 @@ class InstallerView extends StatelessWidget {
bottom: false,
child: Scaffold(
floatingActionButton: Visibility(
visible: !model.isPatching,
visible: !model.isPatching && !model.hasErrors,
child: FloatingActionButton.extended(
label: I18nText('installerView.installButton'),
icon: const Icon(Icons.file_download_outlined),
onPressed: () => model.installTypeDialog(context),
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,
),
),

View File

@ -182,52 +182,54 @@ class InstallerViewModel extends BaseViewModel {
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
icon: const Icon(Icons.file_download_outlined),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
content: 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,
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: 10),
value: 0,
groupValue: value,
onChanged: (selected) {
installType.value = selected!;
},
),
RadioListTile(
title: I18nText('installerView.installRootType'),
contentPadding: const EdgeInsets.symmetric(horizontal: 10),
value: 1,
groupValue: value,
onChanged: (selected) {
installType.value = selected!;
},
),
],
);
},
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(

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.platformBrightnessOf(context) != 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

@ -81,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
@ -99,7 +99,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
),
CustomPopupMenu(
onSelected: (value) =>
{model.onMenuSelection(value, context)},
{model.onMenuSelection(value, context)},
children: {
0: I18nText(
'patchesSelectorView.loadPatchesSelection',
@ -114,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,
@ -199,7 +199,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isChangeEnabled:
_managerAPI.isPatchesChangeEnabled(),
isNew: model.isPatchNew(
patch,
model.getAppInfo().packageName,
@ -221,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) {
@ -236,7 +252,8 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
isChangeEnabled: _managerAPI.isPatchesChangeEnabled(),
isChangeEnabled:
_managerAPI.isPatchesChangeEnabled(),
isNew: false,
isSelected: model.isSelected(patch),
onChanged: (value) => model.selectPatch(

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

@ -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.8.0+100800000
version: 1.10.3+101000300
environment:
sdk: '>=3.0.0 <4.0.0'