chore: merge dev to main (#1573)

This commit is contained in:
oSumAtrIX 2023-12-23 22:43:10 +01:00 committed by GitHub
commit bb1b0da749
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 967 additions and 737 deletions

View file

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

@ -9,6 +9,7 @@
.buildlog/
.history
.svn/
local.properties
# IntelliJ related
*.iml

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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."
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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'),
),
],
),

View file

@ -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';
}
}
}

View file

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

View file

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

View file

@ -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),
],
),

View file

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

View file

@ -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');
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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'),
),
],
),

View file

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

View file

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

View file

@ -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'),
),
],
),

View file

@ -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'),
),
],
),

View file

@ -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'),
),
],
),

View file

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

View file

@ -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'),
),
],
),

View file

@ -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',
),

View file

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

View file

@ -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'),
),
],
),

View file

@ -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'),
),
),
),

View file

@ -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'),
),
],
),

View file

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

View file

@ -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'),
),
],
),

View file

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

View file

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

View file

@ -6,7 +6,8 @@ class SEnablePatchesSelection extends StatefulWidget {
const SEnablePatchesSelection({super.key});
@override
State<SEnablePatchesSelection> createState() => _SEnablePatchesSelectionState();
State<SEnablePatchesSelection> createState() =>
_SEnablePatchesSelectionState();
}
final _settingsViewModel = SettingsViewModel();

View file

@ -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'),
),
],
),

View file

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

View file

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

View file

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

View file

@ -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'),
),
],
),

View file

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

View file

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

View file

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