mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 01:01:56 +01:00
Merge branch 'dev' into ci/next-build
This commit is contained in:
commit
971a24cbae
59 changed files with 1237 additions and 1033 deletions
6
.github/workflows/pr-build.yml
vendored
6
.github/workflows/pr-build.yml
vendored
|
@ -53,10 +53,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: |
|
run: |
|
||||||
gh repo clone ${{ github.repository }}
|
gh repo clone "${{ github.repository }}"
|
||||||
cd revanced-manager
|
cd revanced-manager
|
||||||
gh repo set-default ${{ github.repository }}
|
gh repo set-default "${{ github.repository }}"
|
||||||
gh pr checkout ${{ inputs.pr-number }}
|
gh pr checkout "${{ inputs.pr-number }}"
|
||||||
|
|
||||||
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|
4
.github/workflows/release-build.yml
vendored
4
.github/workflows/release-build.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
- name: Set env
|
- name: Set env
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
|
@ -47,4 +47,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
|
local.properties
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
</picture>
|
</picture>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://revanced.app/">
|
<a href="https://revanced.app/">
|
||||||
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo-round.svg" />
|
||||||
|
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
||||||
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/ReVanced">
|
<a href="https://github.com/ReVanced">
|
||||||
<picture>
|
<picture>
|
||||||
|
@ -21,13 +24,22 @@
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="http://revanced.app/discord">
|
<a href="http://revanced.app/discord">
|
||||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||||
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://reddit.com/r/revancedapp">
|
<a href="https://reddit.com/r/revancedapp">
|
||||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||||
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://t.me/app_revanced">
|
<a href="https://t.me/app_revanced">
|
||||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||||
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://x.com/revancedapp">
|
<a href="https://x.com/revancedapp">
|
||||||
<picture>
|
<picture>
|
||||||
|
@ -36,7 +48,10 @@
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.youtube.com/@ReVanced">
|
<a href="https://www.youtube.com/@ReVanced">
|
||||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
<picture>
|
||||||
|
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||||
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
@ -69,7 +84,7 @@ If you encounter a bug while using the ReVanced Manager app, open an issue using
|
||||||
|
|
||||||
## 📝 How to contribute
|
## 📝 How to contribute
|
||||||
|
|
||||||
> [!NOTE]
|
> [!TIP]
|
||||||
> We recommend that you discuss your changes with
|
> We recommend that you discuss your changes with
|
||||||
> the maintainers of ReVanced Manager before contributing.
|
> the maintainers of ReVanced Manager before contributing.
|
||||||
> This will help you determine whether your change is acceptable.
|
> This will help you determine whether your change is acceptable.
|
||||||
|
|
|
@ -67,7 +67,7 @@ ReVanced Manager is an Android application that uses ReVanced Patcher to add, re
|
||||||
|
|
||||||
## 💪 Features
|
## 💪 Features
|
||||||
|
|
||||||
We provide the some of the features are:
|
Some of the features we provide are:
|
||||||
|
|
||||||
* 📱 **Portable**: ReVanced Patcher that fit in your pocket;
|
* 📱 **Portable**: ReVanced Patcher that fit in your pocket;
|
||||||
* 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface;
|
* 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface;
|
||||||
|
|
|
@ -99,7 +99,7 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation "app.revanced:revanced-patcher:19.0.0"
|
implementation "app.revanced:revanced-patcher:19.1.0"
|
||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
|
|
|
@ -32,17 +32,17 @@
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"/>
|
android:resource="@style/NormalTheme" />
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ExportSettingsActivity"
|
android:name=".ExportSettingsActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</activity>
|
</activity>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
@ -55,5 +55,22 @@
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_paths" />
|
android:resource="@xml/file_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".utils.packageInstaller.InstallerReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="APP_INSTALL_ACTION" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<receiver
|
||||||
|
android:name=".utils.packageInstaller.UninstallerReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="APP_UNINSTALL_ACTION" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
package app.revanced.manager.flutter
|
package app.revanced.manager.flutter
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import app.revanced.manager.flutter.utils.Aapt
|
import app.revanced.manager.flutter.utils.Aapt
|
||||||
import app.revanced.manager.flutter.utils.aligning.ZipAligner
|
import app.revanced.manager.flutter.utils.aligning.ZipAligner
|
||||||
|
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
|
||||||
|
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver
|
||||||
import app.revanced.manager.flutter.utils.signing.Signer
|
import app.revanced.manager.flutter.utils.signing.Signer
|
||||||
import app.revanced.manager.flutter.utils.zip.ZipFile
|
import app.revanced.manager.flutter.utils.zip.ZipFile
|
||||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
||||||
|
@ -184,12 +189,24 @@ class MainActivity : FlutterActivity() {
|
||||||
}.toString().let(result::success)
|
}.toString().let(result::success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"installApk" -> {
|
||||||
|
val apkPath = call.argument<String>("apkPath")!!
|
||||||
|
PackageInstallerManager.result = result
|
||||||
|
installApk(apkPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
"uninstallApp" -> {
|
||||||
|
val packageName = call.argument<String>("packageName")!!
|
||||||
|
uninstallApp(packageName)
|
||||||
|
PackageInstallerManager.result = result
|
||||||
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openBrowser(query: String?) {
|
private fun openBrowser(query: String?) {
|
||||||
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
|
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
|
||||||
putExtra(SearchManager.QUERY, query)
|
putExtra(SearchManager.QUERY, query)
|
||||||
}
|
}
|
||||||
|
@ -349,7 +366,7 @@ class MainActivity : FlutterActivity() {
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(0.8, "Building...", "")
|
updateProgress(0.75, "Building...", "")
|
||||||
|
|
||||||
val res = patcher.get()
|
val res = patcher.get()
|
||||||
patcher.close()
|
patcher.close()
|
||||||
|
@ -382,7 +399,7 @@ class MainActivity : FlutterActivity() {
|
||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(0.9, "Signing...", "Signing APK")
|
updateProgress(0.8, "Signing...", "Signing APK")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Signer("ReVanced", keystorePassword)
|
Signer("ReVanced", keystorePassword)
|
||||||
|
@ -392,7 +409,7 @@ class MainActivity : FlutterActivity() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress(1.0, "Patched", "Patched")
|
updateProgress(.85, "Patched", "Patched APK")
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
if (!cancel) {
|
if (!cancel) {
|
||||||
val stack = ex.stackTraceToString()
|
val stack = ex.stackTraceToString()
|
||||||
|
@ -407,4 +424,44 @@ class MainActivity : FlutterActivity() {
|
||||||
handler.post { result.success(null) }
|
handler.post { result.success(null) }
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun installApk(apkPath: String) {
|
||||||
|
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
|
||||||
|
val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||||
|
val sessionId: Int = packageInstaller.createSession(sessionParams)
|
||||||
|
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
|
||||||
|
session.use { activeSession ->
|
||||||
|
val sessionOutputStream = activeSession.openWrite(applicationContext.packageName, 0, -1)
|
||||||
|
sessionOutputStream.use { outputStream ->
|
||||||
|
val apkFile = File(apkPath)
|
||||||
|
apkFile.inputStream().use { inputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply {
|
||||||
|
action = "APP_INSTALL_ACTION"
|
||||||
|
}
|
||||||
|
val receiverPendingIntent = PendingIntent.getBroadcast(context, sessionId, receiverIntent, PackageInstallerManager.flags)
|
||||||
|
session.commit(receiverPendingIntent.intentSender)
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uninstallApp(packageName: String) {
|
||||||
|
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
|
||||||
|
val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply {
|
||||||
|
action = "APP_UNINSTALL_ACTION"
|
||||||
|
}
|
||||||
|
val receiverPendingIntent = PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags)
|
||||||
|
packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender)
|
||||||
|
}
|
||||||
|
|
||||||
|
object PackageInstallerManager {
|
||||||
|
var result: MethodChannel.Result? = null
|
||||||
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package app.revanced.manager.flutter.utils.packageInstaller
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import app.revanced.manager.flutter.MainActivity
|
||||||
|
|
||||||
|
class InstallerReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
|
||||||
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||||
|
if (confirmationIntent != null) {
|
||||||
|
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||||
|
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
|
val otherPackageName = intent.getStringExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME)
|
||||||
|
MainActivity.PackageInstallerManager.result!!.success(mapOf(
|
||||||
|
"status" to status,
|
||||||
|
"packageName" to packageName,
|
||||||
|
"message" to message,
|
||||||
|
"otherPackageName" to otherPackageName
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package app.revanced.manager.flutter.utils.packageInstaller
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import app.revanced.manager.flutter.MainActivity
|
||||||
|
|
||||||
|
class UninstallerReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
|
||||||
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||||
|
if (confirmationIntent != null) {
|
||||||
|
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
MainActivity.PackageInstallerManager.result!!.success(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -168,7 +168,9 @@
|
||||||
|
|
||||||
"installButton": "Install",
|
"installButton": "Install",
|
||||||
"installRootType": "Mount",
|
"installRootType": "Mount",
|
||||||
"installNonRootType": "Normal",
|
"installNonRootType": "Regular",
|
||||||
|
|
||||||
|
"warning": "Disable auto updates for the patched app to avoid unexpected issues.",
|
||||||
|
|
||||||
"pressBackAgain": "Press back again to cancel",
|
"pressBackAgain": "Press back again to cancel",
|
||||||
"openButton": "Open",
|
"openButton": "Open",
|
||||||
|
@ -302,16 +304,17 @@
|
||||||
"widgetTitle": "App info",
|
"widgetTitle": "App info",
|
||||||
"openButton": "Open",
|
"openButton": "Open",
|
||||||
"uninstallButton": "Uninstall",
|
"uninstallButton": "Uninstall",
|
||||||
"unpatchButton": "Unpatch",
|
"unmountButton": "Unmount",
|
||||||
"rootDialogTitle": "Error",
|
"rootDialogTitle": "Error",
|
||||||
|
|
||||||
"unpatchDialogText": "Are you sure you want to unpatch this app?",
|
"unmountDialogText": "Are you sure you want to unmount this app?",
|
||||||
|
"uninstallDialogText": "Are you sure you want to uninstall this app?",
|
||||||
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
|
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
|
||||||
|
|
||||||
"packageNameLabel": "Package name",
|
"packageNameLabel": "Package name",
|
||||||
"installTypeLabel": "Installation type",
|
"installTypeLabel": "Installation type",
|
||||||
"rootTypeLabel": "Root",
|
"mountTypeLabel": "Mount",
|
||||||
"nonRootTypeLabel": "Non-root",
|
"regularTypeLabel": "Regular",
|
||||||
"patchedDateLabel": "Patched date",
|
"patchedDateLabel": "Patched date",
|
||||||
"appliedPatchesLabel": "Applied patches",
|
"appliedPatchesLabel": "Applied patches",
|
||||||
|
|
||||||
|
@ -327,5 +330,34 @@
|
||||||
"integrationsContributors": "Integrations contributors",
|
"integrationsContributors": "Integrations contributors",
|
||||||
"cliContributors": "CLI contributors",
|
"cliContributors": "CLI contributors",
|
||||||
"managerContributors": "Manager contributors"
|
"managerContributors": "Manager contributors"
|
||||||
|
},
|
||||||
|
"installErrorDialog": {
|
||||||
|
"mount_version_mismatch": "Version mismatch",
|
||||||
|
"mount_no_root": "No root access",
|
||||||
|
"mount_missing_installation": "Installation not found",
|
||||||
|
|
||||||
|
"status_failure_blocked": "Installation blocked",
|
||||||
|
"install_failed_verification_failure": "Verification failed",
|
||||||
|
"status_failure_invalid": "Installation invalid",
|
||||||
|
"install_failed_version_downgrade": "Can't downgrade",
|
||||||
|
"status_failure_conflict": "Installation conflict",
|
||||||
|
"status_failure_storage": "Installation storage issue",
|
||||||
|
"status_failure_incompatible": "Installation incompatible",
|
||||||
|
"status_failure_timeout": "Installation timeout",
|
||||||
|
"status_unknown": "Installation failed",
|
||||||
|
|
||||||
|
"mount_version_mismatch_description": "The installation failed due to the installed app being a different version than the patched app.\n\nInstall the version of the app you are mounting and try again.",
|
||||||
|
"mount_no_root_description": "The installation failed due to root access not being granted.\n\nGrant root access to ReVanced Manager and try again.",
|
||||||
|
"mount_missing_installation_description": "The installation failed due to the unpatched app not being installed on this device in order to mount over it.\n\nInstall the unpatched app before mounting and try again.",
|
||||||
|
|
||||||
|
"status_failure_timeout_description": "The installation took too long to finish.\n\nWould you like to try again?",
|
||||||
|
"status_failure_storage_description": "The installation failed due to insufficient storage.\n\nFree up some space and try again.",
|
||||||
|
"status_failure_invalid_description": "The installation failed due to the patched app being invalid.\n\nUninstall the app and try again?",
|
||||||
|
"status_failure_incompatible_description": "The app is incompatible with this device.\n\nContact the developer of the app and ask for support.",
|
||||||
|
"status_failure_conflict_description": "The installation was prevented by an existing installation of the app.\n\nUninstall the installed app and try again?",
|
||||||
|
"status_failure_blocked_description": "The installation was blocked by {packageName}.\n\nAdjust your security settings and try again.",
|
||||||
|
"install_failed_verification_failure_description": "The installation failed due to a verification issue.\n\nAdjust your security settings and try again.",
|
||||||
|
"install_failed_version_downgrade_description": "The installation failed due to the patched app being a lower version than the installed app.\n\nUninstall the app and try again?",
|
||||||
|
"status_unknown_description": "The installation failed due to an unknown reason. Please try again."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,16 @@ In case you encounter any issues while using ReVanced Manager, please refer to t
|
||||||
- 💉 Patching fails with an error
|
- 💉 Patching fails with an error
|
||||||
|
|
||||||
Make sure ReVanced Manager is up to date by following [🔄 Updating ReVanced Manager](2_3_updating.md) and select the **Default** button when choosing patches.
|
Make sure ReVanced Manager is up to date by following [🔄 Updating ReVanced Manager](2_3_updating.md) and select the **Default** button when choosing patches.
|
||||||
|
|
||||||
- 🚫 App not installed as package conflicts with an existing package
|
- 🚫 App not installed as package conflicts with an existing package
|
||||||
|
|
||||||
An existing installation of the app you're trying to patch is conflicting with the patched app. Uninstall the existing app before installing the patched app.
|
An existing installation of the app you're trying to patch conflicts with the patched app (i.e., signature mismatch or downgrade). Uninstall the existing app before installing the patched app.
|
||||||
|
|
||||||
- ❗️ Error code `135`, `139` or `1` when patching the app
|
- ❗️ Error code `135`, `139` or `1` when patching the app
|
||||||
|
|
||||||
Your device is not supported. Refer to the [Prerequisites](0_prerequisites.md) page for supported devices.
|
You may be trying to patch a split APK[^1]. This may not work under certain circumstances. In such a case, patch a full APK.
|
||||||
|
|
||||||
|
Your device may otherwise be unsupported. Please look at the [Prerequisites](0_prerequisites.md) page for supported devices.
|
||||||
|
|
||||||
Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app.
|
Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the app.
|
||||||
|
|
||||||
|
@ -25,3 +27,5 @@ In case you encounter any issues while using ReVanced Manager, please refer to t
|
||||||
The next page will teach you how to build ReVanced Manager from source.
|
The next page will teach you how to build ReVanced Manager from source.
|
||||||
|
|
||||||
Continue: [🔨 Building from source](4_building.md)
|
Continue: [🔨 Building from source](4_building.md)
|
||||||
|
|
||||||
|
[^1]: https://developer.android.com/guide/app-bundle/app-bundle-format
|
||||||
|
|
|
@ -2,29 +2,31 @@
|
||||||
|
|
||||||
This page will guide you through building ReVanced Manager from source.
|
This page will guide you through building ReVanced Manager from source.
|
||||||
|
|
||||||
1. Setup the Flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
1\. Setup the Flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||||
|
|
||||||
2. Clone the repository
|
2\. Clone the repository
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
|
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
|
||||||
```
|
```
|
||||||
3. Get dependencies
|
3\. Get dependencies
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
flutter pub get
|
flutter pub get
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Delete conflicting outputs
|
4\. Delete conflicting outputs
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Must be run every time you sync your local repository with the remote repository.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!Note]
|
|
||||||
> Must be run every time you sync your local repository with the remote repository.
|
|
||||||
|
|
||||||
5. Build the APK
|
|
||||||
|
5\. Build the APK
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
flutter build apk
|
flutter build apk
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:revanced_manager/services/download_manager.dart';
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -24,6 +25,13 @@ Future main() async {
|
||||||
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||||
locator<GithubAPI>().initialize(repoUrl);
|
locator<GithubAPI>().initialize(repoUrl);
|
||||||
tz.initializeTimeZones();
|
tz.initializeTimeZones();
|
||||||
|
|
||||||
|
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||||
|
final rootAPI = RootAPI();
|
||||||
|
if (await rootAPI.hasRootPermissions()) {
|
||||||
|
await rootAPI.removeOrphanedFiles();
|
||||||
|
}
|
||||||
|
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
|
|
|
@ -75,9 +75,8 @@ class Option {
|
||||||
if (json['valueType'] == null) {
|
if (json['valueType'] == null) {
|
||||||
final type = json['optionClassType'];
|
final type = json['optionClassType'];
|
||||||
if (type is String) {
|
if (type is String) {
|
||||||
json['valueType'] = type
|
json['valueType'] =
|
||||||
.replaceAll('PatchOption', '')
|
type.replaceAll('PatchOption', '').replaceAll('List', 'Array');
|
||||||
.replaceAll('List', 'Array');
|
|
||||||
|
|
||||||
json['optionClassType'] = null;
|
json['optionClassType'] = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ class DownloadManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_userAgent = 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
|
_userAgent =
|
||||||
|
'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
|
||||||
}
|
}
|
||||||
|
|
||||||
Dio initDio(String url) {
|
Dio initDio(String url) {
|
||||||
|
@ -72,4 +73,3 @@ class DownloadManager {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
|
@ -42,11 +41,11 @@ class ManagerAPI {
|
||||||
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
||||||
String defaultApiUrl = 'https://api.revanced.app/';
|
String defaultApiUrl = 'https://api.revanced.app/';
|
||||||
String defaultRepoUrl = 'https://api.github.com';
|
String defaultRepoUrl = 'https://api.github.com';
|
||||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
String defaultPatcherRepo = 'ReVanced/revanced-patcher';
|
||||||
String defaultPatchesRepo = 'revanced/revanced-patches';
|
String defaultPatchesRepo = 'ReVanced/revanced-patches';
|
||||||
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
String defaultIntegrationsRepo = 'ReVanced/revanced-integrations';
|
||||||
String defaultCliRepo = 'revanced/revanced-cli';
|
String defaultCliRepo = 'ReVanced/revanced-cli';
|
||||||
String defaultManagerRepo = 'revanced/revanced-manager';
|
String defaultManagerRepo = 'ReVanced/revanced-manager';
|
||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
String? integrationsVersion = '';
|
String? integrationsVersion = '';
|
||||||
|
|
||||||
|
@ -62,7 +61,8 @@ class ManagerAPI {
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
isRooted = await _rootAPI.isRooted();
|
isRooted = await _rootAPI.isRooted();
|
||||||
isDynamicThemeAvailable = (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
isDynamicThemeAvailable =
|
||||||
|
(await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||||
storedPatchesFile =
|
storedPatchesFile =
|
||||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||||
}
|
}
|
||||||
|
@ -582,10 +582,9 @@ class ManagerAPI {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PopScope(
|
builder: (context) => WillPopScope(
|
||||||
canPop: false,
|
onWillPop: () async => false,
|
||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
content: ValueListenableBuilder(
|
content: ValueListenableBuilder(
|
||||||
valueListenable: noShow,
|
valueListenable: noShow,
|
||||||
|
@ -620,12 +619,12 @@ class ManagerAPI {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setPatchesChangeWarning(noShow.value);
|
setPatchesChangeWarning(noShow.value);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -633,7 +632,7 @@ class ManagerAPI {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reAssessSavedApps() async {
|
Future<void> rePatchedSavedApps() async {
|
||||||
final List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
|
|
||||||
// Remove apps that are not installed anymore.
|
// Remove apps that are not installed anymore.
|
||||||
|
|
|
@ -3,10 +3,11 @@ import 'dart:io';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||||
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:install_plugin/install_plugin.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
|
@ -18,7 +19,7 @@ import 'package:share_plus/share_plus.dart';
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class PatcherAPI {
|
class PatcherAPI {
|
||||||
static const patcherChannel =
|
static const patcherChannel =
|
||||||
MethodChannel('app.revanced.manager.flutter/patcher');
|
MethodChannel('app.revanced.manager.flutter/patcher');
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
final RootAPI _rootAPI = RootAPI();
|
final RootAPI _rootAPI = RootAPI();
|
||||||
late Directory _dataDir;
|
late Directory _dataDir;
|
||||||
|
@ -79,7 +80,8 @@ class PatcherAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||||
bool showUniversalPatches,) async {
|
bool showUniversalPatches,
|
||||||
|
) async {
|
||||||
final List<ApplicationWithIcon> filteredApps = [];
|
final List<ApplicationWithIcon> filteredApps = [];
|
||||||
final bool allAppsIncluded =
|
final bool allAppsIncluded =
|
||||||
_universalPatches.isNotEmpty && showUniversalPatches;
|
_universalPatches.isNotEmpty && showUniversalPatches;
|
||||||
|
@ -121,11 +123,11 @@ class PatcherAPI {
|
||||||
final List<Patch> patches = _patches
|
final List<Patch> patches = _patches
|
||||||
.where(
|
.where(
|
||||||
(patch) =>
|
(patch) =>
|
||||||
patch.compatiblePackages.isEmpty ||
|
patch.compatiblePackages.isEmpty ||
|
||||||
!patch.name.contains('settings') &&
|
!patch.name.contains('settings') &&
|
||||||
patch.compatiblePackages
|
patch.compatiblePackages
|
||||||
.any((pack) => pack.name == packageName),
|
.any((pack) => pack.name == packageName),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||||
filteredPatches[packageName] = patches
|
filteredPatches[packageName] = patches
|
||||||
|
@ -137,22 +139,27 @@ class PatcherAPI {
|
||||||
return filteredPatches[packageName];
|
return filteredPatches[packageName];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches,) async {
|
Future<List<Patch>> getAppliedPatches(
|
||||||
|
List<String> appliedPatches,
|
||||||
|
) async {
|
||||||
return _patches
|
return _patches
|
||||||
.where((patch) => appliedPatches.contains(patch.name))
|
.where((patch) => appliedPatches.contains(patch.name))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> runPatcher(String packageName,
|
Future<void> runPatcher(
|
||||||
String apkFilePath,
|
String packageName,
|
||||||
List<Patch> selectedPatches,) async {
|
String apkFilePath,
|
||||||
|
List<Patch> selectedPatches,
|
||||||
|
) async {
|
||||||
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
||||||
final Map<String, Map<String, dynamic>> options = {};
|
final Map<String, Map<String, dynamic>> options = {};
|
||||||
for (final patch in selectedPatches) {
|
for (final patch in selectedPatches) {
|
||||||
if (patch.options.isNotEmpty) {
|
if (patch.options.isNotEmpty) {
|
||||||
final Map<String, dynamic> patchOptions = {};
|
final Map<String, dynamic> patchOptions = {};
|
||||||
for (final option in patch.options) {
|
for (final option in patch.options) {
|
||||||
final patchOption = _managerAPI.getPatchOption(packageName, patch.name, option.key);
|
final patchOption =
|
||||||
|
_managerAPI.getPatchOption(packageName, patch.name, option.key);
|
||||||
if (patchOption != null) {
|
if (patchOption != null) {
|
||||||
patchOptions[patchOption.key] = patchOption.value;
|
patchOptions[patchOption.key] = patchOption.value;
|
||||||
}
|
}
|
||||||
|
@ -194,131 +201,314 @@ class PatcherAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopPatcher() async {
|
Future<void> stopPatcher() async {
|
||||||
try {
|
try {
|
||||||
await patcherChannel.invokeMethod('stopPatcher');
|
await patcherChannel.invokeMethod('stopPatcher');
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
Future<int> installPatchedFile(
|
||||||
if (outFile != null) {
|
BuildContext context,
|
||||||
try {
|
PatchedApplication patchedApp,
|
||||||
if (patchedApp.isRooted) {
|
) async {
|
||||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
if (outFile != null) {
|
||||||
if (hasRootPermissions) {
|
_managerAPI.ctx = context;
|
||||||
return _rootAPI.installApp(
|
try {
|
||||||
patchedApp.packageName,
|
if (patchedApp.isRooted) {
|
||||||
patchedApp.apkFilePath,
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
outFile!.path,
|
final packageVersion = await DeviceApps.getApp(patchedApp.packageName)
|
||||||
);
|
.then((app) => app?.versionName);
|
||||||
|
if (!hasRootPermissions) {
|
||||||
|
installErrorDialog(1);
|
||||||
|
} else if (packageVersion == null) {
|
||||||
|
installErrorDialog(1.2);
|
||||||
|
} else if (packageVersion == patchedApp.version) {
|
||||||
|
return await _rootAPI.install(
|
||||||
|
patchedApp.packageName,
|
||||||
|
patchedApp.apkFilePath,
|
||||||
|
outFile!.path,
|
||||||
|
)
|
||||||
|
? 0
|
||||||
|
: 1;
|
||||||
|
} else {
|
||||||
|
installErrorDialog(1.1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (await _rootAPI.hasRootPermissions()) {
|
||||||
|
await _rootAPI.uninstall(patchedApp.packageName);
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
return await installApk(
|
||||||
|
context,
|
||||||
|
outFile!.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> installApk(
|
||||||
|
BuildContext context,
|
||||||
|
String apkPath,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final status = await patcherChannel.invokeMethod('installApk', {
|
||||||
|
'apkPath': apkPath,
|
||||||
|
});
|
||||||
|
final int statusCode = status['status'];
|
||||||
|
final String message = status['message'];
|
||||||
|
final bool hasExtra =
|
||||||
|
message.contains('INSTALL_FAILED_VERIFICATION_FAILURE') ||
|
||||||
|
message.contains('INSTALL_FAILED_VERSION_DOWNGRADE');
|
||||||
|
if (statusCode == 0 || (statusCode == 3 && !hasExtra)) {
|
||||||
|
return statusCode;
|
||||||
} else {
|
} else {
|
||||||
final install = await InstallPlugin.installApk(outFile!.path);
|
_managerAPI.ctx = context;
|
||||||
return install['isSuccess'];
|
return await installErrorDialog(
|
||||||
|
statusCode,
|
||||||
|
status,
|
||||||
|
hasExtra,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
return false;
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void exportPatchedFile(String appName, String version) {
|
Future<int> installErrorDialog(
|
||||||
try {
|
num statusCode, [
|
||||||
if (outFile != null) {
|
status,
|
||||||
final String newName = _getFileName(appName, version);
|
bool hasExtra = false,
|
||||||
FlutterFileDialog.saveFile(
|
]) async {
|
||||||
params: SaveFileDialogParams(
|
final String statusValue = InstallStatus.byCode(
|
||||||
sourceFilePath: outFile!.path,
|
hasExtra ? double.parse('$statusCode.1') : statusCode,
|
||||||
fileName: newName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sharePatchedFile(String appName, String version) {
|
|
||||||
try {
|
|
||||||
if (outFile != null) {
|
|
||||||
final String newName = _getFileName(appName, version);
|
|
||||||
final int lastSeparator = outFile!.path.lastIndexOf('/');
|
|
||||||
final String newPath =
|
|
||||||
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
|
||||||
final File shareFile = outFile!.copySync(newPath);
|
|
||||||
Share.shareXFiles([XFile(shareFile.path)]);
|
|
||||||
}
|
|
||||||
} on Exception catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getFileName(String appName, String version) {
|
|
||||||
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
|
||||||
final String newName = '$prefix-revanced_v$version.apk';
|
|
||||||
return newName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> exportPatcherLog(String logs) async {
|
|
||||||
final Directory appCache = await getTemporaryDirectory();
|
|
||||||
final Directory logDir = Directory('${appCache.path}/logs');
|
|
||||||
logDir.createSync();
|
|
||||||
final String dateTime = DateTime.now()
|
|
||||||
.toIso8601String()
|
|
||||||
.replaceAll('-', '')
|
|
||||||
.replaceAll(':', '')
|
|
||||||
.replaceAll('T', '')
|
|
||||||
.replaceAll('.', '');
|
|
||||||
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
|
||||||
final File log = File('${logDir.path}/$fileName');
|
|
||||||
log.writeAsStringSync(logs);
|
|
||||||
FlutterFileDialog.saveFile(
|
|
||||||
params: SaveFileDialogParams(
|
|
||||||
sourceFilePath: log.path,
|
|
||||||
fileName: fileName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getSuggestedVersion(String packageName) {
|
|
||||||
final Map<String, int> versions = {};
|
|
||||||
for (final Patch patch in _patches) {
|
|
||||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
|
||||||
(pack) => pack.name == packageName,
|
|
||||||
);
|
);
|
||||||
if (package != null) {
|
bool cleanInstall = false;
|
||||||
for (final String version in package.versions) {
|
final bool isFixable = statusCode == 4 || statusCode == 5;
|
||||||
versions.update(
|
await showDialog(
|
||||||
version,
|
context: _managerAPI.ctx!,
|
||||||
(value) => versions[version]! + 1,
|
builder: (context) => AlertDialog(
|
||||||
ifAbsent: () => 1,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
title: I18nText('installErrorDialog.$statusValue'),
|
||||||
|
content: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
I18nText(
|
||||||
|
'installErrorDialog.${statusValue}_description',
|
||||||
|
translationParams: statusCode == 2
|
||||||
|
? {
|
||||||
|
'packageName': status['otherPackageName'],
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: (status == null)
|
||||||
|
? <Widget>[
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: <Widget>[
|
||||||
|
if (!isFixable)
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
|
),
|
||||||
|
if (isFixable)
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final int response = await patcherChannel.invokeMethod(
|
||||||
|
'uninstallApp',
|
||||||
|
{'packageName': status['packageName']},
|
||||||
|
);
|
||||||
|
if (response == 0 && context.mounted) {
|
||||||
|
cleanInstall = true;
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return cleanInstall ? 10 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void exportPatchedFile(String appName, String version) {
|
||||||
|
try {
|
||||||
|
if (outFile != null) {
|
||||||
|
final String newName = _getFileName(appName, version);
|
||||||
|
FlutterFileDialog.saveFile(
|
||||||
|
params: SaveFileDialogParams(
|
||||||
|
sourceFilePath: outFile!.path,
|
||||||
|
fileName: newName,
|
||||||
|
mimeTypesFilter: ['application/vnd.android.package-archive'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (versions.isNotEmpty) {
|
|
||||||
final entries = versions.entries.toList()
|
void sharePatchedFile(String appName, String version) {
|
||||||
..sort((a, b) => a.value.compareTo(b.value));
|
try {
|
||||||
versions
|
if (outFile != null) {
|
||||||
..clear()
|
final String newName = _getFileName(appName, version);
|
||||||
..addEntries(entries);
|
final int lastSeparator = outFile!.path.lastIndexOf('/');
|
||||||
versions.removeWhere((key, value) => value != versions.values.last);
|
final String newPath =
|
||||||
return (versions.keys.toList()
|
outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||||
..sort()).last;
|
final File shareFile = outFile!.copySync(newPath);
|
||||||
|
Share.shareXFiles([XFile(shareFile.path)]);
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return '';
|
|
||||||
}}
|
String _getFileName(String appName, String version) {
|
||||||
|
final String patchVersion = _managerAPI.patchesVersion!;
|
||||||
|
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||||
|
final String newName =
|
||||||
|
'$prefix-revanced_v$version-patches_$patchVersion.apk';
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> exportPatcherLog(String logs) async {
|
||||||
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
|
final Directory logDir = Directory('${appCache.path}/logs');
|
||||||
|
logDir.createSync();
|
||||||
|
final String dateTime = DateTime.now()
|
||||||
|
.toIso8601String()
|
||||||
|
.replaceAll('-', '')
|
||||||
|
.replaceAll(':', '')
|
||||||
|
.replaceAll('T', '')
|
||||||
|
.replaceAll('.', '');
|
||||||
|
final String fileName = 'revanced-manager_patcher_$dateTime.txt';
|
||||||
|
final File log = File('${logDir.path}/$fileName');
|
||||||
|
log.writeAsStringSync(logs);
|
||||||
|
FlutterFileDialog.saveFile(
|
||||||
|
params: SaveFileDialogParams(
|
||||||
|
sourceFilePath: log.path,
|
||||||
|
fileName: fileName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSuggestedVersion(String packageName) {
|
||||||
|
final Map<String, int> versions = {};
|
||||||
|
for (final Patch patch in _patches) {
|
||||||
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
|
(pack) => pack.name == packageName,
|
||||||
|
);
|
||||||
|
if (package != null) {
|
||||||
|
for (final String version in package.versions) {
|
||||||
|
versions.update(
|
||||||
|
version,
|
||||||
|
(value) => versions[version]! + 1,
|
||||||
|
ifAbsent: () => 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (versions.isNotEmpty) {
|
||||||
|
final entries = versions.entries.toList()
|
||||||
|
..sort((a, b) => a.value.compareTo(b.value));
|
||||||
|
versions
|
||||||
|
..clear()
|
||||||
|
..addEntries(entries);
|
||||||
|
versions.removeWhere((key, value) => value != versions.values.last);
|
||||||
|
return (versions.keys.toList()..sort()).last;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InstallStatus {
|
||||||
|
mountNoRoot(1),
|
||||||
|
mountVersionMismatch(1.1),
|
||||||
|
mountMissingInstallation(1.2),
|
||||||
|
|
||||||
|
statusFailureBlocked(2),
|
||||||
|
installFailedVerificationFailure(3.1),
|
||||||
|
statusFailureInvalid(4),
|
||||||
|
installFailedVersionDowngrade(4.1),
|
||||||
|
statusFailureConflict(5),
|
||||||
|
statusFailureStorage(6),
|
||||||
|
statusFailureIncompatible(7),
|
||||||
|
statusFailureTimeout(8);
|
||||||
|
|
||||||
|
const InstallStatus(this.statusCode);
|
||||||
|
final double statusCode;
|
||||||
|
|
||||||
|
static String byCode(num code) {
|
||||||
|
try {
|
||||||
|
return InstallStatus.values
|
||||||
|
.firstWhere((flag) => flag.statusCode == code)
|
||||||
|
.status;
|
||||||
|
} catch (e) {
|
||||||
|
return 'status_unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension InstallStatusExtension on InstallStatus {
|
||||||
|
String get status {
|
||||||
|
switch (this) {
|
||||||
|
case InstallStatus.mountNoRoot:
|
||||||
|
return 'mount_no_root';
|
||||||
|
case InstallStatus.mountVersionMismatch:
|
||||||
|
return 'mount_version_mismatch';
|
||||||
|
case InstallStatus.mountMissingInstallation:
|
||||||
|
return 'mount_missing_installation';
|
||||||
|
case InstallStatus.statusFailureBlocked:
|
||||||
|
return 'status_failure_blocked';
|
||||||
|
case InstallStatus.installFailedVerificationFailure:
|
||||||
|
return 'install_failed_verification_failure';
|
||||||
|
case InstallStatus.statusFailureInvalid:
|
||||||
|
return 'status_failure_invalid';
|
||||||
|
case InstallStatus.installFailedVersionDowngrade:
|
||||||
|
return 'install_failed_version_downgrade';
|
||||||
|
case InstallStatus.statusFailureConflict:
|
||||||
|
return 'status_failure_conflict';
|
||||||
|
case InstallStatus.statusFailureStorage:
|
||||||
|
return 'status_failure_storage';
|
||||||
|
case InstallStatus.statusFailureIncompatible:
|
||||||
|
return 'status_failure_incompatible';
|
||||||
|
case InstallStatus.statusFailureTimeout:
|
||||||
|
return 'status_failure_timeout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:root/root.dart';
|
import 'package:root/root.dart';
|
||||||
|
|
||||||
class RootAPI {
|
class RootAPI {
|
||||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||||
final String _revancedOldDirPath = '/data/local/tmp/revanced-manager';
|
|
||||||
final String _revancedDirPath = '/data/adb/revanced';
|
|
||||||
final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
|
final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
|
||||||
|
|
||||||
|
final String _revancedDirPath = '/data/adb/revanced';
|
||||||
final String _serviceDDirPath = '/data/adb/service.d';
|
final String _serviceDDirPath = '/data/adb/service.d';
|
||||||
|
|
||||||
Future<bool> isRooted() async {
|
Future<bool> isRooted() async {
|
||||||
|
@ -43,21 +43,22 @@ class RootAPI {
|
||||||
String filePath,
|
String filePath,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
|
final StringBuffer commands = StringBuffer();
|
||||||
if (permissions.isNotEmpty) {
|
if (permissions.isNotEmpty) {
|
||||||
await Root.exec(
|
commands.writeln('chmod $permissions $filePath');
|
||||||
cmd: 'chmod $permissions "$filePath"',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ownerGroup.isNotEmpty) {
|
if (ownerGroup.isNotEmpty) {
|
||||||
await Root.exec(
|
commands.writeln('chown $ownerGroup $filePath');
|
||||||
cmd: 'chown $ownerGroup "$filePath"',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seLinux.isNotEmpty) {
|
if (seLinux.isNotEmpty) {
|
||||||
await Root.exec(
|
commands.writeln('chcon $seLinux $filePath');
|
||||||
cmd: 'chcon $seLinux "$filePath"',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Root.exec(
|
||||||
|
cmd: commands.toString(),
|
||||||
|
);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
|
@ -75,18 +76,7 @@ class RootAPI {
|
||||||
Future<List<String>> getInstalledApps() async {
|
Future<List<String>> getInstalledApps() async {
|
||||||
final List<String> apps = List.empty(growable: true);
|
final List<String> apps = List.empty(growable: true);
|
||||||
try {
|
try {
|
||||||
String? res = await Root.exec(
|
final String? res = await Root.exec(cmd: 'ls $_revancedDirPath');
|
||||||
cmd: 'ls "$_revancedDirPath"',
|
|
||||||
);
|
|
||||||
if (res != null) {
|
|
||||||
final List<String> list = res.split('\n');
|
|
||||||
list.removeWhere((pack) => pack.isEmpty);
|
|
||||||
apps.addAll(list.map((pack) => pack.trim()).toList());
|
|
||||||
}
|
|
||||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
|
||||||
res = await Root.exec(
|
|
||||||
cmd: 'ls "$_revancedOldDirPath"',
|
|
||||||
);
|
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
final List<String> list = res.split('\n');
|
final List<String> list = res.split('\n');
|
||||||
list.removeWhere((pack) => pack.isEmpty);
|
list.removeWhere((pack) => pack.isEmpty);
|
||||||
|
@ -100,49 +90,45 @@ class RootAPI {
|
||||||
return apps;
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteApp(String packageName, String originalFilePath) async {
|
Future<void> uninstall(String packageName) async {
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'am force-stop "$packageName"',
|
cmd: '''
|
||||||
);
|
grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done
|
||||||
await Root.exec(
|
rm -rf $_revancedDirPath/$packageName $_serviceDDirPath/$packageName.sh
|
||||||
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
''',
|
||||||
);
|
|
||||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'rm -rf "$_revancedOldDirPath/$packageName"',
|
|
||||||
);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'rm -rf "$_revancedDirPath/$packageName"',
|
|
||||||
);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'rm -rf "$_serviceDDirPath/$packageName.sh"',
|
|
||||||
);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'rm -rf "$_postFsDataDirPath/$packageName.sh"',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> installApp(
|
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||||
|
Future<void> removeOrphanedFiles() async {
|
||||||
|
await Root.exec(
|
||||||
|
cmd: '''
|
||||||
|
find $_revancedDirPath -type f -name original.apk -delete
|
||||||
|
for file in "$_serviceDDirPath"/*; do
|
||||||
|
filename=\$(basename "\$file")
|
||||||
|
if [ -f "$_postFsDataDirPath/\$filename" ]; then
|
||||||
|
rm "$_postFsDataDirPath/\$filename"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> install(
|
||||||
String packageName,
|
String packageName,
|
||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
String patchedFilePath,
|
String patchedFilePath,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await deleteApp(packageName, originalFilePath);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
|
|
||||||
);
|
|
||||||
await setPermissions(
|
await setPermissions(
|
||||||
'0755',
|
'0755',
|
||||||
'shell:shell',
|
'shell:shell',
|
||||||
'',
|
'',
|
||||||
'$_revancedDirPath/$packageName',
|
'$_revancedDirPath/$packageName',
|
||||||
);
|
);
|
||||||
await saveOriginalFilePath(packageName, originalFilePath);
|
await installPatchedApk(packageName, patchedFilePath);
|
||||||
await installServiceDScript(packageName);
|
await installServiceDScript(packageName);
|
||||||
await installPostFsDataScript(packageName);
|
await runMountScript(packageName);
|
||||||
await installApk(packageName, patchedFilePath);
|
|
||||||
await mountApk(packageName, originalFilePath);
|
|
||||||
return true;
|
return true;
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
|
@ -154,38 +140,48 @@ class RootAPI {
|
||||||
|
|
||||||
Future<void> installServiceDScript(String packageName) async {
|
Future<void> installServiceDScript(String packageName) async {
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'mkdir -p "$_serviceDDirPath"',
|
cmd: 'mkdir -p $_serviceDDirPath',
|
||||||
);
|
);
|
||||||
final String content = '#!/system/bin/sh\n'
|
final String mountScript = '''
|
||||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
#!/system/bin/sh
|
||||||
'base_path=$_revancedDirPath/$packageName/base.apk\n'
|
# Mount using Magisk mirror, if available.
|
||||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
MAGISKTMP="$( magisk --path )" || MAGISKTMP=/sbin
|
||||||
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
|
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
||||||
|
if [ ! -f ${'$'}MIRROR ]; then
|
||||||
|
MIRROR=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done
|
||||||
|
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
||||||
|
|
||||||
|
# Unmount any existing installation to prevent multiple unnecessary mounts.
|
||||||
|
grep $packageName /proc/mounts | while read -r line; do echo \$line | cut -d " " -f 2 | sed "s/apk.*/apk/" | xargs -r umount -l; done
|
||||||
|
|
||||||
|
base_path=$_revancedDirPath/$packageName/base.apk
|
||||||
|
stock_path=\$(pm path $packageName | grep base | sed "s/package://g" )
|
||||||
|
|
||||||
|
chcon u:object_r:apk_data_file:s0 \$base_path
|
||||||
|
mount -o bind \$MIRROR\$base_path \$stock_path
|
||||||
|
|
||||||
|
# Kill the app to force it to restart the mounted APK in case it is already running
|
||||||
|
am force-stop $packageName
|
||||||
|
'''
|
||||||
|
.trimMultilineString();
|
||||||
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
cmd: 'echo \'$mountScript\' > "$scriptFilePath"',
|
||||||
);
|
);
|
||||||
await setPermissions('0744', '', '', scriptFilePath);
|
await setPermissions('0744', '', '', scriptFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installPostFsDataScript(String packageName) async {
|
Future<void> installPatchedApk(
|
||||||
await Root.exec(
|
String packageName, String patchedFilePath,) async {
|
||||||
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';
|
|
||||||
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
|
||||||
);
|
|
||||||
await setPermissions('0744', '', '', scriptFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> installApk(String packageName, String patchedFilePath) async {
|
|
||||||
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
cmd: '''
|
||||||
|
mkdir -p $_revancedDirPath/$packageName
|
||||||
|
cp "$patchedFilePath" $newPatchedFilePath
|
||||||
|
''',
|
||||||
);
|
);
|
||||||
await setPermissions(
|
await setPermissions(
|
||||||
'0644',
|
'0644',
|
||||||
|
@ -195,50 +191,10 @@ class RootAPI {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> mountApk(String packageName, String originalFilePath) async {
|
Future<void> runMountScript(
|
||||||
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'am force-stop "$packageName"',
|
|
||||||
);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'su -mm -c "umount -l $originalFilePath"',
|
|
||||||
);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'su -mm -c "mount -o bind $newPatchedFilePath $originalFilePath"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> isMounted(String packageName) async {
|
|
||||||
final String? res = await Root.exec(
|
|
||||||
cmd: 'cat /proc/mounts | grep $packageName',
|
|
||||||
);
|
|
||||||
return res != null && res.isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> saveOriginalFilePath(
|
|
||||||
String packageName,
|
String packageName,
|
||||||
String originalFilePath,
|
|
||||||
) async {
|
) async {
|
||||||
final String originalRootPath =
|
await Root.exec(cmd: '.$_serviceDDirPath/$packageName.sh');
|
||||||
'$_revancedDirPath/$packageName/original.apk';
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
|
|
||||||
);
|
|
||||||
await setPermissions(
|
|
||||||
'0755',
|
|
||||||
'shell:shell',
|
|
||||||
'',
|
|
||||||
'$_revancedDirPath/$packageName',
|
|
||||||
);
|
|
||||||
await Root.exec(
|
|
||||||
cmd: 'cp "$originalFilePath" "$originalRootPath"',
|
|
||||||
);
|
|
||||||
await setPermissions(
|
|
||||||
'0644',
|
|
||||||
'shell:shell',
|
|
||||||
'u:object_r:apk_data_file:s0',
|
|
||||||
originalFilePath,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> fileExists(String path) async {
|
Future<bool> fileExists(String path) async {
|
||||||
|
@ -255,3 +211,10 @@ class RootAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove leading spaces manually until
|
||||||
|
// https://github.com/dart-lang/language/issues/559 is closed
|
||||||
|
extension StringExtension on String {
|
||||||
|
String trimMultilineString() =>
|
||||||
|
split('\n').map((line) => line.trim()).join('\n').trim();
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ class DynamicThemeBuilder extends StatefulWidget {
|
||||||
State<DynamicThemeBuilder> createState() => _DynamicThemeBuilderState();
|
State<DynamicThemeBuilder> createState() => _DynamicThemeBuilderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsBindingObserver {
|
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
Brightness brightness = PlatformDispatcher.instance.platformBrightness;
|
Brightness brightness = PlatformDispatcher.instance.platformBrightness;
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
|
@ -43,8 +44,9 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
|
||||||
if (_managerAPI.getThemeMode() < 2) {
|
if (_managerAPI.getThemeMode() < 2) {
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
SystemUiOverlayStyle(
|
SystemUiOverlayStyle(
|
||||||
systemNavigationBarIconBrightness:
|
systemNavigationBarIconBrightness: brightness == Brightness.light
|
||||||
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
? Brightness.dark
|
||||||
|
: Brightness.light,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,24 +85,31 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
|
||||||
return DynamicTheme(
|
return DynamicTheme(
|
||||||
themeCollection: ThemeCollection(
|
themeCollection: ThemeCollection(
|
||||||
themes: {
|
themes: {
|
||||||
0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
|
0: brightness == Brightness.light
|
||||||
1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme,
|
? lightCustomTheme
|
||||||
|
: darkCustomTheme,
|
||||||
|
1: brightness == Brightness.light
|
||||||
|
? lightDynamicTheme
|
||||||
|
: darkDynamicTheme,
|
||||||
2: lightCustomTheme,
|
2: lightCustomTheme,
|
||||||
3: lightDynamicTheme,
|
3: lightDynamicTheme,
|
||||||
4: darkCustomTheme,
|
4: darkCustomTheme,
|
||||||
5: darkDynamicTheme,
|
5: darkDynamicTheme,
|
||||||
},
|
},
|
||||||
fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
|
fallbackTheme: PlatformDispatcher.instance.platformBrightness ==
|
||||||
|
Brightness.light
|
||||||
|
? lightCustomTheme
|
||||||
|
: darkCustomTheme,
|
||||||
),
|
),
|
||||||
builder: (context, theme) => MaterialApp(
|
builder: (context, theme) => MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
navigatorKey: StackedService.navigatorKey,
|
navigatorKey: StackedService.navigatorKey,
|
||||||
onGenerateRoute: StackedRouter().onGenerateRoute,
|
onGenerateRoute: StackedRouter().onGenerateRoute,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
home: widget.home,
|
home: widget.home,
|
||||||
localizationsDelegates: widget.localizationsDelegates,
|
localizationsDelegates: widget.localizationsDelegates,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,8 +40,8 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||||
),
|
),
|
||||||
titleTextStyle: TextStyle(
|
titleTextStyle: TextStyle(
|
||||||
fontSize: 22.0,
|
fontSize: 22.0,
|
||||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
|
@ -94,9 +94,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
...model
|
...model.getFilteredApps(_query).map(
|
||||||
.getFilteredApps(_query)
|
|
||||||
.map(
|
|
||||||
(app) => InstalledAppItem(
|
(app) => InstalledAppItem(
|
||||||
name: app.appName,
|
name: app.appName,
|
||||||
pkgName: app.packageName,
|
pkgName: app.packageName,
|
||||||
|
@ -117,11 +115,8 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||||
packageName: app.packageName,
|
packageName: app.packageName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
,
|
...model.getFilteredAppsNames(_query).map(
|
||||||
...model
|
|
||||||
.getFilteredAppsNames(_query)
|
|
||||||
.map(
|
|
||||||
(app) => NotInstalledAppItem(
|
(app) => NotInstalledAppItem(
|
||||||
name: app,
|
name: app,
|
||||||
patchesCount: model.patchesCount(app),
|
patchesCount: model.patchesCount(app),
|
||||||
|
@ -135,8 +130,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||||
packageName: app,
|
packageName: app,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
,
|
|
||||||
const SizedBox(height: 70.0),
|
const SizedBox(height: 70.0),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -13,7 +13,6 @@ import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
|
@ -105,7 +104,8 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||||
]) async {
|
]) async {
|
||||||
final String suggestedVersion =
|
final String suggestedVersion =
|
||||||
getSuggestedVersion(application.packageName);
|
getSuggestedVersion(application.packageName);
|
||||||
if (application.versionName != suggestedVersion && suggestedVersion.isNotEmpty) {
|
if (application.versionName != suggestedVersion &&
|
||||||
|
suggestedVersion.isNotEmpty) {
|
||||||
_managerAPI.suggestedAppVersionSelected = false;
|
_managerAPI.suggestedAppVersionSelected = false;
|
||||||
if (_managerAPI.isRequireSuggestedAppVersionEnabled() &&
|
if (_managerAPI.isRequireSuggestedAppVersionEnabled() &&
|
||||||
context.mounted) {
|
context.mounted) {
|
||||||
|
@ -168,7 +168,6 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'appSelectorView.requireSuggestedAppVersionDialogText',
|
'appSelectorView.requireSuggestedAppVersionDialogText',
|
||||||
|
@ -185,9 +184,9 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -232,12 +231,12 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(innerContext);
|
Navigator.pop(innerContext);
|
||||||
await selectAppFromStorage(context);
|
await selectAppFromStorage(context);
|
||||||
},
|
},
|
||||||
label: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.sd_card),
|
const Icon(Icons.sd_card),
|
||||||
|
@ -247,12 +246,11 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(innerContext);
|
Navigator.pop(innerContext);
|
||||||
},
|
},
|
||||||
label: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:install_plugin/install_plugin.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/app/app.router.dart';
|
import 'package:revanced_manager/app/app.router.dart';
|
||||||
|
@ -21,7 +20,6 @@ import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
|
@ -40,11 +38,16 @@ class HomeViewModel extends BaseViewModel {
|
||||||
File? downloadedApk;
|
File? downloadedApk;
|
||||||
|
|
||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
_managerAPI.rePatchedSavedApps().then((_) => _getPatchedApps());
|
||||||
|
|
||||||
if (!_managerAPI.getPatchesConsent()) {
|
if (!_managerAPI.getPatchesConsent()) {
|
||||||
await showPatchesConsent(context);
|
await showPatchesConsent(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||||
|
|
||||||
await _patcherAPI.initialize();
|
await _patcherAPI.initialize();
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
const InitializationSettings(
|
const InitializationSettings(
|
||||||
android: AndroidInitializationSettings('ic_notification'),
|
android: AndroidInitializationSettings('ic_notification'),
|
||||||
|
@ -54,7 +57,7 @@ class HomeViewModel extends BaseViewModel {
|
||||||
_toast.showBottom('homeView.installingMessage');
|
_toast.showBottom('homeView.installingMessage');
|
||||||
final File? managerApk = await _managerAPI.downloadManager();
|
final File? managerApk = await _managerAPI.downloadManager();
|
||||||
if (managerApk != null) {
|
if (managerApk != null) {
|
||||||
await InstallPlugin.installApk(managerApk.path);
|
await _patcherAPI.installApk(context, managerApk.path);
|
||||||
} else {
|
} else {
|
||||||
_toast.showBottom('homeView.errorDownloadMessage');
|
_toast.showBottom('homeView.errorDownloadMessage');
|
||||||
}
|
}
|
||||||
|
@ -65,24 +68,24 @@ class HomeViewModel extends BaseViewModel {
|
||||||
.resolvePlatformSpecificImplementation<
|
.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.requestNotificationsPermission();
|
?.requestNotificationsPermission();
|
||||||
final bool isConnected = await Connectivity().checkConnectivity() !=
|
|
||||||
ConnectivityResult.none;
|
final bool isConnected =
|
||||||
|
await Connectivity().checkConnectivity() != ConnectivityResult.none;
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
_toast.showBottom('homeView.noConnection');
|
_toast.showBottom('homeView.noConnection');
|
||||||
}
|
}
|
||||||
|
|
||||||
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
|
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
|
||||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||||
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
|
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
|
||||||
_toast.showBottom('homeView.installingMessage');
|
_toast.showBottom('homeView.installingMessage');
|
||||||
final File? managerApk = await _managerAPI.downloadManager();
|
final File? managerApk = await _managerAPI.downloadManager();
|
||||||
if (managerApk != null) {
|
if (managerApk != null) {
|
||||||
await InstallPlugin.installApk(managerApk.path);
|
await _patcherAPI.installApk(context, managerApk.path);
|
||||||
} else {
|
} else {
|
||||||
_toast.showBottom('homeView.errorDownloadMessage');
|
_toast.showBottom('homeView.errorDownloadMessage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToAppInfo(PatchedApplication app) {
|
void navigateToAppInfo(PatchedApplication app) {
|
||||||
|
@ -223,21 +226,20 @@ class HomeViewModel extends BaseViewModel {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _managerAPI.setPatchesConsent(false);
|
await _managerAPI.setPatchesConsent(false);
|
||||||
SystemNavigator.pop();
|
SystemNavigator.pop();
|
||||||
},
|
},
|
||||||
label: I18nText('quitButton'),
|
child: I18nText('quitButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _managerAPI.setPatchesConsent(true);
|
await _managerAPI.setPatchesConsent(true);
|
||||||
await _managerAPI.setPatchesAutoUpdate(autoUpdate.value);
|
await _managerAPI.setPatchesAutoUpdate(autoUpdate.value);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
label: I18nText('okButton'),
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -270,6 +272,7 @@ class HomeViewModel extends BaseViewModel {
|
||||||
valueListenable: downloaded,
|
valueListenable: downloaded,
|
||||||
builder: (context, value, child) {
|
builder: (context, value, child) {
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
contentPadding: const EdgeInsets.all(16.0),
|
contentPadding: const EdgeInsets.all(16.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
!value
|
!value
|
||||||
|
@ -324,12 +327,12 @@ class HomeViewModel extends BaseViewModel {
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: CustomMaterialButton(
|
child: FilledButton(
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_revancedAPI.disposeManagerUpdateProgress();
|
_revancedAPI.disposeManagerUpdateProgress();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -355,24 +358,24 @@ class HomeViewModel extends BaseViewModel {
|
||||||
children: [
|
children: [
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: CustomMaterialButton(
|
child: TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0),
|
const SizedBox(width: 8.0),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: CustomMaterialButton(
|
child: FilledButton(
|
||||||
label: I18nText('updateButton'),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await InstallPlugin.installApk(
|
await _patcherAPI.installApk(
|
||||||
|
context,
|
||||||
downloadedApk!.path,
|
downloadedApk!.path,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: I18nText('updateButton'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -415,7 +418,7 @@ class HomeViewModel extends BaseViewModel {
|
||||||
// UILocalNotificationDateInterpretation.absoluteTime,
|
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||||
// );
|
// );
|
||||||
_toast.showBottom('homeView.installingMessage');
|
_toast.showBottom('homeView.installingMessage');
|
||||||
await InstallPlugin.installApk(managerApk.path);
|
await _patcherAPI.installApk(context, managerApk.path);
|
||||||
} else {
|
} else {
|
||||||
_toast.showBottom('homeView.errorDownloadMessage');
|
_toast.showBottom('homeView.errorDownloadMessage');
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,7 @@ class InstallerView extends StatelessWidget {
|
||||||
return ViewModelBuilder<InstallerViewModel>.reactive(
|
return ViewModelBuilder<InstallerViewModel>.reactive(
|
||||||
onViewModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => InstallerViewModel(),
|
viewModelBuilder: () => InstallerViewModel(),
|
||||||
builder: (context, model, child) => PopScope(
|
builder: (context, model, child) => WillPopScope(
|
||||||
onPopInvoked: (bool didPop) => model.onPopInvoked(context, didPop),
|
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
bottom: model.isPatching,
|
bottom: model.isPatching,
|
||||||
|
@ -84,7 +83,7 @@ class InstallerView extends StatelessWidget {
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
onBackButtonPressed: () => model.onBackButtonInvoked(context),
|
onBackButtonPressed: () => model.onWillPop(context),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size(double.infinity, 1.0),
|
preferredSize: const Size(double.infinity, 1.0),
|
||||||
child: GradientProgressIndicator(progress: model.progress),
|
child: GradientProgressIndicator(progress: model.progress),
|
||||||
|
@ -112,6 +111,7 @@ class InstallerView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onWillPop: () => model.onWillPop(context),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:screenshot_callback/screenshot_callback.dart';
|
import 'package:screenshot_callback/screenshot_callback.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
@ -104,7 +104,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
isPatching = true;
|
isPatching = true;
|
||||||
isInstalled = false;
|
isInstalled = false;
|
||||||
hasErrors = false;
|
hasErrors = false;
|
||||||
} else if (value == 1.0) {
|
} else if (value == .85) {
|
||||||
isPatching = false;
|
isPatching = false;
|
||||||
hasErrors = false;
|
hasErrors = false;
|
||||||
await _managerAPI.savePatches(
|
await _managerAPI.savePatches(
|
||||||
|
@ -115,6 +115,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
} else if (value == -100.0) {
|
} else if (value == -100.0) {
|
||||||
isPatching = false;
|
isPatching = false;
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
|
progress = 0.0;
|
||||||
}
|
}
|
||||||
if (header.isNotEmpty) {
|
if (header.isNotEmpty) {
|
||||||
headerLogs = header;
|
headerLogs = header;
|
||||||
|
@ -127,10 +128,10 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
if (logs[logs.length - 1] == '\n') {
|
if (logs[logs.length - 1] == '\n') {
|
||||||
logs = logs.substring(0, logs.length - 1);
|
logs = logs.substring(0, logs.length - 1);
|
||||||
}
|
}
|
||||||
Future.delayed(const Duration(milliseconds: 500)).then((value) {
|
Future.delayed(const Duration(milliseconds: 100)).then((value) {
|
||||||
scrollController.animateTo(
|
scrollController.animateTo(
|
||||||
scrollController.position.maxScrollExtent,
|
scrollController.position.maxScrollExtent,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 100),
|
||||||
curve: Curves.fastOutSlowIn,
|
curve: Curves.fastOutSlowIn,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -179,40 +180,105 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _trimLogs(List<String> logLines, String keyword, String? newString) {
|
||||||
|
final lineCount = logLines.where((line) => line.endsWith(keyword)).length;
|
||||||
|
final index = logLines.indexWhere((line) => line.endsWith(keyword));
|
||||||
|
if (newString != null && lineCount > 0) {
|
||||||
|
logLines.insert(
|
||||||
|
index,
|
||||||
|
newString.replaceAll('{lineCount}', lineCount.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
logLines.removeWhere((lines) => lines.endsWith(keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic _getPatchOptionValue(String patchName, Option option) {
|
||||||
|
final Option? savedOption =
|
||||||
|
_managerAPI.getPatchOption(_app.packageName, patchName, option.key);
|
||||||
|
if (savedOption != null) {
|
||||||
|
return savedOption.value;
|
||||||
|
} else {
|
||||||
|
return option.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatPatches(List<Patch> patches, String noneString) {
|
||||||
|
return patches.isEmpty
|
||||||
|
? noneString
|
||||||
|
: patches.map((p) {
|
||||||
|
final optionsChanged = p.options
|
||||||
|
.where((o) => _getPatchOptionValue(p.name, o) != o.value)
|
||||||
|
.toList();
|
||||||
|
return p.name +
|
||||||
|
(optionsChanged.isEmpty
|
||||||
|
? ''
|
||||||
|
: ' [${optionsChanged.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]');
|
||||||
|
}).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSuggestedVersion(String packageName) {
|
||||||
|
String suggestedVersion = _patcherAPI.getSuggestedVersion(_app.packageName);
|
||||||
|
if (suggestedVersion.isEmpty) {
|
||||||
|
suggestedVersion = 'Any';
|
||||||
|
} else {
|
||||||
|
suggestedVersion = 'v$suggestedVersion';
|
||||||
|
}
|
||||||
|
return suggestedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> copyLogs() async {
|
Future<void> copyLogs() async {
|
||||||
final info = await AboutInfo.getInfo();
|
final info = await AboutInfo.getInfo();
|
||||||
dynamic getValue(String patchName, Option option) {
|
|
||||||
final Option? savedOption =
|
|
||||||
_managerAPI.getPatchOption(_app.packageName, patchName, option.key);
|
|
||||||
if (savedOption != null) {
|
|
||||||
return savedOption.value;
|
|
||||||
} else {
|
|
||||||
return option.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Trim out extra lines
|
||||||
|
final logsTrimmed = logs.split('\n');
|
||||||
|
_trimLogs(logsTrimmed, 'succeeded', 'Applied {lineCount} patches');
|
||||||
|
_trimLogs(logsTrimmed, '.dex', 'Compiled {lineCount} dex files');
|
||||||
|
|
||||||
|
// Get patches added / removed
|
||||||
|
final defaultPatches = _patcherAPI
|
||||||
|
.getFilteredPatches(_app.packageName)
|
||||||
|
.where((p) => !p.excluded)
|
||||||
|
.toList();
|
||||||
|
final appliedPatchesNames = _patches.map((p) => p.name).toList();
|
||||||
|
|
||||||
|
final patchesAdded = _patches.where((p) => p.excluded).toList();
|
||||||
|
final patchesRemoved = defaultPatches
|
||||||
|
.where((p) => !appliedPatchesNames.contains(p.name))
|
||||||
|
.map((p) => p.name)
|
||||||
|
.toList();
|
||||||
|
final patchesOptionsChanged = defaultPatches
|
||||||
|
.where(
|
||||||
|
(p) =>
|
||||||
|
appliedPatchesNames.contains(p.name) &&
|
||||||
|
p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Add Info
|
||||||
final formattedLogs = [
|
final formattedLogs = [
|
||||||
'- Device Info',
|
'- Device Info',
|
||||||
'ReVanced Manager: ${info['version']}',
|
'ReVanced Manager: ${info['version']}',
|
||||||
'Build: ${info['flavor']}',
|
|
||||||
'Model: ${info['model']}',
|
'Model: ${info['model']}',
|
||||||
'Android version: ${info['androidVersion']}',
|
'Android version: ${info['androidVersion']}',
|
||||||
'Supported architectures: ${info['supportedArch'].join(", ")}',
|
'Supported architectures: ${info['supportedArch'].join(", ")}',
|
||||||
'Root permissions: ${isRooted ? 'Yes' : 'No'}',
|
'Root permissions: ${isRooted ? 'Yes' : 'No'}', //
|
||||||
|
|
||||||
'\n- Patch Info',
|
'\n- Patch Info',
|
||||||
'App: ${_app.packageName} v${_app.version}',
|
'App: ${_app.packageName} v${_app.version} (Suggested: ${_getSuggestedVersion(_app.packageName)})',
|
||||||
'Patches version: ${_managerAPI.patchesVersion}',
|
'Patches version: ${_managerAPI.patchesVersion}',
|
||||||
'Patches: ${_patches.map((p) => p.name + (p.options.isEmpty ? '' : ' [${p.options.map((o) => '${o.title}: ${getValue(p.name, o)}').join(", ")}]')).toList().join(", ")}',
|
'Patches added: ${_formatPatches(patchesAdded, 'Default')}',
|
||||||
|
'Patches removed: ${patchesRemoved.isEmpty ? 'None' : patchesRemoved.join(', ')}',
|
||||||
|
'Default patch options changed: ${_formatPatches(patchesOptionsChanged, 'None')}', //
|
||||||
|
|
||||||
'\n- Settings',
|
'\n- Settings',
|
||||||
'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}',
|
'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}',
|
||||||
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
|
'Version compatibility check: ${_managerAPI.isVersionCompatibilityCheckEnabled()}',
|
||||||
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
'Show universal patches: ${_managerAPI.areUniversalPatchesEnabled()}',
|
||||||
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
'Patches source: ${_managerAPI.getPatchesRepo()}',
|
||||||
'Integration source: ${_managerAPI.getIntegrationsRepo()}',
|
'Integration source: ${_managerAPI.getIntegrationsRepo()}', //
|
||||||
|
|
||||||
'\n- Logs',
|
'\n- Logs',
|
||||||
logs,
|
logsTrimmed.join('\n'),
|
||||||
];
|
];
|
||||||
|
|
||||||
Clipboard.setData(ClipboardData(text: formattedLogs.join('\n')));
|
Clipboard.setData(ClipboardData(text: formattedLogs.join('\n')));
|
||||||
|
@ -226,26 +292,24 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'warning',
|
'warning',
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
icon: const Icon(Icons.warning),
|
icon: const Icon(Icons.warning),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: I18nText('installerView.screenshotDetected'),
|
child: I18nText('installerView.screenshotDetected'),
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
copyLogs();
|
copyLogs();
|
||||||
showPopupScreenshotWarning = true;
|
showPopupScreenshotWarning = true;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -258,11 +322,10 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (context) => AlertDialog(
|
builder: (innerContext) => AlertDialog(
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'installerView.installType',
|
'installerView.installType',
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
icon: const Icon(Icons.file_download_outlined),
|
icon: const Icon(Icons.file_download_outlined),
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
|
@ -310,31 +373,68 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
installType.value = selected!;
|
installType.value = selected!;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: I18nText(
|
||||||
|
'installerView.warning',
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
isFilled: false,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(innerContext).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('installerView.installButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(innerContext).pop();
|
||||||
installResult(context, installType.value == 1);
|
installResult(context, installType.value == 1);
|
||||||
},
|
},
|
||||||
|
child: I18nText('installerView.installButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
installResult(context, false);
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (innerContext) => AlertDialog(
|
||||||
|
title: I18nText(
|
||||||
|
'warning',
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.all(16),
|
||||||
|
content: I18nText('installerView.warning'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(innerContext).pop();
|
||||||
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(innerContext).pop();
|
||||||
|
installResult(context, false);
|
||||||
|
},
|
||||||
|
child: I18nText('installerView.installButton'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,15 +455,16 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
||||||
try {
|
try {
|
||||||
_app.isRooted = installAsRoot;
|
_app.isRooted = installAsRoot;
|
||||||
update(
|
if (headerLogs != 'Installing...') {
|
||||||
1.0,
|
update(
|
||||||
'Installing...',
|
.85,
|
||||||
_app.isRooted
|
'Installing...',
|
||||||
? 'Installing patched file using root method'
|
_app.isRooted ? 'Mounting patched app' : 'Installing patched app',
|
||||||
: 'Installing patched file using nonroot method',
|
);
|
||||||
);
|
}
|
||||||
isInstalled = await _patcherAPI.installPatchedFile(_app);
|
final int response = await _patcherAPI.installPatchedFile(context, _app);
|
||||||
if (isInstalled) {
|
if (response == 0) {
|
||||||
|
isInstalled = true;
|
||||||
_app.isFromStorage = false;
|
_app.isFromStorage = false;
|
||||||
_app.patchDate = DateTime.now();
|
_app.patchDate = DateTime.now();
|
||||||
_app.appliedPatches = _patches.map((p) => p.name).toList();
|
_app.appliedPatches = _patches.map((p) => p.name).toList();
|
||||||
|
@ -378,10 +479,23 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
await _managerAPI.savePatchedApp(_app);
|
await _managerAPI.savePatchedApp(_app);
|
||||||
|
await locator<HomeViewModel>().initialize(context);
|
||||||
|
|
||||||
update(1.0, 'Installed!', 'Installed!');
|
update(1.0, 'Installed', 'Installed');
|
||||||
|
} else if (response == 3) {
|
||||||
|
update(
|
||||||
|
.85,
|
||||||
|
'Installation canceled',
|
||||||
|
'Installation canceled',
|
||||||
|
);
|
||||||
|
} else if (response == 10) {
|
||||||
|
installResult(context, installAsRoot);
|
||||||
} else {
|
} else {
|
||||||
// TODO(aabed): Show error message.
|
update(
|
||||||
|
.85,
|
||||||
|
'Installation failed',
|
||||||
|
'Installation failed',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
|
@ -428,38 +542,25 @@ class InstallerViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canPop() {
|
Future<bool> onWillPop(BuildContext context) async {
|
||||||
return !isPatching;
|
if (isPatching) {
|
||||||
}
|
|
||||||
|
|
||||||
void onBackButtonInvoked(BuildContext context) {
|
|
||||||
if (canPop()) {
|
|
||||||
onPopInvoked(context, true);
|
|
||||||
} else {
|
|
||||||
onPopInvoked(context, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> onPopInvoked(BuildContext context, bool didPop) async {
|
|
||||||
if (didPop) {
|
|
||||||
if (!cancel) {
|
if (!cancel) {
|
||||||
cleanPatcher();
|
cancel = true;
|
||||||
|
_toast.showBottom('installerView.pressBackAgain');
|
||||||
|
} else if (!isCanceled) {
|
||||||
|
await stopPatcher();
|
||||||
} else {
|
} else {
|
||||||
_patcherAPI.cleanPatcher();
|
_toast.showBottom('installerView.noExit');
|
||||||
}
|
|
||||||
screenshotCallback.dispose();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else {
|
|
||||||
if (isPatching) {
|
|
||||||
if (!cancel) {
|
|
||||||
cancel = true;
|
|
||||||
_toast.showBottom('installerView.pressBackAgain');
|
|
||||||
} else if (!isCanceled) {
|
|
||||||
await stopPatcher();
|
|
||||||
} else {
|
|
||||||
_toast.showBottom('installerView.noExit');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!cancel) {
|
||||||
|
cleanPatcher();
|
||||||
|
} else {
|
||||||
|
_patcherAPI.cleanPatcher();
|
||||||
|
}
|
||||||
|
screenshotCallback.dispose();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,13 @@ class NavigationView extends StatelessWidget {
|
||||||
return ViewModelBuilder<NavigationViewModel>.reactive(
|
return ViewModelBuilder<NavigationViewModel>.reactive(
|
||||||
onViewModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => locator<NavigationViewModel>(),
|
viewModelBuilder: () => locator<NavigationViewModel>(),
|
||||||
builder: (context, model, child) => PopScope(
|
builder: (context, model, child) => WillPopScope(
|
||||||
canPop: model.currentIndex == 0,
|
onWillPop: () async {
|
||||||
onPopInvoked: (bool didPop) {
|
if (model.currentIndex == 0) {
|
||||||
if (!didPop) {
|
return true;
|
||||||
|
} else {
|
||||||
model.setIndex(0);
|
model.setIndex(0);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_options_fields.dart';
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_options_fields.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class PatchOptionsView extends StatelessWidget {
|
class PatchOptionsView extends StatelessWidget {
|
||||||
|
@ -82,8 +81,7 @@ class PatchOptionsView extends StatelessWidget {
|
||||||
model.modifyOptions(value, option);
|
model.modifyOptions(value, option);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else if (option.valueType ==
|
else if (option.valueType == 'StringArray' ||
|
||||||
'StringArray' ||
|
|
||||||
option.valueType == 'IntArray' ||
|
option.valueType == 'IntArray' ||
|
||||||
option.valueType == 'LongArray')
|
option.valueType == 'LongArray')
|
||||||
IntStringLongListPatchOption(
|
IntStringLongListPatchOption(
|
||||||
|
@ -104,11 +102,11 @@ class PatchOptionsView extends StatelessWidget {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.showAddOptionDialog(context);
|
model.showAddOptionDialog(context);
|
||||||
},
|
},
|
||||||
label: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.add),
|
const Icon(Icons.add),
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class PatchOptionsViewModel extends BaseViewModel {
|
class PatchOptionsViewModel extends BaseViewModel {
|
||||||
|
@ -32,13 +31,11 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||||
if (savedOptions.isNotEmpty) {
|
if (savedOptions.isNotEmpty) {
|
||||||
visibleOptions = [
|
visibleOptions = [
|
||||||
...savedOptions,
|
...savedOptions,
|
||||||
...options
|
...options.where(
|
||||||
.where(
|
(option) =>
|
||||||
(option) =>
|
option.required &&
|
||||||
option.required &&
|
!savedOptions.any((sOption) => sOption.key == option.key),
|
||||||
!savedOptions.any((sOption) => sOption.key == option.key),
|
),
|
||||||
)
|
|
||||||
,
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
visibleOptions = [
|
visibleOptions = [
|
||||||
|
@ -136,7 +133,6 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: Column(
|
title: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -154,11 +150,11 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
contentPadding: const EdgeInsets.all(8),
|
contentPadding: const EdgeInsets.all(8),
|
||||||
|
@ -227,14 +223,9 @@ Future<void> showRequiredOptionNullDialog(
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('notice'),
|
title: I18nText('notice'),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText(
|
|
||||||
'patchOptionsView.deselectPatch',
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (managerAPI.isPatchesChangeEnabled()) {
|
if (managerAPI.isPatchesChangeEnabled()) {
|
||||||
locator<PatcherViewModel>()
|
locator<PatcherViewModel>()
|
||||||
|
@ -256,12 +247,13 @@ Future<void> showRequiredOptionNullDialog(
|
||||||
PatchesSelectorViewModel().showPatchesChangeDialog(context);
|
PatchesSelectorViewModel().showPatchesChangeDialog(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: I18nText('patchOptionsView.deselectPatch'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
|
|
|
@ -22,7 +22,7 @@ class PatcherView extends StatelessWidget {
|
||||||
child: FloatingActionButton.extended(
|
child: FloatingActionButton.extended(
|
||||||
label: I18nText('patcherView.patchButton'),
|
label: I18nText('patcherView.patchButton'),
|
||||||
icon: const Icon(Icons.build),
|
icon: const Icon(Icons.build),
|
||||||
onPressed: () async{
|
onPressed: () async {
|
||||||
if (model.checkRequiredPatchOption(context)) {
|
if (model.checkRequiredPatchOption(context)) {
|
||||||
final bool proceed = model.showRemovedPatchesDialog(context);
|
final bool proceed = model.showRemovedPatchesDialog(context);
|
||||||
if (proceed && context.mounted) {
|
if (proceed && context.mounted) {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
@ -24,6 +23,7 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
final NavigationService _navigationService = locator<NavigationService>();
|
final NavigationService _navigationService = locator<NavigationService>();
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
|
Set<String> savedPatchNames = {};
|
||||||
PatchedApplication? selectedApp;
|
PatchedApplication? selectedApp;
|
||||||
BuildContext? ctx;
|
BuildContext? ctx;
|
||||||
List<Patch> selectedPatches = [];
|
List<Patch> selectedPatches = [];
|
||||||
|
@ -55,25 +55,23 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('notice'),
|
title: I18nText('notice'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'patcherView.removedPatchesWarningDialogText',
|
'patcherView.removedPatchesWarningDialogText',
|
||||||
translationParams: {'patches': removedPatches.join('\n')},
|
translationParams: {'patches': removedPatches.join('\n')},
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
showArmv7WarningDialog(context);
|
showArmv7WarningDialog(context);
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -97,22 +95,20 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
context: context ?? ctx,
|
context: context ?? ctx,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('notice'),
|
title: I18nText('notice'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('patcherView.requiredOptionDialogText'),
|
content: I18nText('patcherView.requiredOptionDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.pop(context),
|
Navigator.pop(context),
|
||||||
navigateToPatchesSelector(),
|
navigateToPatchesSelector(),
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -130,20 +126,18 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('patcherView.armv7WarningDialogText'),
|
content: I18nText('patcherView.armv7WarningDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
isFilled: false,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
navigateToInstaller();
|
navigateToInstaller();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -174,7 +168,8 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
|
|
||||||
if (suggestedVersion.isNotEmpty) {
|
if (suggestedVersion.isNotEmpty) {
|
||||||
await openDefaultBrowser(
|
await openDefaultBrowser(
|
||||||
'${selectedApp!.packageName} apk version v$suggestedVersion');
|
'${selectedApp!.packageName} apk version v$suggestedVersion',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await openDefaultBrowser('${selectedApp!.packageName} apk');
|
await openDefaultBrowser('${selectedApp!.packageName} apk');
|
||||||
}
|
}
|
||||||
|
@ -216,6 +211,20 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPatchNew(Patch patch) {
|
||||||
|
if (savedPatchNames.isEmpty) {
|
||||||
|
savedPatchNames = _managerAPI
|
||||||
|
.getSavedPatches(selectedApp!.packageName)
|
||||||
|
.map((p) => p.name)
|
||||||
|
.toSet();
|
||||||
|
}
|
||||||
|
if (savedPatchNames.isEmpty) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return !savedPatchNames.contains(patch.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadLastSelectedPatches() async {
|
Future<void> loadLastSelectedPatches() async {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
removedPatches.clear();
|
removedPatches.clear();
|
||||||
|
@ -238,13 +247,24 @@ class PatcherViewModel extends BaseViewModel {
|
||||||
.selectedPatches
|
.selectedPatches
|
||||||
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
.removeWhere((patch) => patch.compatiblePackages.isEmpty);
|
||||||
}
|
}
|
||||||
|
this.selectedPatches.addAll(
|
||||||
|
patches.where(
|
||||||
|
(patch) =>
|
||||||
|
isPatchNew(patch) &&
|
||||||
|
!patch.excluded &&
|
||||||
|
!this.selectedPatches.contains(patch),
|
||||||
|
),
|
||||||
|
);
|
||||||
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
|
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
|
||||||
for (final patch in usedPatches) {
|
for (final patch in usedPatches) {
|
||||||
if (!patches.any((p) => p.name == patch.name)) {
|
if (!patches.any((p) => p.name == patch.name)) {
|
||||||
removedPatches.add('• ${patch.name}');
|
removedPatches.add('• ${patch.name}');
|
||||||
for (final option in patch.options) {
|
for (final option in patch.options) {
|
||||||
_managerAPI.clearPatchOption(
|
_managerAPI.clearPatchOption(
|
||||||
selectedApp!.packageName, patch.name, option.key);
|
selectedApp!.packageName,
|
||||||
|
patch.name,
|
||||||
|
option.key,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,11 +180,16 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (model.getQueriedPatches(_query).any((patch) => model.isPatchNew(patch)))
|
if (model
|
||||||
|
.getQueriedPatches(_query)
|
||||||
|
.any((patch) => model.isPatchNew(patch)))
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
model.getPatchCategory(context, 'patchesSelectorView.newPatches'),
|
model.getPatchCategory(
|
||||||
|
context,
|
||||||
|
'patchesSelectorView.newPatches',
|
||||||
|
),
|
||||||
...model.getQueriedPatches(_query).map((patch) {
|
...model.getQueriedPatches(_query).map((patch) {
|
||||||
if (model.isPatchNew(patch)) {
|
if (model.isPatchNew(patch)) {
|
||||||
return model.getPatchItem(context, patch);
|
return model.getPatchItem(context, patch);
|
||||||
|
@ -192,26 +197,40 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty))
|
if (model.getQueriedPatches(_query).any(
|
||||||
model.getPatchCategory(context, 'patchesSelectorView.patches'),
|
(patch) =>
|
||||||
|
!model.isPatchNew(patch) &&
|
||||||
|
patch.compatiblePackages.isNotEmpty,
|
||||||
|
))
|
||||||
|
model.getPatchCategory(
|
||||||
|
context,
|
||||||
|
'patchesSelectorView.patches',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...model.getQueriedPatches(_query).map(
|
...model.getQueriedPatches(_query).map(
|
||||||
(patch) {
|
(patch) {
|
||||||
if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) {
|
if (patch.compatiblePackages.isNotEmpty &&
|
||||||
|
!model.isPatchNew(patch)) {
|
||||||
return model.getPatchItem(context, patch);
|
return model.getPatchItem(context, patch);
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty))
|
if (model
|
||||||
|
.getQueriedPatches(_query)
|
||||||
|
.any((patch) => patch.compatiblePackages.isEmpty))
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
model.getPatchCategory(context, 'patchesSelectorView.universalPatches'),
|
model.getPatchCategory(
|
||||||
|
context,
|
||||||
|
'patchesSelectorView.universalPatches',
|
||||||
|
),
|
||||||
...model.getQueriedPatches(_query).map((patch) {
|
...model.getQueriedPatches(_query).map((patch) {
|
||||||
if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) {
|
if (patch.compatiblePackages.isEmpty &&
|
||||||
|
!model.isPatchNew(patch)) {
|
||||||
return model.getPatchItem(context, patch);
|
return model.getPatchItem(context, patch);
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
|
|
|
@ -10,7 +10,6 @@ import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
@ -25,6 +24,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
locator<PatcherViewModel>().selectedPatches;
|
locator<PatcherViewModel>().selectedPatches;
|
||||||
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
|
PatchedApplication? selectedApp = locator<PatcherViewModel>().selectedApp;
|
||||||
String? patchesVersion = '';
|
String? patchesVersion = '';
|
||||||
|
|
||||||
bool isDefaultPatchesRepo() {
|
bool isDefaultPatchesRepo() {
|
||||||
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
|
return _managerAPI.getPatchesRepo() == 'revanced/revanced-patches';
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
});
|
});
|
||||||
currentSelection.clear();
|
currentSelection.clear();
|
||||||
currentSelection.addAll(selectedPatches);
|
currentSelection.addAll(selectedPatches);
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +93,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('notice'),
|
title: I18nText('notice'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'patchesSelectorView.setRequiredOption',
|
'patchesSelectorView.setRequiredOption',
|
||||||
translationParams: {
|
translationParams: {
|
||||||
|
@ -100,11 +100,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -128,7 +128,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'patchItem.patchesChangeWarningDialogText',
|
'patchItem.patchesChangeWarningDialogText',
|
||||||
|
@ -141,18 +140,17 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('patchItem.patchesChangeWarningDialogButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
..pop()
|
..pop()
|
||||||
..pop();
|
..pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('patchItem.patchesChangeWarningDialogButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -186,10 +184,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
void selectPatches() {
|
void selectPatches() {
|
||||||
locator<PatcherViewModel>().selectedPatches = selectedPatches;
|
locator<PatcherViewModel>().selectedPatches = selectedPatches;
|
||||||
saveSelectedPatches();
|
saveSelectedPatches();
|
||||||
if (_managerAPI.ctx != null) {
|
|
||||||
Navigator.pop(_managerAPI.ctx!);
|
|
||||||
_managerAPI.ctx = null;
|
|
||||||
}
|
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
locator<PatcherViewModel>().notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,14 +275,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPatchNew(Patch patch) {
|
bool isPatchNew(Patch patch) {
|
||||||
final List<Patch> savedPatches =
|
return locator<PatcherViewModel>().isPatchNew(patch);
|
||||||
_managerAPI.getSavedPatches(selectedApp!.packageName);
|
|
||||||
if (savedPatches.isEmpty) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return !savedPatches
|
|
||||||
.any((p) => p.getSimpleName() == patch.getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getSupportedVersions(Patch patch) {
|
List<String> getSupportedVersions(Patch patch) {
|
||||||
|
|
|
@ -5,9 +5,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class SManageApiUrl extends BaseViewModel {
|
class SManageApiUrl extends BaseViewModel {
|
||||||
|
@ -33,34 +31,35 @@ class SManageApiUrl extends BaseViewModel {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomTextField(
|
TextField(
|
||||||
leadingIcon: Icon(
|
controller: _apiUrlController,
|
||||||
Icons.api_outlined,
|
autocorrect: false,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
inputController: _apiUrlController,
|
|
||||||
label: I18nText('settingsView.selectApiURL'),
|
|
||||||
hint: apiUrl,
|
|
||||||
onChanged: (value) => notifyListeners(),
|
onChanged: (value) => notifyListeners(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.api_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText('settingsView.selectApiURL').toString(),
|
||||||
|
hintText: apiUrl,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_apiUrlController.clear();
|
_apiUrlController.clear();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
String apiUrl = _apiUrlController.text;
|
String apiUrl = _apiUrlController.text;
|
||||||
if (!apiUrl.startsWith('https')) {
|
if (!apiUrl.startsWith('https')) {
|
||||||
|
@ -70,6 +69,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -81,16 +81,13 @@ class SManageApiUrl extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('settingsView.sourcesResetDialogTitle'),
|
title: I18nText('settingsView.sourcesResetDialogTitle'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('settingsView.apiURLResetDialogText'),
|
content: I18nText('settingsView.apiURLResetDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setApiUrl('');
|
_managerAPI.setApiUrl('');
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
|
@ -98,6 +95,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||||
..pop()
|
..pop()
|
||||||
..pop();
|
..pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,9 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class SManageKeystorePassword extends BaseViewModel {
|
class SManageKeystorePassword extends BaseViewModel {
|
||||||
|
@ -33,35 +31,39 @@ class SManageKeystorePassword extends BaseViewModel {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomTextField(
|
TextField(
|
||||||
inputController: _keystorePasswordController,
|
controller: _keystorePasswordController,
|
||||||
label: I18nText('settingsView.selectKeystorePassword'),
|
autocorrect: false,
|
||||||
hint: '',
|
obscureText: true,
|
||||||
onChanged: (value) => notifyListeners(),
|
onChanged: (value) => notifyListeners(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText(
|
||||||
|
'settingsView.selectKeystorePassword',
|
||||||
|
).toString(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_keystorePasswordController.clear();
|
_keystorePasswordController.clear();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final String passwd = _keystorePasswordController.text;
|
final String passwd = _keystorePasswordController.text;
|
||||||
_managerAPI.setKeystorePassword(passwd);
|
_managerAPI.setKeystorePassword(passwd);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,9 +5,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class SManageSources extends BaseViewModel {
|
class SManageSources extends BaseViewModel {
|
||||||
|
@ -43,63 +41,100 @@ class SManageSources extends BaseViewModel {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomTextField(
|
/*
|
||||||
leadingIcon: const Icon(
|
API for accessing the specified repositories
|
||||||
Icons.extension_outlined,
|
If default is used, will use the ReVanced API
|
||||||
color: Colors.transparent,
|
*/
|
||||||
),
|
TextField(
|
||||||
inputController: _hostSourceController,
|
controller: _hostSourceController,
|
||||||
label: I18nText('settingsView.hostRepositoryLabel'),
|
autocorrect: false,
|
||||||
hint: hostRepository,
|
|
||||||
onChanged: (value) => notifyListeners(),
|
onChanged: (value) => notifyListeners(),
|
||||||
),
|
decoration: InputDecoration(
|
||||||
const SizedBox(height: 20),
|
icon: Icon(
|
||||||
CustomTextField(
|
Icons.rocket_launch_outlined,
|
||||||
leadingIcon: Icon(
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
Icons.extension_outlined,
|
),
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText(
|
||||||
|
'settingsView.hostRepositoryLabel',
|
||||||
|
).toString(),
|
||||||
|
hintText: hostRepository,
|
||||||
),
|
),
|
||||||
inputController: _orgPatSourceController,
|
|
||||||
label: I18nText('settingsView.orgPatchesLabel'),
|
|
||||||
hint: patchesRepo.split('/')[0],
|
|
||||||
onChanged: (value) => notifyListeners(),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CustomTextField(
|
// Patches owner's name
|
||||||
leadingIcon: const Icon(
|
TextField(
|
||||||
Icons.extension_outlined,
|
controller: _orgPatSourceController,
|
||||||
color: Colors.transparent,
|
autocorrect: false,
|
||||||
),
|
|
||||||
inputController: _patSourceController,
|
|
||||||
label: I18nText('settingsView.sourcesPatchesLabel'),
|
|
||||||
hint: patchesRepo.split('/')[1],
|
|
||||||
onChanged: (value) => notifyListeners(),
|
onChanged: (value) => notifyListeners(),
|
||||||
),
|
decoration: InputDecoration(
|
||||||
const SizedBox(height: 20),
|
icon: Icon(
|
||||||
CustomTextField(
|
Icons.extension_outlined,
|
||||||
leadingIcon: Icon(
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
Icons.merge_outlined,
|
),
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText(
|
||||||
|
'settingsView.orgPatchesLabel',
|
||||||
|
).toString(),
|
||||||
|
hintText: patchesRepo.split('/')[0],
|
||||||
),
|
),
|
||||||
inputController: _orgIntSourceController,
|
|
||||||
label: I18nText('settingsView.orgIntegrationsLabel'),
|
|
||||||
hint: integrationsRepo.split('/')[0],
|
|
||||||
onChanged: (value) => notifyListeners(),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CustomTextField(
|
// Patches repository's name
|
||||||
leadingIcon: const Icon(
|
TextField(
|
||||||
Icons.merge_outlined,
|
controller: _patSourceController,
|
||||||
color: Colors.transparent,
|
autocorrect: false,
|
||||||
),
|
|
||||||
inputController: _intSourceController,
|
|
||||||
label: I18nText('settingsView.sourcesIntegrationsLabel'),
|
|
||||||
hint: integrationsRepo.split('/')[1],
|
|
||||||
onChanged: (value) => notifyListeners(),
|
onChanged: (value) => notifyListeners(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.extension_outlined,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText(
|
||||||
|
'settingsView.sourcesPatchesLabel',
|
||||||
|
).toString(),
|
||||||
|
hintText: patchesRepo.split('/')[1],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// Integrations owner's name
|
||||||
|
TextField(
|
||||||
|
controller: _orgIntSourceController,
|
||||||
|
autocorrect: false,
|
||||||
|
onChanged: (value) => notifyListeners(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.merge_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText(
|
||||||
|
'settingsView.orgIntegrationsLabel',
|
||||||
|
).toString(),
|
||||||
|
hintText: integrationsRepo.split('/')[0],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
// Integrations repository's name
|
||||||
|
TextField(
|
||||||
|
controller: _intSourceController,
|
||||||
|
autocorrect: false,
|
||||||
|
onChanged: (value) => notifyListeners(),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.merge_outlined,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
labelText: I18nText(
|
||||||
|
'settingsView.sourcesIntegrationsLabel',
|
||||||
|
).toString(),
|
||||||
|
hintText: integrationsRepo.split('/')[1],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
I18nText('settingsView.sourcesUpdateNote'),
|
I18nText('settingsView.sourcesUpdateNote'),
|
||||||
|
@ -107,9 +142,7 @@ class SManageSources extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_orgPatSourceController.clear();
|
_orgPatSourceController.clear();
|
||||||
_patSourceController.clear();
|
_patSourceController.clear();
|
||||||
|
@ -117,9 +150,9 @@ class SManageSources extends BaseViewModel {
|
||||||
_intSourceController.clear();
|
_intSourceController.clear();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
|
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
|
||||||
_managerAPI.setPatchesRepo(
|
_managerAPI.setPatchesRepo(
|
||||||
|
@ -133,6 +166,7 @@ class SManageSources extends BaseViewModel {
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -144,16 +178,13 @@ class SManageSources extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('settingsView.sourcesResetDialogTitle'),
|
title: I18nText('settingsView.sourcesResetDialogTitle'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('settingsView.sourcesResetDialogText'),
|
content: I18nText('settingsView.sourcesResetDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setRepoUrl('');
|
_managerAPI.setRepoUrl('');
|
||||||
_managerAPI.setPatchesRepo('');
|
_managerAPI.setPatchesRepo('');
|
||||||
|
@ -165,6 +196,7 @@ class SManageSources extends BaseViewModel {
|
||||||
..pop()
|
..pop()
|
||||||
..pop();
|
..pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -51,7 +51,6 @@ class SUpdateLanguage extends BaseViewModel {
|
||||||
context: parentContext,
|
context: parentContext,
|
||||||
builder: (context) => SimpleDialog(
|
builder: (context) => SimpleDialog(
|
||||||
title: I18nText('settingsView.languageLabel'),
|
title: I18nText('settingsView.languageLabel'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 500,
|
height: 500,
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
|
|
||||||
class SUpdateThemeUI extends StatefulWidget {
|
class SUpdateThemeUI extends StatefulWidget {
|
||||||
const SUpdateThemeUI({super.key});
|
const SUpdateThemeUI({super.key});
|
||||||
|
@ -36,9 +35,9 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: CustomMaterialButton(
|
trailing: FilledButton(
|
||||||
label: getThemeModeName(),
|
|
||||||
onPressed: () => {showThemeDialog(context)},
|
onPressed: () => {showThemeDialog(context)},
|
||||||
|
child: getThemeModeName(),
|
||||||
),
|
),
|
||||||
onTap: () => {showThemeDialog(context)},
|
onTap: () => {showThemeDialog(context)},
|
||||||
),
|
),
|
||||||
|
@ -122,7 +121,6 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||||
title: I18nText('settingsView.themeModeLabel'),
|
title: I18nText('settingsView.themeModeLabel'),
|
||||||
icon: const Icon(Icons.palette),
|
icon: const Icon(Icons.palette),
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: ValueListenableBuilder(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: newTheme,
|
valueListenable: newTheme,
|
||||||
|
@ -164,19 +162,18 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('cancelButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('cancelButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setThemeMode(context, newTheme.value);
|
setThemeMode(context, newTheme.value);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -12,7 +12,6 @@ import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
@ -52,7 +51,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'settingsView.enablePatchesSelectionWarningText',
|
'settingsView.enablePatchesSelectionWarningText',
|
||||||
|
@ -65,20 +63,19 @@ class SettingsViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setChangingToggleModified(true);
|
_managerAPI.setChangingToggleModified(true);
|
||||||
_managerAPI.setPatchesChangeEnabled(true);
|
_managerAPI.setPatchesChangeEnabled(true);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -87,7 +84,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'settingsView.disablePatchesSelectionWarningText',
|
'settingsView.disablePatchesSelectionWarningText',
|
||||||
|
@ -100,21 +96,20 @@ class SettingsViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setChangingToggleModified(true);
|
_managerAPI.setChangingToggleModified(true);
|
||||||
_patchesSelectorViewModel.selectDefaultPatches();
|
_patchesSelectorViewModel.selectDefaultPatches();
|
||||||
_managerAPI.setPatchesChangeEnabled(false);
|
_managerAPI.setPatchesChangeEnabled(false);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -145,12 +140,13 @@ class SettingsViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void>? showRequireSuggestedAppVersionDialog(
|
Future<void>? showRequireSuggestedAppVersionDialog(
|
||||||
BuildContext context, bool value,) {
|
BuildContext context,
|
||||||
|
bool value,
|
||||||
|
) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'settingsView.requireSuggestedAppVersionDialogText',
|
'settingsView.requireSuggestedAppVersionDialogText',
|
||||||
|
@ -163,19 +159,18 @@ class SettingsViewModel extends BaseViewModel {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.enableRequireSuggestedAppVersionStatus(false);
|
_managerAPI.enableRequireSuggestedAppVersionStatus(false);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -184,7 +184,7 @@ class AppInfoView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
I18nText(
|
I18nText(
|
||||||
'appInfoView.unpatchButton',
|
'appInfoView.unmountButton',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -236,8 +236,8 @@ class AppInfoView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: app.isRooted
|
subtitle: app.isRooted
|
||||||
? I18nText('appInfoView.rootTypeLabel')
|
? I18nText('appInfoView.mountTypeLabel')
|
||||||
: I18nText('appInfoView.nonRootTypeLabel'),
|
: I18nText('appInfoView.regularTypeLabel'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
|
@ -12,7 +12,6 @@ import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class AppInfoViewModel extends BaseViewModel {
|
class AppInfoViewModel extends BaseViewModel {
|
||||||
|
@ -26,18 +25,17 @@ class AppInfoViewModel extends BaseViewModel {
|
||||||
PatchedApplication app,
|
PatchedApplication app,
|
||||||
bool onlyUnpatch,
|
bool onlyUnpatch,
|
||||||
) async {
|
) async {
|
||||||
bool isUninstalled = true;
|
var isUninstalled = onlyUnpatch;
|
||||||
if (app.isRooted) {
|
|
||||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
if (!onlyUnpatch) {
|
||||||
if (hasRootPermissions) {
|
// TODO(Someone): Wait for the app to uninstall successfully.
|
||||||
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
|
||||||
if (!onlyUnpatch) {
|
|
||||||
await DeviceApps.uninstallApp(app.packageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isUninstalled = await DeviceApps.uninstallApp(app.packageName);
|
isUninstalled = await DeviceApps.uninstallApp(app.packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUninstalled && app.isRooted && await _rootAPI.hasRootPermissions()) {
|
||||||
|
await _rootAPI.uninstall(app.packageName);
|
||||||
|
}
|
||||||
|
|
||||||
if (isUninstalled) {
|
if (isUninstalled) {
|
||||||
await _managerAPI.deletePatchedApp(app);
|
await _managerAPI.deletePatchedApp(app);
|
||||||
locator<HomeViewModel>().initialize(context);
|
locator<HomeViewModel>().initialize(context);
|
||||||
|
@ -67,12 +65,11 @@ class AppInfoViewModel extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('appInfoView.rootDialogTitle'),
|
title: I18nText('appInfoView.rootDialogTitle'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('appInfoView.rootDialogText'),
|
content: I18nText('appInfoView.rootDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -83,32 +80,53 @@ class AppInfoViewModel extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'appInfoView.unpatchButton',
|
'appInfoView.unmountButton',
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'appInfoView.unpatchDialogText',
|
'appInfoView.unmountDialogText',
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
uninstallApp(context, app, onlyUnpatch);
|
uninstallApp(context, app, true);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
uninstallApp(context, app, onlyUnpatch);
|
return showDialog(
|
||||||
Navigator.of(context).pop();
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: I18nText(
|
||||||
|
'appInfoView.uninstallButton',
|
||||||
|
),
|
||||||
|
content: I18nText(
|
||||||
|
'appInfoView.uninstallDialogText',
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
uninstallApp(context, app, false);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,14 +149,13 @@ class AppInfoViewModel extends BaseViewModel {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('appInfoView.appliedPatchesLabel'),
|
title: I18nText('appInfoView.appliedPatchesLabel'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Text(getAppliedPatchesString(app.appliedPatches)),
|
child: Text(getAppliedPatchesString(app.appliedPatches)),
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
|
|
||||||
class LatestCommitCard extends StatefulWidget {
|
class LatestCommitCard extends StatefulWidget {
|
||||||
const LatestCommitCard({
|
const LatestCommitCard({
|
||||||
|
@ -58,14 +57,14 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||||
initialData: false,
|
initialData: false,
|
||||||
builder: (context, snapshot) => Opacity(
|
builder: (context, snapshot) => Opacity(
|
||||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||||
child: CustomMaterialButton(
|
child: FilledButton(
|
||||||
label: I18nText('updateButton'),
|
|
||||||
onPressed: snapshot.hasData && snapshot.data!
|
onPressed: snapshot.hasData && snapshot.data!
|
||||||
? () => widget.model.showUpdateConfirmationDialog(
|
? () => widget.model.showUpdateConfirmationDialog(
|
||||||
widget.parentContext,
|
widget.parentContext,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
: () => {},
|
: () => {},
|
||||||
|
child: I18nText('updateButton'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -113,14 +112,14 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||||
initialData: false,
|
initialData: false,
|
||||||
builder: (context, snapshot) => Opacity(
|
builder: (context, snapshot) => Opacity(
|
||||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||||
child: CustomMaterialButton(
|
child: FilledButton(
|
||||||
label: I18nText('updateButton'),
|
|
||||||
onPressed: snapshot.hasData && snapshot.data!
|
onPressed: snapshot.hasData && snapshot.data!
|
||||||
? () => widget.model.showUpdateConfirmationDialog(
|
? () => widget.model.showUpdateConfirmationDialog(
|
||||||
widget.parentContext,
|
widget.parentContext,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
: () => {},
|
: () => {},
|
||||||
|
child: I18nText('updateButton'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
|
|
||||||
class UpdateConfirmationDialog extends StatelessWidget {
|
class UpdateConfirmationDialog extends StatelessWidget {
|
||||||
const UpdateConfirmationDialog({super.key, required this.isPatches});
|
const UpdateConfirmationDialog({super.key, required this.isPatches});
|
||||||
|
@ -86,15 +85,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
isExpanded: true,
|
|
||||||
label: I18nText('updateButton'),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
isPatches
|
isPatches
|
||||||
? model.updatePatches(context)
|
? model.updatePatches(context)
|
||||||
: model.updateManager(context);
|
: model.updateManager(context);
|
||||||
},
|
},
|
||||||
|
child: I18nText('updateButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -58,7 +58,8 @@ class PatchSelectorCard extends StatelessWidget {
|
||||||
|
|
||||||
String _getPatchesSelection() {
|
String _getPatchesSelection() {
|
||||||
String text = '';
|
String text = '';
|
||||||
final List<Patch> selectedPatches = locator<PatcherViewModel>().selectedPatches;
|
final List<Patch> selectedPatches =
|
||||||
|
locator<PatcherViewModel>().selectedPatches;
|
||||||
selectedPatches.sort((a, b) => a.name.compareTo(b.name));
|
selectedPatches.sort((a, b) => a.name.compareTo(b.name));
|
||||||
for (final Patch p in selectedPatches) {
|
for (final Patch p in selectedPatches) {
|
||||||
text += '• ${p.getSimpleName()}\n';
|
text += '• ${p.getSimpleName()}\n';
|
||||||
|
|
|
@ -5,7 +5,6 @@ import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class PatchItem extends StatefulWidget {
|
class PatchItem extends StatefulWidget {
|
||||||
|
@ -216,7 +215,6 @@ class _PatchItemState extends State<PatchItem> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'patchItem.unsupportedDialogText',
|
'patchItem.unsupportedDialogText',
|
||||||
translationParams: {
|
translationParams: {
|
||||||
|
@ -226,9 +224,9 @@ class _PatchItemState extends State<PatchItem> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -240,14 +238,13 @@ class _PatchItemState extends State<PatchItem> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('notice'),
|
title: I18nText('notice'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText(
|
content: I18nText(
|
||||||
'patchItem.unsupportedRequiredOption',
|
'patchItem.unsupportedRequiredOption',
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('okButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('okButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -400,7 +400,9 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||||
final bool isStringOption = widget.optionType.contains('String');
|
final bool isStringOption = widget.optionType.contains('String');
|
||||||
final bool isArrayOption = widget.optionType.contains('Array');
|
final bool isArrayOption = widget.optionType.contains('Array');
|
||||||
selectedKey ??= widget.selectedKey;
|
selectedKey ??= widget.selectedKey;
|
||||||
controller.text = !isStringOption && isArrayOption && selectedKey == '' &&
|
controller.text = !isStringOption &&
|
||||||
|
isArrayOption &&
|
||||||
|
selectedKey == '' &&
|
||||||
(widget.value != null && widget.value.toString().startsWith('['))
|
(widget.value != null && widget.value.toString().startsWith('['))
|
||||||
? ''
|
? ''
|
||||||
: widget.value ?? '';
|
: widget.value ?? '';
|
||||||
|
@ -519,7 +521,8 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'patchOptionsView.selectFolder':
|
case 'patchOptionsView.selectFolder':
|
||||||
final DirectoryLocation? result = await FlutterFileDialog.pickDirectory();
|
final DirectoryLocation? result =
|
||||||
|
await FlutterFileDialog.pickDirectory();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
controller.text = result.toString();
|
controller.text = result.toString();
|
||||||
widget.onChanged(controller.text);
|
widget.onChanged(controller.text);
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CustomSwitch extends StatelessWidget {
|
|
||||||
const CustomSwitch({
|
|
||||||
super.key,
|
|
||||||
required this.onChanged,
|
|
||||||
required this.value,
|
|
||||||
});
|
|
||||||
final ValueChanged<bool> onChanged;
|
|
||||||
final bool value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () => onChanged(!value),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 25,
|
|
||||||
width: 50,
|
|
||||||
child: Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
AnimatedContainer(
|
|
||||||
height: 25,
|
|
||||||
width: 50,
|
|
||||||
curve: Curves.ease,
|
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(25.0),
|
|
||||||
),
|
|
||||||
color: value
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedAlign(
|
|
||||||
curve: Curves.ease,
|
|
||||||
duration: const Duration(milliseconds: 400),
|
|
||||||
alignment: !value ? Alignment.centerLeft : Alignment.centerRight,
|
|
||||||
child: Container(
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: value
|
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
|
||||||
: Theme.of(context).colorScheme.surface,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black12.withOpacity(0.1),
|
|
||||||
spreadRadius: 0.5,
|
|
||||||
blurRadius: 1,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
|
||||||
|
|
||||||
class CustomSwitchTile extends StatelessWidget {
|
|
||||||
const CustomSwitchTile({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.subtitle,
|
|
||||||
required this.value,
|
|
||||||
required this.onTap,
|
|
||||||
this.padding,
|
|
||||||
});
|
|
||||||
final Widget title;
|
|
||||||
final Widget subtitle;
|
|
||||||
final bool value;
|
|
||||||
final Function(bool) onTap;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListTile(
|
|
||||||
contentPadding: padding ?? EdgeInsets.zero,
|
|
||||||
title: title,
|
|
||||||
subtitle: subtitle,
|
|
||||||
onTap: () => onTap(!value),
|
|
||||||
trailing: CustomSwitch(
|
|
||||||
value: value,
|
|
||||||
onChanged: onTap,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CustomTextField extends StatelessWidget {
|
|
||||||
const CustomTextField({
|
|
||||||
super.key,
|
|
||||||
required this.inputController,
|
|
||||||
required this.label,
|
|
||||||
required this.hint,
|
|
||||||
this.leadingIcon,
|
|
||||||
required this.onChanged,
|
|
||||||
});
|
|
||||||
final TextEditingController inputController;
|
|
||||||
final Widget label;
|
|
||||||
final String hint;
|
|
||||||
final Widget? leadingIcon;
|
|
||||||
final Function(String)? onChanged;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 4.0),
|
|
||||||
child: TextField(
|
|
||||||
controller: inputController,
|
|
||||||
onChanged: onChanged,
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
icon: leadingIcon,
|
|
||||||
label: label,
|
|
||||||
filled: true,
|
|
||||||
fillColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
hintText: hint,
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
floatingLabelStyle: MaterialStateTextStyle.resolveWith(
|
|
||||||
(states) => states.contains(MaterialState.focused)
|
|
||||||
? TextStyle(color: Theme.of(context).colorScheme.primary)
|
|
||||||
: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
|
||||||
),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8.0,
|
|
||||||
horizontal: 16.0,
|
|
||||||
),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
width: 2.0,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
errorBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,6 @@ import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart';
|
||||||
|
|
||||||
|
|
||||||
class SAdvancedSection extends StatelessWidget {
|
class SAdvancedSection extends StatelessWidget {
|
||||||
const SAdvancedSection({super.key});
|
const SAdvancedSection({super.key});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ class SEnablePatchesSelection extends StatefulWidget {
|
||||||
const SEnablePatchesSelection({super.key});
|
const SEnablePatchesSelection({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SEnablePatchesSelection> createState() => _SEnablePatchesSelectionState();
|
State<SEnablePatchesSelection> createState() =>
|
||||||
|
_SEnablePatchesSelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
|
@ -151,20 +150,18 @@ class SExportSection extends StatelessWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText(dialogTitle),
|
title: I18nText(dialogTitle),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText(dialogText),
|
content: I18nText(dialogText),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
dialogAction(),
|
dialogAction(),
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -176,20 +173,18 @@ class SExportSection extends StatelessWidget {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
TextButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: I18nText('noButton'),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('yesButton'),
|
|
||||||
onPressed: () => {
|
onPressed: () => {
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
_settingsViewModel.deleteKeystore(),
|
_settingsViewModel.deleteKeystore(),
|
||||||
},
|
},
|
||||||
|
child: I18nText('yesButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,12 +6,14 @@ class SRequireSuggestedAppVersion extends StatefulWidget {
|
||||||
const SRequireSuggestedAppVersion({super.key});
|
const SRequireSuggestedAppVersion({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SRequireSuggestedAppVersion> createState() => _SRequireSuggestedAppVersionState();
|
State<SRequireSuggestedAppVersion> createState() =>
|
||||||
|
_SRequireSuggestedAppVersionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersion> {
|
class _SRequireSuggestedAppVersionState
|
||||||
|
extends State<SRequireSuggestedAppVersion> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
|
@ -29,8 +31,11 @@ class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersio
|
||||||
subtitle: I18nText('settingsView.requireSuggestedAppVersionHint'),
|
subtitle: I18nText('settingsView.requireSuggestedAppVersionHint'),
|
||||||
value: _settingsViewModel.isRequireSuggestedAppVersionEnabled(),
|
value: _settingsViewModel.isRequireSuggestedAppVersionEnabled(),
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
await _settingsViewModel.showRequireSuggestedAppVersionDialog(context, value,);
|
await _settingsViewModel.showRequireSuggestedAppVersionDialog(
|
||||||
setState(() {});
|
context,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,16 +8,14 @@ class SUniversalPatches extends StatefulWidget {
|
||||||
const SUniversalPatches({super.key});
|
const SUniversalPatches({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SUniversalPatches> createState() =>
|
State<SUniversalPatches> createState() => _SUniversalPatchesState();
|
||||||
_SUniversalPatchesState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||||
final _patcherViewModel = PatcherViewModel();
|
final _patcherViewModel = PatcherViewModel();
|
||||||
|
|
||||||
class _SUniversalPatchesState
|
class _SUniversalPatchesState extends State<SUniversalPatches> {
|
||||||
extends State<SUniversalPatches> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
|
|
|
@ -9,14 +9,16 @@ class SVersionCompatibilityCheck extends StatefulWidget {
|
||||||
const SVersionCompatibilityCheck({super.key});
|
const SVersionCompatibilityCheck({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SVersionCompatibilityCheck> createState() => _SVersionCompatibilityCheckState();
|
State<SVersionCompatibilityCheck> createState() =>
|
||||||
|
_SVersionCompatibilityCheckState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||||
final _patcherViewModel = PatcherViewModel();
|
final _patcherViewModel = PatcherViewModel();
|
||||||
|
|
||||||
class _SVersionCompatibilityCheckState extends State<SVersionCompatibilityCheck> {
|
class _SVersionCompatibilityCheckState
|
||||||
|
extends State<SVersionCompatibilityCheck> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
|
|
||||||
class ApplicationItem extends StatefulWidget {
|
class ApplicationItem extends StatefulWidget {
|
||||||
|
@ -24,7 +23,6 @@ class ApplicationItem extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ApplicationItemState extends State<ApplicationItem> {
|
class _ApplicationItemState extends State<ApplicationItem> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -81,9 +79,9 @@ class _ApplicationItemState extends State<ApplicationItem> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomMaterialButton(
|
FilledButton(
|
||||||
label: I18nText('applicationItem.infoButton'),
|
|
||||||
onPressed: widget.onPressed,
|
onPressed: widget.onPressed,
|
||||||
|
child: I18nText('applicationItem.infoButton'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CustomMaterialButton extends StatelessWidget {
|
|
||||||
const CustomMaterialButton({
|
|
||||||
super.key,
|
|
||||||
required this.label,
|
|
||||||
this.isFilled = true,
|
|
||||||
this.isExpanded = false,
|
|
||||||
required this.onPressed,
|
|
||||||
});
|
|
||||||
final Widget label;
|
|
||||||
final bool isFilled;
|
|
||||||
final bool isExpanded;
|
|
||||||
final Function()? onPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return TextButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(
|
|
||||||
isExpanded
|
|
||||||
? const EdgeInsets.symmetric(horizontal: 24, vertical: 12)
|
|
||||||
: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
||||||
),
|
|
||||||
shape: MaterialStateProperty.all(
|
|
||||||
StadiumBorder(
|
|
||||||
side: isFilled
|
|
||||||
? BorderSide.none
|
|
||||||
: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: MaterialStateProperty.all(
|
|
||||||
isFilled ? Theme.of(context).colorScheme.primary : Colors.transparent,
|
|
||||||
),
|
|
||||||
foregroundColor: MaterialStateProperty.all(
|
|
||||||
isFilled
|
|
||||||
? Theme.of(context).colorScheme.surface
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: onPressed,
|
|
||||||
child: label,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
|
||||||
class TimerButton extends StatefulWidget {
|
|
||||||
TimerButton({
|
|
||||||
super.key,
|
|
||||||
required this.seconds,
|
|
||||||
required this.isRunning,
|
|
||||||
required this.onTimerEnd,
|
|
||||||
this.label = const Text(''),
|
|
||||||
this.isFilled = true,
|
|
||||||
});
|
|
||||||
Widget label;
|
|
||||||
bool isFilled;
|
|
||||||
int seconds;
|
|
||||||
final bool isRunning;
|
|
||||||
final Function()? onTimerEnd;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<TimerButton> createState() => _TimerButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TimerButtonState extends State<TimerButton> {
|
|
||||||
void timer(int seconds) {
|
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
|
||||||
if (seconds > 0) {
|
|
||||||
setState(() {
|
|
||||||
seconds--;
|
|
||||||
});
|
|
||||||
timer(seconds);
|
|
||||||
} else {
|
|
||||||
widget.onTimerEnd!();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
//decrement seconds
|
|
||||||
if (widget.isRunning) {
|
|
||||||
timer(widget.seconds);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext build) {
|
|
||||||
return TextButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
shape: MaterialStateProperty.all(
|
|
||||||
StadiumBorder(
|
|
||||||
side: widget.isFilled
|
|
||||||
? BorderSide.none
|
|
||||||
: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: MaterialStateProperty.all(
|
|
||||||
widget.isFilled
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.transparent,
|
|
||||||
),
|
|
||||||
foregroundColor: MaterialStateProperty.all(
|
|
||||||
widget.isFilled
|
|
||||||
? Theme.of(context).colorScheme.surface
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: widget.isRunning ? null : widget.onTimerEnd,
|
|
||||||
child: Text(
|
|
||||||
widget.isRunning ? '${widget.seconds}' : 'Install',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,12 +27,12 @@ bool hasUnsupportedRequiredOption(List<Option> options, Patch patch) {
|
||||||
for (final Option option in options) {
|
for (final Option option in options) {
|
||||||
if (option.required &&
|
if (option.required &&
|
||||||
option.value == null &&
|
option.value == null &&
|
||||||
locator<ManagerAPI>()
|
locator<ManagerAPI>().getPatchOption(
|
||||||
.getPatchOption(
|
locator<PatcherViewModel>().selectedApp!.packageName,
|
||||||
locator<PatcherViewModel>().selectedApp!.packageName,
|
patch.name,
|
||||||
patch.name,
|
option.key,
|
||||||
option.key,
|
) ==
|
||||||
) == null) {
|
null) {
|
||||||
requiredOptionsType.add(option.valueType);
|
requiredOptionsType.add(option.valueType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,8 @@ List<Option> getNullRequiredOptions(List<Patch> patches, String packageName) {
|
||||||
patchOption.required &&
|
patchOption.required &&
|
||||||
patchOption.value == null &&
|
patchOption.value == null &&
|
||||||
locator<ManagerAPI>()
|
locator<ManagerAPI>()
|
||||||
.getPatchOption(packageName, patch.name, patchOption.key) == null) {
|
.getPatchOption(packageName, patch.name, patchOption.key) ==
|
||||||
|
null) {
|
||||||
requiredNullOptions.add(patchOption);
|
requiredNullOptions.add(patchOption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
pubspec.yaml
25
pubspec.yaml
|
@ -1,10 +1,10 @@
|
||||||
name: revanced_manager
|
name: revanced_manager
|
||||||
description: Patch your favorite apps, right on your device.
|
description: Patch your favorite apps, right on your device.
|
||||||
homepage: https://github.com/revanced/revanced-manager
|
homepage: https://github.com/ReVanced/revanced-manager
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.16.0+101600000
|
version: 1.18.0+101800000
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
@ -15,7 +15,7 @@ dependencies:
|
||||||
device_apps:
|
device_apps:
|
||||||
git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
|
git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
|
||||||
url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
|
url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
|
||||||
ref: revanced-manager
|
ref: 0efbeba41657158a66bbc92c55d1226df56d0f1b # Branch: revanced-manager
|
||||||
device_info_plus: ^9.1.0
|
device_info_plus: ^9.1.0
|
||||||
dynamic_color: ^1.6.3
|
dynamic_color: ^1.6.3
|
||||||
dio: ^5.0.0
|
dio: ^5.0.0
|
||||||
|
@ -27,17 +27,14 @@ dependencies:
|
||||||
flutter_background:
|
flutter_background:
|
||||||
git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged
|
git: # remove once https://github.com/JulianAssmann/flutter_background/pull/79 is merged
|
||||||
url: https://github.com/BenjaminHalko/flutter_background
|
url: https://github.com/BenjaminHalko/flutter_background
|
||||||
ref: specify-namespace
|
ref: 560d21c4148b53933313573e7eafca0b0eb9aadf # Branch: specify-namespace
|
||||||
flutter_cache_manager: ^3.3.0
|
flutter_cache_manager: ^3.3.0
|
||||||
flutter_i18n: ^0.34.0
|
flutter_i18n: ^0.34.0
|
||||||
flutter_local_notifications: ^16.1.0
|
flutter_local_notifications: ^16.1.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_svg: ^2.0.4
|
flutter_svg: ^2.0.4
|
||||||
fluttertoast:
|
fluttertoast: ^8.2.4
|
||||||
git: # remove once the next fluttertoast version is release (> 8.2.2)
|
|
||||||
url: https://github.com/ponnamkarthik/FlutterToast
|
|
||||||
ref: f4e7b4e1afc8c760eb5bac80f6a2e299906d83ca
|
|
||||||
font_awesome_flutter: ^10.4.0
|
font_awesome_flutter: ^10.4.0
|
||||||
get_it: ^7.6.4
|
get_it: ^7.6.4
|
||||||
google_fonts: ^6.1.0
|
google_fonts: ^6.1.0
|
||||||
|
@ -48,15 +45,15 @@ dependencies:
|
||||||
logcat:
|
logcat:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/BenjaminHalko/logcat
|
url: https://github.com/BenjaminHalko/logcat
|
||||||
ref: master
|
ref: 4a6d5e0e22292c8eb160cfb9365b9ea29735fd43 # Branch: master
|
||||||
package_info_plus: ^4.2.0
|
package_info_plus: ^5.0.1
|
||||||
path_provider: ^2.0.14
|
path_provider: ^2.0.14
|
||||||
permission_handler: ^11.0.1
|
permission_handler: ^11.0.1
|
||||||
pull_to_refresh: ^2.0.0
|
pull_to_refresh: ^2.0.0
|
||||||
root:
|
root:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/validcube/root
|
url: https://github.com/validcube/root
|
||||||
ref: 68e5678a535a2a3344828a14a39017fa74b9098c
|
ref: 68e5678a535a2a3344828a14a39017fa74b9098c # Branch: libsu-521
|
||||||
shared_preferences: ^2.1.0
|
shared_preferences: ^2.1.0
|
||||||
skeletons: ^0.0.3
|
skeletons: ^0.0.3
|
||||||
stacked: ^3.2.0
|
stacked: ^3.2.0
|
||||||
|
@ -68,14 +65,10 @@ dependencies:
|
||||||
flutter_dotenv: ^5.0.2
|
flutter_dotenv: ^5.0.2
|
||||||
flutter_markdown: ^0.6.14
|
flutter_markdown: ^0.6.14
|
||||||
dio_cache_interceptor: ^3.4.0
|
dio_cache_interceptor: ^3.4.0
|
||||||
install_plugin:
|
|
||||||
git: # remove once https://github.com/hui-z/flutter_install_plugin/pull/67 is merged
|
|
||||||
url: https://github.com/BenjaminHalko/flutter_install_plugin
|
|
||||||
ref: master
|
|
||||||
screenshot_callback:
|
screenshot_callback:
|
||||||
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
||||||
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
||||||
ref: master
|
ref: 1a1616ac91e16cd1f3dd170a81febf27ffce3587 # Branch: master
|
||||||
synchronized: ^3.1.0
|
synchronized: ^3.1.0
|
||||||
connectivity_plus: ^5.0.1
|
connectivity_plus: ^5.0.1
|
||||||
flutter_file_dialog: ^3.0.2
|
flutter_file_dialog: ^3.0.2
|
||||||
|
|
Loading…
Reference in a new issue