Compare commits

..

9 Commits

Author SHA1 Message Date
semantic-release-bot
9552b2ebc5 chore(release): 1.23.0-dev.7 [skip ci]
# [1.23.0-dev.7](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.6...v1.23.0-dev.7) (2024-11-10)

### Features

* Use ReVanced API v4 ([7b7d91d](7b7d91d661))

### Performance Improvements

* Don't recalculate universal patches or compatible packages if not necessary ([7e3afe0](7e3afe0cb2))
2024-11-10 04:04:12 +00:00
oSumAtrIX
7e3afe0cb2 perf: Don't recalculate universal patches or compatible packages if not necessary 2024-11-10 04:53:59 +01:00
oSumAtrIX
7b7d91d661 feat: Use ReVanced API v4 2024-11-10 04:53:59 +01:00
oSumAtrIX
44b8d4ceee build: Configure output file name 2024-11-08 19:43:45 +01:00
semantic-release-bot
aaa97ebb71 chore(release): 1.23.0-dev.6 [skip ci]
# [1.23.0-dev.6](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.5...v1.23.0-dev.6) (2024-11-08)
2024-11-08 17:49:03 +00:00
oSumAtrIX
d99e5af384 build: Fix build 2024-11-08 18:39:02 +01:00
oSumAtrIX
c47c7c0a88 build(Needs bump): Bump dependencies 2024-11-05 20:13:08 +01:00
semantic-release-bot
3e32c0fd90 chore(release): 1.23.0-dev.5 [skip ci]
# [1.23.0-dev.5](https://github.com/ReVanced/revanced-manager/compare/v1.23.0-dev.4...v1.23.0-dev.5) (2024-11-05)

### Features

* Import and export manager settings ([#2268](https://github.com/ReVanced/revanced-manager/issues/2268)) ([a45d959](a45d9598cc))
2024-11-05 18:52:52 +00:00
aAbed
a45d9598cc feat: Import and export manager settings (#2268) 2024-11-05 19:43:35 +01:00
19 changed files with 210 additions and 85 deletions

View File

@@ -7,7 +7,7 @@ plugins {
android {
namespace = "app.revanced.manager.flutter"
compileSdk = 34
compileSdk = 35
ndkVersion = "27.0.12077973"
compileOptions {
@@ -24,9 +24,19 @@ android {
defaultConfig {
applicationId = "app.revanced.manager.flutter"
minSdk = 26
targetSdk = 34
targetSdk = 35
versionCode = flutter.versionCode
versionName = flutter.versionName
resValue("string", "app_name", "ReVanced Manager")
}
applicationVariants.all {
outputs.all {
this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
outputFileName = "revanced-manager-$versionName.apk"
}
}
buildTypes {
@@ -37,7 +47,6 @@ android {
signingConfig = signingConfigs["debug"]
ndk.abiFilters += setOf("armeabi-v7a", "arm64-v8a", "x86_64")
setProperty("archivesBaseName", "revanced-manager-v${flutter.versionName}")
}
release {
@@ -52,19 +61,21 @@ android {
keyAlias = System.getenv("KEYSTORE_ENTRY_ALIAS")
keyPassword = System.getenv("KEYSTORE_ENTRY_PASSWORD")
}
resValue("string", "app_name", "ReVanced Manager")
} else {
resValue("string", "app_name", "ReVanced Manager (Debug)")
applicationIdSuffix = ".debug"
signingConfig = signingConfigs["debug"]
}
resValue("string", "app_name", "ReVanced Manager")
resValue("string", "app_name", "ReVanced Manager (Debug signed)")
}
}
debug {
resValue("string", "app_name", "ReVanced Manager (Debug)")
applicationIdSuffix = ".debug"
resValue("string", "app_name", "ReVanced Manager (Debug)")
}
}
@@ -80,6 +91,7 @@ android {
}
}
flutter {
source = "../.."
}

View File

@@ -3,7 +3,6 @@ package app.revanced.manager.flutter
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Base64
@@ -17,9 +16,8 @@ import java.security.MessageDigest
class ExportSettingsActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callingPackageName = getCallingPackage()!!
if (getFingerprint(callingPackageName) == getFingerprint(getPackageName())) {
if (getFingerprint(callingPackage!!) == getFingerprint(packageName)) {
// Create JSON Object
val json = JSONObject()
@@ -64,7 +62,7 @@ class ExportSettingsActivity : Activity() {
fun getFingerprint(packageName: String): String {
// Get the signature of the app that matches the package name
val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
val signature = packageInfo.signatures[0]
val signature = packageInfo.signatures!![0]
// Get the raw certificate data
val rawCert = signature.toByteArray()

View File

@@ -283,7 +283,6 @@ class MainActivity : FlutterActivity() {
tmpDir,
Aapt.binary(applicationContext).absolutePath,
tmpDir.path,
true // TODO: Add option to disable this
)
)

View File

@@ -1,4 +1,5 @@
import com.android.build.api.dsl.CommonExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
allprojects {
repositories {
@@ -17,6 +18,14 @@ allprojects {
layout.buildDirectory = File("../build")
project(":screenshot_callback") {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "17"
}
}
}
subprojects {
afterEvaluate {
extensions.findByName("android")?.let {

View File

@@ -3,6 +3,5 @@ android.useAndroidX=true
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.caching=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View File

@@ -1,7 +1,7 @@
[versions]
revanced-patcher = "20.0.2"
revanced-library = "3.0.1"
desugar_jdk_libs = "2.1.2"
revanced-patcher = "21.0.0"
revanced-library = "3.0.2"
desugar_jdk_libs = "2.1.3"
[libraries]
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }

View File

@@ -17,7 +17,7 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.5.0" apply false
id("com.android.application") version "8.7.2" apply false
id("org.jetbrains.kotlin.android") version "2.0.20" apply false
}

View File

@@ -197,6 +197,12 @@
"deleteTempDirLabel": "Delete temporary files",
"deleteTempDirHint": "Delete unused temporary files",
"deletedTempDir": "Temporary files deleted",
"exportSettingsLabel": "Export settings",
"exportSettingsHint": "Export settings to a JSON file",
"exportedSettings": "Settings exported",
"importSettingsLabel": "Import settings",
"importSettingsHint": "Import settings from a JSON file",
"importedSettings": "Settings imported",
"exportPatchesLabel": "Export patch selection",
"exportPatchesHint": "Export patch selection to a JSON file",
"exportedPatches": "Patch selection exported",

View File

@@ -50,6 +50,7 @@ Learn how to configure ReVanced Manager.
- 🔑 Keystore used to sign patched apps
- 📄 Remembered selection of patches for each app
- ⚙️ Remembered patch options
- 🛠️ Remembered settings
> Note
> These can be used to backup and restore or reset settings to default in case of issues.

View File

@@ -30,6 +30,7 @@ class ManagerAPI {
final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs;
Map<String, List>? contributors;
List<Patch> patches = [];
List<Option> options = [];
Patch? selectedPatch;
@@ -44,7 +45,7 @@ class ManagerAPI {
String keystoreFile =
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
String defaultKeystorePassword = 's3cur3p@ssw0rd';
String defaultApiUrl = 'https://api.revanced.app/v3';
String defaultApiUrl = 'https://api.revanced.app/v4';
String defaultRepoUrl = 'https://api.github.com';
String defaultPatcherRepo = 'revanced/revanced-patcher';
String defaultPatchesRepo = 'revanced/revanced-patches';
@@ -66,16 +67,23 @@ class ManagerAPI {
releaseBuild = !(await getCurrentManagerVersion()).contains('-dev');
}
// Migrate to new API URL if not done yet as the old one is sunset.
final bool hasMigratedToLatestApi =
_prefs.getBool('migratedToLatestApiUrl') ?? false;
if (!hasMigratedToLatestApi) {
final String apiUrl = getApiUrl().toLowerCase();
if (apiUrl.contains('releases.revanced.app') ||
(apiUrl.contains('api.revanced.app') &&
!apiUrl.contains('v3'))) {
await setApiUrl(''); // Reset to default.
_prefs.setBool('migratedToLatestApiUrl', true);
final hasMigratedToNewMigrationSystem = _prefs.getBool('migratedToNewApiPrefSystem') ?? false;
if (!hasMigratedToNewMigrationSystem) {
final apiUrl = getApiUrl().toLowerCase();
final isReleases = apiUrl.contains('releases.revanced.app');
final isV2 = apiUrl.contains('api.revanced.app/v2');
final isV3 = apiUrl.contains('api.revanced.app/v3');
if (isReleases || isV2 || isV3) {
await resetApiUrl();
// At this point, the preference is removed.
// Now, no more migration is needed because:
// If the user touches the API URL,
// it will be remembered forever as intended.
// On the other hand, if the user resets it or sets it to the default,
// the URL will be updated whenever the app is updated.
_prefs.setBool('migratedToNewApiPrefSystem', true);
}
}
@@ -99,12 +107,25 @@ class ManagerAPI {
return _prefs.getString('apiUrl') ?? defaultApiUrl;
}
Future<void> setApiUrl(String url) async {
if (url.isEmpty || url == ' ') {
url = defaultApiUrl;
}
Future<void> resetApiUrl() async {
await _prefs.remove('apiUrl');
await _revancedAPI.clearAllCache();
_toast.showBottom(t.settingsView.restartAppForChanges);
}
Future<void> setApiUrl(String url) async {
url = url.toLowerCase();
if (url == defaultApiUrl) {
return;
}
if (!url.startsWith('http')) {
url = 'https://$url';
}
await _prefs.setString('apiUrl', url);
await _revancedAPI.clearAllCache();
_toast.showBottom(t.settingsView.restartAppForChanges);
}
@@ -406,7 +427,7 @@ class ManagerAPI {
}
Future<Map<String, List<dynamic>>> getContributors() async {
return await _revancedAPI.getContributors();
return contributors ??= await _revancedAPI.getContributors();
}
Future<List<Patch>> getPatches() async {
@@ -438,6 +459,10 @@ class ManagerAPI {
}
Future<File?> downloadPatches() async {
if (!isUsingAlternativeSources()) {
return await _revancedAPI.getLatestReleaseFile('patches');
}
try {
final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion();
@@ -755,6 +780,36 @@ class ManagerAPI {
return jsonDecode(string);
}
String exportSettings() {
final Map<String, dynamic> settings = _prefs
.getKeys()
.fold<Map<String, dynamic>>({}, (Map<String, dynamic> map, String key) {
map[key] = _prefs.get(key);
return map;
});
return jsonEncode(settings);
}
Future<void> importSettings(String settings) async {
final Map<String, dynamic> settingsMap = jsonDecode(settings);
settingsMap.forEach((key, value) {
if (value is bool) {
_prefs.setBool(key, value);
} else if (value is int) {
_prefs.setInt(key, value);
} else if (value is double) {
_prefs.setDouble(key, value);
} else if (value is String) {
_prefs.setString(key, value);
} else if (value is List<dynamic>) {
_prefs.setStringList(
key,
value.map((a) => json.encode(a.toJson())).toList(),
);
}
});
}
void resetAllOptions() {
_prefs.getKeys().where((key) => key.startsWith('patchOption-')).forEach(
(key) {

View File

@@ -18,8 +18,7 @@ import 'package:share_plus/share_plus.dart';
@lazySingleton
class PatcherAPI {
static const patcherChannel =
MethodChannel('app.revanced.manager.flutter/patcher');
static const patcherChannel = MethodChannel('app.revanced.manager.flutter/patcher');
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final RootAPI _rootAPI = RootAPI();
late Directory _dataDir;
@@ -27,7 +26,7 @@ class PatcherAPI {
late File _keyStoreFile;
List<Patch> _patches = [];
List<Patch> _universalPatches = [];
List<String> _compatiblePackages = [];
Set<String> _compatiblePackages = {};
Map filteredPatches = <String, List<Patch>>{};
File? outFile;
@@ -46,8 +45,8 @@ class PatcherAPI {
}
}
List<String> getCompatiblePackages() {
final List<String> compatiblePackages = [];
Set<String> getCompatiblePackages() {
final Set<String> compatiblePackages = {};
for (final Patch patch in _patches) {
for (final Package package in patch.compatiblePackages) {
if (!compatiblePackages.contains(package.name)) {
@@ -66,16 +65,16 @@ class PatcherAPI {
try {
if (_patches.isEmpty) {
_patches = await _managerAPI.getPatches();
_universalPatches = getUniversalPatches();
_compatiblePackages = getCompatiblePackages();
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
_patches = List.empty();
}
_compatiblePackages = getCompatiblePackages();
_universalPatches = getUniversalPatches();
}
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
@@ -84,6 +83,7 @@ class PatcherAPI {
final List<ApplicationWithIcon> filteredApps = [];
final bool allAppsIncluded =
_universalPatches.isNotEmpty && showUniversalPatches;
if (allAppsIncluded) {
final appList = await DeviceApps.getInstalledApplications(
includeAppIcons: true,
@@ -94,6 +94,7 @@ class PatcherAPI {
filteredApps.add(app as ApplicationWithIcon);
}
}
for (final packageName in _compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == packageName)) {

View File

@@ -51,7 +51,7 @@ class RevancedAPI {
}
return getToolsLock.synchronized(() async {
try {
final response = await _dio.get('/$toolName/latest');
final response = await _dio.get('/$toolName');
return response.data;
} on Exception catch (e) {
if (kDebugMode) {
@@ -89,7 +89,7 @@ class RevancedAPI {
toolName,
);
if (release != null) {
final String url = release['assets'][0]['download_url'];
final String url = release['download_url'];
return await _downloadManager.getSingleFile(url);
}
} on Exception catch (e) {

View File

@@ -30,25 +30,13 @@ class ContributorsView extends StatelessWidget {
sliver: SliverList(
delegate: SliverChildListDelegate.fixed(
<Widget>[
ContributorsCard(
title: 'ReVanced Patcher',
contributors: model.patcherContributors,
),
const SizedBox(height: 20),
ContributorsCard(
title: 'ReVanced Patches',
contributors: model.patchesContributors,
),
const SizedBox(height: 20),
ContributorsCard(
title: 'ReVanced CLI',
contributors: model.cliContributors,
),
const SizedBox(height: 20),
ContributorsCard(
title: 'ReVanced Manager',
contributors: model.managerContributors,
),
for (final String tool in model.contributors.keys) ...[
ContributorsCard(
title: tool,
contributors: model.contributors[tool]!,
),
const SizedBox(height: 20),
],
SizedBox(height: MediaQuery.viewPaddingOf(context).bottom),
],
),

View File

@@ -4,20 +4,10 @@ import 'package:stacked/stacked.dart';
class ContributorsViewModel extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>();
List<dynamic> patcherContributors = [];
List<dynamic> patchesContributors = [];
List<dynamic> cliContributors = [];
List<dynamic> managerContributors = [];
String repoName(String repo) => repo.split('/').last;
Map<String, List<dynamic>> contributors = {};
Future<void> getContributors() async {
final Map<String, List<dynamic>> contributors =
await _managerAPI.getContributors();
patcherContributors = contributors[repoName(_managerAPI.defaultPatcherRepo)] ?? [];
patchesContributors = contributors[repoName(_managerAPI.defaultPatchesRepo)] ?? [];
cliContributors = contributors[repoName(_managerAPI.defaultCliRepo)] ?? [];
managerContributors = contributors[repoName(_managerAPI.defaultManagerRepo)] ?? [];
contributors = await _managerAPI.getContributors();
notifyListeners();
}
}

View File

@@ -13,8 +13,9 @@ class SManageApiUrl extends BaseViewModel {
final TextEditingController _apiUrlController = TextEditingController();
Future<void> showApiUrlDialog(BuildContext context) async {
final String apiUrl = _managerAPI.getApiUrl();
_apiUrlController.text = apiUrl.replaceAll('https://', '');
final apiUrl = _managerAPI.getApiUrl();
_apiUrlController.text = apiUrl;
return showDialog(
context: context,
builder: (context) => AlertDialog(
@@ -60,11 +61,7 @@ class SManageApiUrl extends BaseViewModel {
),
FilledButton(
onPressed: () {
String apiUrl = _apiUrlController.text;
if (!apiUrl.startsWith('https')) {
apiUrl = 'https://$apiUrl';
}
_managerAPI.setApiUrl(apiUrl);
_managerAPI.setApiUrl(_apiUrlController.text);
Navigator.of(context).pop();
},
child: Text(t.okButton),
@@ -87,7 +84,7 @@ class SManageApiUrl extends BaseViewModel {
),
FilledButton(
onPressed: () {
_managerAPI.setApiUrl('');
_managerAPI.resetApiUrl();
Navigator.of(context)
..pop()
..pop();

View File

@@ -222,6 +222,53 @@ class SettingsViewModel extends BaseViewModel {
notifyListeners();
}
Future<void> exportSettings() async {
try {
final String settings = _managerAPI.exportSettings();
final Directory tempDir = await getTemporaryDirectory();
final String filePath = '${tempDir.path}/manager_settings.json';
final File file = File(filePath);
await file.writeAsString(settings);
final String? result = await FlutterFileDialog.saveFile(
params: SaveFileDialogParams(
sourceFilePath: file.path,
fileName: 'manager_settings.json',
mimeTypesFilter: ['application/json'],
),
);
if (result != null) {
_toast.showBottom(t.settingsView.exportedSettings);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
Future<void> importSettings() async {
try {
final String? result = await FlutterFileDialog.pickFile(
params: const OpenFileDialogParams(
fileExtensionsFilter: ['json'],
),
);
if (result != null) {
final File inFile = File(result);
final String settings = inFile.readAsStringSync();
inFile.delete();
_managerAPI.importSettings(settings);
_toast.showBottom(t.settingsView.importedSettings);
_toast.showBottom(t.settingsView.restartAppForChanges);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
_toast.showBottom(t.settingsView.jsonSelectorErrorMessage);
}
}
Future<void> exportPatches() async {
try {
final File outFile = File(_managerAPI.storedPatchesFile);

View File

@@ -49,7 +49,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
child: GestureDetector(
onTap: () => launchUrl(
Uri.parse(
widget.contributors[index]['html_url'],
widget.contributors[index]['url'],
),
mode: LaunchMode.externalApplication,
),

View File

@@ -14,6 +14,30 @@ class SExportSection extends StatelessWidget {
return SettingsSection(
title: t.settingsView.exportSectionTitle,
children: <Widget>[
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: Text(
t.settingsView.exportSettingsLabel,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(t.settingsView.exportSettingsHint),
onTap: () => _settingsViewModel.exportSettings(),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: Text(
t.settingsView.importSettingsLabel,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(t.settingsView.importSettingsHint),
onTap: () => _settingsViewModel.importSettings(),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: Text(
@@ -114,7 +138,6 @@ class SExportSection extends StatelessWidget {
subtitle: Text(t.settingsView.regenerateKeystoreHint),
onTap: () => _showDeleteKeystoreDialog(context),
),
// SManageKeystorePasswordUI(),
],
);
}

View File

@@ -4,7 +4,7 @@ homepage: https://revanced.app
publish_to: 'none'
version: 1.23.0-dev.4+101800044
version: 1.23.0-dev.7+101800047
environment:
sdk: ^3.5.3