build: Bump dependencies (#1311)

This commit is contained in:
oSumAtrIX 2023-10-01 19:03:26 +02:00 committed by GitHub
commit 76b89baee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 199 additions and 554 deletions

View File

@ -85,7 +85,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced // ReVanced
implementation "app.revanced:revanced-patcher:14.2.2" implementation "app.revanced:revanced-patcher:15.0.3"
// Signing & aligning // Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70")

View File

@ -10,20 +10,18 @@ import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.include
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.json.JSONArray
import org.json.JSONObject
import java.io.File import java.io.File
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.lang.Error
import java.util.logging.LogRecord import java.util.logging.LogRecord
import java.util.logging.Logger import java.util.logging.Logger
@ -93,29 +91,42 @@ class MainActivity : FlutterActivity() {
} }
"getPatches" -> { "getPatches" -> {
val patchBundleFilePath = call.argument<String>("patchBundleFilePath") val patchBundleFilePath = call.argument<String>("patchBundleFilePath")!!
val cacheDirPath = call.argument<String>("cacheDirPath") val cacheDirPath = call.argument<String>("cacheDirPath")!!
if (patchBundleFilePath != null) { JSONArray().apply {
val patches = PatchBundleLoader.Dex( try {
PatchBundleLoader.Dex(
File(patchBundleFilePath), File(patchBundleFilePath),
optimizedDexDirectory = File(cacheDirPath) optimizedDexDirectory = File(cacheDirPath)
).map { patch -> )
val map = HashMap<String, Any>() } catch (ex: Exception) {
map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\"" return@setMethodCallHandler result.notImplemented()
map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\"" } catch (err: Error) {
map["\"excluded\""] = !patch.include return@setMethodCallHandler result.notImplemented()
map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList<Any>() }.forEach {
map["\"compatiblePackages\""] = patch.compatiblePackages?.map { JSONObject().apply {
val map2 = HashMap<String, Any>() put("name", it.name)
map2["\"name\""] = "\"${it.name}\"" put("description", it.description)
map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" } put("excluded", !it.use)
map2 put("compatiblePackages", JSONArray().apply {
} ?: emptyList<Any>() it.compatiblePackages?.forEach { compatiblePackage ->
map val compatiblePackageJson = JSONObject().apply {
put("name", compatiblePackage.name)
put(
"versions",
JSONArray().apply {
compatiblePackage.versions?.forEach { version ->
put(version)
} }
result.success(patches) })
} else result.notImplemented() }
put(compatiblePackageJson)
}
})
}.let(::put)
}
}.toString().let(result::success)
} }
else -> result.notImplemented() else -> result.notImplemented()
@ -168,8 +179,11 @@ class MainActivity : FlutterActivity() {
} }
object : java.util.logging.Handler() { object : java.util.logging.Handler() {
override fun publish(record: LogRecord) = override fun publish(record: LogRecord) {
if (record.loggerName?.startsWith("app.revanced") != true) return
updateProgress(-1.0, "", record.message) updateProgress(-1.0, "", record.message)
}
override fun flush() = Unit override fun flush() = Unit
override fun close() = flush() override fun close() = flush()
@ -220,7 +234,7 @@ class MainActivity : FlutterActivity() {
val compatibleOrUniversal = val compatibleOrUniversal =
isCompatible || patch.compatiblePackages.isNullOrEmpty() isCompatible || patch.compatiblePackages.isNullOrEmpty()
compatibleOrUniversal && selectedPatches.any { it == patch.patchName } compatibleOrUniversal && selectedPatches.any { it == patch.name }
} }
if (cancel) { if (cancel) {
@ -251,9 +265,9 @@ class MainActivity : FlutterActivity() {
val msg = patchResult.exception?.let { val msg = patchResult.exception?.let {
val writer = StringWriter() val writer = StringWriter()
it.printStackTrace(PrintWriter(writer)) it.printStackTrace(PrintWriter(writer))
"${patchResult.patchName} failed: $writer" "${patchResult.patch.name} failed: $writer"
} ?: run { } ?: run {
"${patchResult.patchName} succeeded" "${patchResult.patch.name} succeeded"
} }
updateProgress(progress, "", msg) updateProgress(progress, "", msg)

View File

@ -23,13 +23,13 @@
"widgetTitle": "Dashboard", "widgetTitle": "Dashboard",
"updatesSubtitle": "Updates", "updatesSubtitle": "Updates",
"patchedSubtitle": "Patched applications", "patchedSubtitle": "Patched apps",
"noUpdates": "No updates available", "noUpdates": "No updates available",
"WIP": "Work in progress...", "WIP": "Work in progress...",
"noInstallations": "No patched applications installed", "noInstallations": "No patched apps installed",
"installUpdate": "Continue to install the update?", "installUpdate": "Continue to install the update?",
"updateDialogTitle": "Update Manager", "updateDialogTitle": "Update Manager",
@ -56,9 +56,7 @@
"updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again." "updatesDisabled": "Updating a patched app is currently disabled. Repatch the app again."
}, },
"applicationItem": { "applicationItem": {
"patchButton": "Patch", "infoButton": "Info"
"infoButton": "Info",
"changelogLabel": "Changelog"
}, },
"latestCommitCard": { "latestCommitCard": {
"loadingLabel": "Loading...", "loadingLabel": "Loading...",
@ -71,9 +69,8 @@
"widgetTitle": "Patcher", "widgetTitle": "Patcher",
"patchButton": "Patch", "patchButton": "Patch",
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?", "armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
"splitApkWarningDialogText": "Patching a split APK is not yet supported and might fail. Proceed anyways?",
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?" "removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?"
}, },
"appSelectorCard": { "appSelectorCard": {
@ -148,9 +145,8 @@
"installTypeDescription": "Select the installation type to proceed with.", "installTypeDescription": "Select the installation type to proceed with.",
"installButton": "Install", "installButton": "Install",
"installRootType": "Root", "installRootType": "Mount",
"installNonRootType": "Non-root", "installNonRootType": "Normal",
"installRecommendedType": "Recommended",
"pressBackAgain": "Press back again to cancel", "pressBackAgain": "Press back again to cancel",
"openButton": "Open", "openButton": "Open",
@ -162,10 +158,6 @@
"exportApkButtonTooltip": "Export patched APK", "exportApkButtonTooltip": "Export patched APK",
"exportLogButtonTooltip": "Export log", "exportLogButtonTooltip": "Export log",
"installErrorDialogTitle": "Error",
"installErrorDialogText1": "Root install is not possible with the current patches selection.\nRepatch your app or choose non-root install.",
"installErrorDialogText2": "Non-root install is not possible with the current patches selection.\nRepatch your app or choose root install if you have your device rooted.",
"installErrorDialogText3": "Root install is not possible as the original APK was selected from storage.\nSelect an installed app or choose non-root install.",
"noExit": "Installer is still running, cannot exit..." "noExit": "Installer is still running, cannot exit..."
}, },
"settingsView": { "settingsView": {
@ -285,7 +277,6 @@
"rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.", "rootDialogText": "App was installed with superuser permissions, but currently ReVanced Manager has no permissions.\nPlease grant superuser permissions first.",
"packageNameLabel": "Package name", "packageNameLabel": "Package name",
"originalPackageNameLabel": "Original package name",
"installTypeLabel": "Installation type", "installTypeLabel": "Installation type",
"rootTypeLabel": "Root", "rootTypeLabel": "Root",
"nonRootTypeLabel": "Non-root", "nonRootTypeLabel": "Non-root",

View File

@ -1,5 +1,4 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:revanced_manager/utils/string.dart';
part 'patch.g.dart'; part 'patch.g.dart';
@ -9,26 +8,19 @@ class Patch {
required this.name, required this.name,
required this.description, required this.description,
required this.excluded, required this.excluded,
required this.dependencies,
required this.compatiblePackages, required this.compatiblePackages,
}); });
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json); factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
final String name; final String name;
final String description; final String? description;
final bool excluded; final bool excluded;
final List<String> dependencies;
final List<Package> compatiblePackages; final List<Package> compatiblePackages;
Map<String, dynamic> toJson() => _$PatchToJson(this); Map<String, dynamic> toJson() => _$PatchToJson(this);
String getSimpleName() { String getSimpleName() {
return name return name;
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG');
} }
} }

View File

@ -9,23 +9,19 @@ class PatchedApplication {
PatchedApplication({ PatchedApplication({
required this.name, required this.name,
required this.packageName, required this.packageName,
required this.originalPackageName,
required this.version, required this.version,
required this.apkFilePath, required this.apkFilePath,
required this.icon, required this.icon,
required this.patchDate, required this.patchDate,
this.isRooted = false, this.isRooted = false,
this.isFromStorage = false, this.isFromStorage = false,
this.hasUpdates = false,
this.appliedPatches = const [], this.appliedPatches = const [],
this.changelog = const [],
}); });
factory PatchedApplication.fromJson(Map<String, dynamic> json) => factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
_$PatchedApplicationFromJson(json); _$PatchedApplicationFromJson(json);
String name; String name;
String packageName; String packageName;
String originalPackageName;
String version; String version;
final String apkFilePath; final String apkFilePath;
@JsonKey( @JsonKey(
@ -36,9 +32,7 @@ class PatchedApplication {
DateTime patchDate; DateTime patchDate;
bool isRooted; bool isRooted;
bool isFromStorage; bool isFromStorage;
bool hasUpdates;
List<String> appliedPatches; List<String> appliedPatches;
List<String> changelog;
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this); Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@ -7,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
@lazySingleton @lazySingleton
@ -21,17 +19,6 @@ class GithubAPI {
priority: CachePriority.high, priority: CachePriority.high,
); );
final Map<String, String> repoAppPath = {
'com.google.android.youtube': 'youtube',
'com.google.android.apps.youtube.music': 'music',
'com.twitter.android': 'twitter',
'com.reddit.frontpage': 'reddit',
'com.zhiliaoapp.musically': 'tiktok',
'de.dwd.warnapp': 'warnwetter',
'com.garzotto.pflotsh.ecmwf_a': 'ecmwf',
'com.spotify.music': 'spotify',
};
Future<void> initialize(String repoUrl) async { Future<void> initialize(String repoUrl) async {
try { try {
_dio = Dio( _dio = Dio(
@ -142,38 +129,6 @@ class GithubAPI {
} }
} }
Future<List<String>> getCommits(
String packageName,
String repoName,
DateTime since,
) async {
final String path =
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
try {
final response = await _dio.get(
'/repos/$repoName/commits',
queryParameters: {
'path': path,
'since': since.toIso8601String(),
},
);
final List<dynamic> commits = response.data;
return commits
.map(
(commit) => commit['commit']['message'].split('\n')[0] +
' - ' +
commit['commit']['author']['name'] +
'\n' as String,
)
.toList();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
return [];
}
Future<File?> getLatestReleaseFile( Future<File?> getLatestReleaseFile(
String extension, String extension,
String repoName, String repoName,
@ -237,30 +192,4 @@ class GithubAPI {
} }
return null; return null;
} }
Future<List<Patch>> getPatches(
String repoName,
String version,
String url,
) async {
List<Patch> patches = [];
try {
final File? f = await getPatchesReleaseFile(
'.json',
repoName,
version,
url,
);
if (f != null) {
final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
return patches;
}
} }

View File

@ -42,12 +42,14 @@ class ManagerAPI {
String defaultManagerRepo = 'revanced/revanced-manager'; String defaultManagerRepo = 'revanced/revanced-manager';
String? patchesVersion = ''; String? patchesVersion = '';
String? integrationsVersion = ''; String? integrationsVersion = '';
bool isDefaultPatchesRepo() { bool isDefaultPatchesRepo() {
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches'; return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
} }
bool isDefaultIntegrationsRepo() { bool isDefaultIntegrationsRepo() {
return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations'; return getIntegrationsRepo().toLowerCase() ==
'revanced/revanced-integrations';
} }
Future<void> initialize() async { Future<void> initialize() async {
@ -315,18 +317,18 @@ class ManagerAPI {
if (patchBundleFile != null) { if (patchBundleFile != null) {
try { try {
final patchesObject = await PatcherAPI.patcherChannel.invokeMethod( final String patchesJson = await PatcherAPI.patcherChannel.invokeMethod(
'getPatches', 'getPatches',
{ {
'patchBundleFilePath': patchBundleFile.path, 'patchBundleFilePath': patchBundleFile.path,
'cacheDirPath': cacheDir.path, 'cacheDirPath': cacheDir.path,
}, },
); );
final List<Map<String, dynamic>> patchesMap = [];
patchesObject.forEach((patch) { final List<dynamic> patchesJsonList = jsonDecode(patchesJson);
patchesMap.add(jsonDecode('$patch')); patches = patchesJsonList
}); .map((patchJson) => Patch.fromJson(patchJson))
patches = patchesMap.map((patch) => Patch.fromJson(patch)).toList(); .toList();
return patches; return patches;
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
@ -503,25 +505,21 @@ class ManagerAPI {
return toRemove; return toRemove;
} }
Future<List<PatchedApplication>> getUnsavedApps( Future<List<PatchedApplication>> getMountedApps() async {
List<PatchedApplication> patchedApps, final List<PatchedApplication> mountedApps = [];
) async {
final List<PatchedApplication> unsavedApps = [];
final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) { if (hasRootPermissions) {
final List<String> installedApps = await _rootAPI.getInstalledApps(); final List<String> installedApps = await _rootAPI.getInstalledApps();
for (final String packageName in installedApps) { for (final String packageName in installedApps) {
if (!patchedApps.any((app) => app.packageName == packageName)) {
final ApplicationWithIcon? application = await DeviceApps.getApp( final ApplicationWithIcon? application = await DeviceApps.getApp(
packageName, packageName,
true, true,
) as ApplicationWithIcon?; ) as ApplicationWithIcon?;
if (application != null) { if (application != null) {
unsavedApps.add( mountedApps.add(
PatchedApplication( PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: application.apkFilePath, apkFilePath: application.apkFilePath,
icon: application.icon, icon: application.icon,
@ -532,33 +530,8 @@ class ManagerAPI {
} }
} }
} }
}
final List<Application> userApps = return mountedApps;
await DeviceApps.getInstalledApplications();
for (final Application app in userApps) {
if (app.packageName.startsWith('app.revanced') &&
!app.packageName.startsWith('app.revanced.manager.') &&
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
final ApplicationWithIcon? application = await DeviceApps.getApp(
app.packageName,
true,
) as ApplicationWithIcon?;
if (application != null) {
unsavedApps.add(
PatchedApplication(
name: application.appName,
packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!,
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
),
);
}
}
}
return unsavedApps;
} }
Future<void> showPatchesChangeWarningDialog(BuildContext context) { Future<void> showPatchesChangeWarningDialog(BuildContext context) {
@ -620,34 +593,20 @@ class ManagerAPI {
Future<void> reAssessSavedApps() async { Future<void> reAssessSavedApps() async {
final List<PatchedApplication> patchedApps = getPatchedApps(); final List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> unsavedApps =
await getUnsavedApps(patchedApps); // Remove apps that are not installed anymore.
patchedApps.addAll(unsavedApps);
final List<PatchedApplication> toRemove = final List<PatchedApplication> toRemove =
await getAppsToRemove(patchedApps); await getAppsToRemove(patchedApps);
patchedApps.removeWhere((a) => toRemove.contains(a)); patchedApps.removeWhere((a) => toRemove.contains(a));
for (final PatchedApplication app in patchedApps) {
app.hasUpdates = // Determine all apps that are installed by mounting.
await hasAppUpdates(app.originalPackageName, app.patchDate); final List<PatchedApplication> mountedApps = await getMountedApps();
app.changelog = mountedApps.removeWhere(
await getAppChangelog(app.originalPackageName, app.patchDate); (app) => patchedApps
if (!app.hasUpdates) { .any((patchedApp) => patchedApp.packageName == app.packageName),
final String? currentInstalledVersion =
(await DeviceApps.getApp(app.packageName))?.versionName;
if (currentInstalledVersion != null) {
final String currentSavedVersion = app.version;
final int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
); );
final int currentSavedVersionInt = int.parse( patchedApps.addAll(mountedApps);
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
);
if (currentInstalledVersionInt > currentSavedVersionInt) {
app.hasUpdates = true;
}
}
}
}
await setPatchedApps(patchedApps); await setPatchedApps(patchedApps);
} }
@ -664,37 +623,6 @@ class ManagerAPI {
return !existsNonRoot; return !existsNonRoot;
} }
Future<bool> hasAppUpdates(
String packageName,
DateTime patchDate,
) async {
final List<String> commits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
);
return commits.isNotEmpty;
}
Future<List<String>> getAppChangelog(
String packageName,
DateTime patchDate,
) async {
List<String> newCommits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
);
if (newCommits.isEmpty) {
newCommits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
);
}
return newCommits;
}
Future<bool> isSplitApk(PatchedApplication patchedApp) async { Future<bool> isSplitApk(PatchedApplication patchedApp) async {
Application? app; Application? app;
if (patchedApp.isFromStorage) { if (patchedApp.isFromStorage) {
@ -762,6 +690,8 @@ class ManagerAPI {
Future<void> resetLastSelectedPatches() async { Future<void> resetLastSelectedPatches() async {
final File selectedPatchesFile = File(storedPatchesFile); final File selectedPatchesFile = File(storedPatchesFile);
if (selectedPatchesFile.existsSync()) {
selectedPatchesFile.deleteSync(); selectedPatchesFile.deleteSync();
} }
} }
}

View File

@ -28,7 +28,7 @@ class PatcherAPI {
List<Patch> _universalPatches = []; List<Patch> _universalPatches = [];
List<String> _compatiblePackages = []; List<String> _compatiblePackages = [];
Map filteredPatches = <String, List<Patch>>{}; Map filteredPatches = <String, List<Patch>>{};
File? _outFile; File? outFile;
Future<void> initialize() async { Future<void> initialize() async {
await _loadPatches(); await _loadPatches();
@ -149,46 +149,11 @@ class PatcherAPI {
.toList(); .toList();
} }
Future<bool> needsResourcePatching(
List<Patch> selectedPatches,
) async {
return selectedPatches.any(
(patch) => patch.dependencies.any(
(dep) => dep.contains('resource-'),
),
);
}
Future<bool> needsSettingsPatch(List<Patch> selectedPatches) async {
return selectedPatches.any(
(patch) => patch.dependencies.any(
(dep) => dep.contains('settings'),
),
);
}
Future<void> runPatcher( Future<void> runPatcher(
String packageName, String packageName,
String apkFilePath, String apkFilePath,
List<Patch> selectedPatches, List<Patch> selectedPatches,
) async { ) async {
final bool includeSettings = await needsSettingsPatch(selectedPatches);
if (includeSettings) {
try {
final Patch? settingsPatch = _patches.firstWhereOrNull(
(patch) =>
patch.name.contains('settings') &&
patch.compatiblePackages.any((pack) => pack.name == packageName),
);
if (settingsPatch != null) {
selectedPatches.add(settingsPatch);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
final File? patchBundleFile = await _managerAPI.downloadPatches(); final File? patchBundleFile = await _managerAPI.downloadPatches();
final File? integrationsFile = await _managerAPI.downloadIntegrations(); final File? integrationsFile = await _managerAPI.downloadIntegrations();
if (patchBundleFile != null) { if (patchBundleFile != null) {
@ -197,7 +162,7 @@ class PatcherAPI {
final Directory workDir = _tmpDir.createTempSync('tmp-'); final Directory workDir = _tmpDir.createTempSync('tmp-');
final File inputFile = File('${workDir.path}/base.apk'); final File inputFile = File('${workDir.path}/base.apk');
final File patchedFile = File('${workDir.path}/patched.apk'); final File patchedFile = File('${workDir.path}/patched.apk');
_outFile = File('${workDir.path}/out.apk'); outFile = File('${workDir.path}/out.apk');
final Directory cacheDir = Directory('${workDir.path}/cache'); final Directory cacheDir = Directory('${workDir.path}/cache');
cacheDir.createSync(); cacheDir.createSync();
final String originalFilePath = apkFilePath; final String originalFilePath = apkFilePath;
@ -209,7 +174,7 @@ class PatcherAPI {
'originalFilePath': originalFilePath, 'originalFilePath': originalFilePath,
'inputFilePath': inputFile.path, 'inputFilePath': inputFile.path,
'patchedFilePath': patchedFile.path, 'patchedFilePath': patchedFile.path,
'outFilePath': _outFile!.path, 'outFilePath': outFile!.path,
'integrationsPath': integrationsFile!.path, 'integrationsPath': integrationsFile!.path,
'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'cacheDirPath': cacheDir.path, 'cacheDirPath': cacheDir.path,
@ -236,7 +201,7 @@ class PatcherAPI {
} }
Future<bool> installPatchedFile(PatchedApplication patchedApp) async { Future<bool> installPatchedFile(PatchedApplication patchedApp) async {
if (_outFile != null) { if (outFile != null) {
try { try {
if (patchedApp.isRooted) { if (patchedApp.isRooted) {
final bool hasRootPermissions = await _rootAPI.hasRootPermissions(); final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
@ -244,11 +209,11 @@ class PatcherAPI {
return _rootAPI.installApp( return _rootAPI.installApp(
patchedApp.packageName, patchedApp.packageName,
patchedApp.apkFilePath, patchedApp.apkFilePath,
_outFile!.path, outFile!.path,
); );
} }
} else { } else {
final install = await InstallPlugin.installApk(_outFile!.path); final install = await InstallPlugin.installApk(outFile!.path);
return install['isSuccess']; return install['isSuccess'];
} }
} on Exception catch (e) { } on Exception catch (e) {
@ -263,11 +228,11 @@ class PatcherAPI {
void exportPatchedFile(String appName, String version) { void exportPatchedFile(String appName, String version) {
try { try {
if (_outFile != null) { if (outFile != null) {
final String newName = _getFileName(appName, version); final String newName = _getFileName(appName, version);
CRFileSaver.saveFileWithDialog( CRFileSaver.saveFileWithDialog(
SaveFileDialogParams( SaveFileDialogParams(
sourceFilePath: _outFile!.path, sourceFilePath: outFile!.path,
destinationFileName: newName, destinationFileName: newName,
), ),
); );
@ -281,12 +246,12 @@ class PatcherAPI {
void sharePatchedFile(String appName, String version) { void sharePatchedFile(String appName, String version) {
try { try {
if (_outFile != null) { if (outFile != null) {
final String newName = _getFileName(appName, version); final String newName = _getFileName(appName, version);
final int lastSeparator = _outFile!.path.lastIndexOf('/'); final int lastSeparator = outFile!.path.lastIndexOf('/');
final String newPath = final String newPath =
_outFile!.path.substring(0, lastSeparator + 1) + newName; outFile!.path.substring(0, lastSeparator + 1) + newName;
final File shareFile = _outFile!.copySync(newPath); final File shareFile = outFile!.copySync(newPath);
ShareExtend.share(shareFile.path, 'file'); ShareExtend.share(shareFile.path, 'file');
} }
} on Exception catch (e) { } on Exception catch (e) {

View File

@ -7,12 +7,15 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:synchronized/synchronized.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
@lazySingleton @lazySingleton
class RevancedAPI { class RevancedAPI {
late Dio _dio = Dio(); late Dio _dio = Dio();
final Lock getToolsLock = Lock();
final _cacheOptions = CacheOptions( final _cacheOptions = CacheOptions(
store: MemCacheStore(), store: MemCacheStore(),
maxStale: const Duration(days: 1), maxStale: const Duration(days: 1),
@ -66,7 +69,8 @@ class RevancedAPI {
Future<Map<String, dynamic>?> _getLatestRelease( Future<Map<String, dynamic>?> _getLatestRelease(
String extension, String extension,
String repoName, String repoName,
) async { ) {
return getToolsLock.synchronized(() async {
try { try {
final response = await _dio.get('/tools'); final response = await _dio.get('/tools');
final List<dynamic> tools = response.data['tools']; final List<dynamic> tools = response.data['tools'];
@ -81,6 +85,7 @@ class RevancedAPI {
} }
return null; return null;
} }
});
} }
Future<String?> getLatestReleaseVersion( Future<String?> getLatestReleaseVersion(

View File

@ -73,7 +73,6 @@ class AppSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp = PatchedApplication( locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: application.apkFilePath, apkFilePath: application.apkFilePath,
icon: application.icon, icon: application.icon,
@ -202,7 +201,6 @@ class AppSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp = PatchedApplication( locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName, name: application.appName,
packageName: application.packageName, packageName: application.packageName,
originalPackageName: application.packageName,
version: application.versionName!, version: application.versionName!,
apkFilePath: result.files.single.path!, apkFilePath: result.files.single.path!,
icon: application.icon, icon: application.icon,

View File

@ -37,7 +37,6 @@ class HomeViewModel extends BaseViewModel {
DateTime? _lastUpdate; DateTime? _lastUpdate;
bool showUpdatableApps = false; bool showUpdatableApps = false;
List<PatchedApplication> patchedInstalledApps = []; List<PatchedApplication> patchedInstalledApps = [];
List<PatchedApplication> patchedUpdatableApps = [];
String? _latestManagerVersion = ''; String? _latestManagerVersion = '';
File? downloadedApk; File? downloadedApk;
@ -82,7 +81,7 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.errorDownloadMessage'); _toast.showBottom('homeView.errorDownloadMessage');
} }
} }
_getPatchedApps();
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps()); _managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
} }
@ -108,10 +107,6 @@ class HomeViewModel extends BaseViewModel {
void _getPatchedApps() { void _getPatchedApps() {
patchedInstalledApps = _managerAPI.getPatchedApps().toList(); patchedInstalledApps = _managerAPI.getPatchedApps().toList();
patchedUpdatableApps = _managerAPI
.getPatchedApps()
.where((app) => app.hasUpdates == true)
.toList();
notifyListeners(); notifyListeners();
} }
@ -469,11 +464,7 @@ class HomeViewModel extends BaseViewModel {
} }
Future<void> forceRefresh(BuildContext context) async { Future<void> forceRefresh(BuildContext context) async {
await Future.delayed(const Duration(seconds: 1));
if (_lastUpdate == null ||
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
_managerAPI.clearAllData(); _managerAPI.clearAllData();
}
_toast.showBottom('homeView.refreshSuccess'); _toast.showBottom('homeView.refreshSuccess');
initialize(context); initialize(context);
} }

View File

@ -209,7 +209,6 @@ class InstallerViewModel extends BaseViewModel {
), ),
RadioListTile( RadioListTile(
title: I18nText('installerView.installNonRootType'), title: I18nText('installerView.installNonRootType'),
subtitle: I18nText('installerView.installRecommendedType'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 0, value: 0,
groupValue: value, groupValue: value,
@ -270,34 +269,6 @@ class InstallerViewModel extends BaseViewModel {
Future<void> installResult(BuildContext context, bool installAsRoot) async { Future<void> installResult(BuildContext context, bool installAsRoot) async {
try { try {
_app.isRooted = installAsRoot; _app.isRooted = installAsRoot;
final bool hasMicroG =
_patches.any((p) => p.name.endsWith('MicroG support'));
final bool rootMicroG = installAsRoot && hasMicroG;
final bool rootFromStorage = installAsRoot && _app.isFromStorage;
final bool ytWithoutRootMicroG =
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('installerView.installErrorDialogTitle'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
rootMicroG
? 'installerView.installErrorDialogText1'
: rootFromStorage
? 'installerView.installErrorDialogText3'
: 'installerView.installErrorDialogText2',
),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
} else {
update( update(
1.0, 1.0,
'Installing...', 'Installing...',
@ -307,19 +278,23 @@ class InstallerViewModel extends BaseViewModel {
); );
isInstalled = await _patcherAPI.installPatchedFile(_app); isInstalled = await _patcherAPI.installPatchedFile(_app);
if (isInstalled) { if (isInstalled) {
update(1.0, 'Installed!', 'Installed!');
_app.isFromStorage = false; _app.isFromStorage = false;
_app.patchDate = DateTime.now(); _app.patchDate = DateTime.now();
_app.appliedPatches = _patches.map((p) => p.name).toList(); _app.appliedPatches = _patches.map((p) => p.name).toList();
if (hasMicroG) {
_app.name += ' ReVanced'; // In case a patch changed the app name or package name,
_app.packageName = _app.packageName.replaceFirst( // update the app info.
'com.google.', final app = await DeviceApps.getAppFromStorage(_patcherAPI.outFile!.path);
'app.revanced.', if (app != null) {
); _app.name = app.appName;
_app.packageName = app.packageName;
} }
await _managerAPI.savePatchedApp(_app); await _managerAPI.savePatchedApp(_app);
}
update(1.0, 'Installed!', 'Installed!');
} else {
// TODO(aabed): Show error message.
} }
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {

View File

@ -39,9 +39,9 @@ class NavigationViewModel extends IndexTrackingViewModel {
// Force disable Material You on Android 11 and below // Force disable Material You on Android 11 and below
if (dynamicTheme.themeId.isOdd) { if (dynamicTheme.themeId.isOdd) {
const int ANDROID_12_SDK_VERSION = 31; const int android12SdkVersion = 31;
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo; final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
if (info.version.sdkInt < ANDROID_12_SDK_VERSION) { if (info.version.sdkInt < android12SdkVersion) {
await prefs.setInt('themeMode', 0); await prefs.setInt('themeMode', 0);
await prefs.setBool('useDynamicTheme', false); await prefs.setBool('useDynamicTheme', false);
await dynamicTheme.setTheme(0); await dynamicTheme.setTheme(0);

View File

@ -44,49 +44,6 @@ class PatcherViewModel extends BaseViewModel {
return selectedApp == null; return selectedApp == null;
} }
Future<bool> isValidPatchConfig() async {
final bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
selectedPatches,
);
if (needsResourcePatching && selectedApp != null) {
final bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
return !isSplit;
}
return true;
}
Future<void> showPatchConfirmationDialog(BuildContext context) async {
final bool isValid = await isValidPatchConfig();
if (context.mounted) {
if (isValid) {
showArmv7WarningDialog(context);
} else {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.splitApkWarningDialogText'),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
isFilled: false,
onPressed: () {
Navigator.of(context).pop();
showArmv7WarningDialog(context);
},
),
],
),
);
}
}
}
Future<void> showRemovedPatchesDialog(BuildContext context) async { Future<void> showRemovedPatchesDialog(BuildContext context) async {
if (removedPatches.isNotEmpty) { if (removedPatches.isNotEmpty) {
return showDialog( return showDialog(
@ -115,7 +72,7 @@ class PatcherViewModel extends BaseViewModel {
), ),
); );
} else { } else {
showArmv7WarningDialog(context); showArmv7WarningDialog(context); // TODO(aabed): Find out why this is here
} }
} }
@ -185,9 +142,9 @@ class PatcherViewModel extends BaseViewModel {
this.selectedPatches.clear(); this.selectedPatches.clear();
removedPatches.clear(); removedPatches.clear();
final List<String> selectedPatches = final List<String> selectedPatches =
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName); await _managerAPI.getSelectedPatches(selectedApp!.packageName);
final List<Patch> patches = final List<Patch> patches =
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName); _patcherAPI.getFilteredPatches(selectedApp!.packageName);
this this
.selectedPatches .selectedPatches
.addAll(patches.where((patch) => selectedPatches.contains(patch.name))); .addAll(patches.where((patch) => selectedPatches.contains(patch.name)));
@ -203,7 +160,7 @@ class PatcherViewModel extends BaseViewModel {
.selectedPatches .selectedPatches
.removeWhere((patch) => patch.compatiblePackages.isEmpty); .removeWhere((patch) => patch.compatiblePackages.isEmpty);
} }
final usedPatches = _managerAPI.getUsedPatches(selectedApp!.originalPackageName); final usedPatches = _managerAPI.getUsedPatches(selectedApp!.packageName);
for (final patch in usedPatches){ for (final patch in usedPatches){
if (!patches.any((p) => p.name == patch.name)){ if (!patches.any((p) => p.name == patch.name)){
removedPatches.add('\u2022 ${patch.name}'); removedPatches.add('\u2022 ${patch.name}');

View File

@ -194,7 +194,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
return PatchItem( return PatchItem(
name: patch.name, name: patch.name,
simpleName: patch.getSimpleName(), simpleName: patch.getSimpleName(),
description: patch.description, description: patch.description ?? '',
packageVersion: model.getAppInfo().version, packageVersion: model.getAppInfo().version,
supportedPackageVersions: supportedPackageVersions:
model.getSupportedVersions(patch), model.getSupportedVersions(patch),
@ -246,7 +246,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
return PatchItem( return PatchItem(
name: patch.name, name: patch.name,
simpleName: patch.getSimpleName(), simpleName: patch.getSimpleName(),
description: patch.description, description: patch.description ?? '',
packageVersion: packageVersion:
model.getAppInfo().version, model.getAppInfo().version,
supportedPackageVersions: supportedPackageVersions:

View File

@ -28,7 +28,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
getPatchesVersion().whenComplete(() => notifyListeners()); getPatchesVersion().whenComplete(() => notifyListeners());
patches.addAll( patches.addAll(
_patcherAPI.getFilteredPatches( _patcherAPI.getFilteredPatches(
selectedApp!.originalPackageName, selectedApp!.packageName,
), ),
); );
patches.sort((a, b) { patches.sort((a, b) {
@ -98,11 +98,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
void selectDefaultPatches() { void selectDefaultPatches() {
selectedPatches.clear(); selectedPatches.clear();
if (locator<PatcherViewModel>().selectedApp?.originalPackageName != null) { if (locator<PatcherViewModel>().selectedApp?.packageName != null) {
selectedPatches.addAll( selectedPatches.addAll(
_patcherAPI _patcherAPI
.getFilteredPatches( .getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName, locator<PatcherViewModel>().selectedApp!.packageName,
) )
.where( .where(
(element) => (element) =>
@ -187,7 +187,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
final List<String> selectedPatches = final List<String> selectedPatches =
this.selectedPatches.map((patch) => patch.name).toList(); this.selectedPatches.map((patch) => patch.name).toList();
await _managerAPI.setSelectedPatches( await _managerAPI.setSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName, locator<PatcherViewModel>().selectedApp!.packageName,
selectedPatches, selectedPatches,
); );
} }
@ -195,7 +195,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
Future<void> loadSelectedPatches(BuildContext context) async { Future<void> loadSelectedPatches(BuildContext context) async {
if (_managerAPI.isPatchesChangeEnabled()) { if (_managerAPI.isPatchesChangeEnabled()) {
final List<String> selectedPatches = await _managerAPI.getSelectedPatches( final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName, locator<PatcherViewModel>().selectedApp!.packageName,
); );
if (selectedPatches.isNotEmpty) { if (selectedPatches.isNotEmpty) {
this.selectedPatches.clear(); this.selectedPatches.clear();

View File

@ -71,12 +71,6 @@ class SettingsViewModel extends BaseViewModel {
actions: [ actions: [
CustomMaterialButton( CustomMaterialButton(
isFilled: false, isFilled: false,
label: I18nText('noButton'),
onPressed: () {
Navigator.of(context).pop();
},
),
CustomMaterialButton(
label: I18nText('yesButton'), label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setChangingToggleModified(true); _managerAPI.setChangingToggleModified(true);
@ -84,6 +78,12 @@ class SettingsViewModel extends BaseViewModel {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
CustomMaterialButton(
label: I18nText('noButton'),
onPressed: () {
Navigator.of(context).pop();
},
),
], ],
), ),
); );

View File

@ -222,22 +222,6 @@ class AppInfoView extends StatelessWidget {
subtitle: Text(app.packageName), subtitle: Text(app.packageName),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
ListTile(
contentPadding:
const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'appInfoView.originalPackageNameLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: Text(app.originalPackageName),
),
const SizedBox(height: 4),
ListTile( ListTile(
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 20.0), const EdgeInsets.symmetric(horizontal: 20.0),

View File

@ -13,7 +13,6 @@ import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart'; import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/utils/string.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class AppInfoViewModel extends BaseViewModel { class AppInfoViewModel extends BaseViewModel {
@ -147,17 +146,7 @@ class AppInfoViewModel extends BaseViewModel {
} }
String getAppliedPatchesString(List<String> appliedPatches) { String getAppliedPatchesString(List<String> appliedPatches) {
final List<String> names = appliedPatches return '\u2022 ${appliedPatches.join('\n\u2022 ')}';
.map(
(p) => p
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG'),
)
.toList();
return '\u2022 ${names.join('\n\u2022 ')}';
} }
void openApp(PatchedApplication app) { void openApp(PatchedApplication app) {

View File

@ -79,8 +79,6 @@ class InstalledAppsCard extends StatelessWidget {
icon: app.icon, icon: app.icon,
name: app.name, name: app.name,
patchDate: app.patchDate, patchDate: app.patchDate,
changelog: app.changelog,
isUpdatableApp: false,
onPressed: () => onPressed: () =>
locator<HomeViewModel>().navigateToAppInfo(app), locator<HomeViewModel>().navigateToAppInfo(app),
), ),

View File

@ -5,8 +5,8 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart'; import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';

View File

@ -1,6 +1,5 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
@ -13,63 +12,30 @@ class ApplicationItem extends StatefulWidget {
required this.icon, required this.icon,
required this.name, required this.name,
required this.patchDate, required this.patchDate,
required this.changelog,
required this.isUpdatableApp,
required this.onPressed, required this.onPressed,
}) : super(key: key); }) : super(key: key);
final Uint8List icon; final Uint8List icon;
final String name; final String name;
final DateTime patchDate; final DateTime patchDate;
final List<String> changelog;
final bool isUpdatableApp;
final Function() onPressed; final Function() onPressed;
@override @override
State<ApplicationItem> createState() => _ApplicationItemState(); State<ApplicationItem> createState() => _ApplicationItemState();
} }
class _ApplicationItemState extends State<ApplicationItem> class _ApplicationItemState extends State<ApplicationItem> {
with TickerProviderStateMixin {
late AnimationController _animationController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ExpandableController expController = ExpandableController();
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16.0), margin: const EdgeInsets.only(bottom: 16.0),
child: CustomCard( child: CustomCard(
onTap: () { child: Row(
expController.toggle();
_animationController.isCompleted
? _animationController.reverse()
: _animationController.forward();
},
child: ExpandablePanel(
controller: expController,
theme: const ExpandableThemeData(
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
tapBodyToCollapse: false,
tapBodyToExpand: false,
tapHeaderToExpand: false,
hasIcon: false,
animationDuration: Duration(milliseconds: 450),
),
header: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible( Flexible(
@ -110,23 +76,13 @@ class _ApplicationItemState extends State<ApplicationItem>
), ),
Row( Row(
children: [ children: [
RotationTransition(
turns: Tween(begin: 0.0, end: 0.50)
.animate(_animationController),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.arrow_drop_down),
),
),
const SizedBox(width: 8), const SizedBox(width: 8),
Column( Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[ children: <Widget>[
CustomMaterialButton( CustomMaterialButton(
label: widget.isUpdatableApp label: I18nText('applicationItem.infoButton'),
? I18nText('applicationItem.patchButton')
: I18nText('applicationItem.infoButton'),
onPressed: widget.onPressed, onPressed: widget.onPressed,
), ),
], ],
@ -135,30 +91,6 @@ class _ApplicationItemState extends State<ApplicationItem>
), ),
], ],
), ),
collapsed: const SizedBox(),
expanded: Padding(
padding: const EdgeInsets.only(
top: 16.0,
left: 4.0,
right: 4.0,
bottom: 4.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
I18nText(
'applicationItem.changelogLabel',
child: const Text(
'',
style: TextStyle(fontWeight: FontWeight.w700),
),
),
const SizedBox(height: 4),
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
],
),
),
),
), ),
); );
} }

View File

@ -75,6 +75,7 @@ dependencies:
flutter_markdown: ^0.6.14 flutter_markdown: ^0.6.14
dio_cache_interceptor: ^3.4.0 dio_cache_interceptor: ^3.4.0
install_plugin: ^2.1.0 install_plugin: ^2.1.0
synchronized: ^3.1.0
dev_dependencies: dev_dependencies:
json_serializable: ^6.6.1 json_serializable: ^6.6.1