mirror of
https://github.com/revanced/revanced-manager
synced 2024-05-14 13:56:57 +02:00
chore: merge dev
to main
(#1573)
This commit is contained in:
commit
bb1b0da749
8
.github/workflows/pr-build.yml
vendored
8
.github/workflows/pr-build.yml
vendored
@ -45,10 +45,10 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh repo clone ${{ github.repository }}
|
||||
gh repo clone "${{ github.repository }}"
|
||||
cd revanced-manager
|
||||
gh repo set-default ${{ github.repository }}
|
||||
gh pr checkout ${{ inputs.pr-number }}
|
||||
gh repo set-default "${{ github.repository }}"
|
||||
gh pr checkout "${{ inputs.pr-number }}"
|
||||
|
||||
echo "DATETIME=$( TZ='UTC+0' date --rfc-email )" >> $GITHUB_ENV
|
||||
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
@ -83,7 +83,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
flutter build apk --${{ inputs.app-flavour }};
|
||||
flutter build apk --"${{ inputs.app-flavour }}";
|
||||
|
||||
- name: Prepare to comment
|
||||
run: |
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
local.properties
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
|
@ -67,7 +67,7 @@ ReVanced Manager is an Android application that uses ReVanced Patcher to add, re
|
||||
|
||||
## 💪 Features
|
||||
|
||||
We provide the some of the features are:
|
||||
Some of the features we provide are:
|
||||
|
||||
* 📱 **Portable**: ReVanced Patcher that fit in your pocket;
|
||||
* 🤗 **Intuitive UI**: Help you manage your patched applications with easy-to-use interface;
|
||||
|
@ -32,17 +32,17 @@
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"/>
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ExportSettingsActivity"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ExportSettingsActivity"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
@ -55,5 +55,22 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</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>
|
||||
</manifest>
|
||||
|
@ -1,11 +1,16 @@
|
||||
package app.revanced.manager.flutter
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import app.revanced.manager.flutter.utils.Aapt
|
||||
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.zip.ZipFile
|
||||
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
|
||||
@ -184,12 +189,24 @@ class MainActivity : FlutterActivity() {
|
||||
}.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openBrowser(query: String?) {
|
||||
private fun openBrowser(query: String?) {
|
||||
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
|
||||
putExtra(SearchManager.QUERY, query)
|
||||
}
|
||||
@ -349,7 +366,7 @@ class MainActivity : FlutterActivity() {
|
||||
return@Thread
|
||||
}
|
||||
|
||||
updateProgress(0.8, "Building...", "")
|
||||
updateProgress(0.75, "Building...", "")
|
||||
|
||||
val res = patcher.get()
|
||||
patcher.close()
|
||||
@ -382,7 +399,7 @@ class MainActivity : FlutterActivity() {
|
||||
return@Thread
|
||||
}
|
||||
|
||||
updateProgress(0.9, "Signing...", "Signing APK")
|
||||
updateProgress(0.8, "Signing...", "Signing APK")
|
||||
|
||||
try {
|
||||
Signer("ReVanced", keystorePassword)
|
||||
@ -392,7 +409,7 @@ class MainActivity : FlutterActivity() {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
updateProgress(1.0, "Patched", "Patched")
|
||||
updateProgress(.85, "Patched", "Patched APK")
|
||||
} catch (ex: Throwable) {
|
||||
if (!cancel) {
|
||||
val stack = ex.stackTraceToString()
|
||||
@ -407,4 +424,44 @@ class MainActivity : FlutterActivity() {
|
||||
handler.post { result.success(null) }
|
||||
}.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
|
||||
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
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -168,7 +168,9 @@
|
||||
|
||||
"installButton": "Install",
|
||||
"installRootType": "Mount",
|
||||
"installNonRootType": "Normal",
|
||||
"installNonRootType": "Regular",
|
||||
|
||||
"warning": "Disable auto updates for the patched app to avoid unexpected issues.",
|
||||
|
||||
"pressBackAgain": "Press back again to cancel",
|
||||
"openButton": "Open",
|
||||
@ -302,16 +304,17 @@
|
||||
"widgetTitle": "App info",
|
||||
"openButton": "Open",
|
||||
"uninstallButton": "Uninstall",
|
||||
"unpatchButton": "Unpatch",
|
||||
"unmountButton": "Unmount",
|
||||
"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.",
|
||||
|
||||
"packageNameLabel": "Package name",
|
||||
"installTypeLabel": "Installation type",
|
||||
"rootTypeLabel": "Root",
|
||||
"nonRootTypeLabel": "Non-root",
|
||||
"mountTypeLabel": "Mount",
|
||||
"regularTypeLabel": "Regular",
|
||||
"patchedDateLabel": "Patched date",
|
||||
"appliedPatchesLabel": "Applied patches",
|
||||
|
||||
@ -327,5 +330,34 @@
|
||||
"integrationsContributors": "Integrations contributors",
|
||||
"cliContributors": "CLI 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@ -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.
|
||||
|
||||
Continue: [🔨 Building from source](4_building.md)
|
||||
|
||||
[^1]: https://developer.android.com/guide/app-bundle/app-bundle-format
|
||||
|
@ -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/manager_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/views/navigation/navigation_view.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@ -24,6 +25,13 @@ Future main() async {
|
||||
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||
locator<GithubAPI>().initialize(repoUrl);
|
||||
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();
|
||||
|
||||
runApp(const MyApp());
|
||||
|
@ -75,9 +75,8 @@ class Option {
|
||||
if (json['valueType'] == null) {
|
||||
final type = json['optionClassType'];
|
||||
if (type is String) {
|
||||
json['valueType'] = type
|
||||
.replaceAll('PatchOption', '')
|
||||
.replaceAll('List', 'Array');
|
||||
json['valueType'] =
|
||||
type.replaceAll('PatchOption', '').replaceAll('List', 'Array');
|
||||
|
||||
json['optionClassType'] = null;
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ class DownloadManager {
|
||||
);
|
||||
|
||||
Future<void> initialize() async {
|
||||
_userAgent = 'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
|
||||
_userAgent =
|
||||
'ReVanced-Manager/${await _managerAPI.getCurrentManagerVersion()}';
|
||||
}
|
||||
|
||||
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/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/root_api.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
@ -62,7 +61,8 @@ class ManagerAPI {
|
||||
Future<void> initialize() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
isRooted = await _rootAPI.isRooted();
|
||||
isDynamicThemeAvailable = (await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||
isDynamicThemeAvailable =
|
||||
(await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||
storedPatchesFile =
|
||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||
}
|
||||
@ -585,7 +585,6 @@ class ManagerAPI {
|
||||
builder: (context) => WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('warning'),
|
||||
content: ValueListenableBuilder(
|
||||
valueListenable: noShow,
|
||||
@ -620,12 +619,12 @@ class ManagerAPI {
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
setPatchesChangeWarning(noShow.value);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -3,10 +3,11 @@ import 'dart:io';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:install_plugin/install_plugin.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
@ -18,7 +19,7 @@ import 'package:share_plus/share_plus.dart';
|
||||
@lazySingleton
|
||||
class PatcherAPI {
|
||||
static const patcherChannel =
|
||||
MethodChannel('app.revanced.manager.flutter/patcher');
|
||||
MethodChannel('app.revanced.manager.flutter/patcher');
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final RootAPI _rootAPI = RootAPI();
|
||||
late Directory _dataDir;
|
||||
@ -79,7 +80,8 @@ class PatcherAPI {
|
||||
}
|
||||
|
||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||
bool showUniversalPatches,) async {
|
||||
bool showUniversalPatches,
|
||||
) async {
|
||||
final List<ApplicationWithIcon> filteredApps = [];
|
||||
final bool allAppsIncluded =
|
||||
_universalPatches.isNotEmpty && showUniversalPatches;
|
||||
@ -121,11 +123,11 @@ class PatcherAPI {
|
||||
final List<Patch> patches = _patches
|
||||
.where(
|
||||
(patch) =>
|
||||
patch.compatiblePackages.isEmpty ||
|
||||
!patch.name.contains('settings') &&
|
||||
patch.compatiblePackages
|
||||
.any((pack) => pack.name == packageName),
|
||||
)
|
||||
patch.compatiblePackages.isEmpty ||
|
||||
!patch.name.contains('settings') &&
|
||||
patch.compatiblePackages
|
||||
.any((pack) => pack.name == packageName),
|
||||
)
|
||||
.toList();
|
||||
if (!_managerAPI.areUniversalPatchesEnabled()) {
|
||||
filteredPatches[packageName] = patches
|
||||
@ -137,22 +139,27 @@ class PatcherAPI {
|
||||
return filteredPatches[packageName];
|
||||
}
|
||||
|
||||
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches,) async {
|
||||
Future<List<Patch>> getAppliedPatches(
|
||||
List<String> appliedPatches,
|
||||
) async {
|
||||
return _patches
|
||||
.where((patch) => appliedPatches.contains(patch.name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> runPatcher(String packageName,
|
||||
String apkFilePath,
|
||||
List<Patch> selectedPatches,) async {
|
||||
Future<void> runPatcher(
|
||||
String packageName,
|
||||
String apkFilePath,
|
||||
List<Patch> selectedPatches,
|
||||
) async {
|
||||
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
||||
final Map<String, Map<String, dynamic>> options = {};
|
||||
for (final patch in selectedPatches) {
|
||||
if (patch.options.isNotEmpty) {
|
||||
final Map<String, dynamic> patchOptions = {};
|
||||
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) {
|
||||
patchOptions[patchOption.key] = patchOption.value;
|
||||
}
|
||||
@ -194,133 +201,314 @@ class PatcherAPI {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopPatcher() async {
|
||||
try {
|
||||
await patcherChannel.invokeMethod('stopPatcher');
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
Future<void> stopPatcher() async {
|
||||
try {
|
||||
await patcherChannel.invokeMethod('stopPatcher');
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
|
||||
if (outFile != null) {
|
||||
try {
|
||||
if (patchedApp.isRooted) {
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
return _rootAPI.installApp(
|
||||
patchedApp.packageName,
|
||||
patchedApp.apkFilePath,
|
||||
outFile!.path,
|
||||
);
|
||||
Future<int> installPatchedFile(
|
||||
BuildContext context,
|
||||
PatchedApplication patchedApp,
|
||||
) async {
|
||||
if (outFile != null) {
|
||||
_managerAPI.ctx = context;
|
||||
try {
|
||||
if (patchedApp.isRooted) {
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
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 {
|
||||
final install = await InstallPlugin.installApk(outFile!.path);
|
||||
return install['isSuccess'];
|
||||
_managerAPI.ctx = context;
|
||||
return await installErrorDialog(
|
||||
statusCode,
|
||||
status,
|
||||
hasExtra,
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return false;
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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,
|
||||
Future<int> installErrorDialog(
|
||||
num statusCode, [
|
||||
status,
|
||||
bool hasExtra = false,
|
||||
]) async {
|
||||
final String statusValue = InstallStatus.byCode(
|
||||
hasExtra ? double.parse('$statusCode.1') : statusCode,
|
||||
);
|
||||
if (package != null) {
|
||||
for (final String version in package.versions) {
|
||||
versions.update(
|
||||
version,
|
||||
(value) => versions[version]! + 1,
|
||||
ifAbsent: () => 1,
|
||||
bool cleanInstall = false;
|
||||
final bool isFixable = statusCode == 4 || statusCode == 5;
|
||||
await showDialog(
|
||||
context: _managerAPI.ctx!,
|
||||
builder: (context) => AlertDialog(
|
||||
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()
|
||||
..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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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';
|
||||
|
||||
class RootAPI {
|
||||
// TODO(ponces): remove in the future, keep it for now during migration.
|
||||
final String _revancedOldDirPath = '/data/local/tmp/revanced-manager';
|
||||
final String _revancedDirPath = '/data/adb/revanced';
|
||||
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||
final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
|
||||
|
||||
final String _revancedDirPath = '/data/adb/revanced';
|
||||
final String _serviceDDirPath = '/data/adb/service.d';
|
||||
|
||||
Future<bool> isRooted() async {
|
||||
@ -43,21 +43,22 @@ class RootAPI {
|
||||
String filePath,
|
||||
) async {
|
||||
try {
|
||||
final StringBuffer commands = StringBuffer();
|
||||
if (permissions.isNotEmpty) {
|
||||
await Root.exec(
|
||||
cmd: 'chmod $permissions "$filePath"',
|
||||
);
|
||||
commands.writeln('chmod $permissions $filePath');
|
||||
}
|
||||
|
||||
if (ownerGroup.isNotEmpty) {
|
||||
await Root.exec(
|
||||
cmd: 'chown $ownerGroup "$filePath"',
|
||||
);
|
||||
commands.writeln('chown $ownerGroup $filePath');
|
||||
}
|
||||
|
||||
if (seLinux.isNotEmpty) {
|
||||
await Root.exec(
|
||||
cmd: 'chcon $seLinux "$filePath"',
|
||||
);
|
||||
commands.writeln('chcon $seLinux $filePath');
|
||||
}
|
||||
|
||||
await Root.exec(
|
||||
cmd: commands.toString(),
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -75,18 +76,7 @@ class RootAPI {
|
||||
Future<List<String>> getInstalledApps() async {
|
||||
final List<String> apps = List.empty(growable: true);
|
||||
try {
|
||||
String? res = await Root.exec(
|
||||
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"',
|
||||
);
|
||||
final String? res = await Root.exec(cmd: 'ls $_revancedDirPath');
|
||||
if (res != null) {
|
||||
final List<String> list = res.split('\n');
|
||||
list.removeWhere((pack) => pack.isEmpty);
|
||||
@ -100,49 +90,45 @@ class RootAPI {
|
||||
return apps;
|
||||
}
|
||||
|
||||
Future<void> deleteApp(String packageName, String originalFilePath) async {
|
||||
Future<void> uninstall(String packageName) async {
|
||||
await Root.exec(
|
||||
cmd: 'am force-stop "$packageName"',
|
||||
);
|
||||
await Root.exec(
|
||||
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"',
|
||||
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
|
||||
rm -rf $_revancedDirPath/$packageName $_serviceDDirPath/$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 originalFilePath,
|
||||
String patchedFilePath,
|
||||
) async {
|
||||
try {
|
||||
await deleteApp(packageName, originalFilePath);
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_revancedDirPath/$packageName"',
|
||||
);
|
||||
await setPermissions(
|
||||
'0755',
|
||||
'shell:shell',
|
||||
'',
|
||||
'$_revancedDirPath/$packageName',
|
||||
);
|
||||
await saveOriginalFilePath(packageName, originalFilePath);
|
||||
await installPatchedApk(packageName, patchedFilePath);
|
||||
await installServiceDScript(packageName);
|
||||
await installPostFsDataScript(packageName);
|
||||
await installApk(packageName, patchedFilePath);
|
||||
await mountApk(packageName, originalFilePath);
|
||||
await runMountScript(packageName);
|
||||
return true;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -154,38 +140,44 @@ class RootAPI {
|
||||
|
||||
Future<void> installServiceDScript(String packageName) async {
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_serviceDDirPath"',
|
||||
cmd: 'mkdir -p $_serviceDDirPath',
|
||||
);
|
||||
final String content = '#!/system/bin/sh\n'
|
||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
||||
'base_path=$_revancedDirPath/$packageName/base.apk\n'
|
||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
|
||||
final String mountScript = '''
|
||||
#!/system/bin/sh
|
||||
MAGISKTMP="\$(magisk --path)" || MAGISKTMP=/sbin
|
||||
MIRROR="\$MAGISKTMP/.magisk/mirror"
|
||||
|
||||
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';
|
||||
await Root.exec(
|
||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||
cmd: 'echo \'$mountScript\' > "$scriptFilePath"',
|
||||
);
|
||||
await setPermissions('0744', '', '', scriptFilePath);
|
||||
}
|
||||
|
||||
Future<void> installPostFsDataScript(String packageName) async {
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_postFsDataDirPath"',
|
||||
);
|
||||
final String content = '#!/system/bin/sh\n'
|
||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
||||
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 {
|
||||
Future<void> installPatchedApk(
|
||||
String packageName, String patchedFilePath) async {
|
||||
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
||||
await Root.exec(
|
||||
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
||||
cmd: '''
|
||||
mkdir -p $_revancedDirPath/$packageName
|
||||
cp "$patchedFilePath" $newPatchedFilePath
|
||||
''',
|
||||
);
|
||||
await setPermissions(
|
||||
'0644',
|
||||
@ -195,50 +187,10 @@ class RootAPI {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> mountApk(String packageName, String originalFilePath) async {
|
||||
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(
|
||||
Future<void> runMountScript(
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
) async {
|
||||
final String originalRootPath =
|
||||
'$_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,
|
||||
);
|
||||
await Root.exec(cmd: '.$_serviceDDirPath/$packageName.sh');
|
||||
}
|
||||
|
||||
Future<bool> fileExists(String path) async {
|
||||
@ -255,3 +207,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();
|
||||
}
|
||||
|
||||
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsBindingObserver {
|
||||
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder>
|
||||
with WidgetsBindingObserver {
|
||||
Brightness brightness = PlatformDispatcher.instance.platformBrightness;
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
|
||||
@ -43,8 +44,9 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
|
||||
if (_managerAPI.getThemeMode() < 2) {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
systemNavigationBarIconBrightness:
|
||||
brightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||
systemNavigationBarIconBrightness: brightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -83,24 +85,31 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
|
||||
return DynamicTheme(
|
||||
themeCollection: ThemeCollection(
|
||||
themes: {
|
||||
0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
|
||||
1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme,
|
||||
0: brightness == Brightness.light
|
||||
? lightCustomTheme
|
||||
: darkCustomTheme,
|
||||
1: brightness == Brightness.light
|
||||
? lightDynamicTheme
|
||||
: darkDynamicTheme,
|
||||
2: lightCustomTheme,
|
||||
3: lightDynamicTheme,
|
||||
4: darkCustomTheme,
|
||||
5: darkDynamicTheme,
|
||||
},
|
||||
fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme,
|
||||
fallbackTheme: PlatformDispatcher.instance.platformBrightness ==
|
||||
Brightness.light
|
||||
? lightCustomTheme
|
||||
: darkCustomTheme,
|
||||
),
|
||||
builder: (context, theme) => MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: widget.title,
|
||||
navigatorKey: StackedService.navigatorKey,
|
||||
onGenerateRoute: StackedRouter().onGenerateRoute,
|
||||
theme: theme,
|
||||
home: widget.home,
|
||||
localizationsDelegates: widget.localizationsDelegates,
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: widget.title,
|
||||
navigatorKey: StackedService.navigatorKey,
|
||||
onGenerateRoute: StackedRouter().onGenerateRoute,
|
||||
theme: theme,
|
||||
home: widget.home,
|
||||
localizationsDelegates: widget.localizationsDelegates,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -40,8 +40,8 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 22.0,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
@ -94,9 +94,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
...model
|
||||
.getFilteredApps(_query)
|
||||
.map(
|
||||
...model.getFilteredApps(_query).map(
|
||||
(app) => InstalledAppItem(
|
||||
name: app.appName,
|
||||
pkgName: app.packageName,
|
||||
@ -117,11 +115,8 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
packageName: app.packageName,
|
||||
),
|
||||
),
|
||||
)
|
||||
,
|
||||
...model
|
||||
.getFilteredAppsNames(_query)
|
||||
.map(
|
||||
),
|
||||
...model.getFilteredAppsNames(_query).map(
|
||||
(app) => NotInstalledAppItem(
|
||||
name: app,
|
||||
patchesCount: model.patchesCount(app),
|
||||
@ -135,8 +130,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
packageName: app,
|
||||
),
|
||||
),
|
||||
)
|
||||
,
|
||||
),
|
||||
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/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
@ -105,7 +104,8 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
]) async {
|
||||
final String suggestedVersion =
|
||||
getSuggestedVersion(application.packageName);
|
||||
if (application.versionName != suggestedVersion && suggestedVersion.isNotEmpty) {
|
||||
if (application.versionName != suggestedVersion &&
|
||||
suggestedVersion.isNotEmpty) {
|
||||
_managerAPI.suggestedAppVersionSelected = false;
|
||||
if (_managerAPI.isRequireSuggestedAppVersionEnabled() &&
|
||||
context.mounted) {
|
||||
@ -168,7 +168,6 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('warning'),
|
||||
content: I18nText(
|
||||
'appSelectorView.requireSuggestedAppVersionDialogText',
|
||||
@ -185,9 +184,9 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -232,12 +231,12 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
CustomMaterialButton(
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(innerContext);
|
||||
await selectAppFromStorage(context);
|
||||
},
|
||||
label: Row(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.sd_card),
|
||||
@ -247,12 +246,11 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(innerContext);
|
||||
},
|
||||
label: Row(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
|
@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:install_plugin/install_plugin.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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/patcher/patcher_viewmodel.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_services/stacked_services.dart';
|
||||
|
||||
@ -54,7 +52,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await InstallPlugin.installApk(managerApk.path);
|
||||
await _patcherAPI.installApk(context, managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
@ -65,8 +63,8 @@ class HomeViewModel extends BaseViewModel {
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestNotificationsPermission();
|
||||
final bool isConnected = await Connectivity().checkConnectivity() !=
|
||||
ConnectivityResult.none;
|
||||
final bool isConnected =
|
||||
await Connectivity().checkConnectivity() != ConnectivityResult.none;
|
||||
if (!isConnected) {
|
||||
_toast.showBottom('homeView.noConnection');
|
||||
}
|
||||
@ -76,7 +74,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await InstallPlugin.installApk(managerApk.path);
|
||||
await _patcherAPI.installApk(context, managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
@ -223,21 +221,20 @@ class HomeViewModel extends BaseViewModel {
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await _managerAPI.setPatchesConsent(false);
|
||||
SystemNavigator.pop();
|
||||
},
|
||||
label: I18nText('quitButton'),
|
||||
child: I18nText('quitButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
await _managerAPI.setPatchesConsent(true);
|
||||
await _managerAPI.setPatchesAutoUpdate(autoUpdate.value);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
label: I18nText('okButton'),
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -270,6 +267,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
valueListenable: downloaded,
|
||||
builder: (context, value, child) {
|
||||
return SimpleDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
contentPadding: const EdgeInsets.all(16.0),
|
||||
title: I18nText(
|
||||
!value
|
||||
@ -324,12 +322,12 @@ class HomeViewModel extends BaseViewModel {
|
||||
const SizedBox(height: 16.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('cancelButton'),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
_revancedAPI.disposeManagerUpdateProgress();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -355,24 +353,24 @@ class HomeViewModel extends BaseViewModel {
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
child: FilledButton(
|
||||
onPressed: () async {
|
||||
await InstallPlugin.installApk(
|
||||
await _patcherAPI.installApk(
|
||||
context,
|
||||
downloadedApk!.path,
|
||||
);
|
||||
},
|
||||
child: I18nText('updateButton'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -415,7 +413,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||
// );
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
await InstallPlugin.installApk(managerApk.path);
|
||||
await _patcherAPI.installApk(context, managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
|
@ -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/root_api.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/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/about_info.dart';
|
||||
import 'package:screenshot_callback/screenshot_callback.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
@ -104,7 +104,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||
isPatching = true;
|
||||
isInstalled = false;
|
||||
hasErrors = false;
|
||||
} else if (value == 1.0) {
|
||||
} else if (value == .85) {
|
||||
isPatching = false;
|
||||
hasErrors = false;
|
||||
await _managerAPI.savePatches(
|
||||
@ -115,6 +115,7 @@ class InstallerViewModel extends BaseViewModel {
|
||||
} else if (value == -100.0) {
|
||||
isPatching = false;
|
||||
hasErrors = true;
|
||||
progress = 0.0;
|
||||
}
|
||||
if (header.isNotEmpty) {
|
||||
headerLogs = header;
|
||||
@ -127,10 +128,10 @@ class InstallerViewModel extends BaseViewModel {
|
||||
if (logs[logs.length - 1] == '\n') {
|
||||
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.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
});
|
||||
@ -184,7 +185,9 @@ class InstallerViewModel extends BaseViewModel {
|
||||
final index = logLines.indexWhere((line) => line.endsWith(keyword));
|
||||
if (newString != null && lineCount > 0) {
|
||||
logLines.insert(
|
||||
index, newString.replaceAll('{lineCount}', lineCount.toString()));
|
||||
index,
|
||||
newString.replaceAll('{lineCount}', lineCount.toString()),
|
||||
);
|
||||
}
|
||||
logLines.removeWhere((lines) => lines.endsWith(keyword));
|
||||
}
|
||||
@ -199,18 +202,18 @@ class InstallerViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
String _formatPatches(List<Patch> patches) {
|
||||
if (patches.isEmpty) {
|
||||
return 'None';
|
||||
}
|
||||
return patches
|
||||
.map((p) =>
|
||||
p.name +
|
||||
(p.options.isEmpty
|
||||
? ''
|
||||
: ' [${p.options.map((o) => '${o.title}: ${_getPatchOptionValue(p.name, o)}').join(", ")}]'))
|
||||
.toList()
|
||||
.join(', ');
|
||||
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) {
|
||||
@ -236,23 +239,25 @@ class InstallerViewModel extends BaseViewModel {
|
||||
.getFilteredPatches(_app.packageName)
|
||||
.where((p) => !p.excluded)
|
||||
.toList();
|
||||
final patchesAdded =
|
||||
_patches.where((p) => !defaultPatches.contains(p)).toList();
|
||||
final patchesRemoved =
|
||||
defaultPatches.where((p) => !_patches.contains(p)).toList();
|
||||
final appliedPatchesNames = _patches.map((p) => p.name).toList();
|
||||
|
||||
// Options changed
|
||||
final patchesChanged = defaultPatches
|
||||
.where((p) =>
|
||||
_patches.contains(p) &&
|
||||
p.options.any((o) => _getPatchOptionValue(p.name, o) != o.value))
|
||||
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 = [
|
||||
'- Device Info',
|
||||
'ReVanced Manager: ${info['version']}',
|
||||
'Build: ${info['flavor']}',
|
||||
'Model: ${info['model']}',
|
||||
'Android version: ${info['androidVersion']}',
|
||||
'Supported architectures: ${info['supportedArch'].join(", ")}',
|
||||
@ -261,9 +266,9 @@ class InstallerViewModel extends BaseViewModel {
|
||||
'\n- Patch Info',
|
||||
'App: ${_app.packageName} v${_app.version} (Suggested: ${_getSuggestedVersion(_app.packageName)})',
|
||||
'Patches version: ${_managerAPI.patchesVersion}',
|
||||
'Patches added: ${_formatPatches(patchesAdded)}',
|
||||
'Patches removed: ${_formatPatches(patchesRemoved)}',
|
||||
'Options changed: ${_formatPatches(patchesChanged)}', //
|
||||
'Patches added: ${_formatPatches(patchesAdded, 'Default')}',
|
||||
'Patches removed: ${patchesRemoved.isEmpty ? 'None' : patchesRemoved.join(', ')}',
|
||||
'Default patch options changed: ${_formatPatches(patchesOptionsChanged, 'None')}', //
|
||||
|
||||
'\n- Settings',
|
||||
'Allow changing patch selection: ${_managerAPI.isPatchesChangeEnabled()}',
|
||||
@ -287,26 +292,24 @@ class InstallerViewModel extends BaseViewModel {
|
||||
title: I18nText(
|
||||
'warning',
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
icon: const Icon(Icons.warning),
|
||||
content: SingleChildScrollView(
|
||||
child: I18nText('installerView.screenshotDetected'),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
copyLogs();
|
||||
showPopupScreenshotWarning = true;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -319,11 +322,10 @@ class InstallerViewModel extends BaseViewModel {
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
builder: (innerContext) => AlertDialog(
|
||||
title: I18nText(
|
||||
'installerView.installType',
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
icon: const Icon(Icons.file_download_outlined),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
content: SingleChildScrollView(
|
||||
@ -371,31 +373,68 @@ class InstallerViewModel extends BaseViewModel {
|
||||
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: [
|
||||
CustomMaterialButton(
|
||||
label: I18nText('cancelButton'),
|
||||
isFilled: false,
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(innerContext).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('installerView.installButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(innerContext).pop();
|
||||
installResult(context, installType.value == 1);
|
||||
},
|
||||
child: I18nText('installerView.installButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} 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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,15 +455,16 @@ class InstallerViewModel extends BaseViewModel {
|
||||
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
||||
try {
|
||||
_app.isRooted = installAsRoot;
|
||||
update(
|
||||
1.0,
|
||||
'Installing...',
|
||||
_app.isRooted
|
||||
? 'Installing patched file using root method'
|
||||
: 'Installing patched file using nonroot method',
|
||||
);
|
||||
isInstalled = await _patcherAPI.installPatchedFile(_app);
|
||||
if (isInstalled) {
|
||||
if (headerLogs != 'Installing...') {
|
||||
update(
|
||||
.85,
|
||||
'Installing...',
|
||||
_app.isRooted ? 'Mounting patched app' : 'Installing patched app',
|
||||
);
|
||||
}
|
||||
final int response = await _patcherAPI.installPatchedFile(context, _app);
|
||||
if (response == 0) {
|
||||
isInstalled = true;
|
||||
_app.isFromStorage = false;
|
||||
_app.patchDate = DateTime.now();
|
||||
_app.appliedPatches = _patches.map((p) => p.name).toList();
|
||||
@ -439,10 +479,23 @@ class InstallerViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
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 {
|
||||
// TODO(aabed): Show error message.
|
||||
update(
|
||||
.85,
|
||||
'Installation failed',
|
||||
'Installation failed',
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
|
@ -4,7 +4,6 @@ import 'package:google_fonts/google_fonts.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/widgets/patchesSelectorView/patch_options_fields.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class PatchOptionsView extends StatelessWidget {
|
||||
@ -82,8 +81,7 @@ class PatchOptionsView extends StatelessWidget {
|
||||
model.modifyOptions(value, option);
|
||||
},
|
||||
)
|
||||
else if (option.valueType ==
|
||||
'StringArray' ||
|
||||
else if (option.valueType == 'StringArray' ||
|
||||
option.valueType == 'IntArray' ||
|
||||
option.valueType == 'LongArray')
|
||||
IntStringLongListPatchOption(
|
||||
@ -104,11 +102,11 @@ class PatchOptionsView extends StatelessWidget {
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
CustomMaterialButton(
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
model.showAddOptionDialog(context);
|
||||
},
|
||||
label: Row(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
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/patches_selector/patches_selector_viewmodel.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';
|
||||
|
||||
class PatchOptionsViewModel extends BaseViewModel {
|
||||
@ -32,13 +31,11 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
if (savedOptions.isNotEmpty) {
|
||||
visibleOptions = [
|
||||
...savedOptions,
|
||||
...options
|
||||
.where(
|
||||
(option) =>
|
||||
option.required &&
|
||||
!savedOptions.any((sOption) => sOption.key == option.key),
|
||||
)
|
||||
,
|
||||
...options.where(
|
||||
(option) =>
|
||||
option.required &&
|
||||
!savedOptions.any((sOption) => sOption.key == option.key),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
visibleOptions = [
|
||||
@ -136,7 +133,6 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -154,11 +150,11 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
label: I18nText('cancelButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
],
|
||||
contentPadding: const EdgeInsets.all(8),
|
||||
@ -227,14 +223,9 @@ Future<void> showRequiredOptionNullDialog(
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('notice'),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText(
|
||||
'patchOptionsView.deselectPatch',
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (managerAPI.isPatchesChangeEnabled()) {
|
||||
locator<PatcherViewModel>()
|
||||
@ -256,12 +247,13 @@ Future<void> showRequiredOptionNullDialog(
|
||||
PatchesSelectorViewModel().showPatchesChangeDialog(context);
|
||||
}
|
||||
},
|
||||
child: I18nText('patchOptionsView.deselectPatch'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
content: I18nText(
|
||||
|
@ -22,7 +22,7 @@ class PatcherView extends StatelessWidget {
|
||||
child: FloatingActionButton.extended(
|
||||
label: I18nText('patcherView.patchButton'),
|
||||
icon: const Icon(Icons.build),
|
||||
onPressed: () async{
|
||||
onPressed: () async {
|
||||
if (model.checkRequiredPatchOption(context)) {
|
||||
final bool proceed = model.showRemovedPatchesDialog(context);
|
||||
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/services/manager_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/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
@ -56,25 +55,23 @@ class PatcherViewModel extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('notice'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(
|
||||
'patcherView.removedPatchesWarningDialogText',
|
||||
translationParams: {'patches': removedPatches.join('\n')},
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
showArmv7WarningDialog(context);
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -98,22 +95,20 @@ class PatcherViewModel extends BaseViewModel {
|
||||
context: context ?? ctx,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('notice'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patcherView.requiredOptionDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
TextButton(
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () => {
|
||||
Navigator.pop(context),
|
||||
navigateToPatchesSelector(),
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -131,20 +126,18 @@ class PatcherViewModel extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patcherView.armv7WarningDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('noButton'),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
isFilled: false,
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
navigateToInstaller();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
model.getPatchCategory(context, 'patchesSelectorView.newPatches'),
|
||||
model.getPatchCategory(
|
||||
context,
|
||||
'patchesSelectorView.newPatches',
|
||||
),
|
||||
...model.getQueriedPatches(_query).map((patch) {
|
||||
if (model.isPatchNew(patch)) {
|
||||
return model.getPatchItem(context, patch);
|
||||
@ -192,26 +197,40 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
return Container();
|
||||
}
|
||||
}),
|
||||
if (model.getQueriedPatches(_query).any((patch) => !model.isPatchNew(patch) && patch.compatiblePackages.isNotEmpty))
|
||||
model.getPatchCategory(context, 'patchesSelectorView.patches'),
|
||||
if (model.getQueriedPatches(_query).any(
|
||||
(patch) =>
|
||||
!model.isPatchNew(patch) &&
|
||||
patch.compatiblePackages.isNotEmpty,
|
||||
))
|
||||
model.getPatchCategory(
|
||||
context,
|
||||
'patchesSelectorView.patches',
|
||||
),
|
||||
],
|
||||
),
|
||||
...model.getQueriedPatches(_query).map(
|
||||
(patch) {
|
||||
if (patch.compatiblePackages.isNotEmpty && !model.isPatchNew(patch)) {
|
||||
if (patch.compatiblePackages.isNotEmpty &&
|
||||
!model.isPatchNew(patch)) {
|
||||
return model.getPatchItem(context, patch);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (model.getQueriedPatches(_query).any((patch) => patch.compatiblePackages.isEmpty))
|
||||
if (model
|
||||
.getQueriedPatches(_query)
|
||||
.any((patch) => patch.compatiblePackages.isEmpty))
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
model.getPatchCategory(context, 'patchesSelectorView.universalPatches'),
|
||||
model.getPatchCategory(
|
||||
context,
|
||||
'patchesSelectorView.universalPatches',
|
||||
),
|
||||
...model.getQueriedPatches(_query).map((patch) {
|
||||
if (patch.compatiblePackages.isEmpty && !model.isPatchNew(patch)) {
|
||||
if (patch.compatiblePackages.isEmpty &&
|
||||
!model.isPatchNew(patch)) {
|
||||
return model.getPatchItem(context, patch);
|
||||
} else {
|
||||
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/ui/views/patcher/patcher_viewmodel.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:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
@ -94,7 +93,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('notice'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(
|
||||
'patchesSelectorView.setRequiredOption',
|
||||
translationParams: {
|
||||
@ -102,11 +100,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
},
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -130,7 +128,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('warning'),
|
||||
content: I18nText(
|
||||
'patchItem.patchesChangeWarningDialogText',
|
||||
@ -143,18 +140,17 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('okButton'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('patchItem.patchesChangeWarningDialogButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
..pop();
|
||||
},
|
||||
child: I18nText('patchItem.patchesChangeWarningDialogButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -188,10 +184,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
void selectPatches() {
|
||||
locator<PatcherViewModel>().selectedPatches = selectedPatches;
|
||||
saveSelectedPatches();
|
||||
if (_managerAPI.ctx != null) {
|
||||
Navigator.pop(_managerAPI.ctx!);
|
||||
_managerAPI.ctx = null;
|
||||
}
|
||||
locator<PatcherViewModel>().notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ import 'package:revanced_manager/services/manager_api.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/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageApiUrl extends BaseViewModel {
|
||||
@ -33,7 +32,6 @@ class SManageApiUrl extends BaseViewModel {
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
@ -51,16 +49,14 @@ class SManageApiUrl extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_apiUrlController.clear();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
String apiUrl = _apiUrlController.text;
|
||||
if (!apiUrl.startsWith('https')) {
|
||||
@ -70,6 +66,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -81,16 +78,13 @@ class SManageApiUrl extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('settingsView.sourcesResetDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('settingsView.apiURLResetDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_managerAPI.setApiUrl('');
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
@ -98,6 +92,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||
..pop()
|
||||
..pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -6,7 +6,6 @@ import 'package:revanced_manager/app/app.locator.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/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageKeystorePassword extends BaseViewModel {
|
||||
@ -33,7 +32,6 @@ class SManageKeystorePassword extends BaseViewModel {
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
@ -47,21 +45,20 @@ class SManageKeystorePassword extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_keystorePasswordController.clear();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
final String passwd = _keystorePasswordController.text;
|
||||
_managerAPI.setKeystorePassword(passwd);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -7,7 +7,6 @@ import 'package:revanced_manager/services/manager_api.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/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageSources extends BaseViewModel {
|
||||
@ -43,7 +42,6 @@ class SManageSources extends BaseViewModel {
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
@ -107,9 +105,7 @@ class SManageSources extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_orgPatSourceController.clear();
|
||||
_patSourceController.clear();
|
||||
@ -117,9 +113,9 @@ class SManageSources extends BaseViewModel {
|
||||
_intSourceController.clear();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
|
||||
_managerAPI.setPatchesRepo(
|
||||
@ -133,6 +129,7 @@ class SManageSources extends BaseViewModel {
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -144,16 +141,13 @@ class SManageSources extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('settingsView.sourcesResetDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('settingsView.sourcesResetDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_managerAPI.setRepoUrl('');
|
||||
_managerAPI.setPatchesRepo('');
|
||||
@ -165,6 +159,7 @@ class SManageSources extends BaseViewModel {
|
||||
..pop()
|
||||
..pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -51,7 +51,6 @@ class SUpdateLanguage extends BaseViewModel {
|
||||
context: parentContext,
|
||||
builder: (context) => SimpleDialog(
|
||||
title: I18nText('settingsView.languageLabel'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
children: [
|
||||
SizedBox(
|
||||
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/services/manager_api.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 {
|
||||
const SUpdateThemeUI({super.key});
|
||||
@ -36,9 +35,9 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: CustomMaterialButton(
|
||||
label: getThemeModeName(),
|
||||
trailing: FilledButton(
|
||||
onPressed: () => {showThemeDialog(context)},
|
||||
child: getThemeModeName(),
|
||||
),
|
||||
onTap: () => {showThemeDialog(context)},
|
||||
),
|
||||
@ -122,7 +121,6 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
title: I18nText('settingsView.themeModeLabel'),
|
||||
icon: const Icon(Icons.palette),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: newTheme,
|
||||
@ -164,19 +162,18 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
setThemeMode(context, newTheme.value);
|
||||
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/patches_selector/patches_selector_viewmodel.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:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
@ -52,7 +51,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('warning'),
|
||||
content: I18nText(
|
||||
'settingsView.enablePatchesSelectionWarningText',
|
||||
@ -65,20 +63,19 @@ class SettingsViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('yesButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_managerAPI.setChangingToggleModified(true);
|
||||
_managerAPI.setPatchesChangeEnabled(true);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('noButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -87,7 +84,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('warning'),
|
||||
content: I18nText(
|
||||
'settingsView.disablePatchesSelectionWarningText',
|
||||
@ -100,21 +96,20 @@ class SettingsViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_managerAPI.setChangingToggleModified(true);
|
||||
_patchesSelectorViewModel.selectDefaultPatches();
|
||||
_managerAPI.setPatchesChangeEnabled(false);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -145,12 +140,13 @@ class SettingsViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<void>? showRequireSuggestedAppVersionDialog(
|
||||
BuildContext context, bool value,) {
|
||||
BuildContext context,
|
||||
bool value,
|
||||
) {
|
||||
if (!value) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
title: I18nText('warning'),
|
||||
content: I18nText(
|
||||
'settingsView.requireSuggestedAppVersionDialogText',
|
||||
@ -163,19 +159,18 @@ class SettingsViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('yesButton'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_managerAPI.enableRequireSuggestedAppVersionStatus(false);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('noButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -210,7 +205,7 @@ class SettingsViewModel extends BaseViewModel {
|
||||
final String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
await FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
fileName: 'selected_patches_$dateTime.json',
|
||||
),
|
||||
@ -261,7 +256,7 @@ class SettingsViewModel extends BaseViewModel {
|
||||
final String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
await FlutterFileDialog.saveFile(
|
||||
params: SaveFileDialogParams(
|
||||
params: SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
fileName: 'keystore_$dateTime.keystore',
|
||||
),
|
||||
|
@ -184,7 +184,7 @@ class AppInfoView extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
I18nText(
|
||||
'appInfoView.unpatchButton',
|
||||
'appInfoView.unmountButton',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
@ -236,8 +236,8 @@ class AppInfoView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
subtitle: app.isRooted
|
||||
? I18nText('appInfoView.rootTypeLabel')
|
||||
: I18nText('appInfoView.nonRootTypeLabel'),
|
||||
? I18nText('appInfoView.mountTypeLabel')
|
||||
: I18nText('appInfoView.regularTypeLabel'),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
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/navigation/navigation_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';
|
||||
|
||||
class AppInfoViewModel extends BaseViewModel {
|
||||
@ -26,18 +25,17 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
PatchedApplication app,
|
||||
bool onlyUnpatch,
|
||||
) async {
|
||||
bool isUninstalled = true;
|
||||
if (app.isRooted) {
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
||||
if (!onlyUnpatch) {
|
||||
await DeviceApps.uninstallApp(app.packageName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var isUninstalled = onlyUnpatch;
|
||||
|
||||
if (!onlyUnpatch) {
|
||||
// TODO(Someone): Wait for the app to uninstall successfully.
|
||||
isUninstalled = await DeviceApps.uninstallApp(app.packageName);
|
||||
}
|
||||
|
||||
if (isUninstalled && app.isRooted && await _rootAPI.hasRootPermissions()) {
|
||||
await _rootAPI.uninstall(app.packageName);
|
||||
}
|
||||
|
||||
if (isUninstalled) {
|
||||
await _managerAPI.deletePatchedApp(app);
|
||||
locator<HomeViewModel>().initialize(context);
|
||||
@ -67,12 +65,11 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('appInfoView.rootDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('appInfoView.rootDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -83,32 +80,53 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText(
|
||||
'appInfoView.unpatchButton',
|
||||
'appInfoView.unmountButton',
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(
|
||||
'appInfoView.unpatchDialogText',
|
||||
'appInfoView.unmountDialogText',
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
uninstallApp(context, app, onlyUnpatch);
|
||||
uninstallApp(context, app, true);
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
uninstallApp(context, app, onlyUnpatch);
|
||||
Navigator.of(context).pop();
|
||||
return showDialog(
|
||||
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,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('appInfoView.appliedPatchesLabel'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Text(getAppliedPatchesString(app.appliedPatches)),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
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/ui/views/home/home_viewmodel.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 {
|
||||
const LatestCommitCard({
|
||||
@ -58,14 +57,14 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
child: FilledButton(
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? () => widget.model.showUpdateConfirmationDialog(
|
||||
widget.parentContext,
|
||||
false,
|
||||
)
|
||||
: () => {},
|
||||
child: I18nText('updateButton'),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -113,14 +112,14 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
child: FilledButton(
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? () => widget.model.showUpdateConfirmationDialog(
|
||||
widget.parentContext,
|
||||
true,
|
||||
)
|
||||
: () => {},
|
||||
child: I18nText('updateButton'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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 {
|
||||
const UpdateConfirmationDialog({super.key, required this.isPatches});
|
||||
@ -86,15 +85,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
isExpanded: true,
|
||||
label: I18nText('updateButton'),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
isPatches
|
||||
? model.updatePatches(context)
|
||||
: model.updateManager(context);
|
||||
},
|
||||
child: I18nText('updateButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -58,7 +58,8 @@ class PatchSelectorCard extends StatelessWidget {
|
||||
|
||||
String _getPatchesSelection() {
|
||||
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));
|
||||
for (final Patch p in selectedPatches) {
|
||||
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/toast.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
|
||||
class PatchItem extends StatefulWidget {
|
||||
@ -216,7 +215,6 @@ class _PatchItemState extends State<PatchItem> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(
|
||||
'patchItem.unsupportedDialogText',
|
||||
translationParams: {
|
||||
@ -226,9 +224,9 @@ class _PatchItemState extends State<PatchItem> {
|
||||
},
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -240,14 +238,13 @@ class _PatchItemState extends State<PatchItem> {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('notice'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(
|
||||
'patchItem.unsupportedRequiredOption',
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
FilledButton(
|
||||
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 isArrayOption = widget.optionType.contains('Array');
|
||||
selectedKey ??= widget.selectedKey;
|
||||
controller.text = !isStringOption && isArrayOption && selectedKey == '' &&
|
||||
controller.text = !isStringOption &&
|
||||
isArrayOption &&
|
||||
selectedKey == '' &&
|
||||
(widget.value != null && widget.value.toString().startsWith('['))
|
||||
? ''
|
||||
: widget.value ?? '';
|
||||
@ -519,7 +521,8 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||
}
|
||||
break;
|
||||
case 'patchOptionsView.selectFolder':
|
||||
final DirectoryLocation? result = await FlutterFileDialog.pickDirectory();
|
||||
final DirectoryLocation? result =
|
||||
await FlutterFileDialog.pickDirectory();
|
||||
if (result != null) {
|
||||
controller.text = result.toString();
|
||||
widget.onChanged(controller.text);
|
||||
|
@ -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_version_compatibility_check.dart';
|
||||
|
||||
|
||||
class SAdvancedSection extends StatelessWidget {
|
||||
const SAdvancedSection({super.key});
|
||||
|
||||
|
@ -6,7 +6,8 @@ class SEnablePatchesSelection extends StatefulWidget {
|
||||
const SEnablePatchesSelection({super.key});
|
||||
|
||||
@override
|
||||
State<SEnablePatchesSelection> createState() => _SEnablePatchesSelectionState();
|
||||
State<SEnablePatchesSelection> createState() =>
|
||||
_SEnablePatchesSelectionState();
|
||||
}
|
||||
|
||||
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/settings_viewmodel.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();
|
||||
|
||||
@ -151,20 +150,18 @@ class SExportSection extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText(dialogTitle),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(dialogText),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
dialogAction(),
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -176,20 +173,18 @@ class SExportSection extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('settingsView.regenerateKeystoreDialogTitle'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('settingsView.regenerateKeystoreDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: I18nText('noButton'),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
FilledButton(
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
_settingsViewModel.deleteKeystore(),
|
||||
},
|
||||
child: I18nText('yesButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -6,12 +6,14 @@ class SRequireSuggestedAppVersion extends StatefulWidget {
|
||||
const SRequireSuggestedAppVersion({super.key});
|
||||
|
||||
@override
|
||||
State<SRequireSuggestedAppVersion> createState() => _SRequireSuggestedAppVersionState();
|
||||
State<SRequireSuggestedAppVersion> createState() =>
|
||||
_SRequireSuggestedAppVersionState();
|
||||
}
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
|
||||
class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersion> {
|
||||
class _SRequireSuggestedAppVersionState
|
||||
extends State<SRequireSuggestedAppVersion> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
@ -29,8 +31,11 @@ class _SRequireSuggestedAppVersionState extends State<SRequireSuggestedAppVersio
|
||||
subtitle: I18nText('settingsView.requireSuggestedAppVersionHint'),
|
||||
value: _settingsViewModel.isRequireSuggestedAppVersionEnabled(),
|
||||
onChanged: (value) async {
|
||||
await _settingsViewModel.showRequireSuggestedAppVersionDialog(context, value,);
|
||||
setState(() {});
|
||||
await _settingsViewModel.showRequireSuggestedAppVersionDialog(
|
||||
context,
|
||||
value,
|
||||
);
|
||||
setState(() {});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -8,16 +8,14 @@ class SUniversalPatches extends StatefulWidget {
|
||||
const SUniversalPatches({super.key});
|
||||
|
||||
@override
|
||||
State<SUniversalPatches> createState() =>
|
||||
_SUniversalPatchesState();
|
||||
State<SUniversalPatches> createState() => _SUniversalPatchesState();
|
||||
}
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||
final _patcherViewModel = PatcherViewModel();
|
||||
|
||||
class _SUniversalPatchesState
|
||||
extends State<SUniversalPatches> {
|
||||
class _SUniversalPatchesState extends State<SUniversalPatches> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
|
@ -9,14 +9,16 @@ class SVersionCompatibilityCheck extends StatefulWidget {
|
||||
const SVersionCompatibilityCheck({super.key});
|
||||
|
||||
@override
|
||||
State<SVersionCompatibilityCheck> createState() => _SVersionCompatibilityCheckState();
|
||||
State<SVersionCompatibilityCheck> createState() =>
|
||||
_SVersionCompatibilityCheckState();
|
||||
}
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
final _patchesSelectorViewModel = PatchesSelectorViewModel();
|
||||
final _patcherViewModel = PatcherViewModel();
|
||||
|
||||
class _SVersionCompatibilityCheckState extends State<SVersionCompatibilityCheck> {
|
||||
class _SVersionCompatibilityCheckState
|
||||
extends State<SVersionCompatibilityCheck> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
|
@ -3,7 +3,6 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.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_material_button.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
|
||||
class ApplicationItem extends StatefulWidget {
|
||||
@ -24,7 +23,6 @@ class ApplicationItem extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ApplicationItemState extends State<ApplicationItem> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -81,9 +79,9 @@ class _ApplicationItemState extends State<ApplicationItem> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('applicationItem.infoButton'),
|
||||
FilledButton(
|
||||
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) {
|
||||
if (option.required &&
|
||||
option.value == null &&
|
||||
locator<ManagerAPI>()
|
||||
.getPatchOption(
|
||||
locator<PatcherViewModel>().selectedApp!.packageName,
|
||||
patch.name,
|
||||
option.key,
|
||||
) == null) {
|
||||
locator<ManagerAPI>().getPatchOption(
|
||||
locator<PatcherViewModel>().selectedApp!.packageName,
|
||||
patch.name,
|
||||
option.key,
|
||||
) ==
|
||||
null) {
|
||||
requiredOptionsType.add(option.valueType);
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,8 @@ List<Option> getNullRequiredOptions(List<Patch> patches, String packageName) {
|
||||
patchOption.required &&
|
||||
patchOption.value == null &&
|
||||
locator<ManagerAPI>()
|
||||
.getPatchOption(packageName, patch.name, patchOption.key) == null) {
|
||||
.getPatchOption(packageName, patch.name, patchOption.key) ==
|
||||
null) {
|
||||
requiredNullOptions.add(patchOption);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ homepage: https://github.com/ReVanced/revanced-manager
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.17.3+101700300
|
||||
version: 1.18.0+101800000
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@ -46,7 +46,7 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/BenjaminHalko/logcat
|
||||
ref: 4a6d5e0e22292c8eb160cfb9365b9ea29735fd43 # Branch: master
|
||||
package_info_plus: ^4.2.0
|
||||
package_info_plus: ^5.0.1
|
||||
path_provider: ^2.0.14
|
||||
permission_handler: ^11.0.1
|
||||
pull_to_refresh: ^2.0.0
|
||||
@ -65,10 +65,6 @@ dependencies:
|
||||
flutter_dotenv: ^5.0.2
|
||||
flutter_markdown: ^0.6.14
|
||||
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: 5f9b1a8c956fc3355ae655eefcbcadb457bd10f7 # Branch: master
|
||||
screenshot_callback:
|
||||
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
||||
|
Loading…
Reference in New Issue
Block a user